diff --git a/AFSK.cpp b/AFSK.cpp new file mode 100644 index 0000000..aa9b75d --- /dev/null +++ b/AFSK.cpp @@ -0,0 +1,472 @@ +#include +#include "AFSK.h" + +extern unsigned long custom_preamble; +extern unsigned long custom_tail; + +bool hw_afsk_dac_isr = false; +bool hw_5v_ref = false; +Afsk *AFSK_modem; + + +// Forward declerations +int afsk_getchar(void); +void afsk_putchar(char c); + +void AFSK_hw_refDetect(void) { + // This is manual for now + #if ADC_REFERENCE == REF_5V + hw_5v_ref = true; + #else + hw_5v_ref = false; + #endif +} + +void AFSK_hw_init(void) { + // Set up ADC + + AFSK_hw_refDetect(); + + TCCR1A = 0; + TCCR1B = _BV(CS10) | _BV(WGM13) | _BV(WGM12); + ICR1 = (((CPU_FREQ+FREQUENCY_CORRECTION)) / 9600) - 1; + + if (hw_5v_ref) { + ADMUX = _BV(REFS0) | 0; + } else { + ADMUX = 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); + + 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)); + + // Fill delay FIFO with zeroes + for (int i = 0; idelayFifo, 0); + } + + 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; + #if OPEN_SQUELCH == false + LED_RX_ON(); + #endif + } else { + // If the buffer is full, we have a problem + // and abort by setting the return value to + // 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; + } + } + } + +} + +extern void APRS_poll(); +uint8_t poll_timer = 0; +ISR(ADC_vect) { + 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) | _BV(3); + } else { + DAC_PORT = 128; + } + + poll_timer++; + if (poll_timer > 4) { + poll_timer = 0; + APRS_poll(); + } +} \ No newline at end of file diff --git a/AFSK.h b/AFSK.h new file mode 100644 index 0000000..fe2ebad --- /dev/null +++ b/AFSK.h @@ -0,0 +1,138 @@ +#ifndef AFSK_H +#define AFSK_H + +#include "device.h" +#include +#include +#include +#include +#include "FIFO.h" +#include "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 |= 0xF8; } 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 { LED_DDR |= _BV(1); } while (0) +#define LED_TX_ON() do { LED_PORT |= _BV(1); } while (0) +#define LED_TX_OFF() do { LED_PORT &= ~_BV(1); } while (0) + +#define LED_RX_INIT() do { LED_DDR |= _BV(2); } while (0) +#define LED_RX_ON() do { LED_PORT |= _BV(2); } while (0) +#define LED_RX_OFF() do { LED_PORT &= ~_BV(2); } while (0) + +void AFSK_init(Afsk *afsk); +void AFSK_transmit(char *buffer, size_t size); +void AFSK_poll(Afsk *afsk); + +void afsk_putchar(char c); +int afsk_getchar(void); + +#endif \ No newline at end of file diff --git a/AX25.cpp b/AX25.cpp new file mode 100644 index 0000000..a1bff8a --- /dev/null +++ b/AX25.cpp @@ -0,0 +1,155 @@ +// Based on work by Francesco Sacchi + +#include "Arduino.h" +#include +#include +#include "AX25.h" +#include "HDLC.h" +#include "CRC-CCIT.h" +#include "AFSK.h" + +#define countof(a) sizeof(a)/sizeof(a[0]) +#define MIN(a,b) ({ typeof(a) _a = (a); typeof(b) _b = (b); ((typeof(_a))((_a < _b) ? _a : _b)); }) +#define DECODE_CALL(buf, addr) for (unsigned i = 0; i < sizeof((addr)); i++) { char c = (*(buf)++ >> 1); (addr)[i] = (c == ' ') ? '\x0' : c; } +#define AX25_SET_REPEATED(msg, idx, val) do { if (val) { (msg)->rpt_flags |= _BV(idx); } else { (msg)->rpt_flags &= ~_BV(idx) ; } } while(0) + +void ax25_init(AX25Ctx *ctx, ax25_callback_t hook) { + memset(ctx, 0, sizeof(*ctx)); + ctx->hook = hook; + ctx->crc_in = ctx->crc_out = CRC_CCIT_INIT_VAL; +} + +static void ax25_decode(AX25Ctx *ctx) { + AX25Msg msg; + uint8_t *buf = ctx->buf; + + DECODE_CALL(buf, msg.dst.call); + msg.dst.ssid = (*buf++ >> 1) & 0x0F; + + DECODE_CALL(buf, msg.src.call); + msg.src.ssid = (*buf >> 1) & 0x0F; + + for (msg.rpt_count = 0; !(*buf++ & 0x01) && (msg.rpt_count < countof(msg.rpt_list)); msg.rpt_count++) { + DECODE_CALL(buf, msg.rpt_list[msg.rpt_count].call); + msg.rpt_list[msg.rpt_count].ssid = (*buf >> 1) & 0x0F; + AX25_SET_REPEATED(&msg, msg.rpt_count, (*buf & 0x80)); + } + + msg.ctrl = *buf++; + if (msg.ctrl != AX25_CTRL_UI) { return; } + + msg.pid = *buf++; + if (msg.pid != AX25_PID_NOLAYER3) { return; } + + msg.len = ctx->frame_len - 2 - (buf - ctx->buf); + msg.info = buf; + + if (ctx->hook) ctx->hook(&msg); + +} + +void ax25_poll(AX25Ctx *ctx) { + int c; + + while ((c = afsk_getchar()) != EOF) { + if (!ctx->escape && c == HDLC_FLAG) { + if (ctx->frame_len >= AX25_MIN_FRAME_LEN) { + if (ctx->crc_in == AX25_CRC_CORRECT) { + #if OPEN_SQUELCH == true + LED_RX_ON(); + #endif + 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) afsk_putchar(AX25_ESC); + ctx->crc_out = update_crc_ccit(c, ctx->crc_out); + afsk_putchar(c); +} + +void ax25_sendRaw(AX25Ctx *ctx, void *_buf, size_t len) { + ctx->crc_out = CRC_CCIT_INIT_VAL; + afsk_putchar(HDLC_FLAG); + 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); + + afsk_putchar(HDLC_FLAG); +} + +static void ax25_sendCall(AX25Ctx *ctx, const AX25Call *addr, bool last){ + unsigned len = MIN(sizeof(addr->call), strlen(addr->call)); + + for (unsigned i = 0; i < len; i++) { + uint8_t c = addr->call[i]; + c = toupper(c); + ax25_putchar(ctx, c << 1); + } + + if (len < sizeof(addr->call)) { + for (unsigned i = 0; i < sizeof(addr->call) - len; i++) { + ax25_putchar(ctx, ' ' << 1); + } + } + + uint8_t ssid = 0x60 | (addr->ssid << 1) | (last ? 0x01 : 0); + ax25_putchar(ctx, ssid); +} + +void ax25_sendVia(AX25Ctx *ctx, const AX25Call *path, size_t path_len, const void *_buf, size_t len) { + const uint8_t *buf = (const uint8_t *)_buf; + + ctx->crc_out = CRC_CCIT_INIT_VAL; + afsk_putchar(HDLC_FLAG); + + for (size_t i = 0; i < path_len; i++) { + ax25_sendCall(ctx, &path[i], (i == path_len - 1)); + } + + ax25_putchar(ctx, AX25_CTRL_UI); + ax25_putchar(ctx, AX25_PID_NOLAYER3); + + 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); + + afsk_putchar(HDLC_FLAG); +} diff --git a/AX25.h b/AX25.h new file mode 100644 index 0000000..fae90f6 --- /dev/null +++ b/AX25.h @@ -0,0 +1,67 @@ +#ifndef PROTOCOL_AX25_H +#define PROTOCOL_AX25_H + +#include +#include +#include "device.h" + +#define AX25_MIN_FRAME_LEN 18 +#ifndef CUSTOM_FRAME_SIZE + #define AX25_MAX_FRAME_LEN 620 +#else + #define AX25_MAX_FRAME_LEN CUSTOM_FRAME_SIZE +#endif + +#define AX25_CRC_CORRECT 0xF0B8 + +#define AX25_CTRL_UI 0x03 +#define AX25_PID_NOLAYER3 0xF0 + +struct AX25Ctx; // Forward declarations +struct AX25Msg; + +typedef void (*ax25_callback_t)(struct AX25Msg *msg); + +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; + + +#define AX25_CALL(str, id) {.call = (str), .ssid = (id) } +#define AX25_MAX_RPT 8 +#define AX25_REPEATED(msg, n) ((msg)->rpt_flags & BV(n)) + +typedef struct AX25Call { + char call[6]; + uint8_t ssid; +} AX25Call; + +typedef struct AX25Msg { + AX25Call src; + AX25Call dst; + AX25Call rpt_list[AX25_MAX_RPT]; + uint8_t rpt_count; + uint8_t rpt_flags; + uint16_t ctrl; + uint8_t pid; + const uint8_t *info; + size_t len; +} AX25Msg; + +void ax25_sendVia(AX25Ctx *ctx, const AX25Call *path, size_t path_len, const void *_buf, size_t len); +#define ax25_send(ctx, dst, src, buf, len) ax25_sendVia(ctx, ({static AX25Call __path[]={dst, src}; __path;}), 2, buf, len) + + + +void ax25_poll(AX25Ctx *ctx); +void ax25_sendRaw(AX25Ctx *ctx, void *_buf, size_t len); +void ax25_init(AX25Ctx *ctx, ax25_callback_t hook); + +#endif \ No newline at end of file diff --git a/CRC-CCIT.c b/CRC-CCIT.c new file mode 100644 index 0000000..df468b2 --- /dev/null +++ b/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/CRC-CCIT.h b/CRC-CCIT.h new file mode 100644 index 0000000..a354ba7 --- /dev/null +++ b/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/FIFO.h b/FIFO.h new file mode 100644 index 0000000..b9850a1 --- /dev/null +++ b/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/HDLC.h b/HDLC.h new file mode 100644 index 0000000..3ee0808 --- /dev/null +++ b/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/LibAPRS.cpp b/LibAPRS.cpp new file mode 100644 index 0000000..83ed9e5 --- /dev/null +++ b/LibAPRS.cpp @@ -0,0 +1,309 @@ +#include "Arduino.h" +#include "AFSK.h" +#include "AX25.h" + +Afsk modem; +AX25Ctx AX25; +extern void aprs_msg_callback(struct AX25Msg *msg); +#define countof(a) sizeof(a)/sizeof(a[0]) + +unsigned long custom_preamble = 350UL; +unsigned long custom_tail = 50UL; + +AX25Call src; +AX25Call dst; +AX25Call path1; +AX25Call path2; + +char CALL[7] = "NOCALL"; +int CALL_SSID = 0; +char DST[7] = "APZMDM"; +int DST_SSID = 0; +char PATH1[7] = "WIDE1"; +int PATH1_SSID = 1; +char PATH2[7] = "WIDE2"; +int PATH2_SSID = 2; + +AX25Call path[4]; + +// Location packet assembly fields +char latitude[9]; +char longtitude[10]; +char symbolTable = '/'; +char symbol = 'n'; + +uint8_t power = 10; +uint8_t height = 10; +uint8_t gain = 10; +uint8_t directivity = 10; +///////////////////////// + +// Message packet assembly fields +char message_recip[7]; +int message_recip_ssid = -1; + +int message_seq = 0; +char lastMessage[67]; +size_t lastMessageLen; +bool message_autoAck = false; +///////////////////////// + +void APRS_init(void) { + AFSK_init(&modem); + ax25_init(&AX25, aprs_msg_callback); +} + +void APRS_poll(void) { + ax25_poll(&AX25); +} + +void APRS_setCallsign(char *call, int ssid) { + Serial.println("Setting to: "); + memset(CALL, 0, 7); + int i = 0; + while (i < 6 && call[i] != 0) { + CALL[i] = call[i]; + i++; + } + CALL_SSID = ssid; +} + +void APRS_setDestination(char *call, int ssid) { + memset(DST, 0, 7); + int i = 0; + while (i < 6 && call[i] != 0) { + DST[i] = call[i]; + i++; + } + DST_SSID = ssid; +} + +void APRS_setPath1(char *call, int ssid) { + memset(PATH1, 0, 7); + int i = 0; + while (i < 6 && call[i] != 0) { + PATH1[i] = call[i]; + i++; + } + PATH1_SSID = ssid; +} + +void APRS_setPath2(char *call, int ssid) { + memset(PATH2, 0, 7); + int i = 0; + while (i < 6 && call[i] != 0) { + PATH2[i] = call[i]; + i++; + } + PATH2_SSID = ssid; +} + +void APRS_setMessageDestination(char *call, int ssid) { + memset(message_recip, 0, 7); + int i = 0; + while (i < 6 && call[i] != 0) { + message_recip[i] = call[i]; + i++; + } + message_recip_ssid = ssid; +} + +void APRS_setPreamble(unsigned long pre) { + custom_preamble = pre; +} + +void APRS_setTail(unsigned long tail) { + custom_tail = tail; +} + +void APRS_useAlternateSymbolTable(bool use) { + if (use) { + symbolTable = '\\'; + } else { + symbolTable = '/'; + } +} + +void APRS_setSymbol(char sym) { + symbol = sym; +} + +void APRS_setLat(char *lat) { + memset(latitude, 0, 9); + int i = 0; + while (i < 8 && lat[i] != 0) { + latitude[i] = lat[i]; + i++; + } +} + +void APRS_setLon(char *lon) { + memset(longtitude, 0, 10); + int i = 0; + while (i < 9 && lon[i] != 0) { + longtitude[i] = lon[i]; + i++; + } +} + +void APRS_setPower(int s) { + if (s >= 0 && s < 10) { + power = s; + } +} + +void APRS_setHeight(int s) { + if (s >= 0 && s < 10) { + height = s; + } +} + +void APRS_setGain(int s) { + if (s >= 0 && s < 10) { + gain = s; + } +} + +void APRS_setDirectivity(int s) { + if (s >= 0 && s < 10) { + directivity = s; + } +} + +void APRS_printSettings() { + Serial.println("LibAPRS Settings:"); + Serial.print("Callsign: "); Serial.print(CALL); Serial.print("-"); Serial.println(CALL_SSID); + Serial.print("Destination: "); Serial.print(DST); Serial.print("-"); Serial.println(DST_SSID); + Serial.print("Path1: "); Serial.print(PATH1); Serial.print("-"); Serial.println(PATH1_SSID); + Serial.print("Path2: "); Serial.print(PATH2); Serial.print("-"); Serial.println(PATH2_SSID); + Serial.print("Message dst: "); if (message_recip[0] == 0) { Serial.println("N/A"); } else { Serial.print(message_recip); Serial.print("-"); Serial.println(message_recip_ssid); } + Serial.print("TX Preamble: "); Serial.println(custom_preamble); + Serial.print("TX Tail: "); Serial.println(custom_tail); + Serial.print("Symbol table: "); if (symbolTable = '/') { Serial.println("Normal"); } else { Serial.println("Alternate"); } + Serial.print("Symbol: "); Serial.println(symbol); + Serial.print("Power: "); if (power < 10) { Serial.println(power); } else { Serial.println("N/A"); } + Serial.print("Height: "); if (height < 10) { Serial.println(height); } else { Serial.println("N/A"); } + Serial.print("Gain: "); if (gain < 10) { Serial.println(gain); } else { Serial.println("N/A"); } + Serial.print("Directivity: "); if (directivity < 10) { Serial.println(directivity); } else { Serial.println("N/A"); } + Serial.print("Latitude: "); if (latitude[0] != 0) { Serial.println(latitude); } else { Serial.println("N/A"); } + Serial.print("Longtitude: "); if (longtitude[0] != 0) { Serial.println(longtitude); } else { Serial.println("N/A"); } + +} + +void APRS_sendPkt(void *_buffer, size_t length) { + + uint8_t *buffer = (uint8_t *)_buffer; + + memcpy(dst.call, DST, 6); + dst.ssid = DST_SSID; + + memcpy(src.call, CALL, 6); + src.ssid = CALL_SSID; + + memcpy(path1.call, PATH1, 6); + path1.ssid = PATH1_SSID; + + memcpy(path2.call, PATH2, 6); + path2.ssid = PATH2_SSID; + + path[0] = dst; + path[1] = src; + path[2] = path1; + path[3] = path2; + + ax25_sendVia(&AX25, path, countof(path), buffer, length); +} + +void APRS_sendLoc(void *_buffer, size_t length) { + size_t payloadLength = 20+length; + bool usePHG = false; + if (power < 10 && height < 10 && gain < 10 && directivity < 9) { + usePHG = true; + payloadLength += 7; + } + uint8_t *packet = (uint8_t*)malloc(payloadLength); + uint8_t *ptr = packet; + packet[0] = '='; + packet[9] = symbolTable; + packet[19] = symbol; + ptr++; + memcpy(ptr, latitude, 8); + ptr += 9; + memcpy(ptr, longtitude, 9); + ptr += 10; + if (usePHG) { + packet[20] = 'P'; + packet[21] = 'H'; + packet[22] = 'G'; + packet[23] = power+48; + packet[24] = height+48; + packet[25] = gain+48; + packet[26] = directivity+48; + ptr+=7; + } + if (length > 0) { + uint8_t *buffer = (uint8_t *)_buffer; + memcpy(ptr, buffer, length); + } + + APRS_sendPkt(packet, payloadLength); + free(packet); +} + +void APRS_sendMsg(void *_buffer, size_t length) { + if (length > 67) length = 67; + size_t payloadLength = 11+length+4; + + uint8_t *packet = (uint8_t*)malloc(payloadLength); + uint8_t *ptr = packet; + packet[0] = ':'; + int callSize = 6; + int count = 0; + while (callSize--) { + if (message_recip[count] != 0) { + packet[1+count] = message_recip[count]; + count++; + } + } + if (message_recip_ssid != -1) { + packet[1+count] = '-'; count++; + if (message_recip_ssid < 10) { + packet[1+count] = message_recip_ssid+48; count++; + } else { + packet[1+count] = 49; count++; + packet[1+count] = message_recip_ssid-10+48; count++; + } + } + while (count < 9) { + packet[1+count] = ' '; count++; + } + packet[1+count] = ':'; + ptr += 11; + if (length > 0) { + uint8_t *buffer = (uint8_t *)_buffer; + memcpy(ptr, buffer, length); + memcpy(lastMessage, buffer, length); + lastMessageLen = length; + } + + message_seq++; + if (message_seq > 999) message_seq = 0; + + packet[11+length] = '{'; + int n = message_seq % 10; + int d = ((message_seq % 100) - n)/10; + int h = (message_seq - d - n) / 100; + + packet[12+length] = h+48; + packet[13+length] = d+48; + packet[14+length] = n+48; + + APRS_sendPkt(packet, payloadLength); + + free(packet); +} + +void APRS_msgRetry() { + message_seq--; + APRS_sendMsg(lastMessage, lastMessageLen); +} \ No newline at end of file diff --git a/LibAPRS.h b/LibAPRS.h new file mode 100644 index 0000000..d546c92 --- /dev/null +++ b/LibAPRS.h @@ -0,0 +1,37 @@ +#include "Arduino.h" +#include +#include + +#include "FIFO.h" +#include "CRC-CCIT.h" +#include "HDLC.h" +#include "AFSK.h" +#include "AX25.h" + +void APRS_init(void); +void APRS_poll(void); + +void APRS_setCallsign(char *call, int ssid); +void APRS_setDestination(char *call, int ssid); +void APRS_setMessageDestination(char *call, int ssid); +void APRS_setPath1(char *call, int ssid); +void APRS_setPath2(char *call, int ssid); + +void APRS_setPreamble(unsigned long pre); +void APRS_setTail(unsigned long tail); +void APRS_useAlternateSymbolTable(bool use); +void APRS_setSymbol(char sym); + +void APRS_setLat(char *lat); +void APRS_setLon(char *lon); +void APRS_setPower(int s); +void APRS_setHeight(int s); +void APRS_setGain(int s); +void APRS_setDirectivity(int s); + +void APRS_sendPkt(void *_buffer, size_t length); +void APRS_sendLoc(void *_buffer, size_t length); +void APRS_sendMsg(void *_buffer, size_t length); +void APRS_msgRetry(); + +void APRS_printSettings(); \ No newline at end of file diff --git a/README.md b/README.md index e69de29..1845ba0 100644 --- a/README.md +++ b/README.md @@ -0,0 +1,20 @@ +LibAPRS +========== + +LibAPRS is an Arduino IDE library that makes it easy to send and receive APRS packets with a [MicroModem](http://unsigned.io/micromodem)-compatible modem. + +You can buy a complete modem from [my shop](http://unsigned.io/shop), or you can build one yourself pretty easily. Take a look at the documentation in the [MicroModem](https://github.com/markqvist/MicroModem) repository for information and getting started guides! + +See the example included in the library for info on how to use it! + +## Some features + +- Send and receive AX.25 APRS packets +- Full modulation and demodulation in software +- Easy configuration of callsign and path settings +- Easily process incoming packets +- Shorthand functions for sending location updates and messages, so you don't need to manually create the packets +- Ability to send raw packets +- Support for settings APRS symbols +- Support for power/height/gain info in location updates +- Can run with open squelch \ No newline at end of file diff --git a/constants.h b/constants.h new file mode 100644 index 0000000..ee89fc3 --- /dev/null +++ b/constants.h @@ -0,0 +1,6 @@ +#define m328p 0x01 +#define m1284p 0x02 +#define m644p 0x03 + +#define REF_3V3 0x01 +#define REF_5V 0x02 \ No newline at end of file diff --git a/device.h b/device.h new file mode 100644 index 0000000..0373412 --- /dev/null +++ b/device.h @@ -0,0 +1,38 @@ +#include "constants.h" + +#ifndef DEVICE_CONFIGURATION +#define DEVICE_CONFIGURATION + +// CPU settings +#ifndef TARGET_CPU + #define TARGET_CPU m328p +#endif + +#ifndef F_CPU + #define F_CPU 16000000 +#endif + +#ifndef FREQUENCY_CORRECTION + #define FREQUENCY_CORRECTION 0 +#endif + +// ADC settings +#define OPEN_SQUELCH false +#define ADC_REFERENCE REF_3V3 +// OR +//#define ADC_REFERENCE REF_5V + +// Sampling & timer setup +#define CONFIG_AFSK_DAC_SAMPLERATE 9600 + +// Port settings +#if TARGET_CPU == m328p + #define DAC_PORT PORTD + #define DAC_DDR DDRD + #define LED_PORT PORTB + #define LED_DDR DDRB + #define ADC_PORT PORTC + #define ADC_DDR DDRC +#endif + +#endif \ No newline at end of file diff --git a/examples/Basic_usage/Basic_usage.ino b/examples/Basic_usage/Basic_usage.ino new file mode 100644 index 0000000..3aaae2d --- /dev/null +++ b/examples/Basic_usage/Basic_usage.ino @@ -0,0 +1,92 @@ +// Include LibAPRS +#include + +// You always need to include this function. It will +// get called by the library every time a packet is +// received, so you can process incoming packets. +void aprs_msg_callback(struct AX25Msg *msg) { + Serial.print("Received APRS packet. Data: "); + for (int i = 0; i < msg->len; i++) { Serial.write(msg->info[i]); } + Serial.println(""); +} + +void setup() { + // Set up serial port + Serial.begin(9600); + + // Initialise APRS library - This starts the modem + APRS_init(); + + // You must at a minimum configure your callsign and SSID + APRS_setCallsign("NOCALL", 1); + + // You don't need to set the destination identifier, but + // if you want to, this is how you do it: + APRS_setDestination("APZMDM", 0); + + // Path parameters are set to sensible values by + // default, but this is how you can configure them: + APRS_setPath1("WIDE1", 1); + APRS_setPath2("WIDE2", 2); + + // You can define preamble and tail like this: + APRS_setPreamble(350); + APRS_setTail(50); + + // You can use the normal or alternate symbol table: + APRS_useAlternateSymbolTable(false); + + // And set what symbol you want to use: + APRS_setSymbol('n'); + + // We can print out all the settings + APRS_printSettings(); +} + +void locationUpdateExample() { + // Let's first set our latitude and longtitude. + // These should be in NMEA format! + APRS_setLat("5530.80N"); + APRS_setLon("01143.89E"); + + // We can optionally set power/height/gain/directivity + // information. These functions accept ranges + // from 0 to 10, directivity 0 to 9. + // See this site for a calculator: + // http://www.aprsfl.net/phgr.php + // LibAPRS will only add PHG info if all four variables + // are defined! + APRS_setPower(2); + APRS_setHeight(4); + APRS_setGain(7); + APRS_setDirectivity(0); + + // We'll define a comment string + char *comment = "LibAPRS location update"; + + // And send the update + APRS_sendLoc(comment, sizeof(comment)); + +} + +void messageExample() { + // We first need to set the message recipient + APRS_setMessageDestination("AA3BBB", 0); + + // And define a string to send + char *message = "Hi there! This is a message"; + APRS_sendMsg(message, sizeof(message)); + +} + +void loop() { + delay(2000); + locationUpdateExample(); + + delay(2000); + messageExample(); + + // Just for fun we print out the settings + APRS_printSettings(); + +} \ No newline at end of file