Commenting

This commit is contained in:
Mark Qvist 2014-04-06 17:17:13 +02:00
parent 0c4bc75be9
commit 017a1e6887
6 changed files with 263 additions and 62 deletions

View File

@ -1,17 +1,31 @@
#include "afsk.h"
#include "config.h"
#include "hardware.h"
//////////////////////////////////////////////////////
// First things first, all the includes we need //
//////////////////////////////////////////////////////
#include <drv/timer.h>
#include <cfg/module.h>
#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 <cpu/power.h>
#include <cpu/pgm.h>
#include <struct/fifobuf.h>
#include <string.h>
#include <drv/timer.h> // Timer driver from BertOS
//FIXME: is this needed ? #include <cfg/module.h>
// Sine table for DAC DDS
#define SIN_LEN 512 // Length of a full wave. Table is 1/4 wave.
#include <cpu/power.h> // Power management from BertOS
#include <cpu/pgm.h> // Access to PROGMEM from BertOS
#include <struct/fifobuf.h> // FIFO buffer implementation from BertOS
#include <string.h> // 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;

View File

@ -1,7 +1,7 @@
#include "hardware.h"
#include "afsk.h"
#include <net/afsk.h>
#include <cpu/irq.h>
#include <avr/io.h>
@ -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;
}

View File

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

View File

@ -1,79 +1,120 @@
#include <cpu/irq.h>
#include <cfg/debug.h>
#include <cpu/irq.h> // Interrupt functionality from BertOS
#include <cfg/debug.h> // Debug configuration from BertOS
#include <drv/ser.h> // Serial driver from BertOS
#include <drv/timer.h> // Timer driver from BertOS
#include <stdio.h> // Standard input/output
#include <string.h> // String operations
#include "afsk.h" // Header for AFSK modem
#include "protocol/mp1.h" // Header for MP.1 protocol
#include <drv/ser.h>
#include <drv/timer.h>
static Afsk afsk; // Declare a AFSK modem struct
static MP1 mp1; // Declare a protocol struct
static Serial ser; // Declare a serial interface struct
#include <stdio.h>
#include <string.h>
#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));
}
}

View File

@ -1,18 +1,30 @@
#include "mp1.h"
#include <string.h>
#include <drv/ser.h>
//#include <ctype.h>
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;
}

View File

@ -1,2 +1,2 @@
#define VERS_BUILD 308
#define VERS_BUILD 357
#define VERS_HOST "vixen"