From 017a1e68872e4040744b0035973120e66acba7e7 Mon Sep 17 00:00:00 2001 From: Mark Qvist Date: Sun, 6 Apr 2014 17:17:13 +0200 Subject: [PATCH] Commenting --- Modem/afsk.c | 190 +++++++++++++++++++++++++++++++++++++------ Modem/hardware.c | 4 +- Modem/hardware.h | 18 ++-- Modem/main.c | 89 ++++++++++++++------ Modem/protocol/mp1.c | 22 ++++- buildrev.h | 2 +- 6 files changed, 263 insertions(+), 62 deletions(-) diff --git a/Modem/afsk.c b/Modem/afsk.c index 307328d..37cfebd 100644 --- a/Modem/afsk.c +++ b/Modem/afsk.c @@ -1,17 +1,31 @@ -#include "afsk.h" -#include "config.h" -#include "hardware.h" +////////////////////////////////////////////////////// +// First things first, all the includes we need // +////////////////////////////////////////////////////// -#include -#include +#include "afsk.h" // We need the header file for the modem +#include "config.h" // This stores basic configuration +#include "hardware.h" // Hardware functions are nice to have too :) -#include -#include -#include -#include +#include // Timer driver from BertOS +//FIXME: is this needed ? #include -// Sine table for DAC DDS -#define SIN_LEN 512 // Length of a full wave. Table is 1/4 wave. +#include // Power management from BertOS +#include // Access to PROGMEM from BertOS +#include // FIFO buffer implementation from BertOS +#include // String operations, primarily used for memset function + + +////////////////////////////////////////////////////// +// Definitions and some useful macros // +////////////////////////////////////////////////////// + +// Sine table for Direct Digital Synthesis DAC +// Since it would be inefficient to calculate a sine value each +// time we process a sample, we store the values in program memory +// as a look-up table. We only need to store values for a quarter +// wave, since we can easily reconstruct the entire 512 values +// from only these 128 values. +#define SIN_LEN 512 static const uint8_t PROGMEM sin_table[] = { 128, 129, 131, 132, 134, 135, 137, 138, 140, 142, 143, 145, 146, 148, 149, 151, @@ -26,17 +40,44 @@ static const uint8_t PROGMEM sin_table[] = // Calculate any sine value from quarter wave sine table +// The reason we declare this inline is to eliminate an extra +// call for the code. The code is essentially inserted directly +// in the calling functions code. This makes stuff faster :) INLINE uint8_t sinSample(uint16_t i) { + // Make sure that the index asked for is in the correct range ASSERT(i < SIN_LEN); + // First we make a new index value, and restrict it to only + // the first half-wave of the sine. uint16_t newI = i % (SIN_LEN/2); + // We then check if this new index is larger than the first + // quarter wave. If it is, we don't have the value for this + // index directly, but we can figure it out by subtracting + // the new index from a half wave, effectively wrapping us + // back into the same place on the wave, whithin the quarter + // wave we have data for, only with the inverse sign. If the + // index was actually in the first quarter, we don't need to + // do anything. newI = (newI >= (SIN_LEN/4)) ? (SIN_LEN/2 - newI -1) : newI; + // Now we just need to read the value from program memory uint8_t sine = pgm_read8(&sin_table[newI]); + // And flip the sign (+/-) if the original index was greater + // than a half wave. return (i >= (SIN_LEN/2)) ? (255 - sine) : sine; } -// Look for signal transition. Used for phase sync. +// A very basic macro that just checks whether the last bit +// of a whatever is passed into it differ. This is used in the +// next macro. #define BITS_DIFFER(bits1, bits2) (((bits1)^(bits2)) & 0x01) -#define EDGE_FOUND(bits) BITS_DIFFER((bits), (bits) >> 1) + +// This macro is used to look for signal transitions. We need +// to identify these to keep the phase of our demodulator in +// sync with the incoming signal. Each time we find a signal +// transition on the physical medium, we adjust the phase of +// the demodulator. +// The macro effectively looks at the two least significant +// bits in a stream and returns true if they differ. +#define TRANSITION_FOUND(bits) BITS_DIFFER((bits), (bits) >> 1) // Phase sync constants #define PHASE_BITS 8 @@ -51,35 +92,79 @@ INLINE uint8_t sinSample(uint16_t i) { #define SPACE_INC (uint16_t)(DIV_ROUND(SIN_LEN * (uint32_t)SPACE_FREQ, CONFIG_AFSK_DAC_SAMPLERATE)) // HDLC flag bytes -#define HDLC_FLAG 0x7E -#define HDLC_RESET 0x7F -#define AX25_ESC 0x1B +#define HDLC_FLAG 0x7E // An HDLC_FLAG is used to signify the start or end of a frame +#define HDLC_RESET 0x7F // An HDLC_RESET is used to abruptly stop or reset a transmission +#define AX25_ESC 0x1B // We use the AX.25 escape character for escaping bit sequences in + // the actual data. This is similar to escaping an " character in a + // string enclosed by "s. -// Check that sample rate is divisible by bitrate +// Check that sample rate is divisible by bitrate. +// If this is not the case, all of our algorithms will +// fail horribly and we will cry. STATIC_ASSERT(!(CONFIG_AFSK_DAC_SAMPLERATE % BITRATE)); +// How many samples it takes to encode or decode one bit +// on the physical medium. #define DAC_SAMPLESPERBIT (CONFIG_AFSK_DAC_SAMPLERATE / BITRATE) ////////////////////////////////////////////////////// // Link Layer Control and Demodulation // ////////////////////////////////////////////////////// +// hdlcParse ///////////////////////////////////////// +// This function looks at the raw bits demodulated from +// the physical medium and tries to parse actual data +// packets from the bitstream. Note that at this level, +// we don't really try to discriminate when a packet +// starts or ends, or where the payload is. We only try +// to detect that a transmission is taking place, then +// synchronise to the start and end of the transmitted +// bytes, and push these up to the data-link layer, in +// this example the MP.x protocol. It is then the +// protocols job to actually recreate the full packet. +// Also note that the data is not "pushed" per se, but +// stored in a FIFO buffer, that the protocol must +// continously read to recreate the received packets. 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; - // Check if we have received a HDLC flag (01111110) + // 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 + // 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; @@ -87,42 +172,78 @@ static bool hdlcParse(Hdlc *hdlc, bool bit, FIFOBuffer *fifo) { // Check if we have received a RESET flag (01111111) 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 are just receiving noise, don't bother with anything + // 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 + // 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 + // 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 + // 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; } @@ -139,6 +260,15 @@ static bool hdlcParse(Hdlc *hdlc, bool bit, FIFOBuffer *fifo) { return ret; } +// adcISR //////////////////////////////////////////// +// This is the Interrupt Service Routine for the +// Analog to Digital Conversion. It is called 9600 +// times each second to analyze the sample taken from +// the physical medium. The job of this routine is +// to detect whether we have a "mark" or "space" +// frequency present on the baseband (the physical +// medium). The result of this analysis will then +// be passed to the HDLC parser in form of a 1 or a 0 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 @@ -162,7 +292,7 @@ void afsk_adc_isr(Afsk *afsk, int8_t currentSample) { // If there is a signal transition, recalibrate // sampling phase - if (EDGE_FOUND(afsk->sampledBits)) { + if (TRANSITION_FOUND(afsk->sampledBits)) { if (afsk->currentPhase < PHASE_THRESHOLD) { afsk->currentPhase += PHASE_INC; } else { @@ -196,7 +326,7 @@ void afsk_adc_isr(Afsk *afsk, int8_t currentSample) { // We use the EDGE_FOUND function to determine this. // We also check the return of the Link Control parser // to check if an error occured. - if (!hdlcParse(&afsk->hdlc, !EDGE_FOUND(afsk->actualBits), &afsk->rxFifo)) { + if (!hdlcParse(&afsk->hdlc, !TRANSITION_FOUND(afsk->actualBits), &afsk->rxFifo)) { afsk->status |= RX_OVERRUN; } } @@ -215,6 +345,8 @@ static void afsk_txStart(Afsk *afsk) { afsk->phaseAcc = 0; afsk->bitstuffCount = 0; afsk->sending = true; + LED_TX_ON(); + PTT_ON(); afsk->preambleLength = DIV_ROUND(CONFIG_AFSK_PREAMBLE_LEN * BITRATE, 8000); AFSK_DAC_IRQ_START(afsk->dacPin); } @@ -233,6 +365,8 @@ uint8_t afsk_dac_isr(Afsk *afsk) { if (fifo_isempty(&afsk->txFifo) && afsk->tailLength == 0) { AFSK_DAC_IRQ_STOP(afsk->dacPin); afsk->sending = false; + LED_TX_OFF(); + PTT_OFF(); return 0; } else { // Reset the bitstuff counter if we have just sent @@ -262,6 +396,8 @@ uint8_t afsk_dac_isr(Afsk *afsk) { if (fifo_isempty(&afsk->txFifo)) { AFSK_DAC_IRQ_STOP(afsk->dacPin); afsk->sending = false; + LED_TX_OFF(); + PTT_OFF(); return 0; } else { afsk->currentOutputByte = fifo_pop(&afsk->txFifo); @@ -408,7 +544,9 @@ void afsk_init(Afsk *afsk, int _adcPin, int _dacPin) { // Init DAC & ADC AFSK_ADC_INIT(_adcPin, afsk); AFSK_DAC_INIT(_dacPin, afsk); - AFSK_STROBE_INIT(); + LED_TX_INIT(); + LED_RX_INIT(); + PTT_INIT(); DB(afsk->fd._type = KFT_AFSK); afsk->fd.write = afsk_write; diff --git a/Modem/hardware.c b/Modem/hardware.c index da4e5ae..1f3d3d5 100644 --- a/Modem/hardware.c +++ b/Modem/hardware.c @@ -1,7 +1,7 @@ #include "hardware.h" +#include "afsk.h" -#include #include #include @@ -48,7 +48,7 @@ DECLARE_ISR(ADC_vect) { TIFR1 = BV(ICF1); afsk_adc_isr(context, ((int16_t)((ADC) >> 2) - 128)); if (hw_afsk_dac_isr) - PORTD = afsk_dac_isr(context) & 0xF0; + PORTD = (afsk_dac_isr(context) & 0xF0) | BV(3); else PORTD = 128; } \ No newline at end of file diff --git a/Modem/hardware.h b/Modem/hardware.h index 25175b0..21aff90 100644 --- a/Modem/hardware.h +++ b/Modem/hardware.h @@ -13,13 +13,21 @@ void hw_afsk_dacInit(int ch, struct Afsk *_ctx); // ADC initialization #define AFSK_ADC_INIT(ch, ctx) hw_afsk_adcInit(ch, ctx) -// LED on/off (pin 13) -#define AFSK_STROBE_INIT() do { DDRB |= BV(5); } while (0) -#define AFSK_STROBE_ON() do { PORTB |= BV(5); } while (0) -#define AFSK_STROBE_OFF() do { PORTB &= ~BV(5); } while (0) +// LED TX/RX on/off (pin 9/10) +#define LED_TX_INIT() do { DDRB |= BV(1); } while (0) +#define LED_TX_ON() do { PORTB |= BV(1); } while (0) +#define LED_TX_OFF() do { PORTB &= ~BV(1); } while (0) + +#define LED_RX_INIT() do { DDRB |= BV(2); } while (0) +#define LED_RX_ON() do { PORTB |= BV(2); } while (0) +#define LED_RX_OFF() do { PORTB &= ~BV(2); } while (0) + +#define PTT_INIT() do { DDRD |= BV(3); } while (0) +#define PTT_ON() do { PORTD |= BV(3); } while (0) +#define PTT_OFF() do { PORTD &= ~BV(3); } while (0) // Initialization, start and stop for DAC -#define AFSK_DAC_INIT(ch, ctx) do { (void)ch, (void)ctx; DDRD |= 0xF0; DDRB |= BV(3); } while (0) +#define AFSK_DAC_INIT(ch, ctx) do { (void)ch, (void)ctx; DDRD |= 0xF4; DDRB |= BV(3); } while (0) #define AFSK_DAC_IRQ_START(ch) do { (void)ch; extern bool hw_afsk_dac_isr; PORTB |= BV(3); hw_afsk_dac_isr = true; } while (0) #define AFSK_DAC_IRQ_STOP(ch) do { (void)ch; extern bool hw_afsk_dac_isr; PORTB &= ~BV(3); hw_afsk_dac_isr = false; } while (0) diff --git a/Modem/main.c b/Modem/main.c index 889bfcd..1e72caf 100644 --- a/Modem/main.c +++ b/Modem/main.c @@ -1,79 +1,120 @@ -#include -#include +#include // Interrupt functionality from BertOS +#include // Debug configuration from BertOS + +#include // Serial driver from BertOS +#include // Timer driver from BertOS + +#include // Standard input/output +#include // String operations #include "afsk.h" // Header for AFSK modem #include "protocol/mp1.h" // Header for MP.1 protocol -#include -#include +static Afsk afsk; // Declare a AFSK modem struct +static MP1 mp1; // Declare a protocol struct +static Serial ser; // Declare a serial interface struct -#include -#include +#define ADC_CH 0 // Define which channel (pin) we want + // for the ADC (this is A0 on arduino) -static Afsk afsk; -static Serial ser; +#define TEST_TX false // Whether we should send test packets + // periodically, plus what to send: +#define TEST_PACKET "Test MP1 AFSK Packet. This is a test. 1234567890. ABCDEFGHIJKLMNOPQRSTUVWXYZ." -static MP1 mp1; -#define ADC_CH 0 +static uint8_t serialBuffer[MP1_MAX_FRAME_LENGTH]; // This is a buffer for incoming serial data +static int sbyte; // For holding byte read from serial port +static int serialLen = 0; // Counter for counting length of data from serial +static bool sertx = false; // Flag signifying whether it's time to send data + // Received on the serial port. -#define TEST_PACKET "Test MP1 AFSK Packet! This one is longer and probably more prone to errors..." - -static uint8_t serialBuffer[MP1_MAX_FRAME_LENGTH]; -static int sbyte; -static uint8_t serialLen = 0; -static bool sertx = false; +// This is a callback we register with the protocol, +// so we can process each packet as they are decoded. +// Right now it just prints the packet to the serial port. static void mp1Callback(struct MP1Packet *packet) { kfile_printf(&ser.fd, "%.*s\r\n", packet->dataLength, packet->data); } +// Simple initialization function. static void init(void) { + // Enable interrupts IRQ_ENABLE; + // Initialize BertOS debug bridge kdbg_init(); kprintf("Init\n"); + // Initialize hardware timers timer_init(); - afsk_init(&afsk, ADC_CH, 0); - mp1Init(&mp1, &afsk.fd, mp1Callback); - + // Initialize serial comms on UART0, + // which is the hardware serial on arduino ser_init(&ser, SER_UART0); ser_setbaudrate(&ser, 57600); - //ser_settimeouts(&ser, 0, 0); + + // Create a modem context + afsk_init(&afsk, ADC_CH, 0); + // ... and a protocol context with the modem + mp1Init(&mp1, &afsk.fd, mp1Callback); + + // That's all! } int main(void) { + // Start by running the main initialization init(); + // Record the current tick count for time-keeping ticks_t start = timer_clock(); + // Go into ye good ol' infinite loop while (1) { + // First we instruct the protocol to check for + // incoming data mp1Poll(&mp1); + // We then read a byte from the serial port. + // Notice that we use "_nowait" since we can't + // have this blocking execution until a byte + // comes in. sbyte = ser_getchar_nowait(&ser); + // If there was actually some data waiting for us + // there, let's se what it tastes like :) if (sbyte != EOF) { - if (serialLen < MP1_MAX_FRAME_LENGTH && sbyte != 138) { + // If we have not yet surpassed the maximum frame length + // and the byte is not a "transmit" (newline) character, + // we should store it for transmission. + if ((serialLen < MP1_MAX_FRAME_LENGTH) && (sbyte != 138)) { + // Put the read byte into the buffer; serialBuffer[serialLen] = sbyte; + // Increment the read length counter serialLen++; } else { + // If one of the above conditions were actually the + // case, it means we have to transmit, se we set + // transmission flag to true. sertx = true; } } + // Check whether we should send data in our serial buffer if (sertx) { + // If we should, pass the buffer to the protocol's + // send function. mp1Send(&mp1, serialBuffer, serialLen); + // Reset the transmission flag and length counter sertx = false; serialLen = 0; } - // Periodically send test data - if (false && timer_clock() - start > ms_to_ticks(4000L)) - { + // Periodically send test data if we should do so + if (TEST_TX && timer_clock() - start > ms_to_ticks(15000L)) { + // Reset the timer counter; start = timer_clock(); + // And send a test packet! mp1Send(&mp1, TEST_PACKET, sizeof(TEST_PACKET)); } } diff --git a/Modem/protocol/mp1.c b/Modem/protocol/mp1.c index 7dc8ee4..4d8303f 100644 --- a/Modem/protocol/mp1.c +++ b/Modem/protocol/mp1.c @@ -1,18 +1,30 @@ #include "mp1.h" #include #include -//#include static void mp1Decode(MP1 *mp1) { + // This decode function is basic and bare minimum. + // It does nothing more than extract the data + // payload from the buffer and put it into a struct + // for further processing. MP1Packet packet; // A decoded packet struct uint8_t *buffer = mp1->buffer; // Get the buffer from the protocol context + // Set the payload length of the packet to the counted + // length minus 1, so we remove the checksum packet.dataLength = mp1->packetLength - 1; packet.data = buffer; + // If a callback have been specified, let's + // call it and pass the decoded packet if (mp1->callback) mp1->callback(&packet); } +//////////////////////////////////////////////////////////// +// The Poll function reads data from the modem, handles // +// frame recognition and passes data on to higher layers // +// if valid packets are found // +//////////////////////////////////////////////////////////// void mp1Poll(MP1 *mp1) { int byte; @@ -26,13 +38,13 @@ void mp1Poll(MP1 *mp1) { // frame length, which means the flag signifies // the end of the packet. Pass control to the // decoder. - kprintf("Got checksum: %d.\n", mp1->buffer[mp1->packetLength-1]); + // kprintf("Got checksum: %d.\n", mp1->buffer[mp1->packetLength-1]); if ((mp1->checksum_in & 0xff) == 0x00) { //kprintf("Correct checksum. Found %d.\n", mp1->buffer[mp1->packetLength-1]); mp1Decode(mp1); } else { // Checksum was incorrect - mp1Decode(mp1); + //mp1Decode(mp1); //kprintf("Incorrect checksum. Found %d.\n", mp1->buffer[mp1->packetLength]); //kprintf("should be %d", mp1->checksum_in); } @@ -127,7 +139,7 @@ void mp1Send(MP1 *mp1, const void *_buffer, size_t length) { } // Write checksum to end of packet - kprintf("Checksum of this packet is %d\n", mp1->checksum_out); + kprintf("Sending packet with checksum %d\n", mp1->checksum_out); mp1Putbyte(mp1, mp1->checksum_out); // Transmit a HDLC_FLAG to signify end of TX @@ -137,6 +149,8 @@ void mp1Send(MP1 *mp1, const void *_buffer, size_t length) { void mp1Init(MP1 *mp1, KFile *modem, mp1_callback_t callback) { // Allocate memory for our protocol "object" memset(mp1, 0, sizeof(*mp1)); + // Set references to our modem "object" and + // a callback for when a packet has been decoded mp1->modem = modem; mp1->callback = callback; } \ No newline at end of file diff --git a/buildrev.h b/buildrev.h index df659b7..8d68363 100644 --- a/buildrev.h +++ b/buildrev.h @@ -1,2 +1,2 @@ -#define VERS_BUILD 308 +#define VERS_BUILD 357 #define VERS_HOST "vixen"