From 05d62b594ebb5d2e9424b04182ecd1ecb30ea68b Mon Sep 17 00:00:00 2001 From: Mark Qvist Date: Wed, 3 Dec 2014 01:10:06 +0100 Subject: [PATCH] Initial commit --- Makefile | 379 ++++++++++++++++++++++++++++++++++++++ config.h | 4 + device.h | 20 ++ flash | 2 + hardware/AFSK.c | 451 ++++++++++++++++++++++++++++++++++++++++++++++ hardware/AFSK.h | 136 ++++++++++++++ hardware/Serial.c | 47 +++++ hardware/Serial.h | 20 ++ main.c | 48 +++++ protocol/AX25.c | 77 ++++++++ protocol/AX25.h | 37 ++++ protocol/HDLC.h | 8 + protocol/KISS.c | 120 ++++++++++++ protocol/KISS.h | 29 +++ util/CRC-CCIT.c | 36 ++++ util/CRC-CCIT.h | 18 ++ util/FIFO.h | 85 +++++++++ util/time.h | 34 ++++ 18 files changed, 1551 insertions(+) create mode 100644 Makefile create mode 100644 config.h create mode 100644 device.h create mode 100755 flash create mode 100644 hardware/AFSK.c create mode 100644 hardware/AFSK.h create mode 100644 hardware/Serial.c create mode 100644 hardware/Serial.h create mode 100644 main.c create mode 100644 protocol/AX25.c create mode 100644 protocol/AX25.h create mode 100644 protocol/HDLC.h create mode 100644 protocol/KISS.c create mode 100644 protocol/KISS.h create mode 100644 util/CRC-CCIT.c create mode 100644 util/CRC-CCIT.h create mode 100644 util/FIFO.h create mode 100644 util/time.h diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..61b84c8 --- /dev/null +++ b/Makefile @@ -0,0 +1,379 @@ +# AVR Sample makefile written by Eric B. Weddington, Jörg Wunsch, et al. +# Modified (bringing often-changed options to the top) by Elliot Williams + +# make all = Make software and program +# make clean = Clean out built project files. +# make program = Download the hex file to the device, using avrdude. Please +# customize the avrdude settings below first! + +# Microcontroller Type +#MCU = atmega1284p +#MCU = atmega644p +MCU = atmega328p + +# Target file name (without extension). +TARGET = images/OpenAPRS + +# Programming hardware: type avrdude -c ? +# to get a full listing. +AVRDUDE_PROGRAMMER = arduino + +AVRDUDE_PORT = /dev/usb # not really needed for usb +#AVRDUDE_PORT = /dev/parport0 # linux +# AVRDUDE_PORT = lpt1 # windows + +############# Don't need to change below here for most purposes (Elliot) + +# Optimization level, can be [0, 1, 2, 3, s]. 0 turns off optimization. +# (Note: 3 is not always the best optimization level. See avr-libc FAQ.) +OPT = s + +# Output format. (can be srec, ihex, binary) +FORMAT = ihex + +# List C source files here. (C dependencies are automatically generated.) +#SRC = $(TARGET).c +SRC = main.c hardware/Serial.c hardware/AFSK.c util/CRC-CCIT.c protocol/AX25.c protocol/KISS.c + +# If there is more than one source file, append them above, or modify and +# uncomment the following: +#SRC += foo.c bar.c + +# You can also wrap lines by appending a backslash to the end of the line: +#SRC += baz.c \ +#xyzzy.c + + + +# List Assembler source files here. +# Make them always end in a capital .S. Files ending in a lowercase .s +# will not be considered source files but generated files (assembler +# output from the compiler), and will be deleted upon "make clean"! +# Even though the DOS/Win* filesystem matches both .s and .S the same, +# it will preserve the spelling of the filenames, and gcc itself does +# care about how the name is spelled on its command-line. +ASRC = + + +# List any extra directories to look for include files here. +# Each directory must be seperated by a space. +EXTRAINCDIRS = + + +# Optional compiler flags. +# -g: generate debugging information (for GDB, or for COFF conversion) +# -O*: optimization level +# -f...: tuning, see gcc manual and avr-libc documentation +# -Wall...: warning level +# -Wa,...: tell GCC to pass this to the assembler. +# -ahlms: create assembler listing +CFLAGS = -g -O$(OPT) \ +-funsigned-char -funsigned-bitfields -fpack-struct -fshort-enums \ +-Wall -Wstrict-prototypes \ +-Wa,-adhlns=$(<:.c=.lst) \ +$(patsubst %,-I%,$(EXTRAINCDIRS)) + + +# Set a "language standard" compiler flag. +# Unremark just one line below to set the language standard to use. +# gnu99 = C99 + GNU extensions. See GCC manual for more information. +#CFLAGS += -std=c89 +#CFLAGS += -std=gnu89 +#CFLAGS += -std=c99 +CFLAGS += -std=gnu99 + + + +# Optional assembler flags. +# -Wa,...: tell GCC to pass this to the assembler. +# -ahlms: create listing +# -gstabs: have the assembler create line number information; note that +# for use in COFF files, additional information about filenames +# and function names needs to be present in the assembler source +# files -- see avr-libc docs [FIXME: not yet described there] +ASFLAGS = -Wa,-adhlns=$(<:.S=.lst),-gstabs + + + +# Optional linker flags. +# -Wl,...: tell GCC to pass this to linker. +# -Map: create map file +# --cref: add cross reference to map file +LDFLAGS = -Wl,-Map=$(TARGET).map,--cref + + + +# Additional libraries + +# Minimalistic printf version +#LDFLAGS += -Wl,-u,vfprintf -lprintf_min + +# Floating point printf version (requires -lm below) +#LDFLAGS += -Wl,-u,vfprintf -lprintf_flt + +# -lm = math library +LDFLAGS += -lm + + +# Programming support using avrdude. Settings and variables. + + +AVRDUDE_WRITE_FLASH = -U flash:w:$(TARGET).hex +#AVRDUDE_WRITE_EEPROM = -U eeprom:w:$(TARGET).eep + +AVRDUDE_FLAGS = -p $(MCU) -P $(AVRDUDE_PORT) -c $(AVRDUDE_PROGRAMMER) + +# Uncomment the following if you want avrdude's erase cycle counter. +# Note that this counter needs to be initialized first using -Yn, +# see avrdude manual. +#AVRDUDE_ERASE += -y + +# Uncomment the following if you do /not/ wish a verification to be +# performed after programming the device. +#AVRDUDE_FLAGS += -V + +# Increase verbosity level. Please use this when submitting bug +# reports about avrdude. See +# to submit bug reports. +#AVRDUDE_FLAGS += -v -v + +#Run while cable attached or don't +AVRDUDE_FLAGS += -E reset #keep chip disabled while cable attached +#AVRDUDE_FLAGS += -E noreset + +#AVRDUDE_WRITE_FLASH = -U lfuse:w:0x04:m #run with 8 Mhz clock + +#AVRDUDE_WRITE_FLASH = -U lfuse:w:0x21:m #run with 1 Mhz clock #default clock mode + +#AVRDUDE_WRITE_FLASH = -U lfuse:w:0x01:m #run with 1 Mhz clock no start up time + +# --------------------------------------------------------------------------- + +# Define programs and commands. +SHELL = sh + +CC = avr-gcc + +OBJCOPY = avr-objcopy +OBJDUMP = avr-objdump +SIZE = avr-size + + +# Programming support using avrdude. +AVRDUDE = avrdude + + +REMOVE = rm -f +COPY = cp + +HEXSIZE = $(SIZE) --target=$(FORMAT) $(TARGET).hex +ELFSIZE = $(SIZE) -C $(TARGET).elf + + + +# Define Messages +# English +MSG_ERRORS_NONE = Firmware compiled successfully! +MSG_BEGIN = Starting build... +MSG_END = -------- Done -------- +MSG_SIZE_BEFORE = Size before: +MSG_SIZE_AFTER = Size after: +MSG_COFF = Converting to AVR COFF: +MSG_EXTENDED_COFF = Converting to AVR Extended COFF: +MSG_FLASH = Creating load file for Flash: +MSG_EEPROM = Creating load file for EEPROM: +MSG_EXTENDED_LISTING = Creating Extended Listing: +MSG_SYMBOL_TABLE = Creating Symbol Table: +MSG_LINKING = Linking: +MSG_COMPILING = Compiling: +MSG_ASSEMBLING = Assembling: +MSG_CLEANING = Cleaning project: + + + + +# Define all object files. +OBJ = $(SRC:.c=.o) $(ASRC:.S=.o) + +# Define all listing files. +LST = $(ASRC:.S=.lst) $(SRC:.c=.lst) + +# Combine all necessary flags and optional flags. +# Add target processor to flags. +ALL_CFLAGS = -mmcu=$(MCU) -I. $(CFLAGS) +ALL_ASFLAGS = -mmcu=$(MCU) -I. -x assembler-with-cpp $(ASFLAGS) + + + +# Default target: make program! +#all: begin gccversion sizebefore $(TARGET).elf $(TARGET).hex $(TARGET).eep \ +# $(TARGET).lss $(TARGET).sym sizeafter finished end + +all: begin $(TARGET).elf $(TARGET).hex $(TARGET).eep \ + $(TARGET).lss $(TARGET).sym cleanup sizeafter finished +# $(AVRDUDE) $(AVRDUDE_FLAGS) $(AVRDUDE_WRITE_FLASH) $(AVRDUDE_WRITE_EEPROM) + +# Eye candy. +# AVR Studio 3.x does not check make's exit code but relies on +# the following magic strings to be generated by the compile job. +begin: + @echo + @echo $(MSG_BEGIN) + +finished: + @echo $(MSG_ERRORS_NONE) + +end: + @echo $(MSG_END) + @echo + + +# Display size of file. +sizebefore: + @if [ -f $(TARGET).elf ]; then echo; echo $(MSG_SIZE_BEFORE); $(ELFSIZE); echo; fi + +sizeafter: + @if [ -f $(TARGET).elf ]; then echo; $(ELFSIZE); echo; fi + + + +# Display compiler version information. +gccversion : + @$(CC) --version + + + + +# Convert ELF to COFF for use in debugging / simulating in +# AVR Studio or VMLAB. +COFFCONVERT=$(OBJCOPY) --debugging \ + --change-section-address .data-0x800000 \ + --change-section-address .bss-0x800000 \ + --change-section-address .noinit-0x800000 \ + --change-section-address .eeprom-0x810000 + + +coff: $(TARGET).elf +# @echo +# @echo $(MSG_COFF) $(TARGET).cof + @$(COFFCONVERT) -O coff-avr $< $(TARGET).cof + + +extcoff: $(TARGET).elf +# @echo +# @echo $(MSG_EXTENDED_COFF) $(TARGET).cof + @$(COFFCONVERT) -O coff-ext-avr $< $(TARGET).cof + + + + +# Program the device. +program: $(TARGET).hex $(TARGET).eep + @$(AVRDUDE) $(AVRDUDE_FLAGS) $(AVRDUDE_WRITE_FLASH) $(AVRDUDE_WRITE_EEPROM) + + + + +# Create final output files (.hex, .eep) from ELF output file. +%.hex: %.elf +# @echo +# @echo $(MSG_FLASH) $@ + @$(OBJCOPY) -O $(FORMAT) -R .eeprom $< $@ + +%.eep: %.elf +# @echo +# @echo $(MSG_EEPROM) $@ +# @echo Not generating any EEPROM images + @-$(OBJCOPY) -j .eeprom --set-section-flags=.eeprom="alloc,load" --change-section-lma .eeprom=0 -O $(FORMAT) $< $@ + +# Create extended listing file from ELF output file. +%.lss: %.elf +# @echo +# @echo $(MSG_EXTENDED_LISTING) $@ + @$(OBJDUMP) -h -S $< > $@ + +# Create a symbol table from ELF output file. +%.sym: %.elf +# @echo +# @echo $(MSG_SYMBOL_TABLE) $@ + @avr-nm -n $< > $@ + + + +# Link: create ELF output file from object files. +.SECONDARY : $(TARGET).elf +.PRECIOUS : $(OBJ) +%.elf: $(OBJ) + @echo $(MSG_LINKING) $@ + @$(CC) $(ALL_CFLAGS) $(OBJ) --output $@ $(LDFLAGS) + + +# Compile: create object files from C source files. +%.o : %.c + @echo $(MSG_COMPILING) $< + @$(CC) -c $(ALL_CFLAGS) $< -o $@ + + +# Compile: create assembler files from C source files. +%.s : %.c + @$(CC) -S $(ALL_CFLAGS) $< -o $@ + + +# Assemble: create object files from assembler source files. +%.o : %.S + @echo + @echo $(MSG_ASSEMBLING) $< + @$(CC) -c $(ALL_ASFLAGS) $< -o $@ + + + +# Target: clean project. +clean: clean_list finished + +clean_list : + @echo + @echo $(MSG_CLEANING) + $(REMOVE) $(TARGET).hex + $(REMOVE) $(TARGET).eep + $(REMOVE) $(TARGET).obj + $(REMOVE) $(TARGET).cof + $(REMOVE) $(TARGET).elf + $(REMOVE) $(TARGET).map + $(REMOVE) $(TARGET).obj + $(REMOVE) $(TARGET).a90 + $(REMOVE) $(TARGET).sym + $(REMOVE) $(TARGET).lnk + $(REMOVE) $(TARGET).lss + $(REMOVE) $(OBJ) + $(REMOVE) $(LST) + $(REMOVE) $(SRC:.c=.s) + $(REMOVE) $(SRC:.c=.d) + $(REMOVE) *~ + +cleanup: + @$(REMOVE) $(SRC:.c=.s) + @$(REMOVE) $(SRC:.c=.d) + @$(REMOVE) $(LST) + +# Automatically generate C source code dependencies. +# (Code originally taken from the GNU make user manual and modified +# (See README.txt Credits).) +# +# Note that this will work with sh (bash) and sed that is shipped with WinAVR +# (see the SHELL variable defined above). +# This may not work with other shells or other seds. +# +%.d: %.c + @set -e; $(CC) -MM $(ALL_CFLAGS) $< \ + | sed 's,\(.*\)\.o[ :]*,\1.o \1.d : ,g' > $@; \ + [ -s $@ ] || rm -f $@ + + +# Remove the '-' if you want to see the dependency files generated. +-include $(SRC:.c=.d) + + + +# Listing of phony targets. +.PHONY : all begin finish end sizebefore sizeafter gccversion coff extcoff \ + clean clean_list program diff --git a/config.h b/config.h new file mode 100644 index 0000000..86203cf --- /dev/null +++ b/config.h @@ -0,0 +1,4 @@ +#ifndef CONFIG_H +#define CONFIG_H + +#endif \ No newline at end of file diff --git a/device.h b/device.h new file mode 100644 index 0000000..b0f6dc7 --- /dev/null +++ b/device.h @@ -0,0 +1,20 @@ +#ifndef DEVICE_CONFIGURATION +#define DEVICE_CONFIGURATION + +// CPU settings +#define F_CPU 16000000 +#define FREQUENCY_CORRECTION 0 + +// Sampling & timer setup +#define CONFIG_AFSK_DAC_SAMPLERATE 9600 + +// Serial settings +#define BAUD 9600 + +// Port settings +#define DAC_PORT PORTB +#define DAC_DDR DDRB +#define ADC_PORT PORTC +#define ADC_DDR DDRC + +#endif \ No newline at end of file diff --git a/flash b/flash new file mode 100755 index 0000000..e05c4d8 --- /dev/null +++ b/flash @@ -0,0 +1,2 @@ +#!/bin/bash +avrdude -p $2 -c arduino -P /dev/tty$1 -b 115200 -F -U flash:w:images/OpenAPRS.hex diff --git a/hardware/AFSK.c b/hardware/AFSK.c new file mode 100644 index 0000000..a93f80a --- /dev/null +++ b/hardware/AFSK.c @@ -0,0 +1,451 @@ +#include +#include "AFSK.h" +#include "util/time.h" + +extern volatile ticks_t _clock; +extern unsigned long custom_preamble; +extern unsigned long custom_tail; + +bool hw_afsk_dac_isr = false; +Afsk *AFSK_modem; + +// Forward declerations +int afsk_getchar(void); +void afsk_putchar(char c); + +void AFSK_hw_init(void) { + // Disable interrupts while we set up everything + cli(); + + // Set up ADC + TCCR1A = 0; + TCCR1B = _BV(CS10) | _BV(WGM13) | _BV(WGM12); + ICR1 = (((CPU_FREQ+FREQUENCY_CORRECTION)) / 9600) - 1; + + // TODO: Implement reference detection + ADMUX = _BV(REFS0) | 0; + ADC_DDR &= ~_BV(0); + ADC_PORT &= ~_BV(0); + DIDR0 |= _BV(0); + ADCSRB = _BV(ADTS2) | + _BV(ADTS1) | + _BV(ADTS0); + ADCSRA = _BV(ADEN) | + _BV(ADSC) | + _BV(ADATE)| + _BV(ADIE) | + _BV(ADPS2); + + // Enable interrupts - starts DAC/ADC + sei(); + AFSK_DAC_INIT(); + LED_TX_INIT(); + LED_RX_INIT(); +} + +void AFSK_init(Afsk *afsk) { + // Allocate modem struct memory + memset(afsk, 0, sizeof(*afsk)); + AFSK_modem = afsk; + // Set phase increment + afsk->phaseInc = MARK_INC; + // Initialise FIFO buffers + fifo_init(&afsk->delayFifo, (uint8_t *)afsk->delayBuf, sizeof(afsk->delayBuf)); + fifo_init(&afsk->rxFifo, afsk->rxBuf, sizeof(afsk->rxBuf)); + fifo_init(&afsk->txFifo, afsk->txBuf, sizeof(afsk->txBuf)); + + // Set up streams + FILE afsk_fd = FDEV_SETUP_STREAM(afsk_putchar, afsk_getchar, _FDEV_SETUP_RW); + afsk->fd = afsk_fd; + + AFSK_hw_init(); +} + +static void AFSK_txStart(Afsk *afsk) { + if (!afsk->sending) { + afsk->phaseInc = MARK_INC; + afsk->phaseAcc = 0; + afsk->bitstuffCount = 0; + afsk->sending = true; + LED_TX_ON(); + afsk->preambleLength = DIV_ROUND(custom_preamble * BITRATE, 8000); + AFSK_DAC_IRQ_START(); + } + ATOMIC_BLOCK(ATOMIC_RESTORESTATE) { + afsk->tailLength = DIV_ROUND(custom_tail * BITRATE, 8000); + } +} + +void afsk_putchar(char c) { + AFSK_txStart(AFSK_modem); + while(fifo_isfull_locked(&AFSK_modem->txFifo)) { /* Wait */ } + fifo_push_locked(&AFSK_modem->txFifo, c); +} + +int afsk_getchar(void) { + if (fifo_isempty_locked(&AFSK_modem->rxFifo)) { + return EOF; + } else { + return fifo_pop_locked(&AFSK_modem->rxFifo); + } +} + +void AFSK_transmit(char *buffer, size_t size) { + fifo_flush(&AFSK_modem->txFifo); + int i = 0; + while (size--) { + afsk_putchar(buffer[i++]); + } +} + +uint8_t AFSK_dac_isr(Afsk *afsk) { + if (afsk->sampleIndex == 0) { + if (afsk->txBit == 0) { + if (fifo_isempty(&afsk->txFifo) && afsk->tailLength == 0) { + AFSK_DAC_IRQ_STOP(); + afsk->sending = false; + LED_TX_OFF(); + return 0; + } else { + if (!afsk->bitStuff) afsk->bitstuffCount = 0; + afsk->bitStuff = true; + if (afsk->preambleLength == 0) { + if (fifo_isempty(&afsk->txFifo)) { + afsk->tailLength--; + afsk->currentOutputByte = HDLC_FLAG; + } else { + afsk->currentOutputByte = fifo_pop(&afsk->txFifo); + } + } else { + afsk->preambleLength--; + afsk->currentOutputByte = HDLC_FLAG; + } + if (afsk->currentOutputByte == AX25_ESC) { + if (fifo_isempty(&afsk->txFifo)) { + AFSK_DAC_IRQ_STOP(); + afsk->sending = false; + LED_TX_OFF(); + return 0; + } else { + afsk->currentOutputByte = fifo_pop(&afsk->txFifo); + } + } else if (afsk->currentOutputByte == HDLC_FLAG || afsk->currentOutputByte == HDLC_RESET) { + afsk->bitStuff = false; + } + } + afsk->txBit = 0x01; + } + + if (afsk->bitStuff && afsk->bitstuffCount >= BIT_STUFF_LEN) { + afsk->bitstuffCount = 0; + afsk->phaseInc = SWITCH_TONE(afsk->phaseInc); + } else { + if (afsk->currentOutputByte & afsk->txBit) { + afsk->bitstuffCount++; + } else { + afsk->bitstuffCount = 0; + afsk->phaseInc = SWITCH_TONE(afsk->phaseInc); + } + afsk->txBit <<= 1; + } + + afsk->sampleIndex = SAMPLESPERBIT; + } + + afsk->phaseAcc += afsk->phaseInc; + afsk->phaseAcc %= SIN_LEN; + afsk->sampleIndex--; + + return sinSample(afsk->phaseAcc); +} + +static bool hdlcParse(Hdlc *hdlc, bool bit, FIFOBuffer *fifo) { + // Initialise a return value. We start with the + // assumption that all is going to end well :) + bool ret = true; + + // Bitshift our byte of demodulated bits to + // the left by one bit, to make room for the + // next incoming bit + hdlc->demodulatedBits <<= 1; + // And then put the newest bit from the + // demodulator into the byte. + hdlc->demodulatedBits |= bit ? 1 : 0; + + // Now we'll look at the last 8 received bits, and + // check if we have received a HDLC flag (01111110) + if (hdlc->demodulatedBits == HDLC_FLAG) { + // If we have, check that our output buffer is + // not full. + if (!fifo_isfull(fifo)) { + // If it isn't, we'll push the HDLC_FLAG into + // the buffer and indicate that we are now + // receiving data. For bling we also turn + // on the RX LED. + fifo_push(fifo, HDLC_FLAG); + hdlc->receiving = true; + LED_RX_ON(); + } else { + // If the buffer is full, we have a problem + // and abort by setting the return value to msg.len = ctx->frm_len - 2 - (buf - ctx->buf); + + // false and stopping the here. + ret = false; + hdlc->receiving = false; + LED_RX_OFF(); + } + + // Everytime we receive a HDLC_FLAG, we reset the + // storage for our current incoming byte and bit + // position in that byte. This effectively + // synchronises our parsing to the start and end + // of the received bytes. + hdlc->currentByte = 0; + hdlc->bitIndex = 0; + return ret; + } + + // Check if we have received a RESET flag (01111111) + // In this comparison we also detect when no transmission + // (or silence) is taking place, and the demodulator + // returns an endless stream of zeroes. Due to the NRZ + // coding, the actual bits send to this function will + // be an endless stream of ones, which this AND operation + // will also detect. + if ((hdlc->demodulatedBits & HDLC_RESET) == HDLC_RESET) { + // If we have, something probably went wrong at the + // transmitting end, and we abort the reception. + hdlc->receiving = false; + LED_RX_OFF(); + return ret; + } + + // If we have not yet seen a HDLC_FLAG indicating that + // a transmission is actually taking place, don't bother + // with anything. + if (!hdlc->receiving) + return ret; + + // First check if what we are seeing is a stuffed bit. + // Since the different HDLC control characters like + // HDLC_FLAG, HDLC_RESET and such could also occur in + // a normal data stream, we employ a method known as + // "bit stuffing". All control characters have more than + // 5 ones in a row, so if the transmitting party detects + // this sequence in the _data_ to be transmitted, it inserts + // a zero to avoid the receiving party interpreting it as + // a control character. Therefore, if we detect such a + // "stuffed bit", we simply ignore it and wait for the + // next bit to come in. + // + // We do the detection by applying an AND bit-mask to the + // stream of demodulated bits. This mask is 00111111 (0x3f) + // if the result of the operation is 00111110 (0x3e), we + // have detected a stuffed bit. + if ((hdlc->demodulatedBits & 0x3f) == 0x3e) + return ret; + + // If we have an actual 1 bit, push this to the current byte + // If it's a zero, we don't need to do anything, since the + // bit is initialized to zero when we bitshifted earlier. + if (hdlc->demodulatedBits & 0x01) + hdlc->currentByte |= 0x80; + + // Increment the bitIndex and check if we have a complete byte + if (++hdlc->bitIndex >= 8) { + // If we have a HDLC control character, put a AX.25 escape + // in the received data. We know we need to do this, + // because at this point we must have already seen a HDLC + // flag, meaning that this control character is the result + // of a bitstuffed byte that is equal to said control + // character, but is actually part of the data stream. + // By inserting the escape character, we tell the protocol + // layer that this is not an actual control character, but + // data. + if ((hdlc->currentByte == HDLC_FLAG || + hdlc->currentByte == HDLC_RESET || + hdlc->currentByte == AX25_ESC)) { + // We also need to check that our received data buffer + // is not full before putting more data in + if (!fifo_isfull(fifo)) { + fifo_push(fifo, AX25_ESC); + } else { + // If it is, abort and return false + hdlc->receiving = false; + LED_RX_OFF(); + ret = false; + } + } + + // Push the actual byte to the received data FIFO, + // if it isn't full. + if (!fifo_isfull(fifo)) { + fifo_push(fifo, hdlc->currentByte); + } else { + // If it is, well, you know by now! + hdlc->receiving = false; + LED_RX_OFF(); + ret = false; + } + + // Wipe received byte and reset bit index to 0 + hdlc->currentByte = 0; + hdlc->bitIndex = 0; + + } else { + // We don't have a full byte yet, bitshift the byte + // to make room for the next bit + hdlc->currentByte >>= 1; + } + + //digitalWrite(13, LOW); + return ret; +} + + +void AFSK_adc_isr(Afsk *afsk, int8_t currentSample) { + // To determine the received frequency, and thereby + // the bit of the sample, we multiply the sample by + // a sample delayed by (samples per bit / 2). + // We then lowpass-filter the samples with a + // Chebyshev filter. The lowpass filtering serves + // to "smooth out" the variations in the samples. + + afsk->iirX[0] = afsk->iirX[1]; + afsk->iirX[1] = ((int8_t)fifo_pop(&afsk->delayFifo) * currentSample) >> 2; + + afsk->iirY[0] = afsk->iirY[1]; + + afsk->iirY[1] = afsk->iirX[0] + afsk->iirX[1] + (afsk->iirY[0] >> 1); // Chebyshev filter + + + // We put the sampled bit in a delay-line: + // First we bitshift everything 1 left + afsk->sampledBits <<= 1; + // And then add the sampled bit to our delay line + afsk->sampledBits |= (afsk->iirY[1] > 0) ? 1 : 0; + + // Put the current raw sample in the delay FIFO + fifo_push(&afsk->delayFifo, currentSample); + + // We need to check whether there is a signal transition. + // If there is, we can recalibrate the phase of our + // sampler to stay in sync with the transmitter. A bit of + // explanation is required to understand how this works. + // Since we have PHASE_MAX/PHASE_BITS = 8 samples per bit, + // we employ a phase counter (currentPhase), that increments + // by PHASE_BITS everytime a sample is captured. When this + // counter reaches PHASE_MAX, it wraps around by modulus + // PHASE_MAX. We then look at the last three samples we + // captured and determine if the bit was a one or a zero. + // + // This gives us a "window" looking into the stream of + // samples coming from the ADC. Sort of like this: + // + // Past Future + // 0000000011111111000000001111111100000000 + // |________| + // || + // Window + // + // Every time we detect a signal transition, we adjust + // where this window is positioned little. How much we + // adjust it is defined by PHASE_INC. If our current phase + // phase counter value is less than half of PHASE_MAX (ie, + // the window size) when a signal transition is detected, + // add PHASE_INC to our phase counter, effectively moving + // the window a little bit backward (to the left in the + // illustration), inversely, if the phase counter is greater + // than half of PHASE_MAX, we move it forward a little. + // This way, our "window" is constantly seeking to position + // it's center at the bit transitions. Thus, we synchronise + // our timing to the transmitter, even if it's timing is + // a little off compared to our own. + if (SIGNAL_TRANSITIONED(afsk->sampledBits)) { + if (afsk->currentPhase < PHASE_THRESHOLD) { + afsk->currentPhase += PHASE_INC; + } else { + afsk->currentPhase -= PHASE_INC; + } + } + + // We increment our phase counter + afsk->currentPhase += PHASE_BITS; + + // Check if we have reached the end of + // our sampling window. + if (afsk->currentPhase >= PHASE_MAX) { + // If we have, wrap around our phase + // counter by modulus + afsk->currentPhase %= PHASE_MAX; + + // Bitshift to make room for the next + // bit in our stream of demodulated bits + afsk->actualBits <<= 1; + + // We determine the actual bit value by reading + // the last 3 sampled bits. If there is three or + // more 1's, we will assume that the transmitter + // sent us a one, otherwise we assume a zero + uint8_t bits = afsk->sampledBits & 0x07; + if (bits == 0x07 || // 111 + bits == 0x06 || // 110 + bits == 0x05 || // 101 + bits == 0x03 // 011 + ) { + afsk->actualBits |= 1; + } + + //// Alternative using five bits //////////////// + // uint8_t bits = afsk->sampledBits & 0x0f; + // uint8_t c = 0; + // c += bits & BV(1); + // c += bits & BV(2); + // c += bits & BV(3); + // c += bits & BV(4); + // c += bits & BV(5); + // if (c >= 3) afsk->actualBits |= 1; + ///////////////////////////////////////////////// + + // Now we can pass the actual bit to the HDLC parser. + // We are using NRZ coding, so if 2 consecutive bits + // have the same value, we have a 1, otherwise a 0. + // We use the TRANSITION_FOUND function to determine this. + // + // This is smart in combination with bit stuffing, + // since it ensures a transmitter will never send more + // than five consecutive 1's. When sending consecutive + // ones, the signal stays at the same level, and if + // this happens for longer periods of time, we would + // not be able to synchronize our phase to the transmitter + // and would start experiencing "bit slip". + // + // By combining bit-stuffing with NRZ coding, we ensure + // that the signal will regularly make transitions + // that we can use to synchronize our phase. + // + // We also check the return of the Link Control parser + // to check if an error occured. + + if (!hdlcParse(&afsk->hdlc, !TRANSITION_FOUND(afsk->actualBits), &afsk->rxFifo)) { + afsk->status |= 1; + if (fifo_isfull(&afsk->rxFifo)) { + fifo_flush(&afsk->rxFifo); + afsk->status = 0; + } + } + } + +} + + +ISR(ADC_vect) { + ++_clock; + TIFR1 = _BV(ICF1); + AFSK_adc_isr(AFSK_modem, ((int16_t)((ADC) >> 2) - 128)); + if (hw_afsk_dac_isr) { + DAC_PORT = (AFSK_dac_isr(AFSK_modem) & 0xF0) | (DAC_PORT & 0x0F); + } else { + DAC_PORT = 128 | (DAC_PORT & 0x0F); + } +} \ No newline at end of file diff --git a/hardware/AFSK.h b/hardware/AFSK.h new file mode 100644 index 0000000..d1186fe --- /dev/null +++ b/hardware/AFSK.h @@ -0,0 +1,136 @@ +#ifndef AFSK_H +#define AFSK_H + +#include "device.h" +#include +#include +#include +#include +#include "util/FIFO.h" +#include "util/time.h" +#include "protocol/HDLC.h" + +#define SIN_LEN 512 +static const uint8_t sin_table[] PROGMEM = +{ + 128, 129, 131, 132, 134, 135, 137, 138, 140, 142, 143, 145, 146, 148, 149, 151, + 152, 154, 155, 157, 158, 160, 162, 163, 165, 166, 167, 169, 170, 172, 173, 175, + 176, 178, 179, 181, 182, 183, 185, 186, 188, 189, 190, 192, 193, 194, 196, 197, + 198, 200, 201, 202, 203, 205, 206, 207, 208, 210, 211, 212, 213, 214, 215, 217, + 218, 219, 220, 221, 222, 223, 224, 225, 226, 227, 228, 229, 230, 231, 232, 233, + 234, 234, 235, 236, 237, 238, 238, 239, 240, 241, 241, 242, 243, 243, 244, 245, + 245, 246, 246, 247, 248, 248, 249, 249, 250, 250, 250, 251, 251, 252, 252, 252, + 253, 253, 253, 253, 254, 254, 254, 254, 254, 255, 255, 255, 255, 255, 255, 255, +}; + +inline static uint8_t sinSample(uint16_t i) { + uint16_t newI = i % (SIN_LEN/2); + newI = (newI >= (SIN_LEN/4)) ? (SIN_LEN/2 - newI -1) : newI; + uint8_t sine = pgm_read_byte(&sin_table[newI]); + return (i >= (SIN_LEN/2)) ? (255 - sine) : sine; +} + + +#define SWITCH_TONE(inc) (((inc) == MARK_INC) ? SPACE_INC : MARK_INC) +#define BITS_DIFFER(bits1, bits2) (((bits1)^(bits2)) & 0x01) +#define DUAL_XOR(bits1, bits2) ((((bits1)^(bits2)) & 0x03) == 0x03) +#define SIGNAL_TRANSITIONED(bits) DUAL_XOR((bits), (bits) >> 2) +#define TRANSITION_FOUND(bits) BITS_DIFFER((bits), (bits) >> 1) + +#define CPU_FREQ F_CPU + +#define CONFIG_AFSK_RX_BUFLEN 64 +#define CONFIG_AFSK_TX_BUFLEN 64 +#define CONFIG_AFSK_RXTIMEOUT 0 +#define CONFIG_AFSK_PREAMBLE_LEN 150UL +#define CONFIG_AFSK_TRAILER_LEN 50UL +#define SAMPLERATE 9600 +#define BITRATE 1200 +#define SAMPLESPERBIT (SAMPLERATE / BITRATE) +#define BIT_STUFF_LEN 5 +#define MARK_FREQ 1200 +#define SPACE_FREQ 2200 +#define PHASE_BITS 8 // How much to increment phase counter each sample +#define PHASE_INC 1 // Nudge by an eigth of a sample each adjustment +#define PHASE_MAX (SAMPLESPERBIT * PHASE_BITS) // Resolution of our phase counter = 64 +#define PHASE_THRESHOLD (PHASE_MAX / 2) // Target transition point of our phase window + + +typedef struct Hdlc +{ + uint8_t demodulatedBits; + uint8_t bitIndex; + uint8_t currentByte; + bool receiving; +} Hdlc; + +typedef struct Afsk +{ + // Stream access to modem + FILE fd; + + // General values + Hdlc hdlc; // We need a link control structure + uint16_t preambleLength; // Length of sync preamble + uint16_t tailLength; // Length of transmission tail + + // Modulation values + uint8_t sampleIndex; // Current sample index for outgoing bit + uint8_t currentOutputByte; // Current byte to be modulated + uint8_t txBit; // Mask of current modulated bit + bool bitStuff; // Whether bitstuffing is allowed + + uint8_t bitstuffCount; // Counter for bit-stuffing + + uint16_t phaseAcc; // Phase accumulator + uint16_t phaseInc; // Phase increment per sample + + FIFOBuffer txFifo; // FIFO for transmit data + uint8_t txBuf[CONFIG_AFSK_TX_BUFLEN]; // Actial data storage for said FIFO + + volatile bool sending; // Set when modem is sending + + // Demodulation values + FIFOBuffer delayFifo; // Delayed FIFO for frequency discrimination + int8_t delayBuf[SAMPLESPERBIT / 2 + 1]; // Actual data storage for said FIFO + + FIFOBuffer rxFifo; // FIFO for received data + uint8_t rxBuf[CONFIG_AFSK_RX_BUFLEN]; // Actual data storage for said FIFO + + int16_t iirX[2]; // IIR Filter X cells + int16_t iirY[2]; // IIR Filter Y cells + + uint8_t sampledBits; // Bits sampled by the demodulator (at ADC speed) + int8_t currentPhase; // Current phase of the demodulator + uint8_t actualBits; // Actual found bits at correct bitrate + + volatile int status; // Status of the modem, 0 means OK + +} Afsk; + +#define DIV_ROUND(dividend, divisor) (((dividend) + (divisor) / 2) / (divisor)) +#define MARK_INC (uint16_t)(DIV_ROUND(SIN_LEN * (uint32_t)MARK_FREQ, CONFIG_AFSK_DAC_SAMPLERATE)) +#define SPACE_INC (uint16_t)(DIV_ROUND(SIN_LEN * (uint32_t)SPACE_FREQ, CONFIG_AFSK_DAC_SAMPLERATE)) + +#define AFSK_DAC_IRQ_START() do { extern bool hw_afsk_dac_isr; hw_afsk_dac_isr = true; } while (0) +#define AFSK_DAC_IRQ_STOP() do { extern bool hw_afsk_dac_isr; hw_afsk_dac_isr = false; } while (0) +#define AFSK_DAC_INIT() do { DAC_DDR |= 0xF0; } while (0) + +// Here's some macros for controlling the RX/TX LEDs +// THE _INIT() functions writes to the DDRB register +// to configure the pins as output pins, and the _ON() +// and _OFF() functions writes to the PORT registers +// to turn the pins on or off. +#define LED_TX_INIT() do { DAC_DDR |= _BV(1); } while (0) +#define LED_TX_ON() do { DAC_PORT |= _BV(1); } while (0) +#define LED_TX_OFF() do { DAC_PORT &= ~_BV(1); } while (0) + +#define LED_RX_INIT() do { DAC_DDR |= _BV(2); } while (0) +#define LED_RX_ON() do { DAC_PORT |= _BV(2); } while (0) +#define LED_RX_OFF() do { DAC_PORT &= ~_BV(2); } while (0) + +void AFSK_init(Afsk *afsk); +void AFSK_transmit(char *buffer, size_t size); +void AFSK_poll(Afsk *afsk); + +#endif \ No newline at end of file diff --git a/hardware/Serial.c b/hardware/Serial.c new file mode 100644 index 0000000..fa90d11 --- /dev/null +++ b/hardware/Serial.c @@ -0,0 +1,47 @@ +#include "Serial.h" +#include +#include +#include + +void serial_init(Serial *serial) { + memset(serial, 0, sizeof(*serial)); + UBRR0H = UBRRH_VALUE; + UBRR0L = UBRRL_VALUE; + + #if USE_2X + UCSR0A |= _BV(U2X0); + #else + UCSR0A &= ~(_BV(U2X0)); + #endif + + // Set to 8-bit data, enable RX and TX + UCSR0C = _BV(UCSZ01) | _BV(UCSZ00); + UCSR0B = _BV(RXEN0) | _BV(TXEN0); + + FILE uart0_fd = FDEV_SETUP_STREAM(uart0_putchar, uart0_getchar, _FDEV_SETUP_RW); + + serial->uart0 = uart0_fd; +} + +bool serial_available(uint8_t index) { + if (index == 0) { + if (UCSR0A & _BV(RXC0)) return true; + } + return false; +} + + +void uart0_putchar(char c) { + loop_until_bit_is_set(UCSR0A, UDRE0); + UDR0 = c; +} + +char uart0_getchar(void) { + loop_until_bit_is_set(UCSR0A, RXC0); + return UDR0; +} + +char uart0_getchar_nowait(void) { + if (!(UCSR0A & _BV(RXC0))) return EOF; + return UDR0; +} \ No newline at end of file diff --git a/hardware/Serial.h b/hardware/Serial.h new file mode 100644 index 0000000..7671c28 --- /dev/null +++ b/hardware/Serial.h @@ -0,0 +1,20 @@ +#ifndef SERIAL_H +#define SERIAL_H + +#include "device.h" + +#include +#include +#include + +typedef struct Serial { + FILE uart0; +} Serial; + +void serial_init(Serial *serial); +bool serial_available(uint8_t index); +void uart0_putchar(char c); +char uart0_getchar(void); +char uart0_getchar_nowait(void); + +#endif \ No newline at end of file diff --git a/main.c b/main.c new file mode 100644 index 0000000..6c9861a --- /dev/null +++ b/main.c @@ -0,0 +1,48 @@ +#include +#include + +#include "device.h" +#include "util/FIFO.h" +#include "hardware/AFSK.h" +#include "hardware/Serial.h" +#include "protocol/AX25.h" +#include "protocol/KISS.h" + +#define FEND 0xC0 +#define FESC 0xDB +#define TFEND 0xDC +#define TFESC 0xDD + +Serial serial; +Afsk modem; +AX25Ctx AX25; + +static void ax25_callback(struct AX25Ctx *ctx) { + kiss_messageCallback(ctx); +} + +void init(void) { + AFSK_init(&modem); + serial_init(&serial); + ax25_init(&AX25, &modem.fd, ax25_callback); + kiss_init(&AX25, &modem, &serial); + + stdout = &serial.uart0; + stdin = &serial.uart0; +} + +int main (void) { + init(); + + while (true) { + ax25_poll(&AX25); + + if (serial_available(0)) { + char sbyte = uart0_getchar_nowait(); + kiss_serialCallback(sbyte); + } + + } + + return(0); +} \ No newline at end of file diff --git a/protocol/AX25.c b/protocol/AX25.c new file mode 100644 index 0000000..eff1bed --- /dev/null +++ b/protocol/AX25.c @@ -0,0 +1,77 @@ +#include +#include "AX25.h" +#include "protocol/HDLC.h" +#include "util/CRC-CCIT.h" +#include "../hardware/AFSK.h" + +void ax25_init(AX25Ctx *ctx, FILE *channel, ax25_callback_t hook) { + memset(ctx, 0, sizeof(*ctx)); + ctx->ch = channel; + ctx->hook = hook; + ctx->crc_in = ctx->crc_out = CRC_CCIT_INIT_VAL; +} + +static void ax25_decode(AX25Ctx *ctx) { + #if SERIAL_PROTOCOL == PROTOCOL_KISS + if (ctx->hook) ctx->hook(ctx); + #endif +} + +void ax25_poll(AX25Ctx *ctx) { + int c; + + while ((c = fgetc(ctx->ch)) != EOF) { + if (!ctx->escape && c == HDLC_FLAG) { + if (ctx->frame_len >= AX25_MIN_FRAME_LEN) { + if (ctx->crc_in == AX25_CRC_CORRECT) { + ax25_decode(ctx); + } + } + ctx->sync = true; + ctx->crc_in = CRC_CCIT_INIT_VAL; + ctx->frame_len = 0; + continue; + } + + if (!ctx->escape && c == HDLC_RESET) { + ctx->sync = false; + continue; + } + + if (!ctx->escape && c == AX25_ESC) { + ctx->escape = true; + continue; + } + + if (ctx->sync) { + if (ctx->frame_len < AX25_MAX_FRAME_LEN) { + ctx->buf[ctx->frame_len++] = c; + ctx->crc_in = update_crc_ccit(c, ctx->crc_in); + } else { + ctx->sync = false; + } + } + ctx->escape = false; + } +} + +static void ax25_putchar(AX25Ctx *ctx, uint8_t c) +{ + if (c == HDLC_FLAG || c == HDLC_RESET || c == AX25_ESC) fputc(AX25_ESC, ctx->ch); + ctx->crc_out = update_crc_ccit(c, ctx->crc_out); + fputc(c, ctx->ch); +} + +void ax25_sendRaw(AX25Ctx *ctx, void *_buf, size_t len) { + ctx->crc_out = CRC_CCIT_INIT_VAL; + fputc(HDLC_FLAG, ctx->ch); + const uint8_t *buf = (const uint8_t *)_buf; + while (len--) ax25_putchar(ctx, *buf++); + + uint8_t crcl = (ctx->crc_out & 0xff) ^ 0xff; + uint8_t crch = (ctx->crc_out >> 8) ^ 0xff; + ax25_putchar(ctx, crcl); + ax25_putchar(ctx, crch); + + fputc(HDLC_FLAG, ctx->ch); +} \ No newline at end of file diff --git a/protocol/AX25.h b/protocol/AX25.h new file mode 100644 index 0000000..98b210d --- /dev/null +++ b/protocol/AX25.h @@ -0,0 +1,37 @@ +#ifndef PROTOCOL_AX25_H +#define PROTOCOL_AX25_H + +#include +#include + +#define AX25_MIN_FRAME_LEN 18 +#define AX25_MAX_FRAME_LEN 850 + +#define AX25_CRC_CORRECT 0xF0B8 + +#define AX25_CTRL_UI 0x03 +#define AX25_PID_NOLAYER3 0xF0 + +struct AX25Ctx; // Forward declaration + +#if SERIAL_PROTOCOL == PROTOCOL_KISS + typedef void (*ax25_callback_t)(struct AX25Ctx *ctx); +#endif + +typedef struct AX25Ctx +{ + uint8_t buf[AX25_MAX_FRAME_LEN]; + FILE *ch; + size_t frame_len; + uint16_t crc_in; + uint16_t crc_out; + ax25_callback_t hook; + bool sync; + bool escape; +} AX25Ctx; + +void ax25_poll(AX25Ctx *ctx); +void ax25_sendRaw(AX25Ctx *ctx, void *_buf, size_t len); +void ax25_init(AX25Ctx *ctx, FILE *channel, ax25_callback_t hook); + +#endif \ No newline at end of file diff --git a/protocol/HDLC.h b/protocol/HDLC.h new file mode 100644 index 0000000..3ee0808 --- /dev/null +++ b/protocol/HDLC.h @@ -0,0 +1,8 @@ +#ifndef PROTOCOL_HDLC_H +#define PROTOCOL_HDLC_H + +#define HDLC_FLAG 0x7E +#define HDLC_RESET 0x7F +#define AX25_ESC 0x1B + +#endif \ No newline at end of file diff --git a/protocol/KISS.c b/protocol/KISS.c new file mode 100644 index 0000000..8ea100d --- /dev/null +++ b/protocol/KISS.c @@ -0,0 +1,120 @@ +#include +#include + +#include "device.h" +#include "KISS.h" + +static uint8_t serialBuffer[AX25_MAX_FRAME_LEN]; // Buffer for holding incoming serial data +AX25Ctx *ax25ctx; +Afsk *channel; +Serial *serial; +size_t frame_len; +bool IN_FRAME; +bool ESCAPE; + +uint8_t command = CMD_UNKNOWN; +unsigned long custom_preamble = CONFIG_AFSK_PREAMBLE_LEN; +unsigned long custom_tail = CONFIG_AFSK_TRAILER_LEN; + +unsigned long slotTime = 200; +uint8_t p = 63; + +void kiss_init(AX25Ctx *ax25, Afsk *afsk, Serial *ser) { + ax25ctx = ax25; + serial = ser; + channel = afsk; +} + +void kiss_messageCallback(AX25Ctx *ctx) { + fputc(FEND, &serial->uart0); + fputc(0x00, &serial->uart0); + for (unsigned i = 0; i < ctx->frame_len; i++) { + uint8_t b = ctx->buf[i]; + if (b == FEND) { + fputc(FESC, &serial->uart0); + fputc(TFEND, &serial->uart0); + } else if (b == FESC) { + fputc(FESC, &serial->uart0); + fputc(TFESC, &serial->uart0); + } else { + fputc(b, &serial->uart0); + } + } + fputc(FEND, &serial->uart0); +} + +void kiss_csma(AX25Ctx *ctx, uint8_t *buf, size_t len) { + bool sent = false; + while (!sent) { + //puts("Waiting in CSMA"); + if(!channel->hdlc.receiving) { + uint8_t tp = rand() & 0xFF; + if (tp < p) { + ax25_sendRaw(ctx, buf, len); + sent = true; + } else { + ticks_t start = timer_clock(); + long slot_ticks = ms_to_ticks(slotTime); + while (timer_clock() - start < slot_ticks) { + cpu_relax(); + } + } + } else { + while (!sent && channel->hdlc.receiving) { + // Continously poll the modem for data + // while waiting, so we don't overrun + // receive buffers + ax25_poll(ax25ctx); + + if (channel->status != 0) { + // If an overflow or other error + // occurs, we'll back off and drop + // this packet silently. + channel->status = 0; + sent = true; + } + } + } + + } + +} + +void kiss_serialCallback(uint8_t sbyte) { + if (IN_FRAME && sbyte == FEND && command == CMD_DATA) { + IN_FRAME = false; + kiss_csma(ax25ctx, serialBuffer, frame_len); + } else if (sbyte == FEND) { + IN_FRAME = true; + command = CMD_UNKNOWN; + frame_len = 0; + } else if (IN_FRAME && frame_len < AX25_MAX_FRAME_LEN) { + // Have a look at the command byte first + if (frame_len == 0 && command == CMD_UNKNOWN) { + // MicroModem supports only one HDLC port, so we + // strip off the port nibble of the command byte + sbyte = sbyte & 0x0F; + command = sbyte; + } else if (command == CMD_DATA) { + if (sbyte == FESC) { + ESCAPE = true; + } else { + if (ESCAPE) { + if (sbyte == TFEND) sbyte = FEND; + if (sbyte == TFESC) sbyte = FESC; + ESCAPE = false; + } + serialBuffer[frame_len++] = sbyte; + } + } else if (command == CMD_TXDELAY) { + custom_preamble = sbyte * 10UL; + } else if (command == CMD_TXTAIL) { + custom_tail = sbyte * 10; + } else if (command == CMD_SLOTTIME) { + slotTime = sbyte * 10; + } else if (command == CMD_P) { + p = sbyte; + } + + } +} \ No newline at end of file diff --git a/protocol/KISS.h b/protocol/KISS.h new file mode 100644 index 0000000..f39a11b --- /dev/null +++ b/protocol/KISS.h @@ -0,0 +1,29 @@ +#ifndef _PROTOCOL_KISS +#define _PROTOCOL_KISS 0x02 + +#include "../hardware/AFSK.h" +#include "../hardware/Serial.h" +#include "../util/time.h" +#include "AX25.h" + +#define FEND 0xC0 +#define FESC 0xDB +#define TFEND 0xDC +#define TFESC 0xDD + +#define CMD_UNKNOWN 0xFE +#define CMD_DATA 0x00 +#define CMD_TXDELAY 0x01 +#define CMD_P 0x02 +#define CMD_SLOTTIME 0x03 +#define CMD_TXTAIL 0x04 +#define CMD_FULLDUPLEX 0x05 +#define CMD_SETHARDWARE 0x06 +#define CMD_RETURN 0xFF + +void kiss_init(AX25Ctx *ax25, Afsk *afsk, Serial *ser); +void kiss_csma(AX25Ctx *ctx, uint8_t *buf, size_t len); +void kiss_messageCallback(AX25Ctx *ctx); +void kiss_serialCallback(uint8_t sbyte); + +#endif \ No newline at end of file diff --git a/util/CRC-CCIT.c b/util/CRC-CCIT.c new file mode 100644 index 0000000..df468b2 --- /dev/null +++ b/util/CRC-CCIT.c @@ -0,0 +1,36 @@ +#include "CRC-CCIT.h" + +const uint16_t crc_ccit_table[256] PROGMEM = { + 0x0000, 0x1189, 0x2312, 0x329b, 0x4624, 0x57ad, 0x6536, 0x74bf, + 0x8c48, 0x9dc1, 0xaf5a, 0xbed3, 0xca6c, 0xdbe5, 0xe97e, 0xf8f7, + 0x1081, 0x0108, 0x3393, 0x221a, 0x56a5, 0x472c, 0x75b7, 0x643e, + 0x9cc9, 0x8d40, 0xbfdb, 0xae52, 0xdaed, 0xcb64, 0xf9ff, 0xe876, + 0x2102, 0x308b, 0x0210, 0x1399, 0x6726, 0x76af, 0x4434, 0x55bd, + 0xad4a, 0xbcc3, 0x8e58, 0x9fd1, 0xeb6e, 0xfae7, 0xc87c, 0xd9f5, + 0x3183, 0x200a, 0x1291, 0x0318, 0x77a7, 0x662e, 0x54b5, 0x453c, + 0xbdcb, 0xac42, 0x9ed9, 0x8f50, 0xfbef, 0xea66, 0xd8fd, 0xc974, + 0x4204, 0x538d, 0x6116, 0x709f, 0x0420, 0x15a9, 0x2732, 0x36bb, + 0xce4c, 0xdfc5, 0xed5e, 0xfcd7, 0x8868, 0x99e1, 0xab7a, 0xbaf3, + 0x5285, 0x430c, 0x7197, 0x601e, 0x14a1, 0x0528, 0x37b3, 0x263a, + 0xdecd, 0xcf44, 0xfddf, 0xec56, 0x98e9, 0x8960, 0xbbfb, 0xaa72, + 0x6306, 0x728f, 0x4014, 0x519d, 0x2522, 0x34ab, 0x0630, 0x17b9, + 0xef4e, 0xfec7, 0xcc5c, 0xddd5, 0xa96a, 0xb8e3, 0x8a78, 0x9bf1, + 0x7387, 0x620e, 0x5095, 0x411c, 0x35a3, 0x242a, 0x16b1, 0x0738, + 0xffcf, 0xee46, 0xdcdd, 0xcd54, 0xb9eb, 0xa862, 0x9af9, 0x8b70, + 0x8408, 0x9581, 0xa71a, 0xb693, 0xc22c, 0xd3a5, 0xe13e, 0xf0b7, + 0x0840, 0x19c9, 0x2b52, 0x3adb, 0x4e64, 0x5fed, 0x6d76, 0x7cff, + 0x9489, 0x8500, 0xb79b, 0xa612, 0xd2ad, 0xc324, 0xf1bf, 0xe036, + 0x18c1, 0x0948, 0x3bd3, 0x2a5a, 0x5ee5, 0x4f6c, 0x7df7, 0x6c7e, + 0xa50a, 0xb483, 0x8618, 0x9791, 0xe32e, 0xf2a7, 0xc03c, 0xd1b5, + 0x2942, 0x38cb, 0x0a50, 0x1bd9, 0x6f66, 0x7eef, 0x4c74, 0x5dfd, + 0xb58b, 0xa402, 0x9699, 0x8710, 0xf3af, 0xe226, 0xd0bd, 0xc134, + 0x39c3, 0x284a, 0x1ad1, 0x0b58, 0x7fe7, 0x6e6e, 0x5cf5, 0x4d7c, + 0xc60c, 0xd785, 0xe51e, 0xf497, 0x8028, 0x91a1, 0xa33a, 0xb2b3, + 0x4a44, 0x5bcd, 0x6956, 0x78df, 0x0c60, 0x1de9, 0x2f72, 0x3efb, + 0xd68d, 0xc704, 0xf59f, 0xe416, 0x90a9, 0x8120, 0xb3bb, 0xa232, + 0x5ac5, 0x4b4c, 0x79d7, 0x685e, 0x1ce1, 0x0d68, 0x3ff3, 0x2e7a, + 0xe70e, 0xf687, 0xc41c, 0xd595, 0xa12a, 0xb0a3, 0x8238, 0x93b1, + 0x6b46, 0x7acf, 0x4854, 0x59dd, 0x2d62, 0x3ceb, 0x0e70, 0x1ff9, + 0xf78f, 0xe606, 0xd49d, 0xc514, 0xb1ab, 0xa022, 0x92b9, 0x8330, + 0x7bc7, 0x6a4e, 0x58d5, 0x495c, 0x3de3, 0x2c6a, 0x1ef1, 0x0f78, +}; \ No newline at end of file diff --git a/util/CRC-CCIT.h b/util/CRC-CCIT.h new file mode 100644 index 0000000..a354ba7 --- /dev/null +++ b/util/CRC-CCIT.h @@ -0,0 +1,18 @@ +// CRC-CCIT Implementation based on work by Francesco Sacchi + +#ifndef CRC_CCIT_H +#define CRC_CCIT_H + +#include +#include + +#define CRC_CCIT_INIT_VAL ((uint16_t)0xFFFF) + +extern const uint16_t crc_ccit_table[256]; + +inline uint16_t update_crc_ccit(uint8_t c, uint16_t prev_crc) { + return (prev_crc >> 8) ^ pgm_read_word(&crc_ccit_table[(prev_crc ^ c) & 0xff]); +} + + +#endif \ No newline at end of file diff --git a/util/FIFO.h b/util/FIFO.h new file mode 100644 index 0000000..b9850a1 --- /dev/null +++ b/util/FIFO.h @@ -0,0 +1,85 @@ +#ifndef UTIL_FIFO_H +#define UTIL_FIFO_H + +#include +#include + +typedef struct FIFOBuffer +{ + unsigned char *begin; + unsigned char *end; + unsigned char * volatile head; + unsigned char * volatile tail; +} FIFOBuffer; + +inline bool fifo_isempty(const FIFOBuffer *f) { + return f->head == f->tail; +} + +inline bool fifo_isfull(const FIFOBuffer *f) { + return ((f->head == f->begin) && (f->tail == f->end)) || (f->tail == f->head - 1); +} + +inline void fifo_push(FIFOBuffer *f, unsigned char c) { + *(f->tail) = c; + + if (f->tail == f->end) { + f->tail = f->begin; + } else { + f->tail++; + } +} + +inline unsigned char fifo_pop(FIFOBuffer *f) { + if(f->head == f->end) { + f->head = f->begin; + return *(f->end); + } else { + return *(f->head++); + } +} + +inline void fifo_flush(FIFOBuffer *f) { + f->head = f->tail; +} + +inline bool fifo_isempty_locked(const FIFOBuffer *f) { + bool result; + ATOMIC_BLOCK(ATOMIC_RESTORESTATE) { + result = fifo_isempty(f); + } + return result; +} + +inline bool fifo_isfull_locked(const FIFOBuffer *f) { + bool result; + ATOMIC_BLOCK(ATOMIC_RESTORESTATE) { + result = fifo_isfull(f); + } + return result; +} + +inline void fifo_push_locked(FIFOBuffer *f, unsigned char c) { + ATOMIC_BLOCK(ATOMIC_RESTORESTATE) { + fifo_push(f, c); + } +} + +inline unsigned char fifo_pop_locked(FIFOBuffer *f) { + unsigned char c; + ATOMIC_BLOCK(ATOMIC_RESTORESTATE) { + c = fifo_pop(f); + } + return c; +} + +inline void fifo_init(FIFOBuffer *f, unsigned char *buffer, size_t size) { + f->head = f->tail = f->begin = buffer; + f->end = buffer + size -1; +} + +inline size_t fifo_len(FIFOBuffer *f) { + return f->end - f->begin; +} + +#endif \ No newline at end of file diff --git a/util/time.h b/util/time.h new file mode 100644 index 0000000..1f9f276 --- /dev/null +++ b/util/time.h @@ -0,0 +1,34 @@ +#ifndef UTIL_TIME_H +#define UTIL_TIME_H + +#include "device.h" + +#define DIV_ROUND(dividend, divisor) (((dividend) + (divisor) / 2) / (divisor)) +#define CLOCK_TICKS_PER_SEC CONFIG_AFSK_DAC_SAMPLERATE + +typedef int32_t ticks_t; +typedef int32_t mtime_t; + +volatile ticks_t _clock; + +inline ticks_t timer_clock(void) { + ticks_t result; + + ATOMIC_BLOCK(ATOMIC_RESTORESTATE) { + result = _clock; + } + + return result; +} + + +inline ticks_t ms_to_ticks(mtime_t ms) { + return ms * DIV_ROUND(CLOCK_TICKS_PER_SEC, 1000); +} + +inline void cpu_relax(void) { + // Do nothing! +} + + +#endif \ No newline at end of file