commit 6b54764b6ae4c1a7d5c724def2540c982d8b9060 Author: Leon Krieg Date: Fri Oct 18 16:17:12 2024 +0200 Initial commit diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..08cb1ee --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +bin/ +*.swp diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..7ba80c7 --- /dev/null +++ b/Makefile @@ -0,0 +1,143 @@ +.DELETE_ON_ERROR: +.SUFFIXES: + +# Makefile +# Written by Leon Krieg + +# ============================================================================== +# GENERAL SETTINGS +# ============================================================================== + +VERBOSE := false +ARCH := m1284 +MCU := atmega1284p +FREQ := 8000000UL +ISP := usbasp +CC := avr-gcc +LD := $(CC) +OBJCOPY := avr-objcopy +AVD := avrdude +SIM := simavr +MKDIR := mkdir -p +RMR := rm -rf +GIT := git + +LFUSE := 0xC2 +HFUSE := 0xD7 +EFUSE := 0xFF +LOCK := 0xFF + +SRCDIR := src +BINDIR := bin +TMPDIR := $(BINDIR)/build +TARGET := $(BINDIR)/core.hex +ELFFILE := $(BINDIR)/core.elf +LOGFILE := $(BINDIR)/core.log + +CPPFLAGS := -DF_CPU=$(FREQ) -I$(SRCDIR) +CFLAGS := -mmcu=$(MCU) -Os -std=c99 -Wall -Wextra -Werror +OCFLAGS := -j .text -j .data -O ihex +LDFLAGS := -mmcu=$(MCU) -Wl,-u,vfprintf -lprintf_flt +LDLIBS := -lm + +# ============================================================================== +# TARGET FILE LISTS (DERIVED FROM SOURCE TREE) +# ============================================================================== + +PATHS := $(shell find "$(SRCDIR)" -type d -printf '%P ' 2>/dev/null) +FILES := $(shell find "$(SRCDIR)" -type f -name "*.c" -printf '%P ' 2>/dev/null) +TMPDIRS := $(BINDIR) $(TMPDIR) $(PATHS:%=$(TMPDIR)/%) +OBJECTS := $(FILES:%.c=$(TMPDIR)/%.o) +DEPENDS := $(FILES:%.c=$(TMPDIR)/%.d) + +# ============================================================================== +# AUXILIARY TARGETS +# ============================================================================== + +.PHONY: all +all: flash + +.PHONY: flash +flash: $(TARGET) + $(E) "[AVD] Flashing..." + $(Q) $(AVD) \ + -l$(LOGFILE) -B375kHz \ + -c $(ISP) -p $(ARCH) \ + -U lfuse:w:$(LFUSE):m \ + -U hfuse:w:$(HFUSE):m \ + -U efuse:w:$(EFUSE):m \ + -U lock:w:$(LOCK):m \ + -U flash:w:$< + +.PHONY: simulate +simulate: $(TARGET) + $(E) "[SIM] $<" + $(Q) $(SIM) -m $(MCU) -f $(FREQ) $< + +.PHONY: clean +clean: + $(E) "[REM] $(TARGET)" + $(Q) $(RMR) $(TARGET) + $(E) "[REM] $(ELFFILE)" + $(Q) $(RMR) $(ELFFILE) + $(E) "[REM] $(LOGFILE)" + $(Q) $(RMR) $(LOGFILE) + $(E) "[REM] $(TMPDIR)" + $(Q) $(RMR) $(TMPDIR) + +.PHONY: distclean +distclean: clean + $(E) "[REM] $(BINDIR)" + $(Q) $(RMR) $(BINDIR) + +$(TMPDIRS): + $(E) "[DIR] $@" + $(Q) $(MKDIR) $@ + +# ============================================================================== +# PRIMARY BUILD TARGET AND PATTERN RULES +# ============================================================================== + +# We must expand the prerequisite lists a second time to resolve path variable +# $(@D). This means folders can be set as explicit dependencies and created in +# the $TMPDIRS rule. This is better than relying on Make to honor the order of +# prerequisites for the primary target and we will not have to call mkdir for +# each build step preemptively. + +.SECONDEXPANSION: + +$(TARGET): $(ELFFILE) | $$(@D) + $(E) "[HEX] $@" + $(Q) $(OBJCOPY) $(OCFLAGS) $< $@ + +$(ELFFILE): $(OBJECTS) $(DEPENDS) | $$(@D) + $(E) "[ELF] $@" + $(Q) $(LD) -o $@ $(LDFLAGS) $(OBJECTS) $(LDLIBS) + +$(TMPDIR)/%.o: $(SRCDIR)/%.c | $$(@D) + $(E) "[OBJ] $@" + $(Q) $(CC) -c -o $@ $(CFLAGS) $(CPPFLAGS) $< + +$(TMPDIR)/%.d: $(SRCDIR)/%.c | $$(@D) + $(E) "[DEP] $@" + $(Q) $(CC) -c -o $@ $(CFLAGS) $(CPPFLAGS) -MM -MT $(@:.d=.o) $< + +# ============================================================================== +# MAKE PREPROCESSOR INCLUDES AND CONDITIONALS +# ============================================================================== + +# Load header dependency rules +include $(wildcard $(DEPENDS)) + +# Sanity check +ifeq ($(strip $(OBJECTS)),) +$(error No sources found in '$(SRCDIR)/') +endif + +# Handle verbosity setting +ifneq ($(VERBOSE), false) +E = @true +else +E = @echo +Q = @ +endif diff --git a/docs/README.md b/docs/README.md new file mode 100644 index 0000000..bf81188 --- /dev/null +++ b/docs/README.md @@ -0,0 +1,47 @@ +## uwu - Useful Wireless Utilities + +### What's this? + +uwu a-are usefuw wiwewess utiwities fow encoding, sending +a-and weceiving data o-over ampwitude-shift moduwated wadio +fwequencies. cuwwentwy it onwy suppowts manchester (binawy +phase) wine e-encoding〜☆ f-fowwawd ewwow cowwection will +be d-done using hamming codes but i'm still wowking on that. + +Sorry... + +### ASK Module + +| Attribute | Value | +| ------------- | ---------------------------- | +| UHF Band Name | LPD433, SRD860 (ISM) | +| Carrier Freq. | 433 MHz, 868 MHz | +| Bandwidth LPD | 1.7 MHz (433.05 – 434.79) | +| Modulation | ASK (Amplitude-shift keying) | +| Data Rate | 4 KHz, maybe more? | + +#### General Notes + +- Generic 433 MHz RX/TX chips with ASK. +- Transmitter is on if data pin is high. Receivers have + automatic gain control. Will output random noise when + there is no signal. Requires an error checking protocol. +- Use manchester encoding and training preamble for receiver + and transmitter to sync up their clock phases. +- Encrypted serial connection over 433 MHz? + +### Planned Modules + +- Frequency-shift keying (FSK) +- Phase-shift keying (PSK) +- Chirp spread spectrum (CSS) + +### Further Links + +- [Line code](https://en.wikipedia.org/wiki/Line_code) +- [Clock recovery](https://en.wikipedia.org/wiki/Clock_recovery) +- [Manchester code](https://en.wikipedia.org/wiki/Manchester_code) +- [Error correction code](https://en.wikipedia.org/wiki/Error_correction_code) +- [Noisy-channel coding theorem](https://en.wikipedia.org/wiki/Noisy-channel_coding_theorem) +- [Nyquist–Shannon sampling theorem](https://en.wikipedia.org/wiki/Nyquist%E2%80%93Shannon_sampling_theorem) +- [Short range device frequencies](https://de.wikipedia.org/wiki/Short_Range_Device#Frequenzen) diff --git a/docs/external/ATMEGA1284.pdf b/docs/external/ATMEGA1284.pdf new file mode 100644 index 0000000..319e0a5 Binary files /dev/null and b/docs/external/ATMEGA1284.pdf differ diff --git a/docs/external/MANCHESTER-CODING.pdf b/docs/external/MANCHESTER-CODING.pdf new file mode 100644 index 0000000..a2bbc57 Binary files /dev/null and b/docs/external/MANCHESTER-CODING.pdf differ diff --git a/docs/external/TIMER-CAPTURE.pdf b/docs/external/TIMER-CAPTURE.pdf new file mode 100644 index 0000000..01b748c Binary files /dev/null and b/docs/external/TIMER-CAPTURE.pdf differ diff --git a/docs/external/TIMER.pdf b/docs/external/TIMER.pdf new file mode 100644 index 0000000..ba3541f Binary files /dev/null and b/docs/external/TIMER.pdf differ diff --git a/src/common.c b/src/common.c new file mode 100644 index 0000000..752b0ac --- /dev/null +++ b/src/common.c @@ -0,0 +1,91 @@ +#include "common.h" + +#include +#include +#include + +#define UART_BAUDRATE 9600 +#define UART_TXBUF_SIZE 128 + +#define UART_TXBUF_MASK (UART_TXBUF_SIZE - 1) +#define UART_BAUD_PRESCALE ((((F_CPU / 16) + \ + (UART_BAUDRATE / 2)) / (UART_BAUDRATE)) - 1) + +static volatile char txbuf[UART_TXBUF_SIZE]; +static volatile word txhead, txtail; + +static void UART_Init(void); +static void UART_Puts(const char *str); +static void UART_Putc(char ch); + +void Info(const char *fmt, ...) +{ + va_list ap; + char msg[256]; + + // Lazily initialize this module + if ((UCSR0B & BIT(TXEN0)) == 0) { + UART_Init(); + } + + va_start(ap, fmt); + vsnprintf(msg, sizeof(msg), fmt, ap); + va_end(ap); + + UART_Puts("[CORE] "); + UART_Puts(msg); + UART_Puts("\r\n"); +} + +static void UART_Init(void) +{ + txhead = 0; + txtail = 0; + + UCSR0B = BIT(TXEN0); // Enable TX circuitry + UCSR0C = BIT(UCSZ01) | BIT(UCSZ00); // 8-bit data, 1-bit stop, no parity + UBRR0H = (UART_BAUD_PRESCALE >> 8); // Set baud rate upper byte + UBRR0L = UART_BAUD_PRESCALE; // Set baud rate lower byte + + sei(); + Sleep(100); +} + +static void UART_Puts(const char *str) +{ + while (*str != '\0') { + UART_Putc(*str++); + } +} + +static void UART_Putc(char ch) +{ + word head; + + // Wrap around if end of buffer reached + head = (txhead + 1) & UART_TXBUF_MASK; + while (head == txtail); // Wait for space + + txbuf[head] = ch; + txhead = head; + + // Enable interrupt + UCSR0B |= BIT(UDRIE0); +} + +// Data register empty +ISR(USART0_UDRE_vect) +{ + word tail; + + // Anything in TX buffer? + if (txhead != txtail) { + // Write next byte to data register + tail = (txtail + 1) & UART_TXBUF_MASK; + UDR0 = txbuf[tail]; + txtail = tail; + } else { + // Disable interrupt + UCSR0B &= ~BIT(UDRIE0); + } +} diff --git a/src/common.h b/src/common.h new file mode 100644 index 0000000..8023acb --- /dev/null +++ b/src/common.h @@ -0,0 +1,21 @@ +#ifndef MAD_COMMON_H +#define MAD_COMMON_H + +#include +#include + +#define DDR(p) DDR ## p +#define PIN(p) PIN ## p +#define PORT(p) PORT ## p +#define BIT(n) (0x1U << (n)) +#define UNUSED(s) (void)(s) + +typedef unsigned char byte; +typedef unsigned short word; + +void Info(const char *fmt, ...); + +#include +#define Sleep(ms) _delay_ms(ms) + +#endif // MAD_COMMON_H diff --git a/src/main.c b/src/main.c new file mode 100644 index 0000000..341194a --- /dev/null +++ b/src/main.c @@ -0,0 +1,304 @@ +#include "common.h" + +#include +#include + +// Edge direction +#define E_RISING 1 +#define E_FALLING 0 + +// Decoder state +#define S_IDLE 0 +#define S_SYNC 1 +#define S_DATA 2 + +static volatile bool txready; // Aligned with clock? +static volatile int rxstate; // Current encoder state + +static volatile byte rxbuf[128]; // Data buffer for decoder +static volatile byte * rxhead; // Write position for decoder +static volatile byte * rxtail; // End of decoder buffer +static volatile word edgecap; // Current edge capture time +static volatile word edgedir; // Current edge direction +static volatile int lastbit; // Previously read logic value +static volatile word numsync; // Number of preamble bits read +static volatile word numdata; // Number of data bits read +static volatile bool needmid; // Expect short interval next + +static void SendByte(byte data); +static void WaitPulse(void); +static void HandleEdge(void); +static void Synchronize(void); +static void ReadPayload(void); +static void ReadShortPeriod(void); +static void ReadLongPeriod(void); +static void WriteBit(int val); + +void RF_Init(void) +{ + // Set pins for line coded data + DDR(D) |= BIT(5); // Modulator + DDR(B) &= ~BIT(5); // Demodulator + + // Calculate end of decoder buffer + rxtail = rxbuf + sizeof(rxbuf); + + // Data rate = 4000 Hz + // Baud rate = 8000 Hz (edge changes) + // Bit period = 1/f = 1/4000 = 250us + // T (mid-bit time) = 125us + + // Initialize TIMER1 to generate encoder clock pulses + // at half of bit period which equals mid-bit time T. + + TIMSK1 = 0x00; // Disable timer interrupts + TIFR1 = 0x27; // Clear all interrupt flags + TCCR1B = 0x02; // Prescale /8 = 1MHz = 1us per step + OCR1A = 125; // Generate interrupt every T steps + TCNT1 = 0; // Reset counter value to zero + TCCR1A = 0x00; // Timer not connected to port + TCCR1C = 0x00; // Do not force compare match + TIMSK1 = 0x02; // Enable compare interrupt + + // Initialize TIMER3 to interrupt when a rising edge on + // PB5 is detected and when the counter value overflows. + + TIMSK3 = 0x00; // Disable timer interrupts + TIFR3 = 0x27; // Clear all interrupt flags + TCCR3B = 0x02; // Prescale /8 = 1MHz = 1us per step + TCCR3B |= 0x40; // Trigger capture event on rising edge + OCR3A = 0; // Not using output compare interrupt + TCNT3 = 0; // Reset counter value to zero + TCCR3A = 0x00; // Timer not connected to port + TCCR3C = 0x00; // Do not force compare match + TIMSK3 = 0x20; // Enable input capture interrupt + TIMSK3 |= 0x01; // Enable overflow interrupt +} + +void RF_Transmit(const byte *data, int size) +{ + const byte *head = data; + const byte *tail = data + size; + + // The preamble with its alternating symbols is + // line coded with only the actual meat-and-potato + // transitions in the middle of the bit period and + // none of those pesky boundary transitions. This + // makes it possible for the decoder to align the + // clock phase before receiving any data. + + // Preamble for clock synchronization + SendByte(0xAA); // AA = 1010 1010 + + while (head < tail) { + SendByte(*head++); + } +} + +int RF_Receive(byte *data, int size) +{ + int n = 0; + + while (1) { + if (n == size || n == (int) numdata) { + break; // Finished copying + } + data[n] = rxbuf[n]; + n++; + } + + return n; +} + +static void SendByte(byte data) +{ + // Manchester code always has a transition at the + // middle of each bit period and may (depending on + // the information to be transmitted) have one at + // the start of the period also. The direction of + // the mid-bit transition indicates the data. + + // Transitions at the period boundaries do not carry + // information. They only place the signal in the + // correct state to allow the mid-bit transition. + + for (int bit = 0; bit < 8; bit++) { + if (data & (0x80 >> bit)) { + WaitPulse(); + PORT(D) &= ~BIT(5); + WaitPulse(); + // Rising edge + PORT(D) |= BIT(5); + } else { + WaitPulse(); + PORT(D) |= BIT(5); + WaitPulse(); + // Falling edge + PORT(D) &= ~BIT(5); + } + } +} + +static void WaitPulse(void) +{ + txready = false; + while (!txready); +} + +static void HandleEdge(void) +{ + if (edgedir != E_RISING) { + return; // Wrong edge + } + + rxstate = S_SYNC; + numsync = 1; +} + +static void Synchronize(void) +{ + // Preamble only has middle transitions + if (edgecap < 200 || edgecap > 300) { + rxstate = S_IDLE; // Wrong timing + return; + } + + numsync++; + if (numsync == 8) { + rxstate = S_DATA; + rxhead = rxbuf; + needmid = false; + numdata = 0; + lastbit = 0; + } +} + +static void ReadPayload(void) +{ + if (edgecap >= 75 && edgecap <= 175) { + ReadShortPeriod(); + return; + } + + if (edgecap >= 200 && edgecap <= 300) { + ReadLongPeriod(); + return; + } + + // Wrong timing + rxstate = S_IDLE; +} + +static void ReadShortPeriod(void) +{ + // The period length gives us enough information to + // know what the bit value is without even looking + // at the edge direction. + + if (needmid) { + WriteBit(lastbit); + needmid = false; + } else { + needmid = true; + } +} + +static void ReadLongPeriod(void) +{ + // If there was a boundary transition we must expect + // to receive another transition after mid-bit time, + // otherwise something went wrong... + + if (needmid) { + rxstate = S_IDLE; + return; + } + + lastbit = !lastbit; + WriteBit(lastbit); +} + +static void WriteBit(int val) +{ + int bit; + + if (rxhead == rxtail) { + return; // Discard + } + + bit = numdata % 8; + numdata++; + + *rxhead &= ~(0x80 >> bit); + *rxhead |= (val << (7 - bit)); + + if (bit == 7) { + rxhead++; + } +} + +// Encoder clock pulse +ISR(TIMER1_COMPA_vect) +{ + TCNT1 = 0; + txready = true; +} + +// Decoder edge capture +ISR(TIMER3_CAPT_vect) +{ + TCNT3 = 0; + edgecap = ICR3; + edgedir = (PIN(B) & BIT(5)) ? 1 : 0; + + // Must not simply toggle the edge direction bit since + // we can miss very quick edge changes and run out of + // sync with the actual port state. + + TCCR3B = (edgedir) ? 0x02 : 0x42; + + if (rxstate == S_IDLE) { + HandleEdge(); + } else if (rxstate == S_SYNC) { + Synchronize(); + } else if (rxstate == S_DATA) { + ReadPayload(); + } +} + +// Decoder overflow +ISR(TIMER3_OVF_vect) +{ + TCNT3 = 0; + edgecap = 0xFFFF; + if (rxstate == S_SYNC) { + Synchronize(); + } else if (rxstate == S_DATA) { + ReadPayload(); + } +} + +int main(void) +{ + bool running = true; + const byte testmsg[] = "FOOBAR"; + byte recvbuf[128]; + + RF_Init(); + sei(); + + while (running) { + Info("Sending phase encoded message..."); + RF_Transmit(testmsg, sizeof(testmsg)); + + Sleep(1000); + + if (RF_Receive(recvbuf, sizeof(recvbuf))) { + Info("Received message '%s'.", recvbuf); + } + + Sleep(1000); + } + + return 0; +}