Initial commit

This commit is contained in:
2024-10-18 16:17:12 +02:00
commit 6b54764b6a
10 changed files with 608 additions and 0 deletions

2
.gitignore vendored Normal file
View File

@@ -0,0 +1,2 @@
bin/
*.swp

143
Makefile Normal file
View File

@@ -0,0 +1,143 @@
.DELETE_ON_ERROR:
.SUFFIXES:
# Makefile
# Written by Leon Krieg <info@madcow.dev>
# ==============================================================================
# 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

47
docs/README.md Normal file
View File

@@ -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)
- [NyquistShannon 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)

BIN
docs/external/ATMEGA1284.pdf vendored Normal file

Binary file not shown.

BIN
docs/external/MANCHESTER-CODING.pdf vendored Normal file

Binary file not shown.

BIN
docs/external/TIMER-CAPTURE.pdf vendored Normal file

Binary file not shown.

BIN
docs/external/TIMER.pdf vendored Normal file

Binary file not shown.

91
src/common.c Normal file
View File

@@ -0,0 +1,91 @@
#include "common.h"
#include <stdio.h>
#include <avr/io.h>
#include <avr/interrupt.h>
#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);
}
}

21
src/common.h Normal file
View File

@@ -0,0 +1,21 @@
#ifndef MAD_COMMON_H
#define MAD_COMMON_H
#include <stddef.h>
#include <stdbool.h>
#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 <util/delay.h>
#define Sleep(ms) _delay_ms(ms)
#endif // MAD_COMMON_H

304
src/main.c Normal file
View File

@@ -0,0 +1,304 @@
#include "common.h"
#include <avr/io.h>
#include <avr/interrupt.h>
// 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;
}