From 9b2987de0835a3c956dbf12ef026c01d2ffce102 Mon Sep 17 00:00:00 2001 From: Stephen Olesen Date: Tue, 30 Jun 2015 19:22:46 -0600 Subject: [PATCH 01/44] Initial import of the main code from https://github.com/slepp/AX25 Partially functional, but no accuracy tests complete yet. --- AFSK.cpp | 507 ++++++++++++++++++++++++++++++++++++++++++++++++++ AFSK.h | 265 ++++++++++++++++++++++++++ HamShield.cpp | 10 + HamShield.h | 18 +- SimpleFIFO.h | 89 +++++++++ 5 files changed, 886 insertions(+), 3 deletions(-) create mode 100644 AFSK.cpp create mode 100644 AFSK.h create mode 100644 SimpleFIFO.h diff --git a/AFSK.cpp b/AFSK.cpp new file mode 100644 index 0000000..7bd4612 --- /dev/null +++ b/AFSK.cpp @@ -0,0 +1,507 @@ +#include +#include "HamShield.h" +#include "SimpleFIFO.h" +#include + +#define PHASE_BIT 8 +#define PHASE_INC 1 + +#define PHASE_MAX (SAMPLEPERBIT * PHASE_BIT) +#define PHASE_THRES (PHASE_MAX / 2) + +#define BIT_DIFFER(bitline1, bitline2) (((bitline1) ^ (bitline2)) & 0x01) +#define EDGE_FOUND(bitline) BIT_DIFFER((bitline), (bitline) >> 1) + +#define PPOOL_SIZE 2 + +#define ACCUMULATOR_BITS 24 // This is 2^10 bits used from accum +//#undef PROGMEM +//#define PROGMEM __attribute__((section(".progmem.data"))) +const uint8_t PROGMEM sinetable[256] = { + 128,131,134,137,140,143,146,149,152,156,159,162,165,168,171,174, + 176,179,182,185,188,191,193,196,199,201,204,206,209,211,213,216, + 218,220,222,224,226,228,230,232,234,236,237,239,240,242,243,245, + 246,247,248,249,250,251,252,252,253,254,254,255,255,255,255,255, + 255,255,255,255,255,255,254,254,253,252,252,251,250,249,248,247, + 246,245,243,242,240,239,237,236,234,232,230,228,226,224,222,220, + 218,216,213,211,209,206,204,201,199,196,193,191,188,185,182,179, + 176,174,171,168,165,162,159,156,152,149,146,143,140,137,134,131, + 128,124,121,118,115,112,109,106,103,99, 96, 93, 90, 87, 84, 81, + 79, 76, 73, 70, 67, 64, 62, 59, 56, 54, 51, 49, 46, 44, 42, 39, + 37, 35, 33, 31, 29, 27, 25, 23, 21, 19, 18, 16, 15, 13, 12, 10, + 9, 8, 7, 6, 5, 4, 3, 3, 2, 1, 1, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 1, 1, 2, 3, 3, 4, 5, 6, 7, 8, + 9, 10, 12, 13, 15, 16, 18, 19, 21, 23, 25, 27, 29, 31, 33, 35, + 37, 39, 42, 44, 46, 49, 51, 54, 56, 59, 62, 64, 67, 70, 73, 76, + 79, 81, 84, 87, 90, 93, 96, 99, 103,106,109,112,115,118,121,124 +}; + +#define AFSK_SPACE 0 +#define AFSK_MARK 1 + +// Timers +volatile unsigned long lastTx = 0; +volatile unsigned long lastTxEnd = 0; +volatile unsigned long lastRx = 0; + +#define REFCLK 9600 +//#define REFCLK 31372.54902 +//#define REFCLK (16000000.0/510.0) +//#define REFCLK 31200.0 +// 2200Hz = pow(2,32)*2200.0/refclk +// 1200Hz = pow(2,32)*1200.0/refclk +static const unsigned long toneStep[2] = { + pow(2,32)*2200.0/REFCLK, + pow(2,32)*1200.0/REFCLK +}; + +// Set to an arbitrary frequency +void AFSK::Encoder::setFreq(unsigned long freq, byte vol) { + unsigned long newStep = pow(2,32)*freq/REFCLK; + rStep = newStep; // Atomic? (ish) +} + +// This allows a programmatic way to tune the output tones +static const byte toneVolume[2] = { + 255, + 255 +}; + +#define T_BIT ((unsigned int)(REFCLK/1200)) + +void AFSK::Encoder::process() { + // Check what clock pulse we're on + if(bitClock == 0) { // We are onto our next bit timing + // We're on the start of a byte position, so fetch one + if(bitPosition == 0) { + if(preamble) { // Still in preamble + currentByte = HDLC_PREAMBLE; + --preamble; // Decrement by one + } else { + if(!packet) { // We aren't on a packet, grab one + // Unless we already sent enough + if(maxTx-- == 0) { + stop(); + lastTxEnd = millis(); + return; + } + packet = pBuf.getPacket(); + if(!packet) { // There actually weren't any + stop(); // Stop transmitting and return + lastTxEnd = millis(); + return; + } + lastTx = millis(); + currentBytePos = 0; + } + + // We ran out of actual data, provide an HDLC frame (idle) + if(currentBytePos++ == packet->len) { + pBuf.freePacket(packet); + packet = pBuf.getPacket(); // Get the next, if any + currentBytePos = 0; + currentByte = HDLC_FRAME; + hdlc = true; + } else { + // Grab the next byte + currentByte = packet->getByte(); //[currentBytePos++]; + if(currentByte == HDLC_ESCAPE) { + currentByte = packet->getByte(); //[currentBytePos++]; + hdlc = true; + } else { + hdlc = false; + } + } + } + } + + // Pickup the last bit + currentBit = currentByte & 0x1; + + if(lastZero == 5) { + currentBit = 0; // Force a 0 bit output + } else { + currentByte >>= 1; // Bit shift it right, for the next round + ++bitPosition; // Note our increase in position + } + + // To handle NRZI 5 bit stuffing, count the bits + if(!currentBit || hdlc) + lastZero = 0; + else + ++lastZero; + + // NRZI and AFSK uses toggling 0s, "no change" on 1 + // So, if not a 1, toggle to the opposite tone + if(!currentBit) + currentTone = !currentTone; + } + + // Advance the bitclock here, to let first bit be sent early + if(++bitClock == T_BIT) + bitClock = 0; + + accumulator += toneStep[currentTone]; + uint8_t phAng = (accumulator >> ACCUMULATOR_BITS); + /*if(toneVolume[currentTone] != 255) { + OCR2B = pwm * toneVolume[currentTone] / 255; + } else {*/ + // No volume scaling required + OCR2B = pgm_read_byte_near(sinetable + phAng); + /*}*/ +} + +bool AFSK::Encoder::start() { + if(!done || sending) { + return false; + } + + if(randomWait > millis()) { + return false; + } + + accumulator = 0; + // First real byte is a frame + currentBit = 0; + lastZero = 0; + bitPosition = 0; + bitClock = 0; + preamble = 23; // 6.7ms each, 23 = 153ms + done = false; + hdlc = true; + packet = 0x0; // No initial packet, find in the ISR + currentBytePos = 0; + maxTx = 3; + sending = true; + return true; +} + +void AFSK::Encoder::stop() { + randomWait = 0; + sending = false; + done = true; + OCR2B = 0; +} + +AFSK::Decoder::Decoder() { + // Initialize the sampler delay line (phase shift) + for(unsigned char i = 0; i < SAMPLEPERBIT/2; i++) + delay_fifo.enqueue(0); +} + +bool AFSK::HDLCDecode::hdlcParse(bool bit, SimpleFIFO *fifo) { + bool ret = true; + + demod_bits <<= 1; + demod_bits |= bit ? 1 : 0; + + // Flag + if(demod_bits == HDLC_FRAME) { + fifo->enqueue(HDLC_FRAME); + rxstart = true; + currchar = 0; + bit_idx = 0; + return ret; + } + + // Reset + if((demod_bits & HDLC_RESET) == HDLC_RESET) { + rxstart = false; + lastRx = millis(); + return ret; + } + if(!rxstart) { + return ret; + } + + // Stuffed? + if((demod_bits & 0x3f) == 0x3e) + return ret; + + if(demod_bits & 0x01) + currchar |= 0x80; + + if(++bit_idx >= 8) { + if(currchar == HDLC_FRAME || + currchar == HDLC_RESET || + currchar == HDLC_ESCAPE) { + fifo->enqueue(HDLC_ESCAPE); + } + fifo->enqueue(currchar & 0xff); + currchar = 0; + bit_idx = 0; + } else { + currchar >>= 1; + } + + return ret; +} + +// Handle the A/D converter interrupt (hopefully quickly :) +void AFSK::Decoder::process(int8_t curr_sample) { + // Run the same through the phase multiplier and butterworth filter + iir_x[0] = iir_x[1]; + iir_x[1] = ((int8_t)delay_fifo.dequeue() * curr_sample) >> 2; + iir_y[0] = iir_y[1]; + iir_y[1] = iir_x[0] + iir_x[1] + (iir_y[0] >> 1) + (iir_y[0]>>3) + (iir_y[0]>>5); + + // Shift the bit into place based on the output of the discriminator + sampled_bits <<= 1; + sampled_bits |= (iir_y[1] > 0) ? 1 : 0; + + // Place this ADC sample into the delay line + delay_fifo.enqueue(curr_sample); + + // If we found a 0/1 transition, adjust phases to track + if(EDGE_FOUND(sampled_bits)) { + if(curr_phase < PHASE_THRES) + curr_phase += PHASE_INC; + else + curr_phase -= PHASE_INC; + } + + // Move ahead in phase + curr_phase += PHASE_BIT; + + // If we've gone over the phase maximum, we should now have some data + if(curr_phase >= PHASE_MAX) { + curr_phase %= PHASE_MAX; + found_bits <<= 1; + + // If we have 3 bits or more set, it's a positive bit + register uint8_t bits = sampled_bits & 0x07; + if(bits == 0x07 || bits == 0x06 || bits == 0x05 || bits == 0x03) { + found_bits |= 1; + } + + hdlc.hdlcParse(!EDGE_FOUND(found_bits), &rx_fifo); // Process it + } +} + +// This routine uses a pre-allocated Packet structure +// to save on the memory requirements of the stream data +bool AFSK::Decoder::read() { + bool retVal = false; + if(!currentPacket) { // We failed a prior memory allocation + currentPacket = pBuf.makePacket(PACKET_MAX_LEN); + if(!currentPacket) // Still nothing + return false; + } + // While we have AFSK receive FIFO bytes... + while(rx_fifo.count()) { + // Grab the character + char c = rx_fifo.dequeue(); + + bool escaped = false; + if(c == HDLC_ESCAPE) { // We received an escaped byte, mark it + escaped = true; + currentPacket->append(HDLC_ESCAPE); // Append without FCS + c = rx_fifo.dequeue(); // Reset to the next character + } + + // Append all the bytes + // This will include unescaped HDLC_FRAME bytes + //if(c == HDLC_FRAME && !escaped) + //currentPacket->append(c); // Framing bytes don't get FCS updates + //else + if(c != HDLC_FRAME) + currentPacket->appendFCS(c); // Escaped characters and all else go into FCS + + if(currentPacket->len > PACKET_MAX_LEN) { + // We've now gone too far and picked up far too many bytes + // Cancel this frame, start back at the beginning + currentPacket->clear(); + continue; + } + + // We have a frame boundary, if it isn't escaped + // If it's escaped, it was part of the data stream + if(c == HDLC_FRAME && !escaped) { + if(!currentPacket->len) { + currentPacket->clear(); // There wasn't any data, restart stream + continue; + } else { + // We have some bytes in stream, check it meets minimum payload length + // Min payload is 1 (flag) + 14 (addressing) + 2 (control/PID) + 1 (flag) + if(currentPacket->len >= 16) { + // We should end up here with a valid FCS due to the appendFCS + if(currentPacket->crcOK()) { // Magic number for the CRC check passing + // Valid frame, so, let's filter for control + PID + // Maximum search distance is 71 bytes to end of the address fields + // Skip the HDLC frame start + bool filtered = false; + for(unsigned char i = 0; i < (currentPacket->len<70?currentPacket->len:71); ++i) { + if((currentPacket->getByte() & 0x1) == 0x1) { // Found a byte with LSB set + // which marks the final address payload + // next two bytes should be the control/PID + if(currentPacket->getByte() == 0x03 && currentPacket->getByte() == 0xf0) { + filtered = true; + break; // Found it + } + } + } + + if(!filtered) { + // Frame wasn't one we care about, discard + currentPacket->clear(); + continue; + } + + // It's all done and formatted, ready to go + currentPacket->ready = 1; + if(!pBuf.putPacket(currentPacket)) // Put it in the receive FIFO + pBuf.freePacket(currentPacket); // Out of FIFO space, so toss it + + // Allocate a new one of maximum length + currentPacket = pBuf.makePacket(PACKET_MAX_LEN); + retVal = true; + } + } + } + // Restart the stream + currentPacket->clear(); + } + } + return retVal; // This is true if we parsed a packet in this flow +} + +void AFSK::Decoder::start() { + // Do this in start to allocate our first packet + currentPacket = pBuf.makePacket(PACKET_MAX_LEN); + // Configure the ADC and Timer1 to trigger automatic interrupts + TCCR1A = 0; + TCCR1B = _BV(CS11) | _BV(WGM13) | _BV(WGM12); + ICR1 = ((F_CPU / 8) / REFCLK) - 1; + ADMUX = _BV(REFS0) | _BV(ADLAR) | 0; // Channel 0, shift result left (ADCH used) + DDRC &= ~_BV(0); + PORTC &= ~_BV(0); + DIDR0 |= _BV(0); + ADCSRB = _BV(ADTS2) | _BV(ADTS1) | _BV(ADTS0); + ADCSRA = _BV(ADEN) | _BV(ADSC) | _BV(ADATE) | _BV(ADIE) | _BV(ADPS2); // | _BV(ADPS0); +} + +AFSK::PacketBuffer::PacketBuffer() { + nextPacketIn = 0; + nextPacketOut = 0; + inBuffer = 0; + for(unsigned char i = 0; i < PACKET_BUFFER_SIZE; ++i) { + packets[i] = 0x0; + } +} + +unsigned char AFSK::PacketBuffer::readyCount() volatile { + unsigned char i; + unsigned int cnt = 0; + ATOMIC_BLOCK(ATOMIC_RESTORESTATE) { + for(i = 0; i < PACKET_BUFFER_SIZE; ++i) { + if(packets[i] && packets[i]->ready) + ++cnt; + } + } + return cnt; +} + +// Return NULL on empty packet buffers +AFSK::Packet *AFSK::PacketBuffer::getPacket() volatile { + unsigned char i = 0; + AFSK::Packet *p = NULL; + + ATOMIC_BLOCK(ATOMIC_RESTORESTATE) { + if(inBuffer == 0) { + return 0x0; + } + + do { + p = packets[nextPacketOut]; + if(p) { + packets[nextPacketOut] = 0x0; + --inBuffer; + } + nextPacketOut = ++nextPacketOut % PACKET_BUFFER_SIZE; + ++i; + } while(!p && iinit(dlen); + } + return p; // Passes through a null on failure. +} + +// Free a packet struct, mainly convenience +void AFSK::PacketBuffer::freePacket(Packet *p) { + if(!p) + return; + ATOMIC_BLOCK(ATOMIC_RESTORESTATE) { + p->free(); + /*unsigned char i; + for(i = 0; i < PPOOL_SIZE; ++i) + if(p == &(pPool[i])) + break; + if(i < PPOOL_SIZE) + pStatus &= ~(1<= PACKET_BUFFER_SIZE) { + return false; + } + packets[nextPacketIn] = p; + nextPacketIn = ++nextPacketIn % PACKET_BUFFER_SIZE; + ++inBuffer; + } + return true; +} + +// Print a single byte to the data array +size_t AFSK::Packet::write(uint8_t c) { + return (appendFCS(c)?1:0); +} + +size_t AFSK::Packet::write(const uint8_t *ptr, size_t len) { + size_t i; + for(i = 0; i < len; ++i) + if(!appendFCS(ptr[i])) + break; + return i; +} + +// Determine what we want to do on this ADC tick. +void AFSK::timer() { + if(encoder.isSending()) + encoder.process(); + decoder.process(ADCH - 128); +} + +void AFSK::start() { + afskEnabled = true; + decoder.start(); +} diff --git a/AFSK.h b/AFSK.h new file mode 100644 index 0000000..5d96c41 --- /dev/null +++ b/AFSK.h @@ -0,0 +1,265 @@ +#ifndef _AFSK_H_ +#define _AFSK_H_ + +#include +#include + +#define SAMPLERATE 9600 +#define BITRATE 1200 + +#define SAMPLEPERBIT (SAMPLERATE / BITRATE) + +#define RX_FIFO_LEN 16 + +#define PACKET_BUFFER_SIZE 2 +#define PACKET_STATIC 0 + +// This is with all the digis, two addresses, framing and full payload +// Two more bytes are added for HDLC_ESCAPEs +#define PACKET_MAX_LEN 512 + +// HDLC framing bits +#define HDLC_FRAME 0x7E +#define HDLC_RESET 0x7F +#define HDLC_PREAMBLE 0x00 +#define HDLC_ESCAPE 0x1B +#define HDLC_TAIL 0x1C + +class AFSK { +private: + volatile bool afskEnabled; +public: + bool enabled() { return afskEnabled; }; + + class Packet:public Print { + public: + Packet():Print() {}; + virtual size_t write(uint8_t); + // Stock virtual method does what we want here. + //virtual size_t write(const char *); + virtual size_t write(const uint8_t *, size_t); + using Print::write; + unsigned char ready : 1; + unsigned char type : 2; + unsigned char freeData : 1; + unsigned short len; + unsigned short maxLen; + //void init(uint8_t *buf, unsigned int dlen, bool freeData); + void init(unsigned short dlen); + inline void free() { + if(freeData) + ::free(dataPtr); + } + inline const unsigned char getByte(void) { + return *readPos++; + } + inline const unsigned char getByte(uint16_t p) { + return *(dataPtr+p); + } + inline void start() { + fcs = 0xffff; + *dataPos++ = HDLC_ESCAPE; + *dataPos++ = HDLC_FRAME; + len = 2; + } + + inline bool append(char c) { + if(len < maxLen) { + ++len; + *dataPos++ = c; + return true; + } + return false; + } + + #define UPDATE_FCS(d) e=fcs^(d); f=e^(e<<4); fcs=(fcs>>8)^(f<<8)^(f<<3)^(f>>4) + //#define UPDATE_FCS(d) s=(d)^(fcs>>8); t=s^(s>>4); fcs=(fcs<<8)^t^(t<<5)^(t<<12) + inline bool appendFCS(unsigned char c) { + register unsigned char e, f; + if(len < maxLen - 4) { // Leave room for FCS/HDLC + append(c); + UPDATE_FCS(c); + return true; + } + return false; + } + + inline void finish() { + append(~(fcs & 0xff)); + append(~((fcs>>8) & 0xff)); + append(HDLC_ESCAPE); + append(HDLC_FRAME); + ready = 1; + } + + inline void clear() { + fcs = 0xffff; + len = 0; + readPos = dataPtr; + dataPos = dataPtr; + } + + inline bool crcOK() { + return (fcs == 0xF0B8); + } + private: + uint8_t *dataPtr, *dataPos, *readPos; + unsigned short fcs; + }; + + + class PacketBuffer { + public: + // Initialize the buffers + PacketBuffer(); + // How many packets are in the buffer? + unsigned char count() volatile { return inBuffer; }; + // And how many of those are ready? + unsigned char readyCount() volatile; + // Retrieve the next packet + Packet *getPacket() volatile; + // Create a packet structure as needed + // This does not place it in the queue + static Packet *makePacket(unsigned short); + // Conveniently free packet memory + static void freePacket(Packet *); + // Place a packet into the buffer + bool putPacket(Packet *) volatile; + private: + volatile unsigned char inBuffer; + Packet * volatile packets[PACKET_BUFFER_SIZE]; + volatile unsigned char nextPacketIn; + volatile unsigned char nextPacketOut; + }; + + class Encoder { + public: + Encoder() { + randomWait = 1000; // At the very begin, wait at least one second + sending = false; + done = true; + packet = 0x0; + currentBytePos = 0; + } + void setFreq(unsigned long, byte); + volatile inline bool isSending() volatile { + return sending; + } + volatile inline bool isDone() volatile { + return done; + } + volatile inline bool hasPackets() volatile { + return (pBuf.count() > 0); + } + inline bool putPacket(Packet *packet) { + return pBuf.putPacket(packet); + } + inline void setRandomWait() { + randomWait = 250 + (rand() % 1000) + millis(); + } + bool start(); + void stop(); + void process(); + private: + volatile bool sending; + byte currentByte; + byte currentBit : 1; + byte currentTone : 1; + byte lastZero : 3; + byte bitPosition : 3; + byte preamble : 6; + byte bitClock; + bool hdlc; + byte maxTx; + Packet *packet; + PacketBuffer pBuf; + unsigned char currentBytePos; + volatile unsigned long randomWait; + volatile bool done; + // Phase accumulator, 32 bits, we'll use ACCUMULATOR_BITS of it + unsigned long accumulator; + // Current radian step for the accumulator + unsigned long rStep; + }; + + class HDLCDecode { + public: + bool hdlcParse(bool, SimpleFIFO *fifo); + volatile bool rxstart; + private: + uint8_t demod_bits; + uint8_t bit_idx; + uint8_t currchar; + }; + + class Decoder { + public: + Decoder(); + void start(); + bool read(); + void process(int8_t); + inline bool dataAvailable() { + return (rx_fifo.count() > 0); + } + inline uint8_t getByte() { + return rx_fifo.dequeue(); + } + inline uint8_t packetCount() volatile { + return pBuf.count(); + } + inline Packet *getPacket() { + return pBuf.getPacket(); + } + inline bool isReceiving() volatile { + return hdlc.rxstart; + } + private: + Packet *currentPacket; + SimpleFIFO delay_fifo; + SimpleFIFO rx_fifo; // This should be drained fairly often + int16_t iir_x[2]; + int16_t iir_y[2]; + uint8_t sampled_bits; + int8_t curr_phase; + uint8_t found_bits; + PacketBuffer pBuf; + HDLCDecode hdlc; + }; + +public: + inline bool read() { + return decoder.read(); + } + inline bool txReady() volatile { + if(encoder.isDone() && encoder.hasPackets()) + return true; + return false; + } + inline bool isDone() volatile { return encoder.isDone(); } + inline bool txStart() { + if(decoder.isReceiving()) { + encoder.setRandomWait(); + return false; + } + return encoder.start(); + } + inline bool putTXPacket(Packet *packet) { + bool ret = encoder.putPacket(packet); + if(!ret) // No room? + PacketBuffer::freePacket(packet); + return ret; + } + inline Packet *getRXPacket() { + return decoder.getPacket(); + } + inline uint8_t rxPacketCount() volatile { + return decoder.packetCount(); + } + //unsigned long lastTx; + //unsigned long lastRx; + void start(); + void timer(); + Encoder encoder; + Decoder decoder; +}; +#endif /* _AFSK_H_ */ diff --git a/HamShield.cpp b/HamShield.cpp index 334dfcc..6d66d2f 100644 --- a/HamShield.cpp +++ b/HamShield.cpp @@ -1352,3 +1352,13 @@ void HamShield::AFSKOut(char buffer[80]) { } */ + +// This is the ADC timer handler. When enabled, we'll see what we're supposed +// to be reading/handling, and trigger those on the main object. +ISR(ADC_vect) { + TIFR1 = _BV(ICF1); // Clear the timer flag + + if(HamShield::sHamShield->afsk.enabled()) { + HamShield::sHamShield->afsk.timer(); + } +} diff --git a/HamShield.h b/HamShield.h index d43be05..ecd08ac 100644 --- a/HamShield.h +++ b/HamShield.h @@ -9,6 +9,8 @@ #define _HAMSHIELD_H_ #include "I2Cdev_rda.h" +#include "SimpleFIFO.h" +#include "AFSK.h" #include // HamShield constants @@ -19,6 +21,8 @@ #define HAMSHIELD_PWM_PIN 11 // Pin assignment for PWM output #define HAMSHIELD_EMPTY_CHANNEL_RSSI -110 // Default threshold where channel is considered "clear" +#define HAMSHIELD_AFSK_RX_FIFO_LEN 16 + // button modes #define PTT_MODE 1 #define RESET_MODE 2 @@ -531,7 +535,14 @@ class HamShield { bool parityCalc(int code); // void AFSKOut(char buffer[80]); - + // AFSK routines + bool AFSKStart(); + bool AFSKEnabled() { return afsk.enabled(); } + bool AFSKStop(); + bool AFSKOut(const char *); + + class AFSK afsk; + private: uint8_t devAddr; uint16_t radio_i2c_buf[4]; @@ -542,9 +553,10 @@ class HamShield { uint32_t MURS[]; uint32_t WX[]; - public: + // public singleton for ISRs to reference + public: static HamShield *sHamShield; // HamShield singleton, used for ISRs mostly - + // int8_t A1846S::readWord(uint8_t devAddr, uint8_t regAddr, uint16_t *data, uint16_t timeout); // int8_t A1846S::readBits(uint8_t devAddr, uint8_t regAddr, uint8_t bitStart, uint8_t length, uint16_t *data, uint16_t timeout); // int8_t A1846S::readBit(uint8_t devAddr, uint8_t regAddr, uint8_t bitNum, uint16_t *data, uint16_t timeout); diff --git a/SimpleFIFO.h b/SimpleFIFO.h new file mode 100644 index 0000000..6965ce6 --- /dev/null +++ b/SimpleFIFO.h @@ -0,0 +1,89 @@ +#ifndef SimpleFIFO_h +#define SimpleFIFO_h +/* +|| +|| @file SimpleFIFO.h +|| @version 1.2 +|| @author Alexander Brevig +|| @contact alexanderbrevig@gmail.com +|| +|| @description +|| | A simple FIFO class, mostly for primitive types but can be used with classes if assignment to int is allowed +|| | This FIFO is not dynamic, so be sure to choose an appropriate size for it +|| # +|| +|| @license +|| | Copyright (c) 2010 Alexander Brevig +|| | This library is free software; you can redistribute it and/or +|| | modify it under the terms of the GNU Lesser General Public +|| | License as published by the Free Software Foundation; version +|| | 2.1 of the License. +|| | +|| | This library is distributed in the hope that it will be useful, +|| | but WITHOUT ANY WARRANTY; without even the implied warranty of +|| | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +|| | Lesser General Public License for more details. +|| | +|| | You should have received a copy of the GNU Lesser General Public +|| | License along with this library; if not, write to the Free Software +|| | Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +|| # +|| +*/ +template +class SimpleFIFO { +public: + const int size; //speculative feature, in case it's needed + + SimpleFIFO(); + + T dequeue(); //get next element + bool enqueue( T element ); //add an element + T peek() const; //get the next element without releasing it from the FIFO + void flush(); //[1.1] reset to default state + + //how many elements are currently in the FIFO? + unsigned char count() { return numberOfElements; } + +private: +#ifndef SimpleFIFO_NONVOLATILE + volatile unsigned char numberOfElements; + volatile unsigned char nextIn; + volatile unsigned char nextOut; + volatile T raw[rawSize]; +#else + unsigned char numberOfElements; + unsigned char nextIn; + unsigned char nextOut; + T raw[rawSize]; +#endif +}; + +template +SimpleFIFO::SimpleFIFO() : size(rawSize) { + flush(); +} +template +bool SimpleFIFO::enqueue( T element ) { + if ( count() >= rawSize ) { return false; } + numberOfElements++; + nextIn %= size; + raw[nextIn] = element; + nextIn++; //advance to next index + return true; +} +template +T SimpleFIFO::dequeue() { + numberOfElements--; + nextOut %= size; + return raw[ nextOut++]; +} +template +T SimpleFIFO::peek() const { + return raw[ nextOut % size]; +} +template +void SimpleFIFO::flush() { + nextIn = nextOut = numberOfElements = 0; +} +#endif From 55c10c503b359a4b6c2c3c392b1ee0a1a2c5172b Mon Sep 17 00:00:00 2001 From: Stephen Olesen Date: Tue, 30 Jun 2015 20:32:28 -0600 Subject: [PATCH 02/44] Some spacing, added header length minimum. --- AFSK.h | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/AFSK.h b/AFSK.h index 5d96c41..6c4d1dc 100644 --- a/AFSK.h +++ b/AFSK.h @@ -17,6 +17,7 @@ // This is with all the digis, two addresses, framing and full payload // Two more bytes are added for HDLC_ESCAPEs #define PACKET_MAX_LEN 512 +#define AX25_PACKET_HEADER_MINLEN 22 // HDLC framing bits #define HDLC_FRAME 0x7E @@ -48,7 +49,7 @@ public: void init(unsigned short dlen); inline void free() { if(freeData) - ::free(dataPtr); + ::free(dataPtr); } inline const unsigned char getByte(void) { return *readPos++; @@ -232,7 +233,7 @@ public: } inline bool txReady() volatile { if(encoder.isDone() && encoder.hasPackets()) - return true; + return true; return false; } inline bool isDone() volatile { return encoder.isDone(); } @@ -246,7 +247,7 @@ public: inline bool putTXPacket(Packet *packet) { bool ret = encoder.putPacket(packet); if(!ret) // No room? - PacketBuffer::freePacket(packet); + PacketBuffer::freePacket(packet); return ret; } inline Packet *getRXPacket() { From 11175424113617ed84a2bf95a85e3d8ceeb1bafb Mon Sep 17 00:00:00 2001 From: Stephen Olesen Date: Wed, 1 Jul 2015 14:04:20 -0600 Subject: [PATCH 03/44] Created generic DDS class for tone generation, update AFSK to start the right timers. --- AFSK.cpp | 28 ++++++--- DDS.cpp | 82 ++++++++++++++++++++++++++ DDS.h | 171 +++++++++++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 272 insertions(+), 9 deletions(-) create mode 100644 DDS.cpp create mode 100644 DDS.h diff --git a/AFSK.cpp b/AFSK.cpp index 7bd4612..336e055 100644 --- a/AFSK.cpp +++ b/AFSK.cpp @@ -14,6 +14,8 @@ #define PPOOL_SIZE 2 +#define COMPARE_BITS 6 +#define ACCUMULATOR_SIZE 32 #define ACCUMULATOR_BITS 24 // This is 2^10 bits used from accum //#undef PROGMEM //#define PROGMEM __attribute__((section(".progmem.data"))) @@ -44,15 +46,15 @@ volatile unsigned long lastTx = 0; volatile unsigned long lastTxEnd = 0; volatile unsigned long lastRx = 0; -#define REFCLK 9600 +#define REFCLK 38400 //#define REFCLK 31372.54902 //#define REFCLK (16000000.0/510.0) //#define REFCLK 31200.0 // 2200Hz = pow(2,32)*2200.0/refclk // 1200Hz = pow(2,32)*1200.0/refclk static const unsigned long toneStep[2] = { - pow(2,32)*2200.0/REFCLK, - pow(2,32)*1200.0/REFCLK + (2200.0/REFCLK)*pow(2,ACCUMULATOR_SIZE), + (1200.0/REFCLK)*pow(2,ACCUMULATOR_SIZE) }; // Set to an arbitrary frequency @@ -143,12 +145,7 @@ void AFSK::Encoder::process() { accumulator += toneStep[currentTone]; uint8_t phAng = (accumulator >> ACCUMULATOR_BITS); - /*if(toneVolume[currentTone] != 255) { - OCR2B = pwm * toneVolume[currentTone] / 255; - } else {*/ - // No volume scaling required - OCR2B = pgm_read_byte_near(sinetable + phAng); - /*}*/ + OCR2B = pgm_read_byte_near(sinetable + phAng)>>(8-COMPARE_BITS); } bool AFSK::Encoder::start() { @@ -368,6 +365,19 @@ bool AFSK::Decoder::read() { void AFSK::Decoder::start() { // Do this in start to allocate our first packet currentPacket = pBuf.makePacket(PACKET_MAX_LEN); + ASSR &= ~(_BV(EXCLK) | _BV(AS2)); + + // Do non-inverting PWM on pin OC2B (arduino pin 3) (p.159). + // OC2A (arduino pin 11) stays in normal port operation: + // COM2B1=1, COM2B0=0, COM2A1=0, COM2A0=0 + // Mode 1 - Phase correct PWM + TCCR2A = (TCCR2A | _BV(COM2B1)) & ~(_BV(COM2B0) | _BV(COM2A1) | _BV(COM2A0)) | + _BV(WGM21) | _BV(WGM20); + // No prescaler (p.162) + TCCR2B = (TCCR2B & ~(_BV(CS22) | _BV(CS21))) | _BV(CS20) | _BV(WGM22); + + OCR2A = pow(2,COMPARE_BITS)-1; + OCR2B = 0; // Configure the ADC and Timer1 to trigger automatic interrupts TCCR1A = 0; TCCR1B = _BV(CS11) | _BV(WGM13) | _BV(WGM12); diff --git a/DDS.cpp b/DDS.cpp new file mode 100644 index 0000000..b88b556 --- /dev/null +++ b/DDS.cpp @@ -0,0 +1,82 @@ +#include +#include "DDS.h" + +// To start the DDS, we use Timer1, set to the reference clock +// We use Timer2 for the PWM output, running as fast as feasible +void DDS::start() { + // Use the clkIO clock rate + ASSR &= ~(_BV(EXCLK) | _BV(AS2)); + + // First, the timer for the PWM output + // Setup the timer to use OC2B (pin 3) in fast PWM mode with a configurable top + // Run it without the prescaler + TCCR2A = (TCCR2A | _BV(COM2B1)) & ~(_BV(COM2B0) | _BV(COM2A1) | _BV(COM2A0)) | + _BV(WGM21) | _BV(WGM20); + TCCR2B = (TCCR2B & ~(_BV(CS22) | _BV(CS21))) | _BV(CS20) | _BV(WGM22); + + // Set the top limit, which will be our duty cycle accuracy. + // Setting Comparator Bits smaller will allow for higher frequency PWM, + // with the loss of resolution. + OCR2A = pow(2,COMPARATOR_BITS)-1; + OCR2B = 0; + + // Second, setup Timer1 to trigger the ADC interrupt + // This lets us use decoding functions that run at the same reference + // clock as the DDS. + TCCR1B = _BV(CS11) | _BV(WGM13) | _BV(WGM12); + TCCR1A = 0; + ICR1 = ((F_CPU / 8) / DDS_REFCLK_DEAULT) - 1; + + // Configure the ADC here to automatically run and be triggered off Timer1 + ADMUX = _BV(REFS0) | _BV(ADLAR) | 0; // Channel 0, shift result left (ADCH used) + DDRC &= ~_BV(0); + PORTC &= ~_BV(0); + DIDR0 |= _BV(0); + ADCSRB = _BV(ADTS2) | _BV(ADTS1) | _BV(ADTS0); + ADCSRA = _BV(ADEN) | _BV(ADSC) | _BV(ADATE) | _BV(ADIE) | _BV(ADPS2); // | _BV(ADPS0); +} + +void DDS::stop() { + // TODO: Stop the timers. +} + +// Set our current sine wave frequency in Hz +void DDS::setFrequency(unsigned short freq) { + // Fo = (M*Fc)/2^N + // M = (Fo/Fc)*2^N + if(refclk == DDS_REFCLK_DEAULT) { + // Try to use precalculated values if possible + if(freq == 2200) { + stepRate = (2200.0 / DDS_REFCLK_DEAULT) * pow(2,ACCUMULATOR_BITS); + } else if (freq == 1200) { + stepRate = (1200.0 / DDS_REFCLK_DEAULT) * pow(2,ACCUMULATOR_BITS); + } + } else { + // Do the actual math instead. + stepRate = (freq / refclk) * pow(2,ACCUMULATOR_BITS); + } +} + +void DDS::clockTick() { + accumulator += stepRate; + if(running) { + if(tickDuration == 0) { + OCR2B = 0; + running = false; + } else { + OCR2B = getPhaseAngle(); + } + // Reduce our playback duration by one tick + tickDuration--; + } +} + +uint8_t DDS::getPhaseAngle() { +#if ACCUMULATOR_BIT_SHIFT >= 24 + uint16_t phAng; +#else + uint8_t phAng; +#endif + phAng = (accumulator >> ACCUMULATOR_BIT_SHIFT); + return pgm_read_byte_near(ddsSineTable + phAng)>>(8-COMPARATOR_BITS); +} diff --git a/DDS.h b/DDS.h new file mode 100644 index 0000000..edb8dcf --- /dev/null +++ b/DDS.h @@ -0,0 +1,171 @@ +#ifndef _DDS_H_ +#define _DDS_H_ + +#include + +#define SHORT_ACCUMULATOR + +#ifdef SHORT_ACCUMULATOR +#define ACCUMULATOR_BITS 16 +#else +#define ACCUMULATOR_BITS 32 +#endif + +#define COMPARATOR_BITS 6 + +#define DDS_REFCLK_DEAULT 38400 + +#define DDS_TABLE_LARGE + +#ifdef DDS_TABLE_LARGE +#define ACCUMULATOR_BIT_SHIFT (ACCUMULATOR_BITS-10) +static const uint8_t ddsSineTable[1024] PROGMEM = { + 128,128,129,130,131,131,132,133,134,135,135,136,137,138,138,139, + 140,141,142,142,143,144,145,145,146,147,148,149,149,150,151,152, + 152,153,154,155,155,156,157,158,158,159,160,161,162,162,163,164, + 165,165,166,167,167,168,169,170,170,171,172,173,173,174,175,176, + 176,177,178,178,179,180,181,181,182,183,183,184,185,186,186,187, + 188,188,189,190,190,191,192,192,193,194,194,195,196,196,197,198, + 198,199,200,200,201,202,202,203,203,204,205,205,206,207,207,208, + 208,209,210,210,211,211,212,213,213,214,214,215,215,216,217,217, + 218,218,219,219,220,220,221,221,222,222,223,224,224,225,225,226, + 226,227,227,228,228,228,229,229,230,230,231,231,232,232,233,233, + 234,234,234,235,235,236,236,236,237,237,238,238,238,239,239,240, + 240,240,241,241,241,242,242,242,243,243,243,244,244,244,245,245, + 245,246,246,246,246,247,247,247,248,248,248,248,249,249,249,249, + 250,250,250,250,250,251,251,251,251,251,252,252,252,252,252,252, + 253,253,253,253,253,253,253,254,254,254,254,254,254,254,254,254, + 254,254,255,255,255,255,255,255,255,255,255,255,255,255,255,255, + 255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,254, + 254,254,254,254,254,254,254,254,254,254,253,253,253,253,253,253, + 253,252,252,252,252,252,252,251,251,251,251,251,250,250,250,250, + 250,249,249,249,249,248,248,248,248,247,247,247,246,246,246,246, + 245,245,245,244,244,244,243,243,243,242,242,242,241,241,241,240, + 240,240,239,239,238,238,238,237,237,236,236,236,235,235,234,234, + 234,233,233,232,232,231,231,230,230,229,229,228,228,228,227,227, + 226,226,225,225,224,224,223,222,222,221,221,220,220,219,219,218, + 218,217,217,216,215,215,214,214,213,213,212,211,211,210,210,209, + 208,208,207,207,206,205,205,204,203,203,202,202,201,200,200,199, + 198,198,197,196,196,195,194,194,193,192,192,191,190,190,189,188, + 188,187,186,186,185,184,183,183,182,181,181,180,179,178,178,177, + 176,176,175,174,173,173,172,171,170,170,169,168,167,167,166,165, + 165,164,163,162,162,161,160,159,158,158,157,156,155,155,154,153, + 152,152,151,150,149,149,148,147,146,145,145,144,143,142,142,141, + 140,139,138,138,137,136,135,135,134,133,132,131,131,130,129,128, + 128,127,126,125,124,124,123,122,121,120,120,119,118,117,117,116, + 115,114,113,113,112,111,110,110,109,108,107,106,106,105,104,103, + 103,102,101,100,100,99,98,97,97,96,95,94,93,93,92,91, + 90,90,89,88,88,87,86,85,85,84,83,82,82,81,80,79, + 79,78,77,77,76,75,74,74,73,72,72,71,70,69,69,68, + 67,67,66,65,65,64,63,63,62,61,61,60,59,59,58,57, + 57,56,55,55,54,53,53,52,52,51,50,50,49,48,48,47, + 47,46,45,45,44,44,43,42,42,41,41,40,40,39,38,38, + 37,37,36,36,35,35,34,34,33,33,32,31,31,30,30,29, + 29,28,28,27,27,27,26,26,25,25,24,24,23,23,22,22, + 21,21,21,20,20,19,19,19,18,18,17,17,17,16,16,15, + 15,15,14,14,14,13,13,13,12,12,12,11,11,11,10,10, + 10,9,9,9,9,8,8,8,7,7,7,7,6,6,6,6, + 5,5,5,5,5,4,4,4,4,4,3,3,3,3,3,3, + 2,2,2,2,2,2,2,1,1,1,1,1,1,1,1,1, + 1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1, + 1,1,1,1,1,1,1,1,1,1,2,2,2,2,2,2, + 2,3,3,3,3,3,3,4,4,4,4,4,5,5,5,5, + 5,6,6,6,6,7,7,7,7,8,8,8,9,9,9,9, + 10,10,10,11,11,11,12,12,12,13,13,13,14,14,14,15, + 15,15,16,16,17,17,17,18,18,19,19,19,20,20,21,21, + 21,22,22,23,23,24,24,25,25,26,26,27,27,27,28,28, + 29,29,30,30,31,31,32,33,33,34,34,35,35,36,36,37, + 37,38,38,39,40,40,41,41,42,42,43,44,44,45,45,46, + 47,47,48,48,49,50,50,51,52,52,53,53,54,55,55,56, + 57,57,58,59,59,60,61,61,62,63,63,64,65,65,66,67, + 67,68,69,69,70,71,72,72,73,74,74,75,76,77,77,78, + 79,79,80,81,82,82,83,84,85,85,86,87,88,88,89,90, + 90,91,92,93,93,94,95,96,97,97,98,99,100,100,101,102, + 103,103,104,105,106,106,107,108,109,110,110,111,112,113,113,114, + 115,116,117,117,118,119,120,120,121,122,123,124,124,125,126,127 +}; +#else +#define ACCUMULATOR_BIT_SHIFT (ACCUMULATOR_BITS-8) +static const uint8_t ddsSineTable[256] PROGMEM = { + 128,131,134,137,140,143,146,149,152,155,158,162,165,167,170,173, + 176,179,182,185,188,190,193,196,198,201,203,206,208,211,213,215, + 218,220,222,224,226,228,230,232,234,235,237,238,240,241,243,244, + 245,246,248,249,250,250,251,252,253,253,254,254,254,255,255,255, + 255,255,255,255,254,254,254,253,253,252,251,250,250,249,248,246, + 245,244,243,241,240,238,237,235,234,232,230,228,226,224,222,220, + 218,215,213,211,208,206,203,201,198,196,193,190,188,185,182,179, + 176,173,170,167,165,162,158,155,152,149,146,143,140,137,134,131, + 128,124,121,118,115,112,109,106,103,100,97,93,90,88,85,82, + 79,76,73,70,67,65,62,59,57,54,52,49,47,44,42,40, + 37,35,33,31,29,27,25,23,21,20,18,17,15,14,12,11, + 10,9,7,6,5,5,4,3,2,2,1,1,1,0,0,0, + 0,0,0,0,1,1,1,2,2,3,4,5,5,6,7,9, + 10,11,12,14,15,17,18,20,21,23,25,27,29,31,33,35, + 37,40,42,44,47,49,52,54,57,59,62,65,67,70,73,76, + 79,82,85,88,90,93,97,100,103,106,109,112,115,118,121,124 +}; +#endif /* DDS_TABLE_LARGE */ + +class DDS { +public: + DDS(): refclk(DDS_REFCLK_DEAULT), accumulator(0), running(false) {}; + + void start(); + const bool isRunning() { return running; }; + void stop(); + + // Start the PWM output, or stop it + void on() { + running = true; + } + // Provide a duration in ms for the tone + void on(unsigned short duration) { + // Duration in ticks from milliseconds is: + // t = (1/refclk) + tickDuration = duration / (1000.0/refclk); + running = true; + } + void off() { + running = false; + } + + // Generate a tone for a specific amount of time + void play(unsigned short freq, unsigned short duration) { + setFrequency(freq); + on(duration); + } + // Blocking version + void playWait(unsigned short freq, unsigned short duration) { + setFrequency(freq); + on(duration); + delay(duration * 1000); + } + + // Our maximum clock isn't very high, so our highest + // frequency supported will fit in a short. + void setFrequency(unsigned short freq); + + // Adjustable reference clock + void setReferenceClock(unsigned long ref); + + uint8_t getPhaseAngle(); + + void clockTick(); + +private: + bool running; + unsigned long tickDuration; +#ifdef SHORT_ACCUMULATOR + unsigned short accumulator; + unsigned short stepRate; + unsigned short refclk; +#else + unsigned long accumulator; + unsigned long stepRate; + unsigned long refclk; +#endif + static DDS *sDDS; +}; + +#endif /* _DDS_H_ */ From 31eb465ebfbe14edfbe07d48d850c66b3efeb28f Mon Sep 17 00:00:00 2001 From: Stephen Olesen Date: Wed, 1 Jul 2015 14:27:10 -0600 Subject: [PATCH 04/44] Allow for high idle duty cycle when output is 'off', amplitude adjustments. --- DDS.cpp | 14 +++++++++++--- DDS.h | 30 ++++++++++++++++++++---------- 2 files changed, 31 insertions(+), 13 deletions(-) diff --git a/DDS.cpp b/DDS.cpp index b88b556..5b2336b 100644 --- a/DDS.cpp +++ b/DDS.cpp @@ -61,22 +61,30 @@ void DDS::clockTick() { accumulator += stepRate; if(running) { if(tickDuration == 0) { +#ifdef DDS_IDLE_HIGH + // Set the duty cycle to 50% + OCR2B = pow(2,COMPARATOR_BITS)/2; +#else + // Set duty cycle to 0, effectively off OCR2B = 0; +#endif running = false; } else { - OCR2B = getPhaseAngle(); + OCR2B = getDutyCycle(); } // Reduce our playback duration by one tick tickDuration--; } } -uint8_t DDS::getPhaseAngle() { +uint8_t DDS::getDutyCycle() { #if ACCUMULATOR_BIT_SHIFT >= 24 uint16_t phAng; #else uint8_t phAng; #endif phAng = (accumulator >> ACCUMULATOR_BIT_SHIFT); - return pgm_read_byte_near(ddsSineTable + phAng)>>(8-COMPARATOR_BITS); + uint8_t position = pgm_read_byte_near(ddsSineTable + phAng)>>(8-COMPARATOR_BITS); + // Apply scaling and return + return position >> amplitude; } diff --git a/DDS.h b/DDS.h index edb8dcf..8c17432 100644 --- a/DDS.h +++ b/DDS.h @@ -11,6 +11,10 @@ #define ACCUMULATOR_BITS 32 #endif +// If defined, the timer will idle at 50% duty cycle +// This leaves it floating in the centre of the PWM/DAC voltage range +#define DDS_IDLE_HIGH + #define COMPARATOR_BITS 6 #define DDS_REFCLK_DEAULT 38400 @@ -123,7 +127,7 @@ public: void on(unsigned short duration) { // Duration in ticks from milliseconds is: // t = (1/refclk) - tickDuration = duration / (1000.0/refclk); + tickDuration = (duration * refclk) / 1000; running = true; } void off() { @@ -137,8 +141,7 @@ public: } // Blocking version void playWait(unsigned short freq, unsigned short duration) { - setFrequency(freq); - on(duration); + play(freq, duration); delay(duration * 1000); } @@ -149,20 +152,27 @@ public: // Adjustable reference clock void setReferenceClock(unsigned long ref); - uint8_t getPhaseAngle(); + uint8_t getDutyCycle(); + // Set a scaling factor. To keep things quick, this is a power of 2 value. + // Set it with 0 for lowest (which will be off), 8 is highest. + void setAmplitude(unsigned char amp) { + amplitude = 8 - amp; + } + void clockTick(); private: - bool running; - unsigned long tickDuration; + volatile bool running; + volatile unsigned long tickDuration; + volatile unsigned char amplitude; #ifdef SHORT_ACCUMULATOR - unsigned short accumulator; - unsigned short stepRate; + volatile unsigned short accumulator; + volatile unsigned short stepRate; unsigned short refclk; #else - unsigned long accumulator; - unsigned long stepRate; + volatile unsigned long accumulator; + volatile unsigned long stepRate; unsigned long refclk; #endif static DDS *sDDS; From 2f4d17e4eddad3e51c4c342811ea3115079a8a9e Mon Sep 17 00:00:00 2001 From: Stephen Olesen Date: Wed, 1 Jul 2015 14:42:20 -0600 Subject: [PATCH 05/44] Fixed up the duration timers, clockTick needs a cleanup. --- DDS.cpp | 15 +++++++++++++-- DDS.h | 3 +++ 2 files changed, 16 insertions(+), 2 deletions(-) diff --git a/DDS.cpp b/DDS.cpp index 5b2336b..e44101f 100644 --- a/DDS.cpp +++ b/DDS.cpp @@ -57,10 +57,11 @@ void DDS::setFrequency(unsigned short freq) { } } +// TODO: Clean this up a bit.. void DDS::clockTick() { - accumulator += stepRate; if(running) { - if(tickDuration == 0) { + accumulator += stepRate; + if(timeLimited && tickDuration == 0) { #ifdef DDS_IDLE_HIGH // Set the duty cycle to 50% OCR2B = pow(2,COMPARATOR_BITS)/2; @@ -69,11 +70,21 @@ void DDS::clockTick() { OCR2B = 0; #endif running = false; + accumulator = 0; } else { OCR2B = getDutyCycle(); } // Reduce our playback duration by one tick tickDuration--; + } else { + // Hold it low +#ifdef DDS_IDLE_HIGH + // Set the duty cycle to 50% + OCR2B = pow(2,COMPARATOR_BITS)/2; +#else + // Set duty cycle to 0, effectively off + OCR2B = 0; +#endif } } diff --git a/DDS.h b/DDS.h index 8c17432..36df413 100644 --- a/DDS.h +++ b/DDS.h @@ -121,6 +121,7 @@ public: // Start the PWM output, or stop it void on() { + timeLimited = false; running = true; } // Provide a duration in ms for the tone @@ -128,6 +129,7 @@ public: // Duration in ticks from milliseconds is: // t = (1/refclk) tickDuration = (duration * refclk) / 1000; + timeLimited = true; running = true; } void off() { @@ -165,6 +167,7 @@ public: private: volatile bool running; volatile unsigned long tickDuration; + volatile bool timeLimited; volatile unsigned char amplitude; #ifdef SHORT_ACCUMULATOR volatile unsigned short accumulator; From 8f2115adbca32878d3cb2ff4d754bbb4970b1d7d Mon Sep 17 00:00:00 2001 From: Stephen Olesen Date: Wed, 1 Jul 2015 14:54:22 -0600 Subject: [PATCH 06/44] Add some comments. --- DDS.cpp | 2 +- DDS.h | 26 ++++++++++++++++++++++++-- 2 files changed, 25 insertions(+), 3 deletions(-) diff --git a/DDS.cpp b/DDS.cpp index e44101f..da39970 100644 --- a/DDS.cpp +++ b/DDS.cpp @@ -25,7 +25,7 @@ void DDS::start() { // clock as the DDS. TCCR1B = _BV(CS11) | _BV(WGM13) | _BV(WGM12); TCCR1A = 0; - ICR1 = ((F_CPU / 8) / DDS_REFCLK_DEAULT) - 1; + ICR1 = ((F_CPU / 8) / refclk) - 1; // Configure the ADC here to automatically run and be triggered off Timer1 ADMUX = _BV(REFS0) | _BV(ADLAR) | 0; // Channel 0, shift result left (ADCH used) diff --git a/DDS.h b/DDS.h index 36df413..a1d54f5 100644 --- a/DDS.h +++ b/DDS.h @@ -15,13 +15,26 @@ // This leaves it floating in the centre of the PWM/DAC voltage range #define DDS_IDLE_HIGH +// Select how fast the PWM is, at the expense of level accuracy. +// A faster PWM rate will make for easier filtering of the output wave, +// while a slower one will allow for more accurate voltage level outputs, +// but will increase the filtering requirements on the output. +// 8 = 62.5kHz PWM +// 7 = 125kHz PWM +// 6 = 250kHz PWM #define COMPARATOR_BITS 6 +// This is how often we'll perform a phase advance, as well as ADC sampling +// rate. The higher this value, the smoother the output wave will be, at the +// expense of CPU time. It maxes out around 62000 (TBD) #define DDS_REFCLK_DEAULT 38400 +// When defined, use the 1024 element sine lookup table. This improves phase +// accuracy, at the cost of more flash and CPU requirements. #define DDS_TABLE_LARGE #ifdef DDS_TABLE_LARGE +// How many bits to keep from the accumulator to look up in this table #define ACCUMULATOR_BIT_SHIFT (ACCUMULATOR_BITS-10) static const uint8_t ddsSineTable[1024] PROGMEM = { 128,128,129,130,131,131,132,133,134,135,135,136,137,138,138,139, @@ -115,11 +128,14 @@ class DDS { public: DDS(): refclk(DDS_REFCLK_DEAULT), accumulator(0), running(false) {}; + // Start all of the timers needed void start(); + // Is the DDS presently producing a tone? const bool isRunning() { return running; }; + // Stop the DDS timers void stop(); - // Start the PWM output, or stop it + // Start and stop the PWM output void on() { timeLimited = false; running = true; @@ -151,8 +167,13 @@ public: // frequency supported will fit in a short. void setFrequency(unsigned short freq); - // Adjustable reference clock + // Adjustable reference clock. This shoud be done before the timers are + // started, or they will need to be restarted. Frequencies will need to + // be set again to use the new clock. void setReferenceClock(unsigned long ref); + unsigned long getReferenceClock() { + return refclk; + } uint8_t getDutyCycle(); @@ -162,6 +183,7 @@ public: amplitude = 8 - amp; } + // This is the function called by the ADC_vect ISR to produce the tones void clockTick(); private: From 016ad2398a5496364b0392e94c3269670700d015 Mon Sep 17 00:00:00 2001 From: Stephen Olesen Date: Wed, 1 Jul 2015 15:01:55 -0600 Subject: [PATCH 07/44] Added a define to use pin 3 for PWM, instead of the new default pin 11. --- DDS.cpp | 6 ++++++ DDS.h | 5 +++++ 2 files changed, 11 insertions(+) diff --git a/DDS.cpp b/DDS.cpp index da39970..27129aa 100644 --- a/DDS.cpp +++ b/DDS.cpp @@ -10,8 +10,14 @@ void DDS::start() { // First, the timer for the PWM output // Setup the timer to use OC2B (pin 3) in fast PWM mode with a configurable top // Run it without the prescaler +#ifdef DDS_PWM_PIN_3 TCCR2A = (TCCR2A | _BV(COM2B1)) & ~(_BV(COM2B0) | _BV(COM2A1) | _BV(COM2A0)) | _BV(WGM21) | _BV(WGM20); +#else + // Alternatively, use pin 11 + TCCR2A = (TCCR2A | _BV(COM2A1)) & ~(_BV(COM2A0) | _BV(COM2B1) | _BV(COM2B0)) | + _BV(WGM21) | _BV(WGM20); +#endif TCCR2B = (TCCR2B & ~(_BV(CS22) | _BV(CS21))) | _BV(CS20) | _BV(WGM22); // Set the top limit, which will be our duty cycle accuracy. diff --git a/DDS.h b/DDS.h index a1d54f5..edab90a 100644 --- a/DDS.h +++ b/DDS.h @@ -3,6 +3,11 @@ #include +// Use pin 3 for PWM? If not defined, use pin 11 +// #define DDS_PWM_PIN_3 + +// Use a short (16 bit) accumulator. Phase accuracy is reduced, but speed +// is increased, along with a reduction in memory use. #define SHORT_ACCUMULATOR #ifdef SHORT_ACCUMULATOR From 120442533d77f54b110f9c10187e402eff9294d3 Mon Sep 17 00:00:00 2001 From: Stephen Olesen Date: Wed, 1 Jul 2015 17:56:40 -0600 Subject: [PATCH 08/44] Added DDS sample. Fixed pin 11 PWM output, now default (3 works better). --- DDS.cpp | 41 +++++++++++++++++++++++++++++++++-------- DDS.h | 27 ++++++++++++++++++++++++--- examples/DDS/DDS.ino | 32 ++++++++++++++++++++++++++++++++ 3 files changed, 89 insertions(+), 11 deletions(-) create mode 100644 examples/DDS/DDS.ino diff --git a/DDS.cpp b/DDS.cpp index 27129aa..a4e13c0 100644 --- a/DDS.cpp +++ b/DDS.cpp @@ -13,19 +13,30 @@ void DDS::start() { #ifdef DDS_PWM_PIN_3 TCCR2A = (TCCR2A | _BV(COM2B1)) & ~(_BV(COM2B0) | _BV(COM2A1) | _BV(COM2A0)) | _BV(WGM21) | _BV(WGM20); + TCCR2B = (TCCR2B & ~(_BV(CS22) | _BV(CS21))) | _BV(CS20) | _BV(WGM22); #else // Alternatively, use pin 11 - TCCR2A = (TCCR2A | _BV(COM2A1)) & ~(_BV(COM2A0) | _BV(COM2B1) | _BV(COM2B0)) | - _BV(WGM21) | _BV(WGM20); + // Enable output compare on OC2A, toggle mode + TCCR2A = _BV(COM2A1) | _BV(WGM21) | _BV(WGM20); + //TCCR2A = (TCCR2A | _BV(COM2A1)) & ~(_BV(COM2A0) | _BV(COM2B1) | _BV(COM2B0)) | + // _BV(WGM21) | _BV(WGM20); + TCCR2B = _BV(CS20); #endif - TCCR2B = (TCCR2B & ~(_BV(CS22) | _BV(CS21))) | _BV(CS20) | _BV(WGM22); // Set the top limit, which will be our duty cycle accuracy. // Setting Comparator Bits smaller will allow for higher frequency PWM, // with the loss of resolution. +#ifdef DDS_PWM_PIN_3 OCR2A = pow(2,COMPARATOR_BITS)-1; OCR2B = 0; +#else + OCR2A = 0; +#endif +#ifdef DDS_USE_ONLY_TIMER2 + TIMSK2 |= _BV(TOIE2); +#endif + // Second, setup Timer1 to trigger the ADC interrupt // This lets us use decoding functions that run at the same reference // clock as the DDS. @@ -50,16 +61,18 @@ void DDS::stop() { void DDS::setFrequency(unsigned short freq) { // Fo = (M*Fc)/2^N // M = (Fo/Fc)*2^N - if(refclk == DDS_REFCLK_DEAULT) { + if(refclk == DDS_REFCLK_DEFAULT) { // Try to use precalculated values if possible if(freq == 2200) { - stepRate = (2200.0 / DDS_REFCLK_DEAULT) * pow(2,ACCUMULATOR_BITS); + stepRate = (2200.0 / (DDS_REFCLK_DEFAULT+DDS_REFCLK_OFFSET)) * pow(2,ACCUMULATOR_BITS); } else if (freq == 1200) { - stepRate = (1200.0 / DDS_REFCLK_DEAULT) * pow(2,ACCUMULATOR_BITS); + stepRate = (1200.0 / (DDS_REFCLK_DEFAULT+DDS_REFCLK_OFFSET)) * pow(2,ACCUMULATOR_BITS); + } else if (freq == 600) { + stepRate = (600.0 / (DDS_REFCLK_DEFAULT+DDS_REFCLK_OFFSET)) * pow(2,ACCUMULATOR_BITS); } } else { - // Do the actual math instead. - stepRate = (freq / refclk) * pow(2,ACCUMULATOR_BITS); + // BUG: Step rate isn't properly calculated here, it gets the wrong frequency + stepRate = (freq/(refclk+DDS_REFCLK_OFFSET)) * pow(2,ACCUMULATOR_BITS); } } @@ -68,28 +81,40 @@ void DDS::clockTick() { if(running) { accumulator += stepRate; if(timeLimited && tickDuration == 0) { +#ifndef DDS_PWM_PIN_3 + OCR2A = 0; +#else #ifdef DDS_IDLE_HIGH // Set the duty cycle to 50% OCR2B = pow(2,COMPARATOR_BITS)/2; #else // Set duty cycle to 0, effectively off OCR2B = 0; +#endif #endif running = false; accumulator = 0; } else { +#ifdef DDS_PWM_PIN_3 OCR2B = getDutyCycle(); +#else + OCR2A = getDutyCycle(); +#endif } // Reduce our playback duration by one tick tickDuration--; } else { // Hold it low +#ifndef DDS_PWM_PIN_3 + OCR2A = 0; +#else #ifdef DDS_IDLE_HIGH // Set the duty cycle to 50% OCR2B = pow(2,COMPARATOR_BITS)/2; #else // Set duty cycle to 0, effectively off OCR2B = 0; +#endif #endif } } diff --git a/DDS.h b/DDS.h index edab90a..8115dcc 100644 --- a/DDS.h +++ b/DDS.h @@ -4,8 +4,14 @@ #include // Use pin 3 for PWM? If not defined, use pin 11 +// Quality on pin 3 is higher than on 11, as it can be clocked faster +// when the COMPARATOR_BITS value is less than 8 // #define DDS_PWM_PIN_3 +// Normally, we turn on timer2 and timer1, and have ADC sampling as our clock +// Define this to only use Timer2, and not start the ADC clock +// #define DDS_USE_ONLY_TIMER2 + // Use a short (16 bit) accumulator. Phase accuracy is reduced, but speed // is increased, along with a reduction in memory use. #define SHORT_ACCUMULATOR @@ -27,16 +33,29 @@ // 8 = 62.5kHz PWM // 7 = 125kHz PWM // 6 = 250kHz PWM +#ifdef DDS_PWM_PIN_3 #define COMPARATOR_BITS 6 +#else // When using pin 11, we always want 8 bits +#define COMPARATOR_BITS 8 +#endif // This is how often we'll perform a phase advance, as well as ADC sampling // rate. The higher this value, the smoother the output wave will be, at the // expense of CPU time. It maxes out around 62000 (TBD) -#define DDS_REFCLK_DEAULT 38400 +#define DDS_REFCLK_DEFAULT 38400 +// As each Arduino crystal is a little different, this can be fine tuned to +// provide more accurate frequencies. Adjustments in the range of hundreds +// is a good start. +#define DDS_REFCLK_OFFSET 0 + +#ifdef DDS_USE_ONLY_TIMER2 +// TODO: Figure out where this clock value is generated from +#define DDS_REFCLK_DEFAULT 48800 +#endif // When defined, use the 1024 element sine lookup table. This improves phase // accuracy, at the cost of more flash and CPU requirements. -#define DDS_TABLE_LARGE +// #define DDS_TABLE_LARGE #ifdef DDS_TABLE_LARGE // How many bits to keep from the accumulator to look up in this table @@ -131,7 +150,9 @@ static const uint8_t ddsSineTable[256] PROGMEM = { class DDS { public: - DDS(): refclk(DDS_REFCLK_DEAULT), accumulator(0), running(false) {}; + DDS(): refclk(DDS_REFCLK_DEFAULT), accumulator(0), running(false), + timeLimited(false), tickDuration(0), amplitude(0) + {}; // Start all of the timers needed void start(); diff --git a/examples/DDS/DDS.ino b/examples/DDS/DDS.ino new file mode 100644 index 0000000..245bb79 --- /dev/null +++ b/examples/DDS/DDS.ino @@ -0,0 +1,32 @@ +#include +#include + +DDS dds; + +void setup() { + pinMode(2, OUTPUT); + pinMode(3, OUTPUT); + pinMode(11, OUTPUT); + dds.start(); + dds.setFrequency(440); + dds.on(); + delay(5000); +} + +void loop() { + dds.setFrequency(2200); + delay(5000); + dds.setFrequency(1200); + delay(5000); +} + +#ifdef DDS_USE_ONLY_TIMER2 +ISR(TIMER2_OVF_vect) { + dds.clockTick(); +} +#else // Use the ADC timer instead +ISR(ADC_vect) { + TIFR1 = _BV(ICF1); // Clear the timer flag + dds.clockTick(); +} +#endif From 660fe0c602a29f19a8bdee1b187e48de9faf9b52 Mon Sep 17 00:00:00 2001 From: Stephen Olesen Date: Wed, 1 Jul 2015 18:05:17 -0600 Subject: [PATCH 09/44] Corrected the duration calculation on fixed DDS length. --- DDS.h | 4 ++-- examples/DDS/DDS.ino | 3 +-- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/DDS.h b/DDS.h index 8115dcc..be30411 100644 --- a/DDS.h +++ b/DDS.h @@ -170,7 +170,7 @@ public: void on(unsigned short duration) { // Duration in ticks from milliseconds is: // t = (1/refclk) - tickDuration = (duration * refclk) / 1000; + tickDuration = (unsigned long)((unsigned long)duration * (unsigned long)refclk) / 1000; timeLimited = true; running = true; } @@ -186,7 +186,7 @@ public: // Blocking version void playWait(unsigned short freq, unsigned short duration) { play(freq, duration); - delay(duration * 1000); + delay(duration); } // Our maximum clock isn't very high, so our highest diff --git a/examples/DDS/DDS.ino b/examples/DDS/DDS.ino index 245bb79..394ad18 100644 --- a/examples/DDS/DDS.ino +++ b/examples/DDS/DDS.ino @@ -8,9 +8,8 @@ void setup() { pinMode(3, OUTPUT); pinMode(11, OUTPUT); dds.start(); - dds.setFrequency(440); + dds.playWait(600, 3000); dds.on(); - delay(5000); } void loop() { From 5689393c111224423f3b11a0158fe00b6354b821 Mon Sep 17 00:00:00 2001 From: Stephen Olesen Date: Wed, 1 Jul 2015 18:25:59 -0600 Subject: [PATCH 10/44] Replace local DDS with the DDS class. --- AFSK.cpp | 73 ++++++++++++-------------------------------------------- AFSK.h | 10 ++++---- 2 files changed, 19 insertions(+), 64 deletions(-) diff --git a/AFSK.cpp b/AFSK.cpp index 336e055..2e93d60 100644 --- a/AFSK.cpp +++ b/AFSK.cpp @@ -14,62 +14,15 @@ #define PPOOL_SIZE 2 -#define COMPARE_BITS 6 -#define ACCUMULATOR_SIZE 32 -#define ACCUMULATOR_BITS 24 // This is 2^10 bits used from accum -//#undef PROGMEM -//#define PROGMEM __attribute__((section(".progmem.data"))) -const uint8_t PROGMEM sinetable[256] = { - 128,131,134,137,140,143,146,149,152,156,159,162,165,168,171,174, - 176,179,182,185,188,191,193,196,199,201,204,206,209,211,213,216, - 218,220,222,224,226,228,230,232,234,236,237,239,240,242,243,245, - 246,247,248,249,250,251,252,252,253,254,254,255,255,255,255,255, - 255,255,255,255,255,255,254,254,253,252,252,251,250,249,248,247, - 246,245,243,242,240,239,237,236,234,232,230,228,226,224,222,220, - 218,216,213,211,209,206,204,201,199,196,193,191,188,185,182,179, - 176,174,171,168,165,162,159,156,152,149,146,143,140,137,134,131, - 128,124,121,118,115,112,109,106,103,99, 96, 93, 90, 87, 84, 81, - 79, 76, 73, 70, 67, 64, 62, 59, 56, 54, 51, 49, 46, 44, 42, 39, - 37, 35, 33, 31, 29, 27, 25, 23, 21, 19, 18, 16, 15, 13, 12, 10, - 9, 8, 7, 6, 5, 4, 3, 3, 2, 1, 1, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 1, 1, 2, 3, 3, 4, 5, 6, 7, 8, - 9, 10, 12, 13, 15, 16, 18, 19, 21, 23, 25, 27, 29, 31, 33, 35, - 37, 39, 42, 44, 46, 49, 51, 54, 56, 59, 62, 64, 67, 70, 73, 76, - 79, 81, 84, 87, 90, 93, 96, 99, 103,106,109,112,115,118,121,124 -}; - -#define AFSK_SPACE 0 -#define AFSK_MARK 1 +#define AFSK_SPACE 2200 +#define AFSK_MARK 1200 // Timers volatile unsigned long lastTx = 0; volatile unsigned long lastTxEnd = 0; volatile unsigned long lastRx = 0; -#define REFCLK 38400 -//#define REFCLK 31372.54902 -//#define REFCLK (16000000.0/510.0) -//#define REFCLK 31200.0 -// 2200Hz = pow(2,32)*2200.0/refclk -// 1200Hz = pow(2,32)*1200.0/refclk -static const unsigned long toneStep[2] = { - (2200.0/REFCLK)*pow(2,ACCUMULATOR_SIZE), - (1200.0/REFCLK)*pow(2,ACCUMULATOR_SIZE) -}; - -// Set to an arbitrary frequency -void AFSK::Encoder::setFreq(unsigned long freq, byte vol) { - unsigned long newStep = pow(2,32)*freq/REFCLK; - rStep = newStep; // Atomic? (ish) -} - -// This allows a programmatic way to tune the output tones -static const byte toneVolume[2] = { - 255, - 255 -}; - -#define T_BIT ((unsigned int)(REFCLK/1200)) +#define T_BIT ((unsigned int)(9600/1200)) void AFSK::Encoder::process() { // Check what clock pulse we're on @@ -143,9 +96,11 @@ void AFSK::Encoder::process() { if(++bitClock == T_BIT) bitClock = 0; - accumulator += toneStep[currentTone]; - uint8_t phAng = (accumulator >> ACCUMULATOR_BITS); - OCR2B = pgm_read_byte_near(sinetable + phAng)>>(8-COMPARE_BITS); + if(currentTone == 0) { + dds->setFrequency(AFSK_SPACE); + } else { + dds->setFrequency(AFSK_MARK); + } } bool AFSK::Encoder::start() { @@ -157,7 +112,6 @@ bool AFSK::Encoder::start() { return false; } - accumulator = 0; // First real byte is a frame currentBit = 0; lastZero = 0; @@ -170,6 +124,8 @@ bool AFSK::Encoder::start() { currentBytePos = 0; maxTx = 3; sending = true; + dds->setFrequency(0); + dds->on(); return true; } @@ -177,7 +133,7 @@ void AFSK::Encoder::stop() { randomWait = 0; sending = false; done = true; - OCR2B = 0; + dds->off(); } AFSK::Decoder::Decoder() { @@ -365,7 +321,7 @@ bool AFSK::Decoder::read() { void AFSK::Decoder::start() { // Do this in start to allocate our first packet currentPacket = pBuf.makePacket(PACKET_MAX_LEN); - ASSR &= ~(_BV(EXCLK) | _BV(AS2)); +/* ASSR &= ~(_BV(EXCLK) | _BV(AS2)); // Do non-inverting PWM on pin OC2B (arduino pin 3) (p.159). // OC2A (arduino pin 11) stays in normal port operation: @@ -387,7 +343,7 @@ void AFSK::Decoder::start() { PORTC &= ~_BV(0); DIDR0 |= _BV(0); ADCSRB = _BV(ADTS2) | _BV(ADTS1) | _BV(ADTS0); - ADCSRA = _BV(ADEN) | _BV(ADSC) | _BV(ADATE) | _BV(ADIE) | _BV(ADPS2); // | _BV(ADPS0); + ADCSRA = _BV(ADEN) | _BV(ADSC) | _BV(ADATE) | _BV(ADIE) | _BV(ADPS2); // | _BV(ADPS0); */ } AFSK::PacketBuffer::PacketBuffer() { @@ -511,7 +467,8 @@ void AFSK::timer() { decoder.process(ADCH - 128); } -void AFSK::start() { +void AFSK::start(DDS *dds) { afskEnabled = true; + encoder.setDDS(dds); decoder.start(); } diff --git a/AFSK.h b/AFSK.h index 6c4d1dc..13a33f0 100644 --- a/AFSK.h +++ b/AFSK.h @@ -3,6 +3,7 @@ #include #include +#include #define SAMPLERATE 9600 #define BITRATE 1200 @@ -142,7 +143,7 @@ public: packet = 0x0; currentBytePos = 0; } - void setFreq(unsigned long, byte); + void setDDS(DDS *d) { dds = d; } volatile inline bool isSending() volatile { return sending; } @@ -177,10 +178,7 @@ public: unsigned char currentBytePos; volatile unsigned long randomWait; volatile bool done; - // Phase accumulator, 32 bits, we'll use ACCUMULATOR_BITS of it - unsigned long accumulator; - // Current radian step for the accumulator - unsigned long rStep; + DDS *dds; }; class HDLCDecode { @@ -258,7 +256,7 @@ public: } //unsigned long lastTx; //unsigned long lastRx; - void start(); + void start(DDS *); void timer(); Encoder encoder; Decoder decoder; From 429e645ad232c1c1fb9320d12d94c81690f54217 Mon Sep 17 00:00:00 2001 From: Stephen Olesen Date: Wed, 1 Jul 2015 20:30:49 -0600 Subject: [PATCH 11/44] Example for AFSK sending (AX25 format) added. DDS updated to a slower clock rate again. --- DDS.cpp | 3 +- DDS.h | 4 +- examples/AFSK-Send/AFSK-Send.ino | 73 ++++++++++++++++++++++++++++++++ 3 files changed, 77 insertions(+), 3 deletions(-) create mode 100644 examples/AFSK-Send/AFSK-Send.ino diff --git a/DDS.cpp b/DDS.cpp index a4e13c0..388f794 100644 --- a/DDS.cpp +++ b/DDS.cpp @@ -42,7 +42,8 @@ void DDS::start() { // clock as the DDS. TCCR1B = _BV(CS11) | _BV(WGM13) | _BV(WGM12); TCCR1A = 0; - ICR1 = ((F_CPU / 8) / refclk) - 1; + //ICR1 = ((F_CPU / 8) / refclk) - 1; + ICR1 = ((F_CPU / 8) / 9600) - 1; // Configure the ADC here to automatically run and be triggered off Timer1 ADMUX = _BV(REFS0) | _BV(ADLAR) | 0; // Channel 0, shift result left (ADCH used) diff --git a/DDS.h b/DDS.h index be30411..54199a4 100644 --- a/DDS.h +++ b/DDS.h @@ -42,7 +42,7 @@ // This is how often we'll perform a phase advance, as well as ADC sampling // rate. The higher this value, the smoother the output wave will be, at the // expense of CPU time. It maxes out around 62000 (TBD) -#define DDS_REFCLK_DEFAULT 38400 +#define DDS_REFCLK_DEFAULT 9600 // As each Arduino crystal is a little different, this can be fine tuned to // provide more accurate frequencies. Adjustments in the range of hundreds // is a good start. @@ -50,7 +50,7 @@ #ifdef DDS_USE_ONLY_TIMER2 // TODO: Figure out where this clock value is generated from -#define DDS_REFCLK_DEFAULT 48800 +#define DDS_REFCLK_DEFAULT (62500/4) #endif // When defined, use the 1024 element sine lookup table. This improves phase diff --git a/examples/AFSK-Send/AFSK-Send.ino b/examples/AFSK-Send/AFSK-Send.ino new file mode 100644 index 0000000..57916ea --- /dev/null +++ b/examples/AFSK-Send/AFSK-Send.ino @@ -0,0 +1,73 @@ +#include +#include + +HamShield radio; +DDS dds; + +void setup() { + Serial.begin(9600); + // put your setup code here, to run once: + pinMode(2, OUTPUT); + pinMode(10, OUTPUT); + pinMode(3, OUTPUT); + pinMode(11, OUTPUT); + digitalWrite(2, LOW); + digitalWrite(10, LOW); + + dds.start(); + radio.afsk.start(&dds); + Serial.println("Starting..."); +} + +void loop() { + // put your main code here, to run repeatedly: + AFSK::Packet *packet = AFSK::PacketBuffer::makePacket(22 + 32); + packet->start(); + packet->appendFCS('V'<<1); + packet->appendFCS('E'<<1); + packet->appendFCS('6'<<1); + packet->appendFCS('S'<<1); + packet->appendFCS('L'<<1); + packet->appendFCS('P'<<1); + packet->appendFCS(0b11100000); + packet->appendFCS('V'<<1); + packet->appendFCS('A'<<1); + packet->appendFCS('6'<<1); + packet->appendFCS('G'<<1); + packet->appendFCS('A'<<1); + packet->appendFCS(' '<<1); + packet->appendFCS(0b01100001 | (15 & 0xf) << 1); + packet->appendFCS(0x03); + packet->appendFCS(0xf0); + packet->print("Hello "); + packet->println(millis()); + packet->finish(); + + bool ret = radio.afsk.putTXPacket(packet); + if(radio.afsk.txReady()) + if(radio.afsk.txStart()) { + Serial.println("PTT here."); + } + // Wait 2 seconds before we send our beacon again. + Serial.println("tick"); + delay(2000); +} + +/*ISR(TIMER2_OVF_vect) { + TIFR2 = _BV(TOV2); + static uint8_t tcnt = 0; + if(++tcnt == 8) { + digitalWrite(2, HIGH); + dds.clockTick(); + digitalWrite(2, LOW); + tcnt = 0; + } +}*/ +ISR(ADC_vect) { + TIFR1 = _BV(ICF1); // Clear the timer flag + digitalWrite(2, HIGH); + dds.clockTick(); + radio.afsk.timer(); + digitalWrite(2, LOW); +} + From d2dc9adbc059d96824dbcbcbc40a93c0cc019379 Mon Sep 17 00:00:00 2001 From: Stephen Olesen Date: Wed, 1 Jul 2015 20:47:56 -0600 Subject: [PATCH 12/44] Remove ISR from HamShield, add DDS header. --- HamShield.cpp | 4 ++-- HamShield.h | 1 + 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/HamShield.cpp b/HamShield.cpp index 6d66d2f..535935d 100644 --- a/HamShield.cpp +++ b/HamShield.cpp @@ -1355,10 +1355,10 @@ void HamShield::AFSKOut(char buffer[80]) { // This is the ADC timer handler. When enabled, we'll see what we're supposed // to be reading/handling, and trigger those on the main object. -ISR(ADC_vect) { +/*ISR(ADC_vect) { TIFR1 = _BV(ICF1); // Clear the timer flag if(HamShield::sHamShield->afsk.enabled()) { HamShield::sHamShield->afsk.timer(); } -} +}*/ diff --git a/HamShield.h b/HamShield.h index ecd08ac..abcb152 100644 --- a/HamShield.h +++ b/HamShield.h @@ -11,6 +11,7 @@ #include "I2Cdev_rda.h" #include "SimpleFIFO.h" #include "AFSK.h" +#include "DDS.h" #include // HamShield constants From 80a17415752e8becc43b4c9d3cd144c467c21cb4 Mon Sep 17 00:00:00 2001 From: Stephen Olesen Date: Thu, 2 Jul 2015 01:24:55 -0600 Subject: [PATCH 13/44] Preliminary addition of static memory allocations for packet buffers. Fixed(?) DDS frequency calculation on-chip. --- AFSK.cpp | 25 +++++++++++++++++++++---- AFSK.h | 8 +++++++- DDS.cpp | 2 +- 3 files changed, 29 insertions(+), 6 deletions(-) diff --git a/AFSK.cpp b/AFSK.cpp index 2e93d60..f89501b 100644 --- a/AFSK.cpp +++ b/AFSK.cpp @@ -24,6 +24,10 @@ volatile unsigned long lastRx = 0; #define T_BIT ((unsigned int)(9600/1200)) +#ifdef PACKET_PREALLOCATE +SimpleFIFO preallocPool; +#endif + void AFSK::Encoder::process() { // Check what clock pulse we're on if(bitClock == 0) { // We are onto our next bit timing @@ -396,11 +400,16 @@ AFSK::Packet *AFSK::PacketBuffer::getPacket() volatile { void AFSK::Packet::init(unsigned short dlen) { //data = (unsigned char *)buf; ready = 0; - freeData = 1; //freeData; +#ifdef PACKET_PREALLOCATE + freeData = 0; + maxLen = 128; // Put it here instead +#else + freeData = 1; + dataPtr = (uint8_t *)malloc(dlen+16); + maxLen = dlen; // Put it here instead +#endif type = PACKET_STATIC; len = 0; // We had a length, but don't put it here. - maxLen = dlen; // Put it here instead - dataPtr = (uint8_t *)malloc(dlen+16); dataPos = dataPtr; readPos = dataPtr; fcs = 0xffff; @@ -411,7 +420,11 @@ AFSK::Packet *AFSK::PacketBuffer::makePacket(unsigned short dlen) { AFSK::Packet *p; ATOMIC_BLOCK(ATOMIC_RESTORESTATE) { //Packet *p = findPooledPacket(); +#ifdef PACKET_PREALLOCATE + p = preallocPool.dequeue(); +#else p = new Packet(); //(Packet *)malloc(sizeof(Packet)); +#endif if(p) // If allocated p->init(dlen); } @@ -421,8 +434,11 @@ AFSK::Packet *AFSK::PacketBuffer::makePacket(unsigned short dlen) { // Free a packet struct, mainly convenience void AFSK::PacketBuffer::freePacket(Packet *p) { if(!p) - return; + return; ATOMIC_BLOCK(ATOMIC_RESTORESTATE) { +#ifdef PACKET_PREALLOCATE + preallocPool.enqueue(p); +#else p->free(); /*unsigned char i; for(i = 0; i < PPOOL_SIZE; ++i) @@ -431,6 +447,7 @@ void AFSK::PacketBuffer::freePacket(Packet *p) { if(i < PPOOL_SIZE) pStatus &= ~(1< Date: Thu, 2 Jul 2015 01:26:47 -0600 Subject: [PATCH 14/44] Modified to initialize and try to use the radio. --- examples/AFSK-Send/AFSK-Send.ino | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/examples/AFSK-Send/AFSK-Send.ino b/examples/AFSK-Send/AFSK-Send.ino index 57916ea..7f7282a 100644 --- a/examples/AFSK-Send/AFSK-Send.ino +++ b/examples/AFSK-Send/AFSK-Send.ino @@ -14,9 +14,17 @@ void setup() { digitalWrite(2, LOW); digitalWrite(10, LOW); + Serial.println(F("Radio test connection")); + radio.testConnection(); + Serial.println(F("Initialize")); + radio.initialize(); + Serial.println(F("Frequency")); + radio.frequency(446000); + Serial.println(F("DDS Start")); dds.start(); + Serial.println(F("AFSK start")); radio.afsk.start(&dds); - Serial.println("Starting..."); + Serial.println(F("Starting...")); } void loop() { @@ -46,7 +54,7 @@ void loop() { bool ret = radio.afsk.putTXPacket(packet); if(radio.afsk.txReady()) if(radio.afsk.txStart()) { - Serial.println("PTT here."); + radio.setModeTransmit(); } // Wait 2 seconds before we send our beacon again. Serial.println("tick"); From 8e8c88f67c14c17bba6af6ed3267d2e3c341f2a9 Mon Sep 17 00:00:00 2001 From: Stephen Olesen Date: Thu, 2 Jul 2015 01:31:36 -0600 Subject: [PATCH 15/44] Stop transmitting (in a hackish way) when the TX is done. --- examples/AFSK-Send/AFSK-Send.ino | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/examples/AFSK-Send/AFSK-Send.ino b/examples/AFSK-Send/AFSK-Send.ino index 7f7282a..9b15847 100644 --- a/examples/AFSK-Send/AFSK-Send.ino +++ b/examples/AFSK-Send/AFSK-Send.ino @@ -58,6 +58,14 @@ void loop() { } // Wait 2 seconds before we send our beacon again. Serial.println("tick"); + // Wait up to 2.5 seconds to finish sending, and stop transmitter. + // TODO: This is hackery. + for(int i = 0; i < 500; i++) { + if(!radio.afsk.isSending()) + break; + delay(50); + } + radio.setModeReceive(); delay(2000); } From 4a8e9c69e64c7e1c47d0b56a83231b459d95b108 Mon Sep 17 00:00:00 2001 From: Stephen Olesen Date: Thu, 2 Jul 2015 01:32:00 -0600 Subject: [PATCH 16/44] Workaround for lockup on AFSK/DDS startup when using the Wire library. --- I2Cdev_rda.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/I2Cdev_rda.h b/I2Cdev_rda.h index 56850f2..3c8f81d 100644 --- a/I2Cdev_rda.h +++ b/I2Cdev_rda.h @@ -49,7 +49,7 @@ THE SOFTWARE. // ----------------------------------------------------------------------------- // I2C interface implementation setting // ----------------------------------------------------------------------------- -#define I2CDEV_IMPLEMENTATION I2CDEV_ARDUINO_WIRE +#define I2CDEV_IMPLEMENTATION I2CDEV_ARDUINO_FASTWIRE //#define I2CDEV_IMPLEMENTATION I2CDEV_BUILTIN_FASTWIRE // comment this out if you are using a non-optimal IDE/implementation setting From 0e9a549f56c330ad666bdaf30aa7fd5213a6e495 Mon Sep 17 00:00:00 2001 From: Stephen Olesen Date: Thu, 2 Jul 2015 02:38:28 -0600 Subject: [PATCH 17/44] Fix a compile error. Add a quick callsign appending string method. --- AFSK.cpp | 24 +++++++++++++++++++++++- AFSK.h | 6 ++++++ examples/AFSK-Send/AFSK-Send.ino | 18 +++--------------- 3 files changed, 32 insertions(+), 16 deletions(-) diff --git a/AFSK.cpp b/AFSK.cpp index f89501b..b56363e 100644 --- a/AFSK.cpp +++ b/AFSK.cpp @@ -476,7 +476,29 @@ size_t AFSK::Packet::write(const uint8_t *ptr, size_t len) { break; return i; } - + +// Add a callsign, flagged as source, destination, or digi +// Also tell the routine the SSID to use and if this is the final callsign +size_t AFSK::Packet::appendCallsign(const char *callsign, uint8_t ssid, bool final) { + uint8_t i; + for(i = 0; i < strlen(callsign) && i < 6; i++) { + appendFCS(callsign[i]<<1); + } + if(i < 6) { + for(;i<6;i++) { + appendFCS(' '<<1); + } + } + uint8_t ssidField = (ssid&0xf) << 1; + // TODO: Handle digis in the address C bit + if(final) { + ssidField |= 0b01100001; + } else { + ssidField |= 0b11100000; + } + appendFCS(ssidField); +} + // Determine what we want to do on this ADC tick. void AFSK::timer() { if(encoder.isSending()) diff --git a/AFSK.h b/AFSK.h index 694b50e..1f8b860 100644 --- a/AFSK.h +++ b/AFSK.h @@ -14,6 +14,10 @@ #define PACKET_BUFFER_SIZE 2 #define PACKET_STATIC 0 + +// If this is set, all the packet buffers will be pre-allocated at compile time +// This will use more RAM, but can make it easier to do memory planning. +// TODO: Make this actually work right and not crash. //#define PACKET_PREALLOCATE // This is with all the digis, two addresses, framing and full payload @@ -87,6 +91,8 @@ public: return false; } + size_t appendCallsign(const char *callsign, uint8_t ssid, bool final = false); + inline void finish() { append(~(fcs & 0xff)); append(~((fcs>>8) & 0xff)); diff --git a/examples/AFSK-Send/AFSK-Send.ino b/examples/AFSK-Send/AFSK-Send.ino index 9b15847..104d39c 100644 --- a/examples/AFSK-Send/AFSK-Send.ino +++ b/examples/AFSK-Send/AFSK-Send.ino @@ -31,20 +31,8 @@ void loop() { // put your main code here, to run repeatedly: AFSK::Packet *packet = AFSK::PacketBuffer::makePacket(22 + 32); packet->start(); - packet->appendFCS('V'<<1); - packet->appendFCS('E'<<1); - packet->appendFCS('6'<<1); - packet->appendFCS('S'<<1); - packet->appendFCS('L'<<1); - packet->appendFCS('P'<<1); - packet->appendFCS(0b11100000); - packet->appendFCS('V'<<1); - packet->appendFCS('A'<<1); - packet->appendFCS('6'<<1); - packet->appendFCS('G'<<1); - packet->appendFCS('A'<<1); - packet->appendFCS(' '<<1); - packet->appendFCS(0b01100001 | (15 & 0xf) << 1); + packet->appendCallsign("VE6SLP",0); + packet->appendCallsign("VA6GA",15,true); packet->appendFCS(0x03); packet->appendFCS(0xf0); packet->print("Hello "); @@ -61,7 +49,7 @@ void loop() { // Wait up to 2.5 seconds to finish sending, and stop transmitter. // TODO: This is hackery. for(int i = 0; i < 500; i++) { - if(!radio.afsk.isSending()) + if(!radio.afsk.encoder.isSending()) break; delay(50); } From 21521008734c807d69c3ab4eac4dc559d22c76fd Mon Sep 17 00:00:00 2001 From: Stephen Olesen Date: Thu, 2 Jul 2015 18:04:12 -0600 Subject: [PATCH 18/44] Change amplitude scaling. Go back to refclk for ICR1. Added phase changes. --- DDS.cpp | 21 ++++++++++++++++++--- DDS.h | 12 +++++++++--- 2 files changed, 27 insertions(+), 6 deletions(-) diff --git a/DDS.cpp b/DDS.cpp index 8d0c02b..b4cf79e 100644 --- a/DDS.cpp +++ b/DDS.cpp @@ -42,8 +42,8 @@ void DDS::start() { // clock as the DDS. TCCR1B = _BV(CS11) | _BV(WGM13) | _BV(WGM12); TCCR1A = 0; - //ICR1 = ((F_CPU / 8) / refclk) - 1; - ICR1 = ((F_CPU / 8) / 9600) - 1; + ICR1 = ((F_CPU / 8) / refclk) - 1; + //ICR1 = ((F_CPU / 8) / 9600) - 1; // Configure the ADC here to automatically run and be triggered off Timer1 ADMUX = _BV(REFS0) | _BV(ADLAR) | 0; // Channel 0, shift result left (ADCH used) @@ -77,8 +77,21 @@ void DDS::setFrequency(unsigned short freq) { } } +// Degrees should be between -360 and +360 (others don't make much sense) +void DDS::setPhaseDeg(int16_t degrees) { + accumulator = degrees * (pow(2,ACCUMULATOR_BITS)/360.0); +} +void DDS::changePhaseDeg(int16_t degrees) { + accumulator += degrees * (pow(2,ACCUMULATOR_BITS)/360.0); +} + // TODO: Clean this up a bit.. void DDS::clockTick() { +/* if(running) { + accumulator += stepRate; + OCR2A = getDutyCycle(); + } + return;*/ if(running) { accumulator += stepRate; if(timeLimited && tickDuration == 0) { @@ -129,5 +142,7 @@ uint8_t DDS::getDutyCycle() { phAng = (accumulator >> ACCUMULATOR_BIT_SHIFT); uint8_t position = pgm_read_byte_near(ddsSineTable + phAng)>>(8-COMPARATOR_BITS); // Apply scaling and return - return position >> amplitude; + uint16_t scaled = position; + scaled *= amplitude; + return scaled>>8; } diff --git a/DDS.h b/DDS.h index 54199a4..e18f1e9 100644 --- a/DDS.h +++ b/DDS.h @@ -42,7 +42,7 @@ // This is how often we'll perform a phase advance, as well as ADC sampling // rate. The higher this value, the smoother the output wave will be, at the // expense of CPU time. It maxes out around 62000 (TBD) -#define DDS_REFCLK_DEFAULT 9600 +#define DDS_REFCLK_DEFAULT 38400 // As each Arduino crystal is a little different, this can be fine tuned to // provide more accurate frequencies. Adjustments in the range of hundreds // is a good start. @@ -193,10 +193,16 @@ public: // frequency supported will fit in a short. void setFrequency(unsigned short freq); + // Handle phase shifts + void setPhaseDeg(int16_t degrees); + void changePhaseDeg(int16_t degrees); + // Adjustable reference clock. This shoud be done before the timers are // started, or they will need to be restarted. Frequencies will need to // be set again to use the new clock. - void setReferenceClock(unsigned long ref); + void setReferenceClock(unsigned long ref) { + refclk = ref; + } unsigned long getReferenceClock() { return refclk; } @@ -206,7 +212,7 @@ public: // Set a scaling factor. To keep things quick, this is a power of 2 value. // Set it with 0 for lowest (which will be off), 8 is highest. void setAmplitude(unsigned char amp) { - amplitude = 8 - amp; + amplitude = amp; } // This is the function called by the ADC_vect ISR to produce the tones From 437e750e76263b9ced3ef076db31ba1b42100158 Mon Sep 17 00:00:00 2001 From: Stephen Olesen Date: Thu, 2 Jul 2015 18:06:57 -0600 Subject: [PATCH 19/44] Rough PSK31 example, constant transmit of a string. --- examples/PSK31-Transmit/PSK31-Transmit.ino | 78 +++++++++++++ examples/PSK31-Transmit/varicode.h | 130 +++++++++++++++++++++ 2 files changed, 208 insertions(+) create mode 100644 examples/PSK31-Transmit/PSK31-Transmit.ino create mode 100644 examples/PSK31-Transmit/varicode.h diff --git a/examples/PSK31-Transmit/PSK31-Transmit.ino b/examples/PSK31-Transmit/PSK31-Transmit.ino new file mode 100644 index 0000000..1bd827c --- /dev/null +++ b/examples/PSK31-Transmit/PSK31-Transmit.ino @@ -0,0 +1,78 @@ +#include +#include "varicode.h" + +DDS dds; + +void setup() { + Serial.begin(9600); + pinMode(11, OUTPUT); + pinMode(2, OUTPUT); + // put your setup code here, to run once: + dds.setReferenceClock(8000); + dds.start(); + dds.setFrequency(1000); + dds.on(); +} + +volatile bool sent = true; +volatile uint16_t bitsToSend = 0; +volatile uint8_t zeroCount = 0; + +void sendChar(uint8_t c) { + uint16_t bits = varicode[c]; + while((bits&0x8000)==0) { + bits<<=1; + } + while(!sent) {} //delay(32); + cli(); + sent = false; + bitsToSend = bits; + sei(); + while(!sent) {} //delay(32); + //PORTD &= ~_BV(2); // Diagnostic pin (D2) +} + +char *string = "Why hello there, friend. Nice to meet you. Welcome to PSK31. 73, VE6SLP sk\r\n"; +void loop() { + int i; + // put your main code here, to run repeatedly: + //for(i = 0; i<5; i++) + // sendChar(0); + // return; + for(i = 0; i < strlen(string); i++) { + sendChar(string[i]); + Serial.println(string[i]); + } +} + +// This will trigger at 8kHz +ISR(ADC_vect) { + static uint8_t tcnt = 0; + TIFR1 |= _BV(ICF1); + // Wave shaping + PORTD |= _BV(2); + if(tcnt < 32) + dds.setAmplitude(tcnt<<3); + if(tcnt > (255-32)) + dds.setAmplitude((255 - tcnt)<<3); + dds.clockTick(); + if(tcnt++ == 0) { // Next bit + //PORTD ^= _BV(2); // Diagnostic pin (D2) + if(!sent) { + if((bitsToSend & 0x8000) == 0) { + zeroCount++; + dds.changePhaseDeg(+180); + } else { + zeroCount = 0; + } + bitsToSend<<=1; + if(zeroCount == 2) { + sent = true; + } + } else { + // Idle on zeroes + dds.changePhaseDeg(+180); + } + } + PORTD &= ~_BV(2); +} diff --git a/examples/PSK31-Transmit/varicode.h b/examples/PSK31-Transmit/varicode.h new file mode 100644 index 0000000..bac5fa2 --- /dev/null +++ b/examples/PSK31-Transmit/varicode.h @@ -0,0 +1,130 @@ +const uint16_t varicode[] = { +0xAAC0, // ASCII = 0 1010101011 +0xB6C0, // ASCII = 1 1011011011 +0xBB40, // ASCII = 2 1011101101 +0xDDC0, // ASCII = 3 1101110111 +0xBAC0, // ASCII = 4 1011101011 +0xD7C0, // ASCII = 5 1101011111 +0xBBC0, // ASCII = 6 1011101111 +0xBF40, // ASCII = 7 1011111101 +0xBFC0, // ASCII = 8 1011111111 +0xEF00, // ASCII = 9 11101111 +0xE800, // ASCII = 10 11101 +0xDBC0, // ASCII = 11 1101101111 +0xB740, // ASCII = 12 1011011101 +0xF800, // ASCII = 13 11111 +0xDD40, // ASCII = 14 1101110101 +0xEAC0, // ASCII = 15 1110101011 +0xBDC0, // ASCII = 16 1011110111 +0xBD40, // ASCII = 17 1011110101 +0xEB40, // ASCII = 18 1110101101 +0xEBC0, // ASCII = 19 1110101111 +0xD6C0, // ASCII = 20 1101011011 +0xDAC0, // ASCII = 21 1101101011 +0xDB40, // ASCII = 22 1101101101 +0xD5C0, // ASCII = 23 1101010111 +0xDEC0, // ASCII = 24 1101111011 +0xDF40, // ASCII = 25 1101111101 +0xEDC0, // ASCII = 26 1110110111 +0xD540, // ASCII = 27 1101010101 +0xD740, // ASCII = 28 1101011101 +0xEEC0, // ASCII = 29 1110111011 +0xBEC0, // ASCII = 30 1011111011 +0xDFC0, // ASCII = 31 1101111111 +0x8000, // ASCII = ' ' 1 +0xFF80, // ASCII = '!' 111111111 +0xAF80, // ASCII = '"' 101011111 +0xFA80, // ASCII = '#' 111110101 +0xED80, // ASCII = '$' 111011011 +0xB540, // ASCII = '%' 1011010101 +0xAEC0, // ASCII = '&' 1010111011 +0xBF80, // ASCII = ''' 101111111 +0xFB00, // ASCII = '(' 11111011 +0xF700, // ASCII = ')' 11110111 +0xB780, // ASCII = '*' 101101111 +0xEF80, // ASCII = '+' 111011111 +0xEA00, // ASCII = ',' 1110101 +0xD400, // ASCII = '-' 110101 +0xAE00, // ASCII = '.' 1010111 +0xD780, // ASCII = '/' 110101111 +0xB700, // ASCII = '0' 10110111 +0xBD00, // ASCII = '1' 10111101 +0xED00, // ASCII = '2' 11101101 +0xFF00, // ASCII = '3' 11111111 +0xBB80, // ASCII = '4' 101110111 +0xAD80, // ASCII = '5' 101011011 +0xB580, // ASCII = '6' 101101011 +0xD680, // ASCII = '7' 110101101 +0xD580, // ASCII = '8' 110101011 +0xDB80, // ASCII = '9' 110110111 +0xF500, // ASCII = ':' 11110101 +0xDE80, // ASCII = ';' 110111101 +0xF680, // ASCII = '<' 111101101 +0xAA00, // ASCII = '=' 1010101 +0xEB80, // ASCII = '>' 111010111 +0xABC0, // ASCII = '?' 1010101111 +0xAF40, // ASCII = '@' 1010111101 +0xFA00, // ASCII = 'A' 1111101 +0xEB00, // ASCII = 'B' 11101011 +0xAD00, // ASCII = 'C' 10101101 +0xB500, // ASCII = 'D' 10110101 +0xEE00, // ASCII = 'E' 1110111 +0xDB00, // ASCII = 'F' 11011011 +0xFD00, // ASCII = 'G' 11111101 +0xAA80, // ASCII = 'H' 101010101 +0xFE00, // ASCII = 'I' 1111111 +0xFE80, // ASCII = 'J' 111111101 +0xBE80, // ASCII = 'K' 101111101 +0xD700, // ASCII = 'L' 11010111 +0xBB00, // ASCII = 'M' 10111011 +0xDD00, // ASCII = 'N' 11011101 +0xAB00, // ASCII = 'O' 10101011 +0xD500, // ASCII = 'P' 11010101 +0xEE80, // ASCII = 'Q' 111011101 +0xAF00, // ASCII = 'R' 10101111 +0xDE00, // ASCII = 'S' 1101111 +0xDA00, // ASCII = 'T' 1101101 +0xAB80, // ASCII = 'U' 101010111 +0xDA80, // ASCII = 'V' 110110101 +0xAE80, // ASCII = 'W' 101011101 +0xBA80, // ASCII = 'X' 101110101 +0xBD80, // ASCII = 'Y' 101111011 +0xAB40, // ASCII = 'Z' 1010101101 +0xFB80, // ASCII = '[' 1111101110 +0xF780, // ASCII = '\' 111101111 +0xFD80, // ASCII = ']' 111111011 +0xAFC0, // ASCII = '^' 1010111111 +0xB680, // ASCII = '_' 101101101 +0xB7C0, // ASCII = '`' 1011011111 +0xB000, // ASCII = 'a' 1011 +0xBE00, // ASCII = 'b' 1011111 +0xBC00, // ASCII = 'c' 101111 +0xB400, // ASCII = 'd' 101101 +0xC000, // ASCII = 'e' 11 +0xF400, // ASCII = 'f' 111101 +0xB600, // ASCII = 'g' 1011011 +0xAC00, // ASCII = 'h' 101011 +0xD000, // ASCII = 'i' 1101 +0xF580, // ASCII = 'j' 111101011 +0xBF00, // ASCII = 'k' 10111111 +0xD800, // ASCII = 'l' 11011 +0xEC00, // ASCII = 'm' 111011 +0xF000, // ASCII = 'n' 1111 +0xE000, // ASCII = 'o' 111 +0xFC00, // ASCII = 'p' 111111 +0xDF80, // ASCII = 'q' 110111111 +0xA800, // ASCII = 'r' 10101 +0xB800, // ASCII = 's' 10111 +0xA000, // ASCII = 't' 101 +0xDC00, // ASCII = 'u' 110111 +0xF600, // ASCII = 'v' 1111011 +0xD600, // ASCII = 'w' 1101011 +0xDF00, // ASCII = 'x' 11011111 +0xBA00, // ASCII = 'y' 1011101 +0xEA80, // ASCII = 'z' 111010101 +0xADC0, // ASCII = '{' 1010110111 +0xDD80, // ASCII = '|' 110111011 +0xAD40, // ASCII = '}' 1010110101 +0xB5C0, // ASCII = '~' 1011010111 +0xED40 // ASCII = 127 1110110101 +}; From 7db28bff0e4b04bb0ae1e200e1654cf4bbbfb86b Mon Sep 17 00:00:00 2001 From: Stephen Olesen Date: Thu, 2 Jul 2015 19:30:53 -0600 Subject: [PATCH 20/44] Change DDS to using signed integers to try to keep our DC bias averaged out. --- DDS.cpp | 10 +++++++--- DDS.h | 25 +++++++++++++++++++++++-- 2 files changed, 30 insertions(+), 5 deletions(-) diff --git a/DDS.cpp b/DDS.cpp index b4cf79e..6d86f80 100644 --- a/DDS.cpp +++ b/DDS.cpp @@ -140,9 +140,13 @@ uint8_t DDS::getDutyCycle() { uint8_t phAng; #endif phAng = (accumulator >> ACCUMULATOR_BIT_SHIFT); - uint8_t position = pgm_read_byte_near(ddsSineTable + phAng)>>(8-COMPARATOR_BITS); + int8_t position = pgm_read_byte_near(ddsSineTable + phAng)>>(8-COMPARATOR_BITS); // Apply scaling and return - uint16_t scaled = position; + int16_t scaled = position; + // output = ((duty * amplitude) / 256) + 128 + // This keeps amplitudes centered around 50% duty scaled *= amplitude; - return scaled>>8; + scaled >>= 8; + scaled += 128; + return scaled; } diff --git a/DDS.h b/DDS.h index e18f1e9..551c585 100644 --- a/DDS.h +++ b/DDS.h @@ -128,7 +128,28 @@ static const uint8_t ddsSineTable[1024] PROGMEM = { }; #else #define ACCUMULATOR_BIT_SHIFT (ACCUMULATOR_BITS-8) -static const uint8_t ddsSineTable[256] PROGMEM = { +static const int8_t ddsSineTable[256] PROGMEM = { + 0, 3, 6, 9, 12, 16, 19, 22, 25, 28, 31, 34, 37, 40, 43, 46, 49, + 51, 54, 57, 60, 63, 65, 68, 71, 73, 76, 78, 81, 83, 85, 88, 90, + 92, 94, 96, 98, 100, 102, 104, 106, 107, 109, 111, 112, 113, 115, + 116, 117, 118, 120, 121, 122, 122, 123, 124, 125, 125, 126, 126, + 126, 127, 127, 127, 127, 127, 127, 127, 126, 126, 126, 125, 125, + 124, 123, 122, 122, 121, 120, 118, 117, 116, 115, 113, 112, 111, + 109, 107, 106, 104, 102, 100, 98, 96, 94, 92, 90, 88, 85, 83, 81, + 78, 76, 73, 71, 68, 65, 63, 60, 57, 54, 51, 49, 46, 43, 40, 37, + 34, 31, 28, 25, 22, 19, 16, 12, 9, 6, 3, 0, -3, -6, -9, -12, -16, + -19, -22, -25, -28, -31, -34, -37, -40, -43, -46, -49, -51, -54, + -57, -60, -63, -65, -68, -71, -73, -76, -78, -81, -83, -85, -88, + -90, -92, -94, -96, -98, -100, -102, -104, -106, -107, -109, -111, + -112, -113, -115, -116, -117, -118, -120, -121, -122, -122, -123, + -124, -125, -125, -126, -126, -126, -127, -127, -127, -127, -127, + -127, -127, -126, -126, -126, -125, -125, -124, -123, -122, -122, + -121, -120, -118, -117, -116, -115, -113, -112, -111, -109, -107, + -106, -104, -102, -100, -98, -96, -94, -92, -90, -88, -85, -83, + -81, -78, -76, -73, -71, -68, -65, -63, -60, -57, -54, -51, -49, + -46, -43, -40, -37, -34, -31, -28, -25, -22, -19, -16, -12, -9, -6, -3 + }; +/*static const uint8_t ddsSineTable[256] PROGMEM = { 128,131,134,137,140,143,146,149,152,155,158,162,165,167,170,173, 176,179,182,185,188,190,193,196,198,201,203,206,208,211,213,215, 218,220,222,224,226,228,230,232,234,235,237,238,240,241,243,244, @@ -145,7 +166,7 @@ static const uint8_t ddsSineTable[256] PROGMEM = { 10,11,12,14,15,17,18,20,21,23,25,27,29,31,33,35, 37,40,42,44,47,49,52,54,57,59,62,65,67,70,73,76, 79,82,85,88,90,93,97,100,103,106,109,112,115,118,121,124 -}; +};*/ #endif /* DDS_TABLE_LARGE */ class DDS { From 6ff2791963e2540f25709030761a8af6ce42a797 Mon Sep 17 00:00:00 2001 From: Stephen Olesen Date: Thu, 2 Jul 2015 19:31:16 -0600 Subject: [PATCH 21/44] Change to the waveform shaping trying to reduce IMD. --- examples/PSK31-Transmit/PSK31-Transmit.ino | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/examples/PSK31-Transmit/PSK31-Transmit.ino b/examples/PSK31-Transmit/PSK31-Transmit.ino index 1bd827c..a71f9c1 100644 --- a/examples/PSK31-Transmit/PSK31-Transmit.ino +++ b/examples/PSK31-Transmit/PSK31-Transmit.ino @@ -45,16 +45,24 @@ void loop() { } } +const uint8_t amplitudeShape[41] = { + 255, 241, 228, 215, 203, 191, 181, 171, 161, 152, 143, 135, 128, 121, 114, 107, 101, 96, 90, 85, 80, 76, 72, 68, 64, 60, 57, 54, 51, 48, 45, 42, 40, 38, 36, 34, 32, 30, 28, 27, 25 +}; + // This will trigger at 8kHz ISR(ADC_vect) { static uint8_t tcnt = 0; TIFR1 |= _BV(ICF1); // Wave shaping PORTD |= _BV(2); - if(tcnt < 32) - dds.setAmplitude(tcnt<<3); - if(tcnt > (255-32)) - dds.setAmplitude((255 - tcnt)<<3); + // TODO: Improve how this would perform. + if(tcnt < 82) + dds.setAmplitude(amplitudeShape[(82-tcnt)/2]); + if(tcnt > (255-82)) + dds.setAmplitude(amplitudeShape[(tcnt-173)/2]); + //else if(tcnt > (255-64)) + // dds.setAmplitude((255 - tcnt)); + //else dds.setAmplitude(255); dds.clockTick(); if(tcnt++ == 0) { // Next bit //PORTD ^= _BV(2); // Diagnostic pin (D2) From 77a8c62b78a678fd7f6a2c91545783de0b3818ba Mon Sep 17 00:00:00 2001 From: Stephen Olesen Date: Thu, 2 Jul 2015 19:57:32 -0600 Subject: [PATCH 22/44] Correct Pin 3 output to scale to the reduced comparator size. --- DDS.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/DDS.cpp b/DDS.cpp index 6d86f80..eeaabb3 100644 --- a/DDS.cpp +++ b/DDS.cpp @@ -140,13 +140,13 @@ uint8_t DDS::getDutyCycle() { uint8_t phAng; #endif phAng = (accumulator >> ACCUMULATOR_BIT_SHIFT); - int8_t position = pgm_read_byte_near(ddsSineTable + phAng)>>(8-COMPARATOR_BITS); + int8_t position = pgm_read_byte_near(ddsSineTable + phAng); //>>(8-COMPARATOR_BITS); // Apply scaling and return int16_t scaled = position; // output = ((duty * amplitude) / 256) + 128 // This keeps amplitudes centered around 50% duty scaled *= amplitude; - scaled >>= 8; - scaled += 128; + scaled >>= 8+(8-COMPARATOR_BITS); + scaled += 128>>(8-COMPARATOR_BITS); return scaled; } From 26ffcd332b7e279ef7247b9ba0eb6207f9dcd90c Mon Sep 17 00:00:00 2001 From: Stephen Olesen Date: Thu, 2 Jul 2015 19:58:54 -0600 Subject: [PATCH 23/44] Faster reference clock rate (using pin 3), skip outer loop to slow PSK. --- examples/PSK31-Transmit/PSK31-Transmit.ino | 22 ++++++++++++++-------- 1 file changed, 14 insertions(+), 8 deletions(-) diff --git a/examples/PSK31-Transmit/PSK31-Transmit.ino b/examples/PSK31-Transmit/PSK31-Transmit.ino index a71f9c1..70fbc03 100644 --- a/examples/PSK31-Transmit/PSK31-Transmit.ino +++ b/examples/PSK31-Transmit/PSK31-Transmit.ino @@ -5,10 +5,10 @@ DDS dds; void setup() { Serial.begin(9600); - pinMode(11, OUTPUT); + pinMode(3, OUTPUT); pinMode(2, OUTPUT); // put your setup code here, to run once: - dds.setReferenceClock(8000); + dds.setReferenceClock(32000); dds.start(); dds.setFrequency(1000); dds.on(); @@ -41,7 +41,7 @@ void loop() { // return; for(i = 0; i < strlen(string); i++) { sendChar(string[i]); - Serial.println(string[i]); + //Serial.println(string[i]); } } @@ -51,19 +51,25 @@ const uint8_t amplitudeShape[41] = { // This will trigger at 8kHz ISR(ADC_vect) { + static uint8_t outer = 0; static uint8_t tcnt = 0; TIFR1 |= _BV(ICF1); // Wave shaping - PORTD |= _BV(2); // TODO: Improve how this would perform. - if(tcnt < 82) - dds.setAmplitude(amplitudeShape[(82-tcnt)/2]); - if(tcnt > (255-82)) - dds.setAmplitude(amplitudeShape[(tcnt-173)/2]); //else if(tcnt > (255-64)) // dds.setAmplitude((255 - tcnt)); //else dds.setAmplitude(255); + if(tcnt < 81) + dds.setAmplitude(amplitudeShape[(81-tcnt)/2]); + if(tcnt > (255-81)) + dds.setAmplitude(amplitudeShape[(tcnt-174)/2]); dds.clockTick(); + PORTD &= ~_BV(2); + if(outer++ == 3) { + outer = 0; + } else { + return; + } if(tcnt++ == 0) { // Next bit //PORTD ^= _BV(2); // Diagnostic pin (D2) if(!sent) { From 28248365c80559fc0fefd9a98201e9672413eddf Mon Sep 17 00:00:00 2001 From: Stephen Olesen Date: Thu, 2 Jul 2015 21:27:12 -0600 Subject: [PATCH 24/44] Added QPSK63. Set both sketches to run on either pin 3 or 11. --- examples/PSK31-Transmit/PSK31-Transmit.ino | 1 + examples/QPSK63-Transmit/QPSK63-Transmit.ino | 98 ++++++++++++++ examples/QPSK63-Transmit/varicode.h | 130 +++++++++++++++++++ 3 files changed, 229 insertions(+) create mode 100644 examples/QPSK63-Transmit/QPSK63-Transmit.ino create mode 100644 examples/QPSK63-Transmit/varicode.h diff --git a/examples/PSK31-Transmit/PSK31-Transmit.ino b/examples/PSK31-Transmit/PSK31-Transmit.ino index 70fbc03..13a3e0f 100644 --- a/examples/PSK31-Transmit/PSK31-Transmit.ino +++ b/examples/PSK31-Transmit/PSK31-Transmit.ino @@ -5,6 +5,7 @@ DDS dds; void setup() { Serial.begin(9600); + pinMode(11, OUTPUT); pinMode(3, OUTPUT); pinMode(2, OUTPUT); // put your setup code here, to run once: diff --git a/examples/QPSK63-Transmit/QPSK63-Transmit.ino b/examples/QPSK63-Transmit/QPSK63-Transmit.ino new file mode 100644 index 0000000..e57c245 --- /dev/null +++ b/examples/QPSK63-Transmit/QPSK63-Transmit.ino @@ -0,0 +1,98 @@ +#include +#include "varicode.h" + +DDS dds; + +void setup() { + Serial.begin(9600); + pinMode(11, OUTPUT); + pinMode(3, OUTPUT); + pinMode(2, OUTPUT); + // put your setup code here, to run once: + dds.setReferenceClock(32000); + dds.start(); + dds.setFrequency(1000); + dds.on(); +} + +volatile bool sent = true; +volatile uint16_t bitsToSend = 0; +volatile uint8_t zeroCount = 0; + +void sendChar(uint8_t c) { + uint16_t bits = varicode[c]; + while((bits&0x8000)==0) { + bits<<=1; + } + while(!sent) {} //delay(32); + cli(); + sent = false; + bitsToSend = bits; + sei(); + while(!sent) {} //delay(32); + //PORTD &= ~_BV(2); // Diagnostic pin (D2) +} + +char *string = "Why hello there, friend. Nice to meet you. Welcome to PSK31. 73, VE6SLP sk\r\n"; +void loop() { + int i; + // put your main code here, to run repeatedly: + //for(i = 0; i<5; i++) + // sendChar(0); + // return; + for(i = 0; i < strlen(string); i++) { + sendChar(string[i]); + //Serial.println(string[i]); + } +} + +const uint8_t amplitudeShape[41] = { + 255, 241, 228, 215, 203, 191, 181, 171, 161, 152, 143, 135, 128, 121, 114, 107, 101, 96, 90, 85, 80, 76, 72, 68, 64, 60, 57, 54, 51, 48, 45, 42, 40, 38, 36, 34, 32, 30, 28, 27, 25 +}; + +// This will trigger at 8kHz +const uint16_t qpskConvolution[32] = { + 180, 90, -90, 0, -90, 0, 180, 90, + 0, -90, 90, 180, 90, 180, 0, -90, + 90, 180, 0, -90, 0, -90, 90, 180, + -90, 0, 180, 90, 180, 90, -90, 0 +}; +uint8_t last5Bits = 0b00000; +ISR(ADC_vect) { + static uint8_t outer = 0; + static uint8_t tcnt = 0; + TIFR1 |= _BV(ICF1); + // Wave shaping + // TODO: Improve how this would perform. + if(tcnt < 81) + dds.setAmplitude(amplitudeShape[(81-tcnt)/2]); + if(tcnt > (255-81)) + dds.setAmplitude(amplitudeShape[(tcnt-174)/2]); + dds.clockTick(); + + if(outer++ == 1) { + outer = 0; + } else { + return; + } + + if(tcnt++ == 0) { // Next bit + last5Bits <<= 1; + if(!sent) { + if((bitsToSend & 0x8000) == 0) { + zeroCount++; + } else { + zeroCount = 0; + last5Bits |= 1; + } + dds.changePhaseDeg(qpskConvolution[last5Bits&31]); + bitsToSend<<=1; + if(zeroCount == 2) { + sent = true; + } + } else { + // Idle on zeroes + dds.changePhaseDeg(qpskConvolution[last5Bits&31]); + } + } +} diff --git a/examples/QPSK63-Transmit/varicode.h b/examples/QPSK63-Transmit/varicode.h new file mode 100644 index 0000000..bac5fa2 --- /dev/null +++ b/examples/QPSK63-Transmit/varicode.h @@ -0,0 +1,130 @@ +const uint16_t varicode[] = { +0xAAC0, // ASCII = 0 1010101011 +0xB6C0, // ASCII = 1 1011011011 +0xBB40, // ASCII = 2 1011101101 +0xDDC0, // ASCII = 3 1101110111 +0xBAC0, // ASCII = 4 1011101011 +0xD7C0, // ASCII = 5 1101011111 +0xBBC0, // ASCII = 6 1011101111 +0xBF40, // ASCII = 7 1011111101 +0xBFC0, // ASCII = 8 1011111111 +0xEF00, // ASCII = 9 11101111 +0xE800, // ASCII = 10 11101 +0xDBC0, // ASCII = 11 1101101111 +0xB740, // ASCII = 12 1011011101 +0xF800, // ASCII = 13 11111 +0xDD40, // ASCII = 14 1101110101 +0xEAC0, // ASCII = 15 1110101011 +0xBDC0, // ASCII = 16 1011110111 +0xBD40, // ASCII = 17 1011110101 +0xEB40, // ASCII = 18 1110101101 +0xEBC0, // ASCII = 19 1110101111 +0xD6C0, // ASCII = 20 1101011011 +0xDAC0, // ASCII = 21 1101101011 +0xDB40, // ASCII = 22 1101101101 +0xD5C0, // ASCII = 23 1101010111 +0xDEC0, // ASCII = 24 1101111011 +0xDF40, // ASCII = 25 1101111101 +0xEDC0, // ASCII = 26 1110110111 +0xD540, // ASCII = 27 1101010101 +0xD740, // ASCII = 28 1101011101 +0xEEC0, // ASCII = 29 1110111011 +0xBEC0, // ASCII = 30 1011111011 +0xDFC0, // ASCII = 31 1101111111 +0x8000, // ASCII = ' ' 1 +0xFF80, // ASCII = '!' 111111111 +0xAF80, // ASCII = '"' 101011111 +0xFA80, // ASCII = '#' 111110101 +0xED80, // ASCII = '$' 111011011 +0xB540, // ASCII = '%' 1011010101 +0xAEC0, // ASCII = '&' 1010111011 +0xBF80, // ASCII = ''' 101111111 +0xFB00, // ASCII = '(' 11111011 +0xF700, // ASCII = ')' 11110111 +0xB780, // ASCII = '*' 101101111 +0xEF80, // ASCII = '+' 111011111 +0xEA00, // ASCII = ',' 1110101 +0xD400, // ASCII = '-' 110101 +0xAE00, // ASCII = '.' 1010111 +0xD780, // ASCII = '/' 110101111 +0xB700, // ASCII = '0' 10110111 +0xBD00, // ASCII = '1' 10111101 +0xED00, // ASCII = '2' 11101101 +0xFF00, // ASCII = '3' 11111111 +0xBB80, // ASCII = '4' 101110111 +0xAD80, // ASCII = '5' 101011011 +0xB580, // ASCII = '6' 101101011 +0xD680, // ASCII = '7' 110101101 +0xD580, // ASCII = '8' 110101011 +0xDB80, // ASCII = '9' 110110111 +0xF500, // ASCII = ':' 11110101 +0xDE80, // ASCII = ';' 110111101 +0xF680, // ASCII = '<' 111101101 +0xAA00, // ASCII = '=' 1010101 +0xEB80, // ASCII = '>' 111010111 +0xABC0, // ASCII = '?' 1010101111 +0xAF40, // ASCII = '@' 1010111101 +0xFA00, // ASCII = 'A' 1111101 +0xEB00, // ASCII = 'B' 11101011 +0xAD00, // ASCII = 'C' 10101101 +0xB500, // ASCII = 'D' 10110101 +0xEE00, // ASCII = 'E' 1110111 +0xDB00, // ASCII = 'F' 11011011 +0xFD00, // ASCII = 'G' 11111101 +0xAA80, // ASCII = 'H' 101010101 +0xFE00, // ASCII = 'I' 1111111 +0xFE80, // ASCII = 'J' 111111101 +0xBE80, // ASCII = 'K' 101111101 +0xD700, // ASCII = 'L' 11010111 +0xBB00, // ASCII = 'M' 10111011 +0xDD00, // ASCII = 'N' 11011101 +0xAB00, // ASCII = 'O' 10101011 +0xD500, // ASCII = 'P' 11010101 +0xEE80, // ASCII = 'Q' 111011101 +0xAF00, // ASCII = 'R' 10101111 +0xDE00, // ASCII = 'S' 1101111 +0xDA00, // ASCII = 'T' 1101101 +0xAB80, // ASCII = 'U' 101010111 +0xDA80, // ASCII = 'V' 110110101 +0xAE80, // ASCII = 'W' 101011101 +0xBA80, // ASCII = 'X' 101110101 +0xBD80, // ASCII = 'Y' 101111011 +0xAB40, // ASCII = 'Z' 1010101101 +0xFB80, // ASCII = '[' 1111101110 +0xF780, // ASCII = '\' 111101111 +0xFD80, // ASCII = ']' 111111011 +0xAFC0, // ASCII = '^' 1010111111 +0xB680, // ASCII = '_' 101101101 +0xB7C0, // ASCII = '`' 1011011111 +0xB000, // ASCII = 'a' 1011 +0xBE00, // ASCII = 'b' 1011111 +0xBC00, // ASCII = 'c' 101111 +0xB400, // ASCII = 'd' 101101 +0xC000, // ASCII = 'e' 11 +0xF400, // ASCII = 'f' 111101 +0xB600, // ASCII = 'g' 1011011 +0xAC00, // ASCII = 'h' 101011 +0xD000, // ASCII = 'i' 1101 +0xF580, // ASCII = 'j' 111101011 +0xBF00, // ASCII = 'k' 10111111 +0xD800, // ASCII = 'l' 11011 +0xEC00, // ASCII = 'm' 111011 +0xF000, // ASCII = 'n' 1111 +0xE000, // ASCII = 'o' 111 +0xFC00, // ASCII = 'p' 111111 +0xDF80, // ASCII = 'q' 110111111 +0xA800, // ASCII = 'r' 10101 +0xB800, // ASCII = 's' 10111 +0xA000, // ASCII = 't' 101 +0xDC00, // ASCII = 'u' 110111 +0xF600, // ASCII = 'v' 1111011 +0xD600, // ASCII = 'w' 1101011 +0xDF00, // ASCII = 'x' 11011111 +0xBA00, // ASCII = 'y' 1011101 +0xEA80, // ASCII = 'z' 111010101 +0xADC0, // ASCII = '{' 1010110111 +0xDD80, // ASCII = '|' 110111011 +0xAD40, // ASCII = '}' 1010110101 +0xB5C0, // ASCII = '~' 1011010111 +0xED40 // ASCII = 127 1110110101 +}; From acc4aebe03db74bbb8f99807925cc51e55a8d2dc Mon Sep 17 00:00:00 2001 From: Stephen Olesen Date: Fri, 3 Jul 2015 15:23:39 -0600 Subject: [PATCH 25/44] Add calculators for frequency steps. Accumulator has a typedef now. Optimize tick. --- DDS.cpp | 37 ++++++++++++++++++++++++------------- DDS.h | 31 +++++++++++++++++++------------ 2 files changed, 43 insertions(+), 25 deletions(-) diff --git a/DDS.cpp b/DDS.cpp index eeaabb3..86dd8ac 100644 --- a/DDS.cpp +++ b/DDS.cpp @@ -59,22 +59,27 @@ void DDS::stop() { } // Set our current sine wave frequency in Hz -void DDS::setFrequency(unsigned short freq) { +ddsAccumulator_t DDS::calcFrequency(unsigned short freq) { // Fo = (M*Fc)/2^N // M = (Fo/Fc)*2^N + ddsAccumulator_t newStep; if(refclk == DDS_REFCLK_DEFAULT) { // Try to use precalculated values if possible if(freq == 2200) { - stepRate = (2200.0 / (DDS_REFCLK_DEFAULT+DDS_REFCLK_OFFSET)) * pow(2,ACCUMULATOR_BITS); + newStep = (2200.0 / (DDS_REFCLK_DEFAULT+DDS_REFCLK_OFFSET)) * pow(2,ACCUMULATOR_BITS); } else if (freq == 1200) { - stepRate = (1200.0 / (DDS_REFCLK_DEFAULT+DDS_REFCLK_OFFSET)) * pow(2,ACCUMULATOR_BITS); + newStep = (1200.0 / (DDS_REFCLK_DEFAULT+DDS_REFCLK_OFFSET)) * pow(2,ACCUMULATOR_BITS); + } else if(freq == 2400) { + newStep = (2400.0 / (DDS_REFCLK_DEFAULT+DDS_REFCLK_OFFSET)) * pow(2,ACCUMULATOR_BITS); + } else if (freq == 1500) { + newStep = (1500.0 / (DDS_REFCLK_DEFAULT+DDS_REFCLK_OFFSET)) * pow(2,ACCUMULATOR_BITS); } else if (freq == 600) { - stepRate = (600.0 / (DDS_REFCLK_DEFAULT+DDS_REFCLK_OFFSET)) * pow(2,ACCUMULATOR_BITS); + newStep = (600.0 / (DDS_REFCLK_DEFAULT+DDS_REFCLK_OFFSET)) * pow(2,ACCUMULATOR_BITS); } } else { - // BUG: Step rate isn't properly calculated here, it gets the wrong frequency - stepRate = pow(2,ACCUMULATOR_BITS)*freq / (refclk+DDS_REFCLK_OFFSET); + newStep = pow(2,ACCUMULATOR_BITS)*freq / (refclk+DDS_REFCLK_OFFSET); } + return newStep; } // Degrees should be between -360 and +360 (others don't make much sense) @@ -134,19 +139,25 @@ void DDS::clockTick() { } uint8_t DDS::getDutyCycle() { -#if ACCUMULATOR_BIT_SHIFT >= 24 - uint16_t phAng; -#else - uint8_t phAng; -#endif + #if ACCUMULATOR_BIT_SHIFT >= 24 + uint16_t phAng; + #else + uint8_t phAng; + #endif + if(amplitude == 0) // Shortcut out on no amplitude + return 128>>(8-COMPARATOR_BITS); phAng = (accumulator >> ACCUMULATOR_BIT_SHIFT); int8_t position = pgm_read_byte_near(ddsSineTable + phAng); //>>(8-COMPARATOR_BITS); // Apply scaling and return int16_t scaled = position; // output = ((duty * amplitude) / 256) + 128 // This keeps amplitudes centered around 50% duty - scaled *= amplitude; - scaled >>= 8+(8-COMPARATOR_BITS); + if(amplitude != 255) { // Amplitude is reduced, so do the full math + scaled *= amplitude; + scaled >>= 8+(8-COMPARATOR_BITS); + } else { // Otherwise, only shift for the comparator bits + scaled >>= (8-COMPARATOR_BITS); + } scaled += 128>>(8-COMPARATOR_BITS); return scaled; } diff --git a/DDS.h b/DDS.h index 551c585..6ce0b3b 100644 --- a/DDS.h +++ b/DDS.h @@ -6,7 +6,7 @@ // Use pin 3 for PWM? If not defined, use pin 11 // Quality on pin 3 is higher than on 11, as it can be clocked faster // when the COMPARATOR_BITS value is less than 8 -// #define DDS_PWM_PIN_3 +#define DDS_PWM_PIN_3 // Normally, we turn on timer2 and timer1, and have ADC sampling as our clock // Define this to only use Timer2, and not start the ADC clock @@ -18,8 +18,10 @@ #ifdef SHORT_ACCUMULATOR #define ACCUMULATOR_BITS 16 +typedef int16_t ddsAccumulator_t; #else #define ACCUMULATOR_BITS 32 +typedef int32_t ddsAccumulator_t; #endif // If defined, the timer will idle at 50% duty cycle @@ -42,11 +44,16 @@ // This is how often we'll perform a phase advance, as well as ADC sampling // rate. The higher this value, the smoother the output wave will be, at the // expense of CPU time. It maxes out around 62000 (TBD) +// May be overridden in the sketch to improve performance +#ifndef DDS_REFCLK_DEFAULT #define DDS_REFCLK_DEFAULT 38400 +#endif // As each Arduino crystal is a little different, this can be fine tuned to // provide more accurate frequencies. Adjustments in the range of hundreds // is a good start. +#ifndef DDS_REFCLK_OFFSET #define DDS_REFCLK_OFFSET 0 +#endif #ifdef DDS_USE_ONLY_TIMER2 // TODO: Figure out where this clock value is generated from @@ -172,7 +179,7 @@ static const int8_t ddsSineTable[256] PROGMEM = { class DDS { public: DDS(): refclk(DDS_REFCLK_DEFAULT), accumulator(0), running(false), - timeLimited(false), tickDuration(0), amplitude(0) + timeLimited(false), tickDuration(0), amplitude(255) {}; // Start all of the timers needed @@ -210,9 +217,15 @@ public: delay(duration); } + // Use these to get some calculated values for specific frequencies + // or to get the current frequency stepping rate. + ddsAccumulator_t calcFrequency(unsigned short freq); + ddsAccumulator_t getPhaseAdvance() { return stepRate; }; + // Our maximum clock isn't very high, so our highest // frequency supported will fit in a short. - void setFrequency(unsigned short freq); + void setFrequency(unsigned short freq) { stepRate = calcFrequency(freq); }; + void setPrecalcFrequency(ddsAccumulator_t freq) { stepRate = freq; }; // Handle phase shifts void setPhaseDeg(int16_t degrees); @@ -244,15 +257,9 @@ private: volatile unsigned long tickDuration; volatile bool timeLimited; volatile unsigned char amplitude; -#ifdef SHORT_ACCUMULATOR - volatile unsigned short accumulator; - volatile unsigned short stepRate; - unsigned short refclk; -#else - volatile unsigned long accumulator; - volatile unsigned long stepRate; - unsigned long refclk; -#endif + volatile ddsAccumulator_t accumulator; + volatile ddsAccumulator_t stepRate; + ddsAccumulator_t refclk; static DDS *sDDS; }; From 208087693d90261ba7c2223bb188d02d96186ed3 Mon Sep 17 00:00:00 2001 From: Stephen Olesen Date: Fri, 3 Jul 2015 15:24:17 -0600 Subject: [PATCH 26/44] Pretty basic SSTV transmitter in Martin-1. Sends a static image. --- examples/SSTV-M1-Static/SSTV-M1-Static.ino | 414 +++++++++++++++++++++ 1 file changed, 414 insertions(+) create mode 100644 examples/SSTV-M1-Static/SSTV-M1-Static.ino diff --git a/examples/SSTV-M1-Static/SSTV-M1-Static.ino b/examples/SSTV-M1-Static/SSTV-M1-Static.ino new file mode 100644 index 0000000..4a7e9e6 --- /dev/null +++ b/examples/SSTV-M1-Static/SSTV-M1-Static.ino @@ -0,0 +1,414 @@ +// So the precalculated values will get stored +#define DDS_REFCLK_DEFAULT (34965/2) + +#include + +//HamShield radio; +DDS dds; +// Defined at the end of the sketch +extern const uint16_t image[256*20] PROGMEM; + +#define F_1200 0 +#define F_1500 1 +#define F_2400 2 +ddsAccumulator_t freqTable[3]; + +void setup() { + Serial.begin(9600); + pinMode(2, OUTPUT); + pinMode(3, OUTPUT); + // put your setup code here, to run once: + //dds.setReferenceClock(34965/4); + dds.start(); + freqTable[F_1200] = dds.calcFrequency(1200); + freqTable[F_1500] = dds.calcFrequency(1500); + freqTable[F_2400] = dds.calcFrequency(2400); + dds.setFrequency(1000); + dds.on(); + Serial.println("DDS on"); + delay(1000); + dds.off(); + delay(2000); + Serial.println("Next"); +} + +uint8_t code = MARTIN1; +bool parityCalc(int code) { + unsigned int v; // word value to compute the parity of + bool parity = false; // parity will be the parity of v + + while (code) + { + parity = !parity; + code = code & (code - 1); + } + + return parity; +} + +volatile bool registered = false; +volatile bool scanning = false; +volatile bool done = false; +volatile uint16_t nextBlock = 0; +volatile uint8_t currentScanline = 0; +volatile uint16_t scanline[2][20]; + +// Format is 3 'images', one each for green, blue and red +// But we don't have room, so it's monochrome +// 256 rows each +// 10 sets of 32 bits encoding on/off for the colour +//const unsigned long image[256][10] PROGMEM = { + +//}; + +void loadScanline(uint8_t s, int y) { + for(int i = 0; i < 20; i++) { + scanline[s][i] = pgm_read_word_near(image + y*20 + i); + } +} +#define DON() PORTD |= _BV(2); +#define DOFF() PORTD &= ~_BV(2); +void loop() { + // Load the first scanline + loadScanline(0, 0); + + // VIS + dds.playWait(1900,300); + dds.playWait(1200,10); + dds.playWait(1900,300); + dds.playWait(1200,30); + for(int x = 0; x < 7; x++) { + if(bitRead(code,x)) { dds.playWait(1100,30); } else { dds.playWait(1300,30); } + } + if(parityCalc(code)) { dds.playWait(1300,30); } else { dds.playWait(1100,30); } + dds.playWait(1200,30); + dds.on(); + for(int y = 1; y < 256; y++){ + DON(); + dds.setPrecalcFrequency(freqTable[F_1200]); + // Subtract for the timer ticks + delayMicroseconds(3562); // sync pulse (4862 uS) + DOFF(); + DON(); + dds.setPrecalcFrequency(freqTable[F_1500]); + // Subtract for the timer ticks + delayMicroseconds(442); // sync porch (572 uS) + DOFF(); + scanning = true; + for(uint8_t c = 0; c<3; c++) { + scanning = true; + while(!registered); + registered = false; + loadScanline((++currentScanline) & 1, y); + while(!done); + dds.setPrecalcFrequency(freqTable[F_1500]); + done = false; + scanning = false; + DON(); + delayMicroseconds(442); // color separator pulse (572 uS) + DOFF(); + } + } + dds.off(); + delay(10000); + return; +} + +// The DDS is running faster than the pixel clock, so we +// only update the pixel frequency every few ticks. +ISR(ADC_vect) { + static uint8_t tcnt = 0; + static uint8_t shifts = 0; + static uint8_t shiftingLine = 0; + static uint8_t linePos = 0; + static uint16_t pixelBlock; + TIFR1 |= _BV(ICF1); + dds.clockTick(); + if(scanning) { + if(++tcnt == 8) { + tcnt = 0; + if(linePos == 21) { + done = true; + linePos = 0; + } + if(linePos == 0) { + shifts = 0; + shiftingLine = currentScanline&1; + registered = true; + } + if(shifts == 0) { + pixelBlock = scanline[shiftingLine][linePos++]; + } + + if(pixelBlock & 0x8000) { + dds.setPrecalcFrequency(freqTable[F_2400]); + } else { + dds.setPrecalcFrequency(freqTable[F_1500]); + } + if(++shifts == 16) { + shifts = 0; + } + pixelBlock <<= 1; + } + } +} + +// Image is 256 lines * 320 pixels per line, packed to 16 bits at a time +const uint16_t image[256*20] PROGMEM = { +0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // Line 1 +0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // Line 2 +0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // Line 3 +0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // Line 4 +0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // Line 5 +0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // Line 6 +0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // Line 7 +0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // Line 8 +0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // Line 9 +0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // Line 10 +0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // Line 11 +0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // Line 12 +0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // Line 13 +0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // Line 14 +0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // Line 15 +0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // Line 16 +0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // Line 17 +0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // Line 18 +0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // Line 19 +0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // Line 20 +0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // Line 21 +0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // Line 22 +0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // Line 23 +0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // Line 24 +0xFFFF, 0xE0FF, 0x07FF, 0xBFCF, 0xFFFF, 0xFFC1, 0xF3FE, 0x7FF9, 0xFF3F, 0xFC1F, 0xE0FF, 0xF7F9, 0xFFFF, 0xFFF8, 0x3E7F, 0xCFFF, 0x3FE7, 0xFFFF, 0xFFFF, 0xFFFF, // Line 25 +0xFFFF, 0x807C, 0x03FF, 0x3FCF, 0xFFFF, 0xFF80, 0x73FE, 0x7FF9, 0xFF3F, 0xF00F, 0x807F, 0xE7F9, 0xFFFF, 0xFFF0, 0x0E7F, 0xCFFF, 0x3FE7, 0xFFFF, 0xFFFF, 0xFFFF, // Line 26 +0xFFFF, 0x1F38, 0xF9FF, 0x3FCF, 0xFFFF, 0xFF1E, 0x33FF, 0xFFF9, 0xFF3F, 0xE3E7, 0x1F3F, 0xE7F9, 0xFFFF, 0xFFE3, 0xC67F, 0xFFFF, 0x3FE7, 0xFFFF, 0xFFFF, 0xFFFF, // Line 27 +0xFFFF, 0x3F19, 0xFCFF, 0x3FCF, 0xFFFF, 0xFF3F, 0x33FF, 0xFFF9, 0xFF3F, 0xE7E3, 0x3F9F, 0xE7F9, 0xFFFF, 0xFFE7, 0xE67F, 0xFFFF, 0x3FE7, 0xFFFF, 0xFFFF, 0xFFFF, // Line 28 +0xFFFE, 0x7FF3, 0xFCFF, 0x3FCF, 0x0723, 0x8F3F, 0xF21E, 0x7879, 0xE13F, 0xCFFE, 0x7F9F, 0xE7F9, 0xE0E4, 0x71E7, 0xFE43, 0xCF0F, 0x3C27, 0xFFFF, 0xFFFF, 0xFFFF, // Line 29 +0xFFFE, 0x7FF3, 0xFCFF, 0x3FCE, 0x0300, 0x071F, 0xF00E, 0x7019, 0xC03F, 0xCFFE, 0x7F9F, 0xE7F9, 0xC060, 0x00E3, 0xFE01, 0xCE03, 0x3807, 0xFFFF, 0xFFFF, 0xFFFF, // Line 30 +0xFFFE, 0x7FF3, 0xFE7F, 0x000C, 0xF31C, 0x7381, 0xF1E6, 0x6799, 0x9E3F, 0xCFFE, 0x7FCF, 0xE001, 0x9E63, 0x8E70, 0x3E3C, 0xCCF3, 0x33C7, 0xFFFF, 0xFFFF, 0xFFFF, // Line 31 +0xFFFE, 0x7FF3, 0xFC7F, 0x000F, 0x833C, 0xF3F0, 0x73E6, 0x67C9, 0x9F3F, 0xCFFE, 0x7F8F, 0xE001, 0xF067, 0x9E7E, 0x0E7C, 0xCCF9, 0x33E7, 0xFFFF, 0xFFFF, 0xFFFF, // Line 32 +0xFFFE, 0x7FF3, 0xFCFF, 0x3FCE, 0x033C, 0xF3FF, 0x33E6, 0x6009, 0x9F3F, 0xCFFE, 0x7F9F, 0xE7F9, 0xC067, 0x9E7F, 0xE67C, 0xCC01, 0x33E7, 0xFFFF, 0xFFFF, 0xFFFF, // Line 33 +0xFFFE, 0x7FB3, 0xFCFF, 0x3FCC, 0xF33C, 0xF3FF, 0x33E6, 0x67F9, 0x9F3F, 0xCFF6, 0x7F9F, 0xE7F9, 0x9E67, 0x9E7F, 0xE67C, 0xCCFF, 0x33E7, 0xFFFF, 0xFFFF, 0xFFFF, // Line 34 +0xFFFF, 0x3F19, 0xE0FF, 0x3FCC, 0xF33C, 0xF33F, 0x33E6, 0x67F9, 0x9F3F, 0xE7E3, 0x3C1F, 0xE7F9, 0x9E67, 0x9E67, 0xE67C, 0xCCFF, 0x33E7, 0xFFFF, 0xFFFF, 0xFFFF, // Line 35 +0xFFFF, 0x1F38, 0xF1FF, 0x3FCC, 0xE33C, 0xF31F, 0x33E6, 0x6789, 0x9E3F, 0xE3E7, 0x1E3F, 0xE7F9, 0x9C67, 0x9E63, 0xE67C, 0xCCF1, 0x33C7, 0xFFFF, 0xFFFF, 0xFFFF, // Line 36 +0xFFFF, 0x807C, 0x01FF, 0x3FCC, 0x033C, 0xF380, 0x73E6, 0x7019, 0xC03F, 0xF00F, 0x803F, 0xE7F9, 0x8067, 0x9E70, 0x0E7C, 0xCE03, 0x3807, 0xFFFF, 0xFFFF, 0xFFFF, // Line 37 +0xFFFF, 0xE0FF, 0x04FF, 0xBFCE, 0x1B3C, 0xF3C0, 0xF3E6, 0x7839, 0xE13F, 0xFC1F, 0xE09F, 0xF7F9, 0xC367, 0x9E78, 0x1E7C, 0xCF07, 0x3C27, 0xFFFF, 0xFFFF, 0xFFFF, // Line 38 +0xFFFF, 0xFFFF, 0xFEFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFDF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // Line 39 +0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // Line 40 +0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // Line 41 +0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // Line 42 +0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // Line 43 +0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // Line 44 +0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // Line 45 +0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // Line 46 +0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // Line 47 +0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // Line 48 +0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // Line 49 +0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // Line 50 +0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // Line 51 +0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // Line 52 +0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // Line 53 +0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // Line 54 +0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // Line 55 +0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x1FFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // Line 56 +0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFE0, 0x007F, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // Line 57 +0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFF80, 0x001F, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // Line 58 +0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFE00, 0x0007, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // Line 59 +0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFC3, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFC00, 0x0003, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // Line 60 +0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xF800, 0x3FFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xF800, 0x0001, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // Line 61 +0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xC000, 0x0FFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xF800, 0x0000, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // Line 62 +0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x8000, 0x03FF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xF000, 0x0000, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // Line 63 +0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x0000, 0x01FF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xE000, 0x0000, 0x7FFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // Line 64 +0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFE, 0x0000, 0x00FF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xE000, 0x0000, 0x7FFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // Line 65 +0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFC, 0x0000, 0x007F, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xC000, 0x0000, 0x7FFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // Line 66 +0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFC, 0x0000, 0x003F, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xC000, 0x0000, 0x7FFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // Line 67 +0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFF8, 0x0000, 0x001F, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xC000, 0x0000, 0x7FFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // Line 68 +0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFF8, 0x0000, 0x000F, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xC000, 0x0000, 0x7FFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // Line 69 +0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFF8, 0x0000, 0x000F, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x8000, 0x0000, 0x7FFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // Line 70 +0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFF0, 0x0000, 0x000F, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x8000, 0x0000, 0x7FFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // Line 71 +0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFF0, 0x0000, 0x0007, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x8000, 0x0000, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // Line 72 +0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFE0, 0x0000, 0x0007, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x8000, 0x0000, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // Line 73 +0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFE0, 0x0000, 0x0007, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x8000, 0x0001, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // Line 74 +0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFE0, 0x0000, 0x0007, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x8000, 0x0001, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // Line 75 +0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFE0, 0x0000, 0x0007, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x8000, 0x0003, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // Line 76 +0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFE0, 0x0000, 0x0007, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x8000, 0x0003, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // Line 77 +0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFE0, 0x0000, 0x0007, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x8000, 0x0007, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // Line 78 +0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFE0, 0x0000, 0x0007, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xC000, 0x000F, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // Line 79 +0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFE0, 0x0000, 0x000F, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xC000, 0x001F, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // Line 80 +0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFE0, 0x0000, 0x000F, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xE000, 0x007F, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // Line 81 +0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFF0, 0x0000, 0x001F, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xF000, 0x01FF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // Line 82 +0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFF0, 0x0000, 0x001F, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFC00, 0x07FF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // Line 83 +0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFF8, 0x0000, 0x003F, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // Line 84 +0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFC, 0x0000, 0x007F, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // Line 85 +0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFE, 0x0000, 0x01FF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // Line 86 +0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x8000, 0x07FF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // Line 87 +0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xF000, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // Line 88 +0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // Line 89 +0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // Line 90 +0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // Line 91 +0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // Line 92 +0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // Line 93 +0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // Line 94 +0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // Line 95 +0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // Line 96 +0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // Line 97 +0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // Line 98 +0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // Line 99 +0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // Line 100 +0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // Line 101 +0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // Line 102 +0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // Line 103 +0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // Line 104 +0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // Line 105 +0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // Line 106 +0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // Line 107 +0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // Line 108 +0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // Line 109 +0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x87FF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // Line 110 +0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x03FF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // Line 111 +0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFE, 0x01FF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // Line 112 +0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFE, 0x01FF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // Line 113 +0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFE, 0x01FF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // Line 114 +0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFE, 0x01FF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // Line 115 +0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x03FF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // Line 116 +0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x87FF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // Line 117 +0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // Line 118 +0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // Line 119 +0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // Line 120 +0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // Line 121 +0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // Line 122 +0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // Line 123 +0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // Line 124 +0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // Line 125 +0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // Line 126 +0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // Line 127 +0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // Line 128 +0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // Line 129 +0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xF9FF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // Line 130 +0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xE07F, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // Line 131 +0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xC03F, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // Line 132 +0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x801F, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // Line 133 +0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x000F, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // Line 134 +0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x000F, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // Line 135 +0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x000F, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // Line 136 +0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x000F, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // Line 137 +0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x0007, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // Line 138 +0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x0007, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFF83, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // Line 139 +0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x0007, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFE01, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // Line 140 +0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x0007, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFC00, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // Line 141 +0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x0003, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xF800, 0x7FFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // Line 142 +0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x0001, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xF000, 0x7FFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // Line 143 +0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x0001, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xE000, 0x7FFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // Line 144 +0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x8000, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xE000, 0x7FFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // Line 145 +0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x8000, 0x7FFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xC000, 0x7FFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // Line 146 +0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xC000, 0x3FFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x8000, 0x7FFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // Line 147 +0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xC000, 0x1FFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x8000, 0x7FFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // Line 148 +0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xE000, 0x07FF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x0000, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // Line 149 +0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xF000, 0x03FF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFE, 0x0001, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // Line 150 +0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xF000, 0x00FF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFC, 0x0001, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // Line 151 +0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xF800, 0x003F, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFF0, 0x0003, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // Line 152 +0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFC00, 0x001F, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFE0, 0x0007, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // Line 153 +0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFE00, 0x000F, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFC0, 0x0007, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // Line 154 +0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFF00, 0x0003, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFF00, 0x000F, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // Line 155 +0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFF80, 0x0000, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFE00, 0x001F, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // Line 156 +0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFC0, 0x0000, 0x3FFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFC00, 0x003F, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // Line 157 +0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFE0, 0x0000, 0x0FFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xF800, 0x003F, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // Line 158 +0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFF0, 0x0000, 0x03FF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xF000, 0x007F, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // Line 159 +0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFC, 0x0000, 0x00FF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xE000, 0x00FF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // Line 160 +0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFE, 0x0000, 0x003F, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xC000, 0x03FF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // Line 161 +0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x8000, 0x000F, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x0000, 0x07FF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // Line 162 +0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xE000, 0x0003, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFC, 0x0000, 0x1FFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // Line 163 +0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xF000, 0x0000, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFF0, 0x0000, 0x3FFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // Line 164 +0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFC00, 0x0000, 0x3FFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFC0, 0x0000, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // Line 165 +0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFE00, 0x0000, 0x0FFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFF00, 0x0001, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // Line 166 +0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFF80, 0x0000, 0x03FF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFC00, 0x0003, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // Line 167 +0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFE0, 0x0000, 0x00FF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xE000, 0x0007, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // Line 168 +0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFF8, 0x0000, 0x003F, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFE, 0x0000, 0x000F, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // Line 169 +0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFE, 0x0000, 0x000F, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFE0, 0x0000, 0x001F, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // Line 170 +0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x8000, 0x0003, 0xFFFF, 0xFFFF, 0xFFFF, 0xFF80, 0x0000, 0x003F, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // Line 171 +0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xE000, 0x0000, 0xFFFF, 0xFFFF, 0xFFFF, 0xFC00, 0x0000, 0x007F, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // Line 172 +0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFC00, 0x0000, 0x3FFF, 0xFFFF, 0xFFFF, 0xF000, 0x0000, 0x01FF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // Line 173 +0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFF00, 0x0000, 0x07FF, 0xFFFF, 0xFFFF, 0xC000, 0x0000, 0x03FF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // Line 174 +0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFF80, 0x0000, 0x001F, 0xFFFF, 0xFFF8, 0x0000, 0x0000, 0x0FFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // Line 175 +0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFE0, 0x0000, 0x0000, 0x1FFF, 0xA000, 0x0000, 0x0000, 0x7FFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // Line 176 +0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFF8, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0001, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // Line 177 +0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFE, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x000F, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // Line 178 +0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x8000, 0x0000, 0x0000, 0x0000, 0x0000, 0x003F, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // Line 179 +0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xE000, 0x0000, 0x0000, 0x0000, 0x0000, 0x00FF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // Line 180 +0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xF800, 0x0000, 0x0000, 0x0000, 0x0000, 0x0FFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // Line 181 +0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFE00, 0x0000, 0x0000, 0x0000, 0x0000, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // Line 182 +0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFF80, 0x0000, 0x0000, 0x0000, 0x0007, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // Line 183 +0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFE0, 0x0000, 0x0000, 0x0000, 0x001F, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // Line 184 +0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFC, 0x0000, 0x0000, 0x0000, 0x007F, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // Line 185 +0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x0000, 0x0000, 0x0000, 0x03FF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // Line 186 +0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xF000, 0x0000, 0x0000, 0x1FFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // Line 187 +0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFC0, 0x0000, 0x0007, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // Line 188 +0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // Line 189 +0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // Line 190 +0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // Line 191 +0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // Line 192 +0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // Line 193 +0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // Line 194 +0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // Line 195 +0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // Line 196 +0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // Line 197 +0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // Line 198 +0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // Line 199 +0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // Line 200 +0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // Line 201 +0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // Line 202 +0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // Line 203 +0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // Line 204 +0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // Line 205 +0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // Line 206 +0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // Line 207 +0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // Line 208 +0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // Line 209 +0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // Line 210 +0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // Line 211 +0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // Line 212 +0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // Line 213 +0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // Line 214 +0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // Line 215 +0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // Line 216 +0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // Line 217 +0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // Line 218 +0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // Line 219 +0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // Line 220 +0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // Line 221 +0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // Line 222 +0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // Line 223 +0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // Line 224 +0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // Line 225 +0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // Line 226 +0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // Line 227 +0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // Line 228 +0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // Line 229 +0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xF9FF, 0xFFBF, 0xEE00, 0xF87F, 0x07EF, 0xFC03, 0xFFFF, 0xFFFF, // Line 230 +0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xF9FF, 0xFF9F, 0xCC00, 0xF01E, 0x01CF, 0xF801, 0xFFFF, 0xFFFF, // Line 231 +0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xF9FF, 0xFF9F, 0xCCFF, 0xE79C, 0x78CF, 0xF9F8, 0xFFFF, 0xFFFF, // Line 232 +0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xF9FF, 0xFF8F, 0x8CFF, 0xEFDC, 0xFCCF, 0xF9FC, 0xFFFF, 0xFFFF, // Line 233 +0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x09E1, 0xFFCF, 0x9CFF, 0xCFFC, 0xFFCF, 0xF9FC, 0xFFFF, 0xFFFF, // Line 234 +0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFE, 0x01C0, 0x7FCF, 0x9CFF, 0xC83C, 0x7FCF, 0xF9F8, 0xFFFF, 0xFFFF, // Line 235 +0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFC, 0xF19E, 0x7FE7, 0x3C01, 0xC01E, 0x07CF, 0xF801, 0xFFFF, 0xFFFF, // Line 236 +0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFC, 0xF99F, 0x3FE7, 0x3C01, 0xC78F, 0xC1CF, 0xF803, 0xFFFF, 0xFFFF, // Line 237 +0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFC, 0xF980, 0x3FE7, 0x3CFF, 0xCFCF, 0xFCCF, 0xF9FF, 0xFFFF, 0xFFFF, // Line 238 +0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFC, 0xF99F, 0xFFF2, 0x7CFF, 0xCFCF, 0xFCCF, 0xF9FF, 0xFFFF, 0xFFFF, // Line 239 +0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFC, 0xF99F, 0xFFF2, 0x7CFF, 0xCFCC, 0xFCCF, 0xF9FF, 0xFFFF, 0xFFFF, // Line 240 +0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFC, 0xF19E, 0x3FF8, 0xFCFF, 0xE79C, 0x7CCF, 0xF9FF, 0xFFFF, 0xFFFF, // Line 241 +0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFE, 0x01C0, 0x7FF8, 0xFC00, 0xF01E, 0x01E0, 0x09FF, 0xFFFF, 0xFFFF, // Line 242 +0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x09E0, 0xFFF8, 0xFE00, 0xF87F, 0x03E0, 0x0DFF, 0xFFFF, 0xFFFF, // Line 243 +0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // Line 244 +0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // Line 245 +0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // Line 246 +0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // Line 247 +0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // Line 248 +0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // Line 249 +0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // Line 250 +0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // Line 251 +0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // Line 252 +0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // Line 253 +0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // Line 254 +0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // Line 255 +0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // Line 256 +}; From 8ebeabe496d067ac9e70c5e799f6c85cdc34b550 Mon Sep 17 00:00:00 2001 From: Stephen Olesen Date: Fri, 3 Jul 2015 21:21:51 -0600 Subject: [PATCH 27/44] Now using actual radio hardware in AFSK and SSTV. Tuned Tx settling timers. --- AFSK.cpp | 74 ++++++++++++++-------- examples/AFSK-Send/AFSK-Send.ino | 50 ++++++++++----- examples/SSTV-M1-Static/SSTV-M1-Static.ino | 20 +++++- 3 files changed, 101 insertions(+), 43 deletions(-) diff --git a/AFSK.cpp b/AFSK.cpp index b56363e..49ed68c 100644 --- a/AFSK.cpp +++ b/AFSK.cpp @@ -50,29 +50,29 @@ void AFSK::Encoder::process() { lastTxEnd = millis(); return; } - lastTx = millis(); - currentBytePos = 0; - } + lastTx = millis(); + currentBytePos = 0; + } - // We ran out of actual data, provide an HDLC frame (idle) - if(currentBytePos++ == packet->len) { - pBuf.freePacket(packet); - packet = pBuf.getPacket(); // Get the next, if any - currentBytePos = 0; - currentByte = HDLC_FRAME; - hdlc = true; - } else { - // Grab the next byte - currentByte = packet->getByte(); //[currentBytePos++]; - if(currentByte == HDLC_ESCAPE) { - currentByte = packet->getByte(); //[currentBytePos++]; + // We ran out of actual data, provide an HDLC frame (idle) + if(currentBytePos++ == packet->len) { + pBuf.freePacket(packet); + packet = pBuf.getPacket(); // Get the next, if any + currentBytePos = 0; + currentByte = HDLC_FRAME; hdlc = true; } else { - hdlc = false; + // Grab the next byte + currentByte = packet->getByte(); //[currentBytePos++]; + if(currentByte == HDLC_ESCAPE) { + currentByte = packet->getByte(); //[currentBytePos++]; + hdlc = true; + } else { + hdlc = false; + } } } } - } // Pickup the last bit currentBit = currentByte & 0x1; @@ -121,7 +121,7 @@ bool AFSK::Encoder::start() { lastZero = 0; bitPosition = 0; bitClock = 0; - preamble = 23; // 6.7ms each, 23 = 153ms + preamble = 0b11000; // 6.7ms each, 23 = 153ms done = false; hdlc = true; packet = 0x0; // No initial packet, find in the ISR @@ -143,7 +143,7 @@ void AFSK::Encoder::stop() { AFSK::Decoder::Decoder() { // Initialize the sampler delay line (phase shift) for(unsigned char i = 0; i < SAMPLEPERBIT/2; i++) - delay_fifo.enqueue(0); + delay_fifo.enqueue(0); } bool AFSK::HDLCDecode::hdlcParse(bool bit, SimpleFIFO *fifo) { @@ -193,21 +193,42 @@ bool AFSK::HDLCDecode::hdlcParse(bool bit, SimpleFIFO +class FastRing { +private: + T ring[FASTRING_SIZE]; + uint8_t position; +public: + FastRing(): position(0) {} + inline void write(T value) { + ring[(position++) & FASTRING_MASK] = value; + } + inline T read() const { + return ring[position & FASTRING_MASK]; + } + inline T readn(uint8_t n) const { + return ring[(position + (~n+1)) & FASTRING_MASK]; + } +}; +FastRing delayLine; + // Handle the A/D converter interrupt (hopefully quickly :) void AFSK::Decoder::process(int8_t curr_sample) { // Run the same through the phase multiplier and butterworth filter iir_x[0] = iir_x[1]; - iir_x[1] = ((int8_t)delay_fifo.dequeue() * curr_sample) >> 2; + iir_x[1] = ((int8_t)delayLine.read() * curr_sample) >> 2; iir_y[0] = iir_y[1]; iir_y[1] = iir_x[0] + iir_x[1] + (iir_y[0] >> 1) + (iir_y[0]>>3) + (iir_y[0]>>5); + // Place this ADC sample into the delay line + delayLine.write(curr_sample); + // Shift the bit into place based on the output of the discriminator sampled_bits <<= 1; - sampled_bits |= (iir_y[1] > 0) ? 1 : 0; - - // Place this ADC sample into the delay line - delay_fifo.enqueue(curr_sample); + sampled_bits |= (iir_y[1] > 0) ? 1 : 0; // If we found a 0/1 transition, adjust phases to track if(EDGE_FOUND(sampled_bits)) { @@ -503,7 +524,8 @@ size_t AFSK::Packet::appendCallsign(const char *callsign, uint8_t ssid, bool fin void AFSK::timer() { if(encoder.isSending()) encoder.process(); - decoder.process(ADCH - 128); + else + decoder.process(ADCH - 128); } void AFSK::start(DDS *dds) { diff --git a/examples/AFSK-Send/AFSK-Send.ino b/examples/AFSK-Send/AFSK-Send.ino index 104d39c..063c0cb 100644 --- a/examples/AFSK-Send/AFSK-Send.ino +++ b/examples/AFSK-Send/AFSK-Send.ino @@ -1,3 +1,5 @@ +#define DDS_REFCLK_DEFAULT 9600 + #include #include @@ -6,25 +8,29 @@ DDS dds; void setup() { Serial.begin(9600); - // put your setup code here, to run once: + Wire.begin(); pinMode(2, OUTPUT); - pinMode(10, OUTPUT); pinMode(3, OUTPUT); - pinMode(11, OUTPUT); - digitalWrite(2, LOW); - digitalWrite(10, LOW); Serial.println(F("Radio test connection")); - radio.testConnection(); + Serial.println(radio.testConnection(), DEC); Serial.println(F("Initialize")); + delay(100); radio.initialize(); Serial.println(F("Frequency")); - radio.frequency(446000); + delay(100); + radio.setVHF(); + radio.frequency(145050); + radio.setRfPower(0); Serial.println(F("DDS Start")); + delay(100); dds.start(); Serial.println(F("AFSK start")); + delay(100); radio.afsk.start(&dds); Serial.println(F("Starting...")); + pinMode(11, INPUT); // Bodge for now, as pin 3 is hotwired to pin 11 + delay(100); } void loop() { @@ -35,26 +41,34 @@ void loop() { packet->appendCallsign("VA6GA",15,true); packet->appendFCS(0x03); packet->appendFCS(0xf0); - packet->print("Hello "); + packet->print(F("Hello ")); packet->println(millis()); packet->finish(); bool ret = radio.afsk.putTXPacket(packet); - if(radio.afsk.txReady()) + + if(radio.afsk.txReady()) { + Serial.println(F("txReady")); + radio.setModeTransmit(); + //delay(100); if(radio.afsk.txStart()) { - radio.setModeTransmit(); + Serial.println(F("txStart")); + } else { + radio.setModeReceive(); } + } // Wait 2 seconds before we send our beacon again. Serial.println("tick"); // Wait up to 2.5 seconds to finish sending, and stop transmitter. // TODO: This is hackery. for(int i = 0; i < 500; i++) { - if(!radio.afsk.encoder.isSending()) + if(radio.afsk.encoder.isDone()) break; delay(50); } + Serial.println("Done sending"); radio.setModeReceive(); - delay(2000); + delay(30000); } /*ISR(TIMER2_OVF_vect) { @@ -68,10 +82,16 @@ void loop() { } }*/ ISR(ADC_vect) { + static uint8_t tcnt = 0; TIFR1 = _BV(ICF1); // Clear the timer flag - digitalWrite(2, HIGH); + PORTD |= _BV(2); // Diagnostic pin (D2) dds.clockTick(); - radio.afsk.timer(); - digitalWrite(2, LOW); + if(++tcnt == 1) { + if(radio.afsk.encoder.isSending()) { + radio.afsk.timer(); + } + tcnt = 0; + } + PORTD &= ~(_BV(2)); // Pin D2 off again } diff --git a/examples/SSTV-M1-Static/SSTV-M1-Static.ino b/examples/SSTV-M1-Static/SSTV-M1-Static.ino index 4a7e9e6..a86b757 100644 --- a/examples/SSTV-M1-Static/SSTV-M1-Static.ino +++ b/examples/SSTV-M1-Static/SSTV-M1-Static.ino @@ -2,8 +2,9 @@ #define DDS_REFCLK_DEFAULT (34965/2) #include +#include -//HamShield radio; +HamShield radio; DDS dds; // Defined at the end of the sketch extern const uint16_t image[256*20] PROGMEM; @@ -15,8 +16,21 @@ ddsAccumulator_t freqTable[3]; void setup() { Serial.begin(9600); + Wire.begin(); + // Query the HamShield for status information + Serial.print("Radio status: "); + int result = 0; + result = radio.testConnection(); + Serial.println(result,DEC); + + // Tell the HamShield to start up + radio.initialize(); + radio.setRfPower(0); + radio.setVHF(); + radio.setFrequency(145500); pinMode(2, OUTPUT); pinMode(3, OUTPUT); + pinMode(11, INPUT); // HiZ // put your setup code here, to run once: //dds.setReferenceClock(34965/4); dds.start(); @@ -71,7 +85,8 @@ void loadScanline(uint8_t s, int y) { void loop() { // Load the first scanline loadScanline(0, 0); - + radio.setModeTransmit(); + delay(500); // VIS dds.playWait(1900,300); dds.playWait(1200,10); @@ -110,6 +125,7 @@ void loop() { } } dds.off(); + radio.setModeReceive(); delay(10000); return; } From ed594d31288008603c46f653c44864c464177706 Mon Sep 17 00:00:00 2001 From: Stephen Olesen Date: Sat, 4 Jul 2015 00:03:12 -0600 Subject: [PATCH 28/44] Back to Wire library usage, mistaken commit on changing it. --- I2Cdev_rda.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/I2Cdev_rda.h b/I2Cdev_rda.h index 3c8f81d..56850f2 100644 --- a/I2Cdev_rda.h +++ b/I2Cdev_rda.h @@ -49,7 +49,7 @@ THE SOFTWARE. // ----------------------------------------------------------------------------- // I2C interface implementation setting // ----------------------------------------------------------------------------- -#define I2CDEV_IMPLEMENTATION I2CDEV_ARDUINO_FASTWIRE +#define I2CDEV_IMPLEMENTATION I2CDEV_ARDUINO_WIRE //#define I2CDEV_IMPLEMENTATION I2CDEV_BUILTIN_FASTWIRE // comment this out if you are using a non-optimal IDE/implementation setting From 7c67a704c3efa872bb99a0566a601b0bb283731e Mon Sep 17 00:00:00 2001 From: Stephen Olesen Date: Sat, 4 Jul 2015 02:17:03 -0600 Subject: [PATCH 29/44] Use HamShield now, fix refclk rate, increase toggle speed. --- examples/DDS/DDS.ino | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/examples/DDS/DDS.ino b/examples/DDS/DDS.ino index 394ad18..991ea7f 100644 --- a/examples/DDS/DDS.ino +++ b/examples/DDS/DDS.ino @@ -1,22 +1,31 @@ +#define DDS_REFCLK_DEFAULT 9600 #include #include +HamShield radio; DDS dds; void setup() { + Wire.begin(); + radio.initialize(); + radio.setRfPower(0); + radio.setVHF(); + radio.setFrequency(145060); pinMode(2, OUTPUT); pinMode(3, OUTPUT); - pinMode(11, OUTPUT); + pinMode(11, INPUT); + radio.setModeTransmit(); dds.start(); dds.playWait(600, 3000); dds.on(); + //dds.setAmplitude(31); } void loop() { dds.setFrequency(2200); - delay(5000); + delay(1000); dds.setFrequency(1200); - delay(5000); + delay(1000); } #ifdef DDS_USE_ONLY_TIMER2 From 4af33167a0179163c871f3df4a2b384b7b140903 Mon Sep 17 00:00:00 2001 From: Stephen Olesen Date: Sat, 4 Jul 2015 14:13:45 -0600 Subject: [PATCH 30/44] Added Crystal Calibration sketch with simple Serial UI. Fixed ddsAccumulator_t type. --- AFSK.cpp | 2 +- DDS.cpp | 18 ++- DDS.h | 7 +- .../Crystal-Calibration.ino | 152 ++++++++++++++++++ 4 files changed, 173 insertions(+), 6 deletions(-) create mode 100644 examples/Crystal-Calibration/Crystal-Calibration.ino diff --git a/AFSK.cpp b/AFSK.cpp index 49ed68c..4bf18b1 100644 --- a/AFSK.cpp +++ b/AFSK.cpp @@ -121,7 +121,7 @@ bool AFSK::Encoder::start() { lastZero = 0; bitPosition = 0; bitClock = 0; - preamble = 0b11000; // 6.7ms each, 23 = 153ms + preamble = 0b110000; // 6.7ms each, 23 = 153ms done = false; hdlc = true; packet = 0x0; // No initial packet, find in the ISR diff --git a/DDS.cpp b/DDS.cpp index 86dd8ac..e4141bd 100644 --- a/DDS.cpp +++ b/DDS.cpp @@ -40,10 +40,18 @@ void DDS::start() { // Second, setup Timer1 to trigger the ADC interrupt // This lets us use decoding functions that run at the same reference // clock as the DDS. - TCCR1B = _BV(CS11) | _BV(WGM13) | _BV(WGM12); + // We use ICR1 as TOP and prescale by 8 + TCCR1B = _BV(CS10) | _BV(WGM13) | _BV(WGM12); TCCR1A = 0; - ICR1 = ((F_CPU / 8) / refclk) - 1; - //ICR1 = ((F_CPU / 8) / 9600) - 1; + ICR1 = ((F_CPU / 1) / refclk) - 1; +#ifdef DDS_DEBUG_SERIAL + Serial.print(F("DDS SysClk: ")); + Serial.println(F_CPU/8); + Serial.print(F("DDS RefClk: ")); + Serial.println(refclk, DEC); + Serial.print(F("DDS ICR1: ")); + Serial.println(ICR1, DEC); +#endif // Configure the ADC here to automatically run and be triggered off Timer1 ADMUX = _BV(REFS0) | _BV(ADLAR) | 0; // Channel 0, shift result left (ADCH used) @@ -56,6 +64,10 @@ void DDS::start() { void DDS::stop() { // TODO: Stop the timers. +#ifndef DDS_USE_ONLY_TIMER2 + TCCR1B = 0; +#endif + TCCR2B = 0; } // Set our current sine wave frequency in Hz diff --git a/DDS.h b/DDS.h index 6ce0b3b..79b23ef 100644 --- a/DDS.h +++ b/DDS.h @@ -18,10 +18,10 @@ #ifdef SHORT_ACCUMULATOR #define ACCUMULATOR_BITS 16 -typedef int16_t ddsAccumulator_t; +typedef uint16_t ddsAccumulator_t; #else #define ACCUMULATOR_BITS 32 -typedef int32_t ddsAccumulator_t; +typedef uint32_t ddsAccumulator_t; #endif // If defined, the timer will idle at 50% duty cycle @@ -60,6 +60,9 @@ typedef int32_t ddsAccumulator_t; #define DDS_REFCLK_DEFAULT (62500/4) #endif +// Output some of the calculations and information about the DDS over serial +#define DDS_DEBUG_SERIAL + // When defined, use the 1024 element sine lookup table. This improves phase // accuracy, at the cost of more flash and CPU requirements. // #define DDS_TABLE_LARGE diff --git a/examples/Crystal-Calibration/Crystal-Calibration.ino b/examples/Crystal-Calibration/Crystal-Calibration.ino new file mode 100644 index 0000000..fcebe22 --- /dev/null +++ b/examples/Crystal-Calibration/Crystal-Calibration.ino @@ -0,0 +1,152 @@ +#define DDS_REFCLK_DEFAULT 19200 +#define DDS_REFCLK_OFFSET 0 +#define DDS_DEBUG_SERIAL + +#include +#include + +HamShield radio; +DDS dds; + +void setup() { + Serial.begin(9600); + Wire.begin(); + radio.initialize(); + radio.setVHF(); + radio.setRfPower(0); + radio.setFrequency(145050); + + pinMode(2, OUTPUT); + pinMode(3, OUTPUT); + pinMode(11, INPUT); + + dds.start(); + dds.setFrequency(1200); + dds.on(); + + radio.bypassPreDeEmph(); +} + +enum Sets { + SET_REF, + SET_TONE, + SET_AMPLITUDE +} setting = SET_TONE; + +char freqBuffer[8]; +char *freqBufferPtr = freqBuffer; +uint16_t lastFreq = 1200; + +void loop() { + while(Serial.available()) { + char c = Serial.read(); + Serial.print(c); + switch(c) { + case 'h': + Serial.println(F("Commands:")); + Serial.println(F("RefClk: u = +10, U = +100, r XXXX = XXXX")); + Serial.println(F(" d = -10, D = -100")); + Serial.println(F("Radio: T = transmit, R = receive")); + Serial.println(F("Tone: t XXXX = XXXX Hz")); + Serial.println(F("Amp.: a XXX = XXX out of 255")); + Serial.println(F("DDS: o = On, O = Off")); + Serial.println(F("ie. a 31 = 32/255 amplitude, r38400 sets 38400Hz refclk")); + Serial.println("> "); + break; + case 'u': + dds.setReferenceClock(dds.getReferenceClock()+10); + dds.setFrequency(lastFreq); + dds.start(); + Serial.println(F("RefClk + 10 = ")); + Serial.println(dds.getReferenceClock()); + Serial.println("> "); + break; + case 'U': + dds.setReferenceClock(dds.getReferenceClock()+100); + dds.setFrequency(lastFreq); + dds.start(); + Serial.println(F("RefClk + 100 = ")); + Serial.println(dds.getReferenceClock()); + Serial.println("> "); + break; + case 'd': + dds.setReferenceClock(dds.getReferenceClock()-10); + dds.setFrequency(lastFreq); + dds.start(); + Serial.println(F("RefClk - 10 = ")); + Serial.println(dds.getReferenceClock()); + Serial.println("> "); + break; + case 'D': + dds.setReferenceClock(dds.getReferenceClock()-100); + dds.setFrequency(lastFreq); + dds.start(); + Serial.println(F("RefClk - 100 = ")); + Serial.println(dds.getReferenceClock()); + Serial.println("> "); + break; + case 'T': + Serial.println(F("Radio transmit")); + radio.setModeTransmit(); + Serial.println("> "); + break; + case 'R': + Serial.println(F("Radio receive")); + radio.setModeReceive(); + Serial.println("> "); + break; + case 'r': + setting = SET_REF; + break; + case 't': + setting = SET_TONE; + break; + case 'a': + setting = SET_AMPLITUDE; + break; + case 'o': + dds.on(); + Serial.println("> "); + break; + case 'O': + dds.off(); + Serial.println("> "); + break; + default: + if(c >= '0' && c <= '9') { + *freqBufferPtr = c; + freqBufferPtr++; + } + if(c == '\n' && freqBufferPtr != freqBuffer) { + *freqBufferPtr = '\0'; + freqBufferPtr = freqBuffer; + uint16_t freq = atoi(freqBuffer); + if(setting == SET_REF) { + dds.setReferenceClock(freq); + dds.setFrequency(lastFreq); + dds.start(); + Serial.print(F("New Reference Clock: ")); + Serial.println(dds.getReferenceClock()); + } else if(setting == SET_TONE) { + dds.setFrequency(freq); + lastFreq = freq; + Serial.print(F("New Tone: ")); + Serial.println(freq); + } else if(setting == SET_AMPLITUDE) { + dds.setAmplitude((uint8_t)(freq&0xFF)); + Serial.print(F("New Amplitude: ")); + Serial.println((uint8_t)(freq&0xFF)); + } + Serial.println("> "); + } + break; + } + } +} + +ISR(ADC_vect) { + TIFR1 = _BV(ICF1); + PORTD |= _BV(2); + dds.clockTick(); + PORTD &= ~_BV(2); +} From 1c29498e447a9ff0723e9ad303be096746180d2d Mon Sep 17 00:00:00 2001 From: Stephen Olesen Date: Sat, 4 Jul 2015 16:39:17 -0600 Subject: [PATCH 31/44] Received frequency calculation added. --- .../Crystal-Calibration.ino | 88 ++++++++++++++++++- 1 file changed, 85 insertions(+), 3 deletions(-) diff --git a/examples/Crystal-Calibration/Crystal-Calibration.ino b/examples/Crystal-Calibration/Crystal-Calibration.ino index fcebe22..f59b256 100644 --- a/examples/Crystal-Calibration/Crystal-Calibration.ino +++ b/examples/Crystal-Calibration/Crystal-Calibration.ino @@ -1,4 +1,4 @@ -#define DDS_REFCLK_DEFAULT 19200 +#define DDS_REFCLK_DEFAULT 38400 #define DDS_REFCLK_OFFSET 0 #define DDS_DEBUG_SERIAL @@ -30,14 +30,55 @@ void setup() { enum Sets { SET_REF, SET_TONE, - SET_AMPLITUDE + SET_AMPLITUDE, + SET_ADC_HALF } setting = SET_TONE; char freqBuffer[8]; char *freqBufferPtr = freqBuffer; uint16_t lastFreq = 1200; +volatile uint16_t recordedPulseLength; +volatile bool recordedPulse = false; +volatile bool listening = false; +volatile uint8_t maxADC = 0, minADC = 255, adcHalf = 40; + void loop() { + static uint16_t samples = 0; + static uint16_t pulse; + static uint32_t lastOutput = 0; + if(recordedPulse) { + uint32_t pulseAveraging; + uint16_t tmpPulse; + cli(); + recordedPulse = false; + tmpPulse = recordedPulseLength; + sei(); + if(samples++ == 0) { + pulse = tmpPulse; + } else { + pulseAveraging = (pulse + tmpPulse) >> 1; + pulse = pulseAveraging; + } + if((lastOutput + 1000) < millis()) { + Serial.print(F("Pulse: ")); + Serial.println(pulse); + Serial.print(F("Last: ")); + Serial.println(tmpPulse); + Serial.print(F("Samples: ")); + Serial.println(samples); + Serial.print(F("ADC M/M: ")); + Serial.print(minADC); minADC = 255; + Serial.print(F(" / ")); + Serial.println(maxADC); maxADC = 0; + Serial.print(F("Freq: ")); + // F = 1/(pulse*(1/ref)) + // F = ref/pulse + Serial.println((float)(dds.getReferenceClock()+(float)DDS_REFCLK_OFFSET)/(float)pulse); + samples = 0; + lastOutput = millis(); + } + } while(Serial.available()) { char c = Serial.read(); Serial.print(c); @@ -50,6 +91,8 @@ void loop() { Serial.println(F("Tone: t XXXX = XXXX Hz")); Serial.println(F("Amp.: a XXX = XXX out of 255")); Serial.println(F("DDS: o = On, O = Off")); + Serial.println(F("Input: l = Determine received frequency, L = stop")); + Serial.println(F("ADC: m XXX = Set ADC midpoint (zero crossing level)")); Serial.println(F("ie. a 31 = 32/255 amplitude, r38400 sets 38400Hz refclk")); Serial.println("> "); break; @@ -85,6 +128,20 @@ void loop() { Serial.println(dds.getReferenceClock()); Serial.println("> "); break; + case 'l': + Serial.println(F("Start frequency listening, DDS off")); + dds.off(); + listening = true; + lastOutput = millis(); + Serial.println("> "); + break; + case 'L': + Serial.println(F("Stop frequency listening, DDS on")); + listening = false; + samples = 0; + dds.on(); + Serial.println("> "); + break; case 'T': Serial.println(F("Radio transmit")); radio.setModeTransmit(); @@ -104,6 +161,9 @@ void loop() { case 'a': setting = SET_AMPLITUDE; break; + case 'm': + setting = SET_ADC_HALF; + break; case 'o': dds.on(); Serial.println("> "); @@ -117,7 +177,7 @@ void loop() { *freqBufferPtr = c; freqBufferPtr++; } - if(c == '\n' && freqBufferPtr != freqBuffer) { + if((c == '\n' || c == '\r') && freqBufferPtr != freqBuffer) { *freqBufferPtr = '\0'; freqBufferPtr = freqBuffer; uint16_t freq = atoi(freqBuffer); @@ -136,6 +196,10 @@ void loop() { dds.setAmplitude((uint8_t)(freq&0xFF)); Serial.print(F("New Amplitude: ")); Serial.println((uint8_t)(freq&0xFF)); + } else if(setting == SET_ADC_HALF) { + adcHalf = freq&0xFF; + Serial.print(F("ADC midpoint set to ")); + Serial.println((uint8_t)(freq&0xFF)); } Serial.println("> "); } @@ -145,8 +209,26 @@ void loop() { } ISR(ADC_vect) { + static uint16_t pulseLength = 0; + static uint8_t lastADC = 127; TIFR1 = _BV(ICF1); PORTD |= _BV(2); dds.clockTick(); + if(listening) { + pulseLength++; + if(ADCH >= adcHalf && lastADC < adcHalf) { + // Zero crossing, upward + recordedPulseLength = pulseLength; + recordedPulse = true; + pulseLength = 0; + } + if(minADC > ADCH) { + minADC = ADCH; + } + if(maxADC < ADCH) { + maxADC = ADCH; + } + lastADC = ADCH; + } PORTD &= ~_BV(2); } From 7131e46ff06d3f2a11b39fb92af7da17551423b1 Mon Sep 17 00:00:00 2001 From: Stephen Olesen Date: Sat, 4 Jul 2015 18:31:18 -0600 Subject: [PATCH 32/44] Allow refclkOffset to be set on the fly in DDS and the calibration tool. --- DDS.cpp | 2 +- DDS.h | 11 ++++++- .../Crystal-Calibration.ino | 31 +++++++++++++++++-- 3 files changed, 39 insertions(+), 5 deletions(-) diff --git a/DDS.cpp b/DDS.cpp index e4141bd..8772fc7 100644 --- a/DDS.cpp +++ b/DDS.cpp @@ -89,7 +89,7 @@ ddsAccumulator_t DDS::calcFrequency(unsigned short freq) { newStep = (600.0 / (DDS_REFCLK_DEFAULT+DDS_REFCLK_OFFSET)) * pow(2,ACCUMULATOR_BITS); } } else { - newStep = pow(2,ACCUMULATOR_BITS)*freq / (refclk+DDS_REFCLK_OFFSET); + newStep = pow(2,ACCUMULATOR_BITS)*freq / (refclk+refclkOffset); } return newStep; } diff --git a/DDS.h b/DDS.h index 79b23ef..441e22e 100644 --- a/DDS.h +++ b/DDS.h @@ -181,7 +181,8 @@ static const int8_t ddsSineTable[256] PROGMEM = { class DDS { public: - DDS(): refclk(DDS_REFCLK_DEFAULT), accumulator(0), running(false), + DDS(): refclk(DDS_REFCLK_DEFAULT), refclkOffset(DDS_REFCLK_OFFSET), + accumulator(0), running(false), timeLimited(false), tickDuration(0), amplitude(255) {}; @@ -244,6 +245,13 @@ public: return refclk; } + void setReferenceOffset(int16_t offset) { + refclkOffset = offset; + } + int16_t getReferenceOffset() { + return refclkOffset; + } + uint8_t getDutyCycle(); // Set a scaling factor. To keep things quick, this is a power of 2 value. @@ -263,6 +271,7 @@ private: volatile ddsAccumulator_t accumulator; volatile ddsAccumulator_t stepRate; ddsAccumulator_t refclk; + int16_t refclkOffset; static DDS *sDDS; }; diff --git a/examples/Crystal-Calibration/Crystal-Calibration.ino b/examples/Crystal-Calibration/Crystal-Calibration.ino index f59b256..58c8b89 100644 --- a/examples/Crystal-Calibration/Crystal-Calibration.ino +++ b/examples/Crystal-Calibration/Crystal-Calibration.ino @@ -31,7 +31,8 @@ enum Sets { SET_REF, SET_TONE, SET_AMPLITUDE, - SET_ADC_HALF + SET_ADC_HALF, + SET_OFFSET } setting = SET_TONE; char freqBuffer[8]; @@ -47,6 +48,7 @@ void loop() { static uint16_t samples = 0; static uint16_t pulse; static uint32_t lastOutput = 0; + static float pulseFloat = 0.0; if(recordedPulse) { uint32_t pulseAveraging; uint16_t tmpPulse; @@ -56,9 +58,11 @@ void loop() { sei(); if(samples++ == 0) { pulse = tmpPulse; + //pulseFloat = tmpPulse; } else { pulseAveraging = (pulse + tmpPulse) >> 1; pulse = pulseAveraging; + pulseFloat = pulseFloat + 0.01*((float)pulse-pulseFloat); } if((lastOutput + 1000) < millis()) { Serial.print(F("Pulse: ")); @@ -74,7 +78,17 @@ void loop() { Serial.print(F("Freq: ")); // F = 1/(pulse*(1/ref)) // F = ref/pulse - Serial.println((float)(dds.getReferenceClock()+(float)DDS_REFCLK_OFFSET)/(float)pulse); + Serial.print((float)((float)dds.getReferenceClock()+(float)dds.getReferenceOffset())/(float)pulse); + Serial.print(F(" / ")); + Serial.print((float)((float)dds.getReferenceClock()+(float)dds.getReferenceOffset())/pulseFloat); + Serial.print(F(" / ")); + Serial.println(pulseFloat); + Serial.print(F("Freq2: ")); + // F = 1/(pulse*(1/ref)) + // F = ref/pulse + Serial.print((float)dds.getReferenceClock()/(float)pulse); + Serial.print(F(" / ")); + Serial.println((float)dds.getReferenceClock()/pulseFloat); samples = 0; lastOutput = millis(); } @@ -87,6 +101,7 @@ void loop() { Serial.println(F("Commands:")); Serial.println(F("RefClk: u = +10, U = +100, r XXXX = XXXX")); Serial.println(F(" d = -10, D = -100")); + Serial.println(F("Offset: s XXX = Set refclk offset")); Serial.println(F("Radio: T = transmit, R = receive")); Serial.println(F("Tone: t XXXX = XXXX Hz")); Serial.println(F("Amp.: a XXX = XXX out of 255")); @@ -164,6 +179,9 @@ void loop() { case 'm': setting = SET_ADC_HALF; break; + case 's': + setting = SET_OFFSET; + break; case 'o': dds.on(); Serial.println("> "); @@ -173,7 +191,7 @@ void loop() { Serial.println("> "); break; default: - if(c >= '0' && c <= '9') { + if(c == '-' || (c >= '0' && c <= '9')) { *freqBufferPtr = c; freqBufferPtr++; } @@ -200,6 +218,11 @@ void loop() { adcHalf = freq&0xFF; Serial.print(F("ADC midpoint set to ")); Serial.println((uint8_t)(freq&0xFF)); + } else if(setting == SET_OFFSET) { + dds.setReferenceOffset((int16_t)atoi(freqBuffer)); + dds.setFrequency(lastFreq); + Serial.print(F("Refclk offset: ")); + Serial.println(dds.getReferenceOffset()); } Serial.println("> "); } @@ -211,9 +234,11 @@ void loop() { ISR(ADC_vect) { static uint16_t pulseLength = 0; static uint8_t lastADC = 127; + cli(); TIFR1 = _BV(ICF1); PORTD |= _BV(2); dds.clockTick(); + sei(); if(listening) { pulseLength++; if(ADCH >= adcHalf && lastADC < adcHalf) { From 9bcf52ec647015189c340a8472425ca5006470de Mon Sep 17 00:00:00 2001 From: Stephen Olesen Date: Sun, 5 Jul 2015 16:29:27 -0600 Subject: [PATCH 33/44] Update large DDS sine table to use signed integers. --- DDS.h | 98 ++++++++++++++++++++--------------------------------------- 1 file changed, 33 insertions(+), 65 deletions(-) diff --git a/DDS.h b/DDS.h index 441e22e..037425b 100644 --- a/DDS.h +++ b/DDS.h @@ -70,71 +70,39 @@ typedef uint32_t ddsAccumulator_t; #ifdef DDS_TABLE_LARGE // How many bits to keep from the accumulator to look up in this table #define ACCUMULATOR_BIT_SHIFT (ACCUMULATOR_BITS-10) -static const uint8_t ddsSineTable[1024] PROGMEM = { - 128,128,129,130,131,131,132,133,134,135,135,136,137,138,138,139, - 140,141,142,142,143,144,145,145,146,147,148,149,149,150,151,152, - 152,153,154,155,155,156,157,158,158,159,160,161,162,162,163,164, - 165,165,166,167,167,168,169,170,170,171,172,173,173,174,175,176, - 176,177,178,178,179,180,181,181,182,183,183,184,185,186,186,187, - 188,188,189,190,190,191,192,192,193,194,194,195,196,196,197,198, - 198,199,200,200,201,202,202,203,203,204,205,205,206,207,207,208, - 208,209,210,210,211,211,212,213,213,214,214,215,215,216,217,217, - 218,218,219,219,220,220,221,221,222,222,223,224,224,225,225,226, - 226,227,227,228,228,228,229,229,230,230,231,231,232,232,233,233, - 234,234,234,235,235,236,236,236,237,237,238,238,238,239,239,240, - 240,240,241,241,241,242,242,242,243,243,243,244,244,244,245,245, - 245,246,246,246,246,247,247,247,248,248,248,248,249,249,249,249, - 250,250,250,250,250,251,251,251,251,251,252,252,252,252,252,252, - 253,253,253,253,253,253,253,254,254,254,254,254,254,254,254,254, - 254,254,255,255,255,255,255,255,255,255,255,255,255,255,255,255, - 255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,254, - 254,254,254,254,254,254,254,254,254,254,253,253,253,253,253,253, - 253,252,252,252,252,252,252,251,251,251,251,251,250,250,250,250, - 250,249,249,249,249,248,248,248,248,247,247,247,246,246,246,246, - 245,245,245,244,244,244,243,243,243,242,242,242,241,241,241,240, - 240,240,239,239,238,238,238,237,237,236,236,236,235,235,234,234, - 234,233,233,232,232,231,231,230,230,229,229,228,228,228,227,227, - 226,226,225,225,224,224,223,222,222,221,221,220,220,219,219,218, - 218,217,217,216,215,215,214,214,213,213,212,211,211,210,210,209, - 208,208,207,207,206,205,205,204,203,203,202,202,201,200,200,199, - 198,198,197,196,196,195,194,194,193,192,192,191,190,190,189,188, - 188,187,186,186,185,184,183,183,182,181,181,180,179,178,178,177, - 176,176,175,174,173,173,172,171,170,170,169,168,167,167,166,165, - 165,164,163,162,162,161,160,159,158,158,157,156,155,155,154,153, - 152,152,151,150,149,149,148,147,146,145,145,144,143,142,142,141, - 140,139,138,138,137,136,135,135,134,133,132,131,131,130,129,128, - 128,127,126,125,124,124,123,122,121,120,120,119,118,117,117,116, - 115,114,113,113,112,111,110,110,109,108,107,106,106,105,104,103, - 103,102,101,100,100,99,98,97,97,96,95,94,93,93,92,91, - 90,90,89,88,88,87,86,85,85,84,83,82,82,81,80,79, - 79,78,77,77,76,75,74,74,73,72,72,71,70,69,69,68, - 67,67,66,65,65,64,63,63,62,61,61,60,59,59,58,57, - 57,56,55,55,54,53,53,52,52,51,50,50,49,48,48,47, - 47,46,45,45,44,44,43,42,42,41,41,40,40,39,38,38, - 37,37,36,36,35,35,34,34,33,33,32,31,31,30,30,29, - 29,28,28,27,27,27,26,26,25,25,24,24,23,23,22,22, - 21,21,21,20,20,19,19,19,18,18,17,17,17,16,16,15, - 15,15,14,14,14,13,13,13,12,12,12,11,11,11,10,10, - 10,9,9,9,9,8,8,8,7,7,7,7,6,6,6,6, - 5,5,5,5,5,4,4,4,4,4,3,3,3,3,3,3, - 2,2,2,2,2,2,2,1,1,1,1,1,1,1,1,1, - 1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0, - 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1, - 1,1,1,1,1,1,1,1,1,1,2,2,2,2,2,2, - 2,3,3,3,3,3,3,4,4,4,4,4,5,5,5,5, - 5,6,6,6,6,7,7,7,7,8,8,8,9,9,9,9, - 10,10,10,11,11,11,12,12,12,13,13,13,14,14,14,15, - 15,15,16,16,17,17,17,18,18,19,19,19,20,20,21,21, - 21,22,22,23,23,24,24,25,25,26,26,27,27,27,28,28, - 29,29,30,30,31,31,32,33,33,34,34,35,35,36,36,37, - 37,38,38,39,40,40,41,41,42,42,43,44,44,45,45,46, - 47,47,48,48,49,50,50,51,52,52,53,53,54,55,55,56, - 57,57,58,59,59,60,61,61,62,63,63,64,65,65,66,67, - 67,68,69,69,70,71,72,72,73,74,74,75,76,77,77,78, - 79,79,80,81,82,82,83,84,85,85,86,87,88,88,89,90, - 90,91,92,93,93,94,95,96,97,97,98,99,100,100,101,102, - 103,103,104,105,106,106,107,108,109,110,110,111,112,113,113,114, - 115,116,117,117,118,119,120,120,121,122,123,124,124,125,126,127 +static const int8_t ddsSineTable[1024] PROGMEM = { + 0, 0, 1, 2, 3, 3, 4, 5, 6, 7, 7, 8, 9, 10, 10, 11, 12, 13, 13, 14, 15, 16, 17, 17, 18, 19, 20, 20, 21, 22, 23, 24, + 24, 25, 26, 27, 27, 28, 29, 30, 30, 31, 32, 33, 33, 34, 35, 36, 36, 37, 38, 39, 39, 40, 41, 42, 42, 43, 44, 44, 45, 46, 47, 47, + 48, 49, 50, 50, 51, 52, 52, 53, 54, 55, 55, 56, 57, 57, 58, 59, 59, 60, 61, 61, 62, 63, 63, 64, 65, 65, 66, 67, 67, 68, 69, 69, + 70, 71, 71, 72, 73, 73, 74, 75, 75, 76, 76, 77, 78, 78, 79, 79, 80, 81, 81, 82, 82, 83, 84, 84, 85, 85, 86, 87, 87, 88, 88, 89, + 89, 90, 90, 91, 91, 92, 93, 93, 94, 94, 95, 95, 96, 96, 97, 97, 98, 98, 99, 99, 100, 100, 101, 101, 102, 102, 102, 103, 103, 104, 104, 105, + 105, 106, 106, 106, 107, 107, 108, 108, 108, 109, 109, 110, 110, 110, 111, 111, 112, 112, 112, 113, 113, 113, 114, 114, 114, 115, 115, 115, 116, 116, 116, 117, + 117, 117, 117, 118, 118, 118, 119, 119, 119, 119, 120, 120, 120, 120, 121, 121, 121, 121, 121, 122, 122, 122, 122, 123, 123, 123, 123, 123, 123, 124, 124, 124, + 124, 124, 124, 124, 125, 125, 125, 125, 125, 125, 125, 125, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, + 127, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 125, 125, 125, 125, 125, 125, 125, 125, 124, 124, 124, + 124, 124, 124, 124, 123, 123, 123, 123, 123, 123, 122, 122, 122, 122, 121, 121, 121, 121, 121, 120, 120, 120, 120, 119, 119, 119, 119, 118, 118, 118, 117, 117, + 117, 117, 116, 116, 116, 115, 115, 115, 114, 114, 114, 113, 113, 113, 112, 112, 112, 111, 111, 110, 110, 110, 109, 109, 108, 108, 108, 107, 107, 106, 106, 106, + 105, 105, 104, 104, 103, 103, 102, 102, 102, 101, 101, 100, 100, 99, 99, 98, 98, 97, 97, 96, 96, 95, 95, 94, 94, 93, 93, 92, 91, 91, 90, 90, + 89, 89, 88, 88, 87, 87, 86, 85, 85, 84, 84, 83, 82, 82, 81, 81, 80, 79, 79, 78, 78, 77, 76, 76, 75, 75, 74, 73, 73, 72, 71, 71, + 70, 69, 69, 68, 67, 67, 66, 65, 65, 64, 63, 63, 62, 61, 61, 60, 59, 59, 58, 57, 57, 56, 55, 55, 54, 53, 52, 52, 51, 50, 50, 49, + 48, 47, 47, 46, 45, 44, 44, 43, 42, 42, 41, 40, 39, 39, 38, 37, 36, 36, 35, 34, 33, 33, 32, 31, 30, 30, 29, 28, 27, 27, 26, 25, + 24, 24, 23, 22, 21, 20, 20, 19, 18, 17, 17, 16, 15, 14, 13, 13, 12, 11, 10, 10, 9, 8, 7, 7, 6, 5, 4, 3, 3, 2, 1, 0, + 0, 0, -1, -2, -3, -3, -4, -5, -6, -7, -7, -8, -9, -10, -10, -11, -12, -13, -13, -14, -15, -16, -17, -17, -18, -19, -20, -20, -21, -22, -23, -24, + -24, -25, -26, -27, -27, -28, -29, -30, -30, -31, -32, -33, -33, -34, -35, -36, -36, -37, -38, -39, -39, -40, -41, -42, -42, -43, -44, -44, -45, -46, -47, -47, + -48, -49, -50, -50, -51, -52, -52, -53, -54, -55, -55, -56, -57, -57, -58, -59, -59, -60, -61, -61, -62, -63, -63, -64, -65, -65, -66, -67, -67, -68, -69, -69, + -70, -71, -71, -72, -73, -73, -74, -75, -75, -76, -76, -77, -78, -78, -79, -79, -80, -81, -81, -82, -82, -83, -84, -84, -85, -85, -86, -87, -87, -88, -88, -89, + -89, -90, -90, -91, -91, -92, -93, -93, -94, -94, -95, -95, -96, -96, -97, -97, -98, -98, -99, -99, -100, -100, -101, -101, -102, -102, -102, -103, -103, -104, -104, -105, + -105, -106, -106, -106, -107, -107, -108, -108, -108, -109, -109, -110, -110, -110, -111, -111, -112, -112, -112, -113, -113, -113, -114, -114, -114, -115, -115, -115, -116, -116, -116, -117, + -117, -117, -117, -118, -118, -118, -119, -119, -119, -119, -120, -120, -120, -120, -121, -121, -121, -121, -121, -122, -122, -122, -122, -123, -123, -123, -123, -123, -123, -124, -124, -124, + -124, -124, -124, -124, -125, -125, -125, -125, -125, -125, -125, -125, -126, -126, -126, -126, -126, -126, -126, -126, -126, -126, -126, -126, -126, -126, -126, -126, -126, -126, -126, -126, + -127, -126, -126, -126, -126, -126, -126, -126, -126, -126, -126, -126, -126, -126, -126, -126, -126, -126, -126, -126, -126, -125, -125, -125, -125, -125, -125, -125, -125, -124, -124, -124, + -124, -124, -124, -124, -123, -123, -123, -123, -123, -123, -122, -122, -122, -122, -121, -121, -121, -121, -121, -120, -120, -120, -120, -119, -119, -119, -119, -118, -118, -118, -117, -117, + -117, -117, -116, -116, -116, -115, -115, -115, -114, -114, -114, -113, -113, -113, -112, -112, -112, -111, -111, -110, -110, -110, -109, -109, -108, -108, -108, -107, -107, -106, -106, -106, + -105, -105, -104, -104, -103, -103, -102, -102, -102, -101, -101, -100, -100, -99, -99, -98, -98, -97, -97, -96, -96, -95, -95, -94, -94, -93, -93, -92, -91, -91, -90, -90, + -89, -89, -88, -88, -87, -87, -86, -85, -85, -84, -84, -83, -82, -82, -81, -81, -80, -79, -79, -78, -78, -77, -76, -76, -75, -75, -74, -73, -73, -72, -71, -71, + -70, -69, -69, -68, -67, -67, -66, -65, -65, -64, -63, -63, -62, -61, -61, -60, -59, -59, -58, -57, -57, -56, -55, -55, -54, -53, -52, -52, -51, -50, -50, -49, + -48, -47, -47, -46, -45, -44, -44, -43, -42, -42, -41, -40, -39, -39, -38, -37, -36, -36, -35, -34, -33, -33, -32, -31, -30, -30, -29, -28, -27, -27, -26, -25, + -24, -24, -23, -22, -21, -20, -20, -19, -18, -17, -17, -16, -15, -14, -13, -13, -12, -11, -10, -10, -9, -8, -7, -7, -6, -5, -4, -3, -3, -2, -1, 0 }; #else #define ACCUMULATOR_BIT_SHIFT (ACCUMULATOR_BITS-8) From 4fb55520eaf3a3763313098462744dfaccdcfce2 Mon Sep 17 00:00:00 2001 From: Stephen Olesen Date: Mon, 6 Jul 2015 11:40:50 -0600 Subject: [PATCH 34/44] First round of AX25 receiver. --- examples/AX25Receive/AX25Receive.ino | 79 ++++++++++++++++++++++++++++ 1 file changed, 79 insertions(+) create mode 100644 examples/AX25Receive/AX25Receive.ino diff --git a/examples/AX25Receive/AX25Receive.ino b/examples/AX25Receive/AX25Receive.ino new file mode 100644 index 0000000..4f4efca --- /dev/null +++ b/examples/AX25Receive/AX25Receive.ino @@ -0,0 +1,79 @@ +#define DDS_REFCLK_DEFAULT 9600 + +#include +#include + +HamShield radio; +DDS dds; + +void setup() { + Serial.begin(9600); + Wire.begin(); + pinMode(2, OUTPUT); + pinMode(3, OUTPUT); + + Serial.println(F("Radio test connection")); + Serial.println(radio.testConnection(), DEC); + Serial.println(F("Initialize")); + delay(100); + radio.initialize(); + Serial.println(F("Frequency")); + delay(100); +// radio.setVHF(); +// radio.setRfPower(0); +// radio.setModeReceive(); + radio.setVolume1(0xFF); + radio.setVolume2(0xFF); + radio.frequency(145050); + Serial.println(F("DDS Start")); + delay(100); + dds.start(); + Serial.println(F("AFSK start")); + delay(100); + radio.afsk.start(&dds); + Serial.println(F("Starting...")); + pinMode(11, INPUT); // Bodge for now, as pin 3 is hotwired to pin 11 + delay(100); + dds.setAmplitude(255); +} + +uint32_t last = 0; +void loop() { + if(radio.afsk.decoder.read() || radio.afsk.rxPacketCount()) { + // A true return means something was put onto the packet FIFO + Serial.println("Packet"); + // If we actually have data packets in the buffer, process them all now + while(radio.afsk.rxPacketCount()) { + AFSK::Packet *packet = radio.afsk.getRXPacket(); + if(packet) { + for(unsigned short i = 0; i < packet->len; ++i) + Serial.write((uint8_t)packet->getByte()); + AFSK::PacketBuffer::freePacket(packet); + } + } + } + /*if(last < millis()) { + Serial.println(radio.readRSSI()); + last = millis()+1000; + }*/ +} + +/*ISR(TIMER2_OVF_vect) { + TIFR2 = _BV(TOV2); + static uint8_t tcnt = 0; + if(++tcnt == 8) { + digitalWrite(2, HIGH); + dds.clockTick(); + digitalWrite(2, LOW); + tcnt = 0; + } +}*/ +ISR(ADC_vect) { + static uint8_t tcnt = 0; + TIFR1 = _BV(ICF1); // Clear the timer flag + PORTD |= _BV(2); // Diagnostic pin (D2) + //dds.clockTick(); + radio.afsk.timer(); + PORTD &= ~(_BV(2)); // Pin D2 off again +} + From 8ec69c31b99602abdebad28e7648f01d2919b100 Mon Sep 17 00:00:00 2001 From: morgan Date: Thu, 9 Jul 2015 08:29:50 -0700 Subject: [PATCH 35/44] adapted library for au registers --- HamShield.cpp | 313 +++++++++++++++++++------------------ HamShield.h | 29 ++-- examples/Gauges/Gauges.ino | 1 + 3 files changed, 179 insertions(+), 164 deletions(-) diff --git a/HamShield.cpp b/HamShield.cpp index eae5b2f..bfb4d0a 100644 --- a/HamShield.cpp +++ b/HamShield.cpp @@ -70,135 +70,138 @@ void HamShield::initialize() { // Note: these initial settings are for UHF 12.5kHz channel // see the A1846S register table and initial settings for more info - // TODO: update code to make it easier to change from VHF to UHF and 12.5kHz channel to 25kHz channel uint16_t tx_data; // reset all registers in A1846S softReset(); - // set pdn_reg bit in control register (0 or 1?) (now done in softReset) - //I2Cdev::writeBitW(devAddr, A1846S_CTL_REG, A1846S_PWR_DWN_BIT, 1); - tx_data = 0x0698; - I2Cdev::writeWord(devAddr, 0x02, tx_data); // why is this here? See A1846S register init table - + //set up clock to ues 12-14MHz setClkMode(1); - - // set up clock to use 12.8MHz crystal - setXtalFreq(12800); - // set up ADClk frequency to 6.4MHz - setAdcClkFreq(6400); - tx_data = 0xE000; - I2Cdev::writeWord(devAddr, 0x24, tx_data); // why is this here? See A1846S register init word doc + + tx_data = 0x0031; + I2Cdev::writeWord(devAddr, 0x31, tx_data); // included as per AU supplied register table - //could change GPIO voltage levels with writes to 0x08 and 0x09 - // see A1846S register init table - tx_data = 0x03AC; - I2Cdev::writeWord(devAddr, 0x09, tx_data); // why is this here? See A1846S register init word doc + tx_data = 0x44A5; + I2Cdev::writeWord(devAddr, 0x33, tx_data); // agc number - included as per AU supplied register table + + tx_data = 0x2B87; + I2Cdev::writeWord(devAddr, 0x34, tx_data); // Rx digital gain - included as per AU supplied register table - // set PA_bias voltage to 1.68V (and do something else too? what's the 3 for?) - tx_data = 0x0320; - I2Cdev::writeWord(devAddr, 0x0A, tx_data); - - tx_data = 0x1A10; - I2Cdev::writeWord(devAddr, 0x0B, tx_data); // why is this here? See A1846S register init table + tx_data = 0x470F; + I2Cdev::writeWord(devAddr, 0x41, tx_data); // digital gain for Tx - included as per AU supplied register table - tx_data = 0x3E37; - I2Cdev::writeWord(devAddr, 0x11, tx_data); // why is this here? See A1846S register init table + tx_data = 0x0DFF; + I2Cdev::writeWord(devAddr, A1846S_RX_VOLUME_REG, tx_data); - // Automatic Gain Control stuff - // AGC when band is UHF,0x32 = 0x627C;when band is VHF,0x32 = 0x62BC// - tx_data = 0x627c; // this is uhf, for vhf set to 0x62bc - I2Cdev::writeWord(devAddr, 0x32, tx_data); // why is this here? See A1846S register init table - tx_data = 0x0AF2; - I2Cdev::writeWord(devAddr, 0x33, tx_data); // why is this here? See A1846S register init table + tx_data = 0x7FFF; + I2Cdev::writeWord(devAddr, 0x47, tx_data);// soft mute - // why is this here? See A1846S register init word doc - tx_data = 0x0F28; // this is uhf, for vhf set to 0x62bc - I2Cdev::writeWord(devAddr, 0x3C, tx_data); // why is this here? See A1846S register init table - tx_data = 0x200B; - I2Cdev::writeWord(devAddr, 0x3D, tx_data); // why is this here? See A1846S register init table + tx_data = 0x2C62; + I2Cdev::writeWord(devAddr, 0x4F, tx_data);// included as per AU supplied register table + + tx_data = 0x0094; + I2Cdev::writeWord(devAddr, 0x53, tx_data);// included as per AU supplied register table + + tx_data = 0x0081; + I2Cdev::writeWord(devAddr, 0x55, tx_data);// included as per AU supplied register table + + tx_data = 0x0B22; + I2Cdev::writeWord(devAddr, 0x56, tx_data);// sq detection time + + tx_data = 0x1C00; + I2Cdev::writeWord(devAddr, 0x57, tx_data);// bypass rssi lpfilter + + tx_data = 0x0EDB; + I2Cdev::writeWord(devAddr, 0x5A, tx_data);// SQ detection time - // Noise threshold settings - tx_data = 0x1C2F; // see email from Iris - I2Cdev::writeWord(devAddr, 0x47, tx_data); // why is this here? See A1846S register init table + tx_data = 0x101E; + I2Cdev::writeWord(devAddr, 0x60, tx_data);// SQ noise threshold + + tx_data = 0x16AD; + I2Cdev::writeWord(devAddr, 0x63, tx_data);// pre-emphasis bypass threshold - // SNR LPF settings, sq settings - tx_data = 0x293A; - I2Cdev::writeWord(devAddr, 0x4e, tx_data); // why is this here? See A1846S register init table - - // subaudio decode setting,sq_out_sel,noise threshold value db - tx_data = 0x114A; // A1846S_SQ_OUT_SEL_REG is 0x54 - I2Cdev::writeWord(devAddr, A1846S_SQ_OUT_SEL_REG, tx_data); // why is this here? See A1846S register init table - - // bandwide setting of filter when RSSI is high or low - tx_data = 0x0652; - I2Cdev::writeWord(devAddr, 0x56, tx_data); // why is this here? See A1846S register init table - - tx_data = 0x062d; - I2Cdev::writeWord(devAddr, 0x6e, tx_data); // why is this here? See A1846S register init table - - // note, this is for 12.5kHz channel - tx_data = 0x6C1E; - I2Cdev::writeWord(devAddr, 0x71, tx_data); // why is this here? See A1846S register init table - - // see A1846S register init doc for this - tx_data = 0x00FF; - I2Cdev::writeWord(devAddr, 0x44, tx_data); // why is this here? See A1846S register init table - tx_data = 0x0500; - I2Cdev::writeWord(devAddr, 0x1F, tx_data); // set up GPIO for RX/TX mirroring - - - // set RFoutput power (note that the address is 0x85, so do some rigmaroll) - tx_data = 0x1; - I2Cdev::writeWord(devAddr, 0x7F, tx_data); // prep to write to a reg > 0x7F - // If 0x85 is 0x001F, Rfoutput power is 8dBm , ACP is -63dB in 12.5KHz and -65dB in 25KHz - // If 0x85 is 0x0018, Rfoutput power is 6dBm , ACP is -64dB in 12.5KHz and -66dB in 25KHz - // If 0x85 is 0x0017, Rfoutput power is -3dBm , ACP is -68dBc in 12.5KHz and -68dBc in 25KHz - tx_data = 0x001F; - I2Cdev::writeWord(devAddr, 0x5, tx_data); // set output power, reg 0x85 - 0x80 - tx_data = 0x0; - I2Cdev::writeWord(devAddr, 0x7F, tx_data); // finish writing to a reg > 0x7F - - // set control reg for pdn_reg, rx, and mute when rxno - tx_data = 0xA4; - I2Cdev::writeWord(devAddr, A1846S_CTL_REG, tx_data); // finish writing to a reg > 0x7F - - delay(100); - - // set control reg for chip_cal_en, pdn_reg, rx, and mute when rxno - tx_data = 0xA6; - I2Cdev::writeWord(devAddr, A1846S_CTL_REG, tx_data); // finish writing to a reg > 0x7F - - delay(100); - - // set control reg for chip_cal_en, pdn_reg - tx_data = 0x6; - I2Cdev::writeWord(devAddr, A1846S_CTL_REG, tx_data); // finish writing to a reg > 0x7F - - delay(100); - - // and then I have no idea about this nonsense - // some of these settings seem to be for 12.5kHz channels - // TODO: get A1846S to give us a full register table - tx_data = 0x1d40; - I2Cdev::writeWord(devAddr, 0x54, tx_data); - tx_data = 0x062d; - I2Cdev::writeWord(devAddr, 0x6e, tx_data); - tx_data = 0x102a; - I2Cdev::writeWord(devAddr, 0x70, tx_data); - tx_data = 0x6c1e; - I2Cdev::writeWord(devAddr, 0x71, tx_data); - tx_data = 0x0006; + // calibration + tx_data = 0x00A4; I2Cdev::writeWord(devAddr, 0x30, tx_data); - + delay(50); + tx_data = 0x00A6; + I2Cdev::writeWord(devAddr, 0x30, tx_data); + delay(100); + tx_data = 0x0006; + I2Cdev::writeWord(devAddr, 0x30, tx_data); + delay(10); + + // continue default setup in 12.5kHz mode + tx_data = 0x1100; + I2Cdev::writeWord(devAddr, 0x15, tx_data); // tuning bit + + tx_data = 0x4495; + I2Cdev::writeWord(devAddr, 0x32, tx_data); // agc target power + + tx_data = 0x40C3; + I2Cdev::writeWord(devAddr, 0x3A, tx_data); // modu_det_sel sq setting + + tx_data = 0x0F1E; + I2Cdev::writeWord(devAddr, 0x3C, tx_data); // pk_det_thr sq setting + + tx_data = 0x28D0; + I2Cdev::writeWord(devAddr, 0x3F, tx_data); // pk_det_thr sq setting + + tx_data = 0x20BE; + I2Cdev::writeWord(devAddr, 0x48, tx_data); // pk_det_thr sq setting + + tx_data = 0x0A50; + I2Cdev::writeWord(devAddr, 0x59, tx_data); // Tx FM Deviation + + tx_data = 0x0A10; + I2Cdev::writeWord(devAddr, 0x62, tx_data); // Modu_det_thresh (sq setting) + + tx_data = 0x2494; + I2Cdev::writeWord(devAddr, 0x65, tx_data); // setting th_sif for SQ rssi detect + + tx_data = 0x2494; + I2Cdev::writeWord(devAddr, 0x66, tx_data); // setting th_sif for SQ rssi detect + + // AGC gain table settings + // the rest of these settings are to upper register addresses + // set 0x7F to 1 to write to them + tx_data = 0x0001; + I2Cdev::writeWord(devAddr, 0x7F, tx_data); + + tx_data = 0x0014; + I2Cdev::writeWord(devAddr, 0x06, tx_data); + tx_data = 0x020C; + I2Cdev::writeWord(devAddr, 0x07, tx_data); + tx_data = 0x0214; + I2Cdev::writeWord(devAddr, 0x08, tx_data); + tx_data = 0x030C; + I2Cdev::writeWord(devAddr, 0x09, tx_data); + tx_data = 0x0314; + I2Cdev::writeWord(devAddr, 0x0A, tx_data); + tx_data = 0x0324; + I2Cdev::writeWord(devAddr, 0x0B, tx_data); + tx_data = 0x0344; + I2Cdev::writeWord(devAddr, 0x0C, tx_data); + tx_data = 0x1344; + I2Cdev::writeWord(devAddr, 0x0D, tx_data); + tx_data = 0x1B44; + I2Cdev::writeWord(devAddr, 0x0E, tx_data); + tx_data = 0x3F44; + I2Cdev::writeWord(devAddr, 0x0F, tx_data); + tx_data = 0xE0EB; + I2Cdev::writeWord(devAddr, 0x12, tx_data); + + // done writing to upper page addresses, so set 0x7F back + tx_data = 0x0000; + I2Cdev::writeWord(devAddr, 0x7F, tx_data); + delay(100); // setup default values - setFrequency(446000); - setVolume1(0xF); - setVolume2(0xF); + //setVolume1(0xF); + //setVolume2(0xF); setModeReceive(); setTxSourceMic(); setSQLoThresh(80); @@ -211,9 +214,8 @@ void HamShield::initialize() { * @return True if connection is valid, false otherwise */ bool HamShield::testConnection() { - I2Cdev::readWord(devAddr, 0x09, radio_i2c_buf); -// 03ac or 032c - return radio_i2c_buf[0] == 0x03AC; // TODO: find a device ID reg I can use + I2Cdev::readWord(devAddr, 0x00, radio_i2c_buf); + return radio_i2c_buf[0] == 0x1846; } @@ -283,6 +285,7 @@ void HamShield::setNoFilters() { setGpioHi(2); // turn off VHF } +/* // band // 00 - 400-520MHz // 10 - 200-260MHz @@ -308,7 +311,9 @@ uint16_t HamShield::getBand(){ I2Cdev::readBitsW(devAddr, A1846S_BAND_SEL_REG, A1846S_BAND_SEL_BIT, A1846S_BAND_SEL_LENGTH, radio_i2c_buf); return radio_i2c_buf[0]; } +*/ +/* // xtal frequency (kHz) // 12-14MHz crystal: this reg is set to crystal freq_khz // 24-28MHz crystal: this reg is set to crystal freq_khz / 2 @@ -316,11 +321,12 @@ void HamShield::setXtalFreq(uint16_t freq_kHz){ I2Cdev::writeWord(devAddr, A1846S_XTAL_FREQ_REG, freq_kHz); } uint16_t HamShield::getXtalFreq(){ - I2Cdev::readWord(devAddr, A1846S_FREQ_HI_REG, radio_i2c_buf); + I2Cdev::readWord(devAddr, A1846S_XTAL_FREQ_REG, radio_i2c_buf); return radio_i2c_buf[0]; } + // adclk frequency (kHz) // 12-14MHz crystal: this reg is set to crystal freq_khz / 2 // 24-28MHz crystal: this reg is set to crystal freq_khz / 4 @@ -332,15 +338,16 @@ uint16_t HamShield::getAdcClkFreq(){ I2Cdev::readWord(devAddr, A1846S_ADCLK_FREQ_REG, radio_i2c_buf); return radio_i2c_buf[0]; } +*/ // clk mode // 12-14MHz: set to 1 // 24-28MHz: set to 0 void HamShield::setClkMode(bool LFClk){ // include upper bits as default values - uint16_t tx_data = 0x0F11; // NOTE: should this be 0fd1 or 0f11? Programming guide and setup guide disagree + uint16_t tx_data = 0x0FD1; if (!LFClk) { - tx_data = 0x0F10; + tx_data = 0x0FD0; } I2Cdev::writeWord(devAddr, A1846S_CLK_MODE_REG, tx_data); @@ -358,6 +365,8 @@ bool HamShield::getClkMode(){ // TX/RX control +// TODO: create a 25kHz setup option as well as 12.5kHz (as is implemented now) +/* // channel mode // 11 - 25kHz channel // 00 - 12.5kHz channel @@ -369,6 +378,7 @@ uint16_t HamShield::getChanMode(){ I2Cdev::readBitsW(devAddr, A1846S_CTL_REG, A1846S_CHAN_MODE_BIT, A1846S_CHAN_MODE_LENGTH, radio_i2c_buf); return radio_i2c_buf[0]; } +*/ // choose tx or rx void HamShield::setTX(bool on_noff){ @@ -378,19 +388,16 @@ void HamShield::setTX(bool on_noff){ // For RF6886: // first turn on power - // set RX output on + // set RX output off setGpioHi(4); // remember that RX and TX are active low - // set TX output off + // set TX output on setGpioLow(5); // remember that RX and TX are active low // then turn on VREG (PWM output) // then apply RF signal - setRfPower(9); // figure out a good default number (or don't set a default) + setRfPower(9); // figure out a good default number (TODO: or don't set a default) } - // todo: make sure gpio are set correctly after this I2Cdev::writeBitW(devAddr, A1846S_CTL_REG, A1846S_TX_MODE_BIT, on_noff); - - } bool HamShield::getTX(){ I2Cdev::readBitW(devAddr, A1846S_CTL_REG, A1846S_TX_MODE_BIT, radio_i2c_buf); @@ -403,7 +410,7 @@ void HamShield::setRX(bool on_noff){ setTX(false); // set TX output off - setGpioHi(5); // remember that RX and TX are active low + setGpioHiZ(5); // remember that RX and TX are active low // set RX output on setGpioLow(4); // remember that RX and TX are active low } @@ -416,15 +423,16 @@ bool HamShield::getRX(){ } void HamShield::setModeTransmit(){ - // check to see if we should allow them to do this - if(restrictions == true) { - if((radio_frequency > 139999) & (radio_frequency < 148001)) { setRX(false); setTX(true); } - if((radio_frequency > 218999) & (radio_frequency < 225001)) { setRX(false); setTX(true); } - if((radio_frequency > 419999) & (radio_frequency < 450001)) { setRX(false); setTX(true); } - } else { - // turn off rx, turn on tx - setRX(false); // break before make - setTX(true); } + // check to see if we should allow them to do this + if(restrictions == true) { + if((radio_frequency > 139999) & (radio_frequency < 148001)) { setRX(false); setTX(true); } + if((radio_frequency > 218999) & (radio_frequency < 225001)) { setRX(false); setTX(true); } + if((radio_frequency > 419999) & (radio_frequency < 450001)) { setRX(false); setTX(true); } + } else { + // turn off rx, turn on tx + setRX(false); // break before make + setTX(true); + } } void HamShield::setModeReceive(){ // turn on rx, turn off tx @@ -438,33 +446,35 @@ void HamShield::setModeOff(){ } // set tx source -// 00 - Mic source -// 01 - sine source from tone2 -// 10 - tx code from GPIO1 code_in (gpio1<1:0> must be set to 01) -// 11 - no tx source +// 000 - Nothing +// 001 - sine source from tone1 +// 010 - sine source from tone2 +// 011 - sine source from tone1 and tone2 +// 100 - mic void HamShield::setTxSource(uint16_t tx_source){ I2Cdev::writeBitsW(devAddr, A1846S_TX_VOICE_REG, A1846S_VOICE_SEL_BIT, A1846S_VOICE_SEL_LENGTH, tx_source); } void HamShield::setTxSourceMic(){ - setTxSource(0); + setTxSource(4); } -void HamShield::setTxSourceSine(){ +void HamShield::setTxSourceTone1(){ setTxSource(1); } -void HamShield::setTxSourceCode(){ - // note, also set GPIO1 to 01 - setGpioMode(1, 1); - +void HamShield::setTxSourceTone2(){ setTxSource(2); } -void HamShield::setTxSourceNone(){ +void HamShield::setTxSourceTones(){ setTxSource(3); } +void HamShield::setTxSourceNone(){ + setTxSource(0); +} uint16_t HamShield::getTxSource(){ I2Cdev::readBitsW(devAddr, A1846S_TX_VOICE_REG, A1846S_VOICE_SEL_BIT, A1846S_VOICE_SEL_LENGTH, radio_i2c_buf); return radio_i2c_buf[0]; } +/* // set PA_bias voltage // 000000: 1.01V // 000001:1.05V @@ -481,7 +491,7 @@ uint16_t HamShield::getPABiasVoltage(){ I2Cdev::readBitsW(devAddr, A1846S_PABIAS_REG, A1846S_PABIAS_BIT, A1846S_PABIAS_LENGTH, radio_i2c_buf); return radio_i2c_buf[0]; } - +*/ // Subaudio settings // TX and RX code /* @@ -913,10 +923,10 @@ bool HamShield::getPreDeEmphEnabled(){ // Read Only Status Registers int16_t HamShield::readRSSI(){ - I2Cdev::readWord(devAddr, A1846S_RSSI_REG, radio_i2c_buf); + I2Cdev::readBitsW(devAddr, A1846S_RSSI_REG, A1846S_RSSI_BIT, A1846S_RSSI_LENGTH, radio_i2c_buf); - int16_t rssi = (radio_i2c_buf[0] & 0x3FF) / 8 - 135; - return rssi; // only need lowest 10 bits + int16_t rssi = (radio_i2c_buf[0] & 0x3FF); // - 137; + return rssi; } uint16_t HamShield::readVSSI(){ I2Cdev::readWord(devAddr, A1846S_VSSI_REG, radio_i2c_buf); @@ -937,6 +947,7 @@ uint16_t HamShield::readDTMFCode(){ return radio_i2c_buf[0]; } + void HamShield::setRfPower(uint8_t pwr) { // using loop reference voltage input to op-amp @@ -952,24 +963,26 @@ void HamShield::setRfPower(uint8_t pwr) { } + bool HamShield::frequency(uint32_t freq_khz) { + //TODO: there are several "special" frequencies that require extra setup of the AU1846 if((freq_khz >= 137000) && (freq_khz <= 174000)) { setVHF(); - setBand(3); // 0b11 is 134-174MHz + //setBand(3); // 0b11 is 134-174MHz setFrequency(freq_khz); return true; } if((freq_khz >= 200000) && (freq_khz <= 260000)) { setVHF(); - setBand(2); // 10 is 200-260MHz + //setBand(2); // 10 is 200-260MHz setFrequency(freq_khz); return true; } if((freq_khz >= 400000) && (freq_khz <= 520000)) { setUHF(); - setBand(00); // 00 is 400-520MHz + //setBand(00); // 00 is 400-520MHz setFrequency(freq_khz); return true; } diff --git a/HamShield.h b/HamShield.h index 5006af3..8593cb9 100644 --- a/HamShield.h +++ b/HamShield.h @@ -32,14 +32,14 @@ #define A1846S_CTL_REG 0x30 // control register #define A1846S_CLK_MODE_REG 0x04 // clk_mode #define A1846S_PABIAS_REG 0x0A // control register for bias voltage -#define A1846S_BAND_SEL_REG 0x0F // band_sel register <1:0> +//#define A1846S_BAND_SEL_REG 0x0F // band_sel register <1:0> #define A1846S_GPIO_MODE_REG 0x1F // GPIO mode select register #define A1846S_FREQ_HI_REG 0x29 // freq<29:16> #define A1846S_FREQ_LO_REG 0x2A // freq<15:0> -#define A1846S_XTAL_FREQ_REG 0x2B // xtal_freq<15:0> -#define A1846S_ADCLK_FREQ_REG 0x2C // adclk_freq<15:0> +//#define A1846S_XTAL_FREQ_REG 0x2B // xtal_freq<15:0> +//#define A1846S_ADCLK_FREQ_REG 0x2C // adclk_freq<15:0> #define A1846S_INT_MODE_REG 0x2D // interrupt enables -#define A1846S_TX_VOICE_REG 0x3C // tx voice control reg +#define A1846S_TX_VOICE_REG 0x3A // tx voice control reg #define A1846S_TH_H_VOX_REG 0x41 // register holds vox high (open) threshold bits #define A1846S_TH_L_VOX_REG 0x42 // register holds vox low (shut) threshold bits #define A1846S_FM_DEV_REG 0x43 // register holds fm deviation settings @@ -53,8 +53,8 @@ #define A1846S_SQ_OUT_SEL_REG 0x54 // see sq #define A1846S_EMPH_FILTER_REG 0x58 #define A1846S_FLAG_REG 0x5C // holds flags for different statuses -#define A1846S_RSSI_REG 0x5F // holds RSSI (unit 1/8dB) -#define A1846S_VSSI_REG 0x60 // holds VSSI (unit mV) +#define A1846S_RSSI_REG 0x1B // holds RSSI (unit 1dB) +#define A1846S_VSSI_REG 0x1A // holds VSSI (unit mV) #define A1846S_DTMF_CTL_REG 0x63 // see dtmf #define A1846S_DTMF_C01_REG 0x66 // holds frequency value for c0 and c1 #define A1846S_DTMF_C23_REG 0x67 // holds frequency value for c2 and c3 @@ -93,8 +93,8 @@ #define A1846S_PADRV_LENGTH 4 // Bitfields for A1846S_BAND_SEL_REG -#define A1846S_BAND_SEL_BIT 7 // band_sel<1:0> -#define A1846S_BAND_SEL_LENGTH 2 +//#define A1846S_BAND_SEL_BIT 7 // band_sel<1:0> +//#define A1846S_BAND_SEL_LENGTH 2 // Bitfields for RDA1864_GPIO_MODE_REG #define RDA1864_GPIO7_MODE_BIT 15 // <1:0> 00=hi-z,01=vox,10=low,11=hi @@ -127,8 +127,8 @@ #define A1846S_VOX_INT_BIT 0 // vox uint16_t enable // Bitfields for A1846S_TX_VOICE_REG -#define A1846S_VOICE_SEL_BIT 15 //voice_sel<1:0> -#define A1846S_VOICE_SEL_LENGTH 2 +#define A1846S_VOICE_SEL_BIT 14 //voice_sel<1:0> +#define A1846S_VOICE_SEL_LENGTH 3 // Bitfields for A1846S_TH_H_VOX_REG #define A1846S_TH_H_VOX_BIT 14 // th_h_vox<14:0> @@ -181,8 +181,8 @@ #define A1846S_VOX_FLAG_BIT 0 // vox out from dsp // Bitfields for A1846S_RSSI_REG -#define A1846S_RSSI_BIT 9 // RSSI readings <9:0> -#define A1846S_RSSI_LENGTH 10 +#define A1846S_RSSI_BIT 15 // RSSI readings <9:0> +#define A1846S_RSSI_LENGTH 8 // Bitfields for A1846S_VSSI_REG #define A1846S_VSSI_BIT 14 // voice signal strength indicator <14:0> (unit mV) @@ -329,8 +329,9 @@ class HamShield { // 11 - no tx source void setTxSource(uint16_t tx_source); void setTxSourceMic(); - void setTxSourceSine(); - void setTxSourceCode(); + void setTxSourceTone1(); + void setTxSourceTone2(); + void setTxSourceTones(); void setTxSourceNone(); uint16_t getTxSource(); diff --git a/examples/Gauges/Gauges.ino b/examples/Gauges/Gauges.ino index 10c3c20..b18647d 100755 --- a/examples/Gauges/Gauges.ino +++ b/examples/Gauges/Gauges.ino @@ -28,6 +28,7 @@ void setup() { Serial.println(result,DEC); radio.initialize(); radio.setFrequency(446000); + radio.setModeReceive(); Serial.println("Entering gauges..."); tone(9,1000); delay(2000); From 066007d61620a48cfd56a5036df59ece17a41b0b Mon Sep 17 00:00:00 2001 From: morgan Date: Thu, 9 Jul 2015 08:33:12 -0700 Subject: [PATCH 36/44] updated rssi reading --- HamShield.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/HamShield.cpp b/HamShield.cpp index bfb4d0a..5c39012 100644 --- a/HamShield.cpp +++ b/HamShield.cpp @@ -925,7 +925,7 @@ bool HamShield::getPreDeEmphEnabled(){ int16_t HamShield::readRSSI(){ I2Cdev::readBitsW(devAddr, A1846S_RSSI_REG, A1846S_RSSI_BIT, A1846S_RSSI_LENGTH, radio_i2c_buf); - int16_t rssi = (radio_i2c_buf[0] & 0x3FF); // - 137; + int16_t rssi = (radio_i2c_buf[0] & 0x3FF) - 137; return rssi; } uint16_t HamShield::readVSSI(){ From 52ac3def7d98c333af845b98b6222ca53438f17f Mon Sep 17 00:00:00 2001 From: Stephen Olesen Date: Thu, 9 Jul 2015 14:57:22 -0600 Subject: [PATCH 37/44] Finalize merger. --- examples/DDS/DDS.ino | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/examples/DDS/DDS.ino b/examples/DDS/DDS.ino index 991ea7f..78ca76c 100644 --- a/examples/DDS/DDS.ino +++ b/examples/DDS/DDS.ino @@ -34,7 +34,13 @@ ISR(TIMER2_OVF_vect) { } #else // Use the ADC timer instead ISR(ADC_vect) { + static unsigned char tcnt = 0; TIFR1 = _BV(ICF1); // Clear the timer flag + if(++tcnt == 4) { + digitalWrite(2, HIGH); + tcnt = 0; + } dds.clockTick(); + digitalWrite(2, LOW); } #endif From cf93050e52ebf61b7266e1e4940416c311015156 Mon Sep 17 00:00:00 2001 From: Stephen Olesen Date: Fri, 10 Jul 2015 11:58:37 -0600 Subject: [PATCH 38/44] Updated AFSK with print routine, use new delay line. AX25 receive tune to APRS. --- AFSK.cpp | 44 +++++++++++++++------ AFSK.h | 2 + examples/AX25Receive/AX25Receive.ino | 59 ++++++++++++++++++++++------ 3 files changed, 81 insertions(+), 24 deletions(-) diff --git a/AFSK.cpp b/AFSK.cpp index 4bf18b1..26ebbd2 100644 --- a/AFSK.cpp +++ b/AFSK.cpp @@ -194,26 +194,25 @@ bool AFSK::HDLCDecode::hdlcParse(bool bit, SimpleFIFO class FastRing { private: - T ring[FASTRING_SIZE]; + T ring[size]; uint8_t position; public: FastRing(): position(0) {} inline void write(T value) { - ring[(position++) & FASTRING_MASK] = value; + ring[(position++) & (size-1)] = value; } inline T read() const { - return ring[position & FASTRING_MASK]; + return ring[position & (size-1)]; } inline T readn(uint8_t n) const { - return ring[(position + (~n+1)) & FASTRING_MASK]; + return ring[(position + (~n+1)) & (size-1)]; } }; -FastRing delayLine; +// Create a delay line that's half the length of the bit cycle (-90 degrees) +FastRing delayLine; // Handle the A/D converter interrupt (hopefully quickly :) void AFSK::Decoder::process(int8_t curr_sample) { @@ -269,7 +268,6 @@ bool AFSK::Decoder::read() { while(rx_fifo.count()) { // Grab the character char c = rx_fifo.dequeue(); - bool escaped = false; if(c == HDLC_ESCAPE) { // We received an escaped byte, mark it escaped = true; @@ -312,10 +310,10 @@ bool AFSK::Decoder::read() { if((currentPacket->getByte() & 0x1) == 0x1) { // Found a byte with LSB set // which marks the final address payload // next two bytes should be the control/PID - if(currentPacket->getByte() == 0x03 && currentPacket->getByte() == 0xf0) { + //if(currentPacket->getByte() == 0x03 && currentPacket->getByte() == 0xf0) { filtered = true; break; // Found it - } + //} } } @@ -520,12 +518,36 @@ size_t AFSK::Packet::appendCallsign(const char *callsign, uint8_t ssid, bool fin appendFCS(ssidField); } +void AFSK::Packet::print(Stream *s) { + uint8_t i; + // Second 6 bytes are source callsign + for(i=7; i<13; i++) { + s->write(*(dataPtr+i)>>1); + } + // SSID + s->write('-'); + s->print((*(dataPtr+13) >> 1) & 0xF); + s->print(" -> "); + // First 6 bytes are destination callsign + for(i=0; i<6; i++) { + s->write(*(dataPtr+i)>>1); + } + // SSID + s->write('-'); + s->print((*(dataPtr+6) >> 1) & 0xF); + // Control/PID next two bytes + // Skip those, print payload + for(i = 15; iwrite(*(dataPtr+i)); + } +} + // Determine what we want to do on this ADC tick. void AFSK::timer() { if(encoder.isSending()) encoder.process(); else - decoder.process(ADCH - 128); + decoder.process(((int8_t)(ADCH - 128))); } void AFSK::start(DDS *dds) { diff --git a/AFSK.h b/AFSK.h index 1f8b860..68504e7 100644 --- a/AFSK.h +++ b/AFSK.h @@ -111,6 +111,8 @@ public: inline bool crcOK() { return (fcs == 0xF0B8); } + + void print(Stream *s); private: #ifdef PACKET_PREALLOCATE uint8_t dataPtr[128]; diff --git a/examples/AX25Receive/AX25Receive.ino b/examples/AX25Receive/AX25Receive.ino index 4f4efca..c16576d 100644 --- a/examples/AX25Receive/AX25Receive.ino +++ b/examples/AX25Receive/AX25Receive.ino @@ -2,29 +2,44 @@ #include #include +//#include + +//LiquidCrystal_I2C lcd(0x27,16,2); HamShield radio; DDS dds; +volatile uint8_t adcMax=0, adcMin=255; void setup() { Serial.begin(9600); Wire.begin(); pinMode(2, OUTPUT); pinMode(3, OUTPUT); - + /*lcd.init(); + lcd.setCursor(0,0); + lcd.print(F("RSSI:")); + lcd.setCursor(0,1); + lcd.print(F("ADC:"));*/ Serial.println(F("Radio test connection")); Serial.println(radio.testConnection(), DEC); Serial.println(F("Initialize")); delay(100); radio.initialize(); + radio.frequency(144390); + radio.setVHF(); + radio.setSQOff(); + I2Cdev::writeWord(A1846S_DEV_ADDR_SENLOW, 0x30, 0x06); + I2Cdev::writeWord(A1846S_DEV_ADDR_SENLOW, 0x30, 0x26); + I2Cdev::writeWord(A1846S_DEV_ADDR_SENLOW, 0x44, 0b11111111); Serial.println(F("Frequency")); delay(100); -// radio.setVHF(); -// radio.setRfPower(0); -// radio.setModeReceive(); - radio.setVolume1(0xFF); - radio.setVolume2(0xFF); - radio.frequency(145050); + Serial.print(F("Squelch(H/L): ")); + Serial.print(radio.getSQHiThresh()); + Serial.print(F(" / ")); + Serial.println(radio.getSQLoThresh()); + radio.setModeReceive(); + Serial.print(F("RX? ")); + Serial.println(radio.getRX()); Serial.println(F("DDS Start")); delay(100); dds.start(); @@ -35,26 +50,38 @@ void setup() { pinMode(11, INPUT); // Bodge for now, as pin 3 is hotwired to pin 11 delay(100); dds.setAmplitude(255); + //lcd.backlight(); } uint32_t last = 0; void loop() { if(radio.afsk.decoder.read() || radio.afsk.rxPacketCount()) { // A true return means something was put onto the packet FIFO - Serial.println("Packet"); // If we actually have data packets in the buffer, process them all now while(radio.afsk.rxPacketCount()) { AFSK::Packet *packet = radio.afsk.getRXPacket(); + Serial.print(F("Packet: ")); if(packet) { - for(unsigned short i = 0; i < packet->len; ++i) - Serial.write((uint8_t)packet->getByte()); + packet->print(&Serial); AFSK::PacketBuffer::freePacket(packet); } } + Serial.println(""); } - /*if(last < millis()) { - Serial.println(radio.readRSSI()); - last = millis()+1000; +/* if(last < millis()) { + uint16_t buf; + lcd.setCursor(6,0); + lcd.print(radio.readRSSI()); + lcd.print(F(" ")); + lcd.setCursor(6,1); + lcd.print(adcMax); + lcd.print(F(" / ")); + lcd.print(adcMin); + lcd.print(F(" ")); + lcd.setCursor(11,0); + lcd.print(radio.afsk.decoder.isReceiving()); + adcMin=255; adcMax=0; + last = millis()+100; }*/ } @@ -74,6 +101,12 @@ ISR(ADC_vect) { PORTD |= _BV(2); // Diagnostic pin (D2) //dds.clockTick(); radio.afsk.timer(); + if(ADCH>adcMax) { + adcMax = ADCH; + } + if(ADCH Date: Sat, 11 Jul 2015 14:23:03 -0600 Subject: [PATCH 39/44] Rename for library compatability. --- examples/{AFSK-Send/AFSK-Send.ino => AFSKSend/AFSKSend.ino} | 0 .../CrystalCalibration.ino} | 0 .../PSK31-Transmit.ino => PSK31Transmit/PSK31Transmit.ino} | 0 examples/{PSK31-Transmit => PSK31Transmit}/varicode.h | 0 .../QPSK63-Transmit.ino => QPSK63Transmit/QPSK63Transmit.ino} | 0 examples/{QPSK63-Transmit => QPSK63Transmit}/varicode.h | 0 .../SSTV-M1-Static.ino => SSTV_M1_Static/SSTV_M1_Static.ino} | 0 7 files changed, 0 insertions(+), 0 deletions(-) rename examples/{AFSK-Send/AFSK-Send.ino => AFSKSend/AFSKSend.ino} (100%) rename examples/{Crystal-Calibration/Crystal-Calibration.ino => CrystalCalibration/CrystalCalibration.ino} (100%) rename examples/{PSK31-Transmit/PSK31-Transmit.ino => PSK31Transmit/PSK31Transmit.ino} (100%) rename examples/{PSK31-Transmit => PSK31Transmit}/varicode.h (100%) rename examples/{QPSK63-Transmit/QPSK63-Transmit.ino => QPSK63Transmit/QPSK63Transmit.ino} (100%) rename examples/{QPSK63-Transmit => QPSK63Transmit}/varicode.h (100%) rename examples/{SSTV-M1-Static/SSTV-M1-Static.ino => SSTV_M1_Static/SSTV_M1_Static.ino} (100%) diff --git a/examples/AFSK-Send/AFSK-Send.ino b/examples/AFSKSend/AFSKSend.ino similarity index 100% rename from examples/AFSK-Send/AFSK-Send.ino rename to examples/AFSKSend/AFSKSend.ino diff --git a/examples/Crystal-Calibration/Crystal-Calibration.ino b/examples/CrystalCalibration/CrystalCalibration.ino similarity index 100% rename from examples/Crystal-Calibration/Crystal-Calibration.ino rename to examples/CrystalCalibration/CrystalCalibration.ino diff --git a/examples/PSK31-Transmit/PSK31-Transmit.ino b/examples/PSK31Transmit/PSK31Transmit.ino similarity index 100% rename from examples/PSK31-Transmit/PSK31-Transmit.ino rename to examples/PSK31Transmit/PSK31Transmit.ino diff --git a/examples/PSK31-Transmit/varicode.h b/examples/PSK31Transmit/varicode.h similarity index 100% rename from examples/PSK31-Transmit/varicode.h rename to examples/PSK31Transmit/varicode.h diff --git a/examples/QPSK63-Transmit/QPSK63-Transmit.ino b/examples/QPSK63Transmit/QPSK63Transmit.ino similarity index 100% rename from examples/QPSK63-Transmit/QPSK63-Transmit.ino rename to examples/QPSK63Transmit/QPSK63Transmit.ino diff --git a/examples/QPSK63-Transmit/varicode.h b/examples/QPSK63Transmit/varicode.h similarity index 100% rename from examples/QPSK63-Transmit/varicode.h rename to examples/QPSK63Transmit/varicode.h diff --git a/examples/SSTV-M1-Static/SSTV-M1-Static.ino b/examples/SSTV_M1_Static/SSTV_M1_Static.ino similarity index 100% rename from examples/SSTV-M1-Static/SSTV-M1-Static.ino rename to examples/SSTV_M1_Static/SSTV_M1_Static.ino From 8a66c135d57ea6bec794281b5515d39e901c3519 Mon Sep 17 00:00:00 2001 From: morgan Date: Sun, 12 Jul 2015 14:46:45 -0700 Subject: [PATCH 40/44] updated setup code to start integrated LNA --- HamShield.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/HamShield.cpp b/HamShield.cpp index 5c39012..f78819a 100644 --- a/HamShield.cpp +++ b/HamShield.cpp @@ -154,7 +154,7 @@ void HamShield::initialize() { tx_data = 0x0A50; I2Cdev::writeWord(devAddr, 0x59, tx_data); // Tx FM Deviation - tx_data = 0x0A10; + tx_data = 0x1425; //0x0A10; I2Cdev::writeWord(devAddr, 0x62, tx_data); // Modu_det_thresh (sq setting) tx_data = 0x2494; From 4f1c863487687877914125415889fef61ec82c50 Mon Sep 17 00:00:00 2001 From: Stephen Olesen Date: Mon, 13 Jul 2015 18:50:48 -0600 Subject: [PATCH 41/44] Default refclk now 9600, remove old table and serial debugging. --- DDS.h | 22 ++-------------------- 1 file changed, 2 insertions(+), 20 deletions(-) diff --git a/DDS.h b/DDS.h index 037425b..57cf40b 100644 --- a/DDS.h +++ b/DDS.h @@ -46,7 +46,7 @@ typedef uint32_t ddsAccumulator_t; // expense of CPU time. It maxes out around 62000 (TBD) // May be overridden in the sketch to improve performance #ifndef DDS_REFCLK_DEFAULT -#define DDS_REFCLK_DEFAULT 38400 +#define DDS_REFCLK_DEFAULT 9600 #endif // As each Arduino crystal is a little different, this can be fine tuned to // provide more accurate frequencies. Adjustments in the range of hundreds @@ -61,7 +61,7 @@ typedef uint32_t ddsAccumulator_t; #endif // Output some of the calculations and information about the DDS over serial -#define DDS_DEBUG_SERIAL +//#define DDS_DEBUG_SERIAL // When defined, use the 1024 element sine lookup table. This improves phase // accuracy, at the cost of more flash and CPU requirements. @@ -127,24 +127,6 @@ static const int8_t ddsSineTable[256] PROGMEM = { -81, -78, -76, -73, -71, -68, -65, -63, -60, -57, -54, -51, -49, -46, -43, -40, -37, -34, -31, -28, -25, -22, -19, -16, -12, -9, -6, -3 }; -/*static const uint8_t ddsSineTable[256] PROGMEM = { - 128,131,134,137,140,143,146,149,152,155,158,162,165,167,170,173, - 176,179,182,185,188,190,193,196,198,201,203,206,208,211,213,215, - 218,220,222,224,226,228,230,232,234,235,237,238,240,241,243,244, - 245,246,248,249,250,250,251,252,253,253,254,254,254,255,255,255, - 255,255,255,255,254,254,254,253,253,252,251,250,250,249,248,246, - 245,244,243,241,240,238,237,235,234,232,230,228,226,224,222,220, - 218,215,213,211,208,206,203,201,198,196,193,190,188,185,182,179, - 176,173,170,167,165,162,158,155,152,149,146,143,140,137,134,131, - 128,124,121,118,115,112,109,106,103,100,97,93,90,88,85,82, - 79,76,73,70,67,65,62,59,57,54,52,49,47,44,42,40, - 37,35,33,31,29,27,25,23,21,20,18,17,15,14,12,11, - 10,9,7,6,5,5,4,3,2,2,1,1,1,0,0,0, - 0,0,0,0,1,1,1,2,2,3,4,5,5,6,7,9, - 10,11,12,14,15,17,18,20,21,23,25,27,29,31,33,35, - 37,40,42,44,47,49,52,54,57,59,62,65,67,70,73,76, - 79,82,85,88,90,93,97,100,103,106,109,112,115,118,121,124 -};*/ #endif /* DDS_TABLE_LARGE */ class DDS { From 45ec01bd31d48288ebd5bb3a83730694fc202527 Mon Sep 17 00:00:00 2001 From: Stephen Olesen Date: Mon, 13 Jul 2015 18:53:18 -0600 Subject: [PATCH 42/44] Rewrite of framing to always be pure frames, no escapes. Added parser for packets to pretty print them to serial. Added parser for easier packet handling on receipt. Moved bitclock timer to outside ISR loop. --- AFSK.cpp | 297 ++++++++++++++++++++++++++++++++++++++++++++----------- AFSK.h | 59 +++++++---- 2 files changed, 280 insertions(+), 76 deletions(-) diff --git a/AFSK.cpp b/AFSK.cpp index 26ebbd2..1fcfab6 100644 --- a/AFSK.cpp +++ b/AFSK.cpp @@ -22,57 +22,69 @@ volatile unsigned long lastTx = 0; volatile unsigned long lastTxEnd = 0; volatile unsigned long lastRx = 0; -#define T_BIT ((unsigned int)(9600/1200)) +#define T_BIT ((unsigned int)(SAMPLERATE/BITRATE)) #ifdef PACKET_PREALLOCATE SimpleFIFO preallocPool; +AFSK::Packet preallocPackets[PPOOL_SIZE]; #endif void AFSK::Encoder::process() { - // Check what clock pulse we're on - if(bitClock == 0) { // We are onto our next bit timing - // We're on the start of a byte position, so fetch one - if(bitPosition == 0) { - if(preamble) { // Still in preamble - currentByte = HDLC_PREAMBLE; - --preamble; // Decrement by one - } else { - if(!packet) { // We aren't on a packet, grab one - // Unless we already sent enough - if(maxTx-- == 0) { - stop(); - lastTxEnd = millis(); - return; - } - packet = pBuf.getPacket(); - if(!packet) { // There actually weren't any - stop(); // Stop transmitting and return - lastTxEnd = millis(); - return; - } - lastTx = millis(); - currentBytePos = 0; + // We're on the start of a byte position, so fetch one + if(bitPosition == 0) { + if(preamble) { // Still in preamble + currentByte = HDLC_PREAMBLE; + --preamble; // Decrement by one + } else { + if(!packet) { // We aren't on a packet, grab one + // Unless we already sent enough + if(maxTx-- == 0) { + stop(); + lastTxEnd = millis(); + return; } - - // We ran out of actual data, provide an HDLC frame (idle) - if(currentBytePos++ == packet->len) { - pBuf.freePacket(packet); - packet = pBuf.getPacket(); // Get the next, if any - currentBytePos = 0; - currentByte = HDLC_FRAME; - hdlc = true; + packet = pBuf.getPacket(); + if(!packet) { // There actually weren't any + stop(); // Stop transmitting and return + lastTxEnd = millis(); + return; + } + lastTx = millis(); + currentBytePos = 0; + nextByte = HDLC_FRAME; // Our next output should be a frame boundary + hdlc = true; + } + + // We ran out of actual data, provide an HDLC frame (idle) + if(currentBytePos == packet->len && nextByte == 0) { + // We also get here if nextByte isn't set, to handle empty frames + pBuf.freePacket(packet); + packet = pBuf.getPacket(); // Get the next, if any + //packet = NULL; + currentBytePos = 0; + nextByte = 0; + currentByte = HDLC_FRAME; + hdlc = true; + } else { + if(nextByte) { + // We queued up something other than the actual stream to be sent next + currentByte = nextByte; + nextByte = 0; } else { - // Grab the next byte - currentByte = packet->getByte(); //[currentBytePos++]; - if(currentByte == HDLC_ESCAPE) { - currentByte = packet->getByte(); //[currentBytePos++]; - hdlc = true; + // Get the next byte to send, but if it's an HDLC frame, escape it + // and queue the real byte for the next cycle. + currentByte = packet->getByte(); + if(currentByte == HDLC_FRAME) { + nextByte = currentByte; + currentByte = HDLC_ESCAPE; } else { - hdlc = false; + currentBytePos++; } + hdlc = false; // If we get here, it will be NRZI bit stuffed } } } + } // Pickup the last bit currentBit = currentByte & 0x1; @@ -94,15 +106,12 @@ void AFSK::Encoder::process() { // So, if not a 1, toggle to the opposite tone if(!currentBit) currentTone = !currentTone; - } - - // Advance the bitclock here, to let first bit be sent early - if(++bitClock == T_BIT) - bitClock = 0; - + if(currentTone == 0) { + PORTD |= _BV(7); dds->setFrequency(AFSK_SPACE); } else { + PORTD &= ~_BV(7); dds->setFrequency(AFSK_MARK); } } @@ -120,7 +129,7 @@ bool AFSK::Encoder::start() { currentBit = 0; lastZero = 0; bitPosition = 0; - bitClock = 0; + //bitClock = 0; preamble = 0b110000; // 6.7ms each, 23 = 153ms done = false; hdlc = true; @@ -128,6 +137,7 @@ bool AFSK::Encoder::start() { currentBytePos = 0; maxTx = 3; sending = true; + nextByte = 0; dds->setFrequency(0); dds->on(); return true; @@ -137,13 +147,14 @@ void AFSK::Encoder::stop() { randomWait = 0; sending = false; done = true; + dds->setFrequency(0); dds->off(); } AFSK::Decoder::Decoder() { // Initialize the sampler delay line (phase shift) - for(unsigned char i = 0; i < SAMPLEPERBIT/2; i++) - delay_fifo.enqueue(0); + //for(unsigned char i = 0; i < SAMPLEPERBIT/2; i++) + // delay_fifo.enqueue(0); } bool AFSK::HDLCDecode::hdlcParse(bool bit, SimpleFIFO *fifo) { @@ -271,16 +282,14 @@ bool AFSK::Decoder::read() { bool escaped = false; if(c == HDLC_ESCAPE) { // We received an escaped byte, mark it escaped = true; - currentPacket->append(HDLC_ESCAPE); // Append without FCS + // Do we want to keep HDLC_ESCAPEs in the packet? + //currentPacket->append(HDLC_ESCAPE); // Append without FCS c = rx_fifo.dequeue(); // Reset to the next character } // Append all the bytes // This will include unescaped HDLC_FRAME bytes - //if(c == HDLC_FRAME && !escaped) - //currentPacket->append(c); // Framing bytes don't get FCS updates - //else - if(c != HDLC_FRAME) + if(c != HDLC_FRAME || escaped) // Append frame if it is escaped currentPacket->appendFCS(c); // Escaped characters and all else go into FCS if(currentPacket->len > PACKET_MAX_LEN) { @@ -376,6 +385,12 @@ AFSK::PacketBuffer::PacketBuffer() { for(unsigned char i = 0; i < PACKET_BUFFER_SIZE; ++i) { packets[i] = 0x0; } +#ifdef PACKET_PREALLOCATE + for(unsigned char i = 0; i < PPOOL_SIZE; ++i) { + // Put some empty packets in the FIFO + preallocPool.enqueue(&preallocPackets[i]); + } +#endif } unsigned char AFSK::PacketBuffer::readyCount() volatile { @@ -421,7 +436,7 @@ void AFSK::Packet::init(unsigned short dlen) { ready = 0; #ifdef PACKET_PREALLOCATE freeData = 0; - maxLen = 128; // Put it here instead + maxLen = PACKET_MAX_LEN; // Put it here instead #else freeData = 1; dataPtr = (uint8_t *)malloc(dlen+16); @@ -440,7 +455,9 @@ AFSK::Packet *AFSK::PacketBuffer::makePacket(unsigned short dlen) { ATOMIC_BLOCK(ATOMIC_RESTORESTATE) { //Packet *p = findPooledPacket(); #ifdef PACKET_PREALLOCATE - p = preallocPool.dequeue(); + if(preallocPool.count()) + p = preallocPool.dequeue(); + else p = NULL; #else p = new Packet(); //(Packet *)malloc(sizeof(Packet)); #endif @@ -518,8 +535,163 @@ size_t AFSK::Packet::appendCallsign(const char *callsign, uint8_t ssid, bool fin appendFCS(ssidField); } -void AFSK::Packet::print(Stream *s) { +#ifdef PACKET_PARSER +// Process the AX25 frame and turn it into a bunch of useful strings +bool AFSK::Packet::parsePacket() { + uint8_t *d = dataPtr; + int i; + + // First 7 bytes are destination-ssid + for(i = 0; i < 6; i++) { + dstCallsign[i] = (*d++)>>1; + if(dstCallsign[i] == ' ') { + dstCallsign[i] = '\0'; + } + } + dstCallsign[6] = '\0'; + dstSSID = ((*d++)>>1) & 0xF; + + // Next 7 bytes are source-ssid + for(i = 0; i < 6; i++) { + srcCallsign[i] = (*d++)>>1; + if(srcCallsign[i] == ' ') { + srcCallsign[i] = '\0'; + } + } + srcCallsign[6] = '\0'; + srcSSID = *d++; // Don't shift yet, we need the LSB + + digipeater[0][0] = '\0'; // Set null in case we have none anyway + if((srcSSID & 1) == 0) { // Not the last address field + int digi; // Which digi we're on + for(digi = 0; digi < 8; digi++) { + for(i = 0; i < 6; i++) { + digipeater[digi][i] = (*d++)>>1; + if(digipeater[digi][i] == ' ') { + digipeater[digi][i] = '\0'; + } + } + uint8_t last = (*d) & 1; + digipeaterSSID[digi] = ((*d++)>>1) & 0xF; + if(last == 1) + break; + } + digipeater[digi][6] = '\0'; + for(digi += 1; digi<8; digi++) { // Empty out the rest of them + digipeater[digi][0] = '\0'; + } + } + + // Now handle the SSID itself + srcSSID >>= 1; + srcSSID &= 0xF; + + // After the address parsing, we end up on the control field + control = *d++; + // We have a PID if control type is U or I + // Control & 1 == 0 == I frame + // Control & 3 == 3 == U frame + if((control & 1) == 0 || (control & 3) == 3) + pid = *d++; + else pid = 0; + + // If there is no PID, we have no data + if(!pid) { + iFrameData = NULL; + return true; + } + + // At this point, we've walked far enough along that data is just at d + iFrameData = d; + + // Cheat a little by setting the first byte of the FCS to 0, making it a string + // First FCS byte is found at -2, HDLC flags aren't in this buffer + dataPtr[len-2] = '\0'; + + return true; +} +#endif + +void AFSK::Packet::printPacket(Stream *s) { uint8_t i; +#ifdef PACKET_PARSER + if(!parsePacket()) { + s->print(F("Packet not valid")); + return; + } + + s->print(srcCallsign); + if(srcSSID > 0) { + s->write('-'); + s->print(srcSSID); + } + s->print(F(" > ")); + s->print(dstCallsign); + if(dstSSID > 0) { + s->write('-'); + s->print(dstSSID); + } + s->write(' '); + if(digipeater[0][0] != '\0') { + s->print(F("via ")); + for(i = 0; i < 8; i++) { + if(digipeater[i][0] == '\0') + break; + s->print(digipeater[i]); + if(digipeaterSSID[i] != 0) { + s->write('-'); + s->print(digipeaterSSID[i]); + } + if((digipeaterSSID[i] & _BV(7)) == _BV(7)) { + s->write('*'); // Digipeated already + } + // If we might have more, check to add a comma + if(i < 7 && digipeater[i+1][0] != '\0') { + s->write(','); + } + s->write(' '); + } + } + + // This is an S frame, we can only print control info + if(control & 3 == 1) { + switch((control>>2)&3) { + case 0: + s->print(F("RR")); + break; + case 1: + s->print(F("RNR")); + break; + case 2: + s->print(F("REJ")); + break; + case 3: // Undefined + s->print(F("unk")); + break; + } + // Use a + to indicate poll bit + if(control & _BV(4) == _BV(4)) { + s->write('+'); + } + } else if((control & 3) == 3) { // U Frame + s->print(F("U(")); + s->print(control, HEX); + s->write(','); + s->print(pid, HEX); + s->print(F(") ")); + } else if((control & 1) == 0) { // I Frame + s->print(F("I(")); + s->print(control, HEX); + s->write(','); + s->print(pid, HEX); + s->print(F(") ")); + } + s->print(F("len ")); + s->print(len); + s->print(F(": ")); + s->print((char *)iFrameData); + s->println(); +#else // no packet parser, do a rudimentary print // Second 6 bytes are source callsign for(i=7; i<13; i++) { s->write(*(dataPtr+i)>>1); @@ -527,7 +699,7 @@ void AFSK::Packet::print(Stream *s) { // SSID s->write('-'); s->print((*(dataPtr+13) >> 1) & 0xF); - s->print(" -> "); + s->print(F(" -> ")); // First 6 bytes are destination callsign for(i=0; i<6; i++) { s->write(*(dataPtr+i)>>1); @@ -540,14 +712,23 @@ void AFSK::Packet::print(Stream *s) { for(i = 15; iwrite(*(dataPtr+i)); } +#endif } // Determine what we want to do on this ADC tick. void AFSK::timer() { - if(encoder.isSending()) + static uint8_t tcnt = 0; + if(++tcnt == T_BIT && encoder.isSending()) { + PORTD |= _BV(6); + // Only run the encoder every 8th tick + // This is actually DDS RefClk / 1200 = 8, set as T_BIT + // A different refclk needs a different value encoder.process(); - else + tcnt = 0; + PORTD &= ~_BV(6); + } else { decoder.process(((int8_t)(ADCH - 128))); + } } void AFSK::start(DDS *dds) { diff --git a/AFSK.h b/AFSK.h index 68504e7..e7b2418 100644 --- a/AFSK.h +++ b/AFSK.h @@ -15,15 +15,19 @@ #define PACKET_BUFFER_SIZE 2 #define PACKET_STATIC 0 +// Enable the packet parser which will tokenize the AX25 frame into easy strings +#define PACKET_PARSER + // If this is set, all the packet buffers will be pre-allocated at compile time // This will use more RAM, but can make it easier to do memory planning. // TODO: Make this actually work right and not crash. -//#define PACKET_PREALLOCATE +#define PACKET_PREALLOCATE -// This is with all the digis, two addresses, framing and full payload -// Two more bytes are added for HDLC_ESCAPEs -#define PACKET_MAX_LEN 512 -#define AX25_PACKET_HEADER_MINLEN 22 +// This is with all the digis, two addresses, and full payload +// Dst(7) + Src(7) + Digis(56) + Ctl(1) + PID(1) + Data(0-256) + FCS(2) +#define PACKET_MAX_LEN 330 +// Minimum is Dst + Src + Ctl + FCS +#define AX25_PACKET_HEADER_MINLEN 17 // HDLC framing bits #define HDLC_FRAME 0x7E @@ -65,9 +69,11 @@ public: } inline void start() { fcs = 0xffff; - *dataPos++ = HDLC_ESCAPE; - *dataPos++ = HDLC_FRAME; - len = 2; + // No longer put an explicit frame start here + //*dataPos++ = HDLC_ESCAPE; + //*dataPos++ = HDLC_FRAME; + //len = 2; + len = 0; } inline bool append(char c) { @@ -96,8 +102,9 @@ public: inline void finish() { append(~(fcs & 0xff)); append(~((fcs>>8) & 0xff)); - append(HDLC_ESCAPE); - append(HDLC_FRAME); + // No longer append the frame boundaries themselves + //append(HDLC_ESCAPE); + //append(HDLC_FRAME); ready = 1; } @@ -111,13 +118,27 @@ public: inline bool crcOK() { return (fcs == 0xF0B8); } - - void print(Stream *s); +#ifdef PACKET_PARSER + bool parsePacket(); +#endif + void printPacket(Stream *s); private: #ifdef PACKET_PREALLOCATE - uint8_t dataPtr[128]; + uint8_t dataPtr[PACKET_MAX_LEN]; // 256 byte I frame + headers max of 78 #else uint8_t *dataPtr; +#endif +#ifdef PACKET_PARSER + char srcCallsign[7]; + uint8_t srcSSID; + char dstCallsign[7]; + uint8_t dstSSID; + char digipeater[8][7]; + uint8_t digipeaterSSID[8]; + uint8_t *iFrameData; + uint8_t length; + uint8_t control; + uint8_t pid; #endif uint8_t *dataPos, *readPos; unsigned short fcs; @@ -156,6 +177,7 @@ public: done = true; packet = 0x0; currentBytePos = 0; + nextByte = 0; } void setDDS(DDS *d) { dds = d; } volatile inline bool isSending() volatile { @@ -184,12 +206,13 @@ public: byte lastZero : 3; byte bitPosition : 3; byte preamble : 6; - byte bitClock; + //byte bitClock; bool hdlc; + byte nextByte; byte maxTx; Packet *packet; PacketBuffer pBuf; - unsigned char currentBytePos; + unsigned int currentBytePos; volatile unsigned long randomWait; volatile bool done; DDS *dds; @@ -228,7 +251,7 @@ public: } private: Packet *currentPacket; - SimpleFIFO delay_fifo; + //SimpleFIFO delay_fifo; SimpleFIFO rx_fifo; // This should be drained fairly often int16_t iir_x[2]; int16_t iir_y[2]; @@ -243,12 +266,12 @@ public: inline bool read() { return decoder.read(); } - inline bool txReady() volatile { + volatile inline bool txReady() volatile { if(encoder.isDone() && encoder.hasPackets()) return true; return false; } - inline bool isDone() volatile { return encoder.isDone(); } + volatile inline bool isDone() volatile { return encoder.isDone(); } inline bool txStart() { if(decoder.isReceiving()) { encoder.setRandomWait(); From a22b814d63e7dc241b1ffa691b7e36dabaff6621 Mon Sep 17 00:00:00 2001 From: Stephen Olesen Date: Mon, 13 Jul 2015 18:55:07 -0600 Subject: [PATCH 43/44] KISS TNC mode for the AX25 stack and example. --- KISS.cpp | 88 ++++++++++++++++++++++++++++++++++++++++++ KISS.h | 35 +++++++++++++++++ examples/KISS/KISS.ino | 36 +++++++++++++++++ 3 files changed, 159 insertions(+) create mode 100644 KISS.cpp create mode 100644 KISS.h create mode 100644 examples/KISS/KISS.ino diff --git a/KISS.cpp b/KISS.cpp new file mode 100644 index 0000000..06a2115 --- /dev/null +++ b/KISS.cpp @@ -0,0 +1,88 @@ +#include +#include "AFSK.h" +#include "KISS.h" + +//AFSK::Packet kissPacket; +bool inFrame = false; +uint8_t kissBuffer[PACKET_MAX_LEN]; +uint16_t kissLen = 0; + +// Inside the KISS loop, we basically wait for data to come in from the +// KISS equipment, and look if we have anything to relay along +void KISS::loop() { + static bool currentlySending = false; + if(radio->afsk.decoder.read() || radio->afsk.rxPacketCount()) { + // A true return means something was put onto the packet FIFO + // If we actually have data packets in the buffer, process them all now + while(radio->afsk.rxPacketCount()) { + AFSK::Packet *packet = radio->afsk.getRXPacket(); + if(packet) { + writePacket(packet); + AFSK::PacketBuffer::freePacket(packet); + } + } + } + // Check if we have incoming data to turn into a packet + if(io->available()) { + uint8_t c = (uint8_t)io->read(); + if(c == KISS_FEND) { + if(inFrame && kissLen > 0) { + int i; + AFSK::Packet *packet = AFSK::PacketBuffer::makePacket(PACKET_MAX_LEN); + packet->start(); + for(i = 0; i < kissLen; i++) { + packet->append(kissBuffer[i]); + } + packet->finish(); + radio->afsk.encoder.putPacket(packet); + } + kissLen = 0; + inFrame = false; + } + // We're inside the boundaries of a FEND + if(inFrame) { + // Unescape the incoming data + if(c == KISS_FESC) { + c = io->read(); + if(c == KISS_TFESC) { + c = KISS_FESC; + } else { + c = KISS_FEND; + } + } + kissBuffer[kissLen++] = c; + } + if(kissLen == 0 && c != KISS_FEND) { + if((c & 0xf) == 0) // First byte<3:0> should be a 0, otherwise we're having options + inFrame = true; + } + } + if(radio->afsk.txReady()) { + radio->setModeTransmit(); + currentlySending = true; + if(!radio->afsk.txStart()) { // Unable to start for some reason + radio->setModeReceive(); + currentlySending = false; + } + } + if(currentlySending && radio->afsk.encoder.isDone()) { + radio->setModeReceive(); + currentlySending = false; + } +} + +void KISS::writePacket(AFSK::Packet *p) { + int i; + io->write(KISS_FEND); + io->write((uint8_t)0); // Host to TNC port identifier + for(i = 0; i < p->len-2; i++) { + char c = p->getByte(i); + if(c == KISS_FEND || c == KISS_FESC) { + io->write(KISS_FESC); + io->write((c==KISS_FEND?KISS_TFEND:KISS_TFESC)); + } else { + io->write(c); + } + } + io->write(KISS_FEND); +} diff --git a/KISS.h b/KISS.h new file mode 100644 index 0000000..dd839e6 --- /dev/null +++ b/KISS.h @@ -0,0 +1,35 @@ +#ifndef _KISS_H_ +#define _KISS_H_ + +#include +#include "AFSK.h" + +#define KISS_FEND 0xC0 +#define KISS_FESC 0xDB +#define KISS_TFEND 0xDC +#define KISS_TFESC 0xDD + +class KISS { +public: + KISS(Stream *_io, HamShield *h, DDS *d) : io(_io), radio(h), dds(d) {} + bool read(); + void writePacket(AFSK::Packet *); + void loop(); + inline void isr() { + static uint8_t tcnt = 0; + TIFR1 = _BV(ICF1); // Clear the timer flag + dds->clockTick(); + if(++tcnt == (DDS_REFCLK_DEFAULT/9600)) { + PORTD |= _BV(2); // Diagnostic pin (D2) + radio->afsk.timer(); + tcnt = 0; + } + PORTD &= ~(_BV(2)); + } +private: + Stream *io; + HamShield *radio; + DDS *dds; +}; + +#endif /* _KISS_H_ */ diff --git a/examples/KISS/KISS.ino b/examples/KISS/KISS.ino new file mode 100644 index 0000000..7cec7e1 --- /dev/null +++ b/examples/KISS/KISS.ino @@ -0,0 +1,36 @@ +#define DDS_REFCLK_DEFAULT 19200 + +#include +#include +#include + +HamShield radio; +DDS dds; +KISS kiss(&Serial, &radio, &dds); + +void setup() { + pinMode(2, OUTPUT); + pinMode(3, OUTPUT); + pinMode(11, INPUT); + + Serial.begin(9600); + Wire.begin(); + radio.initialize(); + radio.setVHF(); + radio.setSQOff(); + radio.setFrequency(145010); + I2Cdev::writeWord(A1846S_DEV_ADDR_SENLOW, 0x30, 0x06); + I2Cdev::writeWord(A1846S_DEV_ADDR_SENLOW, 0x30, 0x26); + I2Cdev::writeWord(A1846S_DEV_ADDR_SENLOW, 0x44, 0b0000011111111111); + + dds.start(); + radio.afsk.start(&dds); +} + +void loop() { + kiss.loop(); +} + +ISR(ADC_vect) { + kiss.isr(); +} From ab7613c8d322c6d328d9457c699013db95694f0b Mon Sep 17 00:00:00 2001 From: Stephen Olesen Date: Mon, 13 Jul 2015 19:01:32 -0600 Subject: [PATCH 44/44] Updated AFSK/AX25 examples for modifications to AFSK library. --- examples/AFSKSend/AFSKSend.ino | 61 +++++++++++++--------------- examples/AX25Receive/AX25Receive.ino | 49 +--------------------- 2 files changed, 31 insertions(+), 79 deletions(-) diff --git a/examples/AFSKSend/AFSKSend.ino b/examples/AFSKSend/AFSKSend.ino index 063c0cb..fbc9f99 100644 --- a/examples/AFSKSend/AFSKSend.ino +++ b/examples/AFSKSend/AFSKSend.ino @@ -1,17 +1,22 @@ -#define DDS_REFCLK_DEFAULT 9600 - #include #include HamShield radio; DDS dds; +#define DON(p) PORTD |= _BV((p)) +#define DOFF(p) PORTD &= ~(_BV((p))) + void setup() { Serial.begin(9600); Wire.begin(); pinMode(2, OUTPUT); pinMode(3, OUTPUT); - + pinMode(4, OUTPUT); + pinMode(5, OUTPUT); + pinMode(6, OUTPUT); + pinMode(7, OUTPUT); + Serial.println(F("Radio test connection")); Serial.println(radio.testConnection(), DEC); Serial.println(F("Initialize")); @@ -20,21 +25,24 @@ void setup() { Serial.println(F("Frequency")); delay(100); radio.setVHF(); - radio.frequency(145050); - radio.setRfPower(0); - Serial.println(F("DDS Start")); + radio.frequency(145010); + //radio.setRfPower(0); delay(100); dds.start(); - Serial.println(F("AFSK start")); delay(100); radio.afsk.start(&dds); - Serial.println(F("Starting...")); pinMode(11, INPUT); // Bodge for now, as pin 3 is hotwired to pin 11 delay(100); + dds.setFrequency(0); + dds.on(); + dds.setAmplitude(255); + I2Cdev::writeWord(A1846S_DEV_ADDR_SENLOW, 0x44, 0b0000011111111111); + //I2Cdev::writeWord(A1846S_DEV_ADDR_SENLOW, 0x53, 0x0); + //I2Cdev::writeWord(A1846S_DEV_ADDR_SENLOW, 0x32, 0xffff); } void loop() { - // put your main code here, to run repeatedly: + DON(6); AFSK::Packet *packet = AFSK::PacketBuffer::makePacket(22 + 32); packet->start(); packet->appendCallsign("VE6SLP",0); @@ -42,7 +50,8 @@ void loop() { packet->appendFCS(0x03); packet->appendFCS(0xf0); packet->print(F("Hello ")); - packet->println(millis()); + packet->print(millis()); + packet->println(F("\r\nThis is a test of the HamShield Kickstarter prototype. de VE6SLP")); packet->finish(); bool ret = radio.afsk.putTXPacket(packet); @@ -50,10 +59,10 @@ void loop() { if(radio.afsk.txReady()) { Serial.println(F("txReady")); radio.setModeTransmit(); - //delay(100); if(radio.afsk.txStart()) { Serial.println(F("txStart")); } else { + Serial.println(F("Tx Start failure")); radio.setModeReceive(); } } @@ -61,37 +70,25 @@ void loop() { Serial.println("tick"); // Wait up to 2.5 seconds to finish sending, and stop transmitter. // TODO: This is hackery. + DOFF(6); for(int i = 0; i < 500; i++) { if(radio.afsk.encoder.isDone()) break; delay(50); + Serial.println("Not done"); } Serial.println("Done sending"); + delay(100); radio.setModeReceive(); - delay(30000); + delay(2000); } -/*ISR(TIMER2_OVF_vect) { - TIFR2 = _BV(TOV2); - static uint8_t tcnt = 0; - if(++tcnt == 8) { - digitalWrite(2, HIGH); - dds.clockTick(); - digitalWrite(2, LOW); - tcnt = 0; - } -}*/ ISR(ADC_vect) { - static uint8_t tcnt = 0; TIFR1 = _BV(ICF1); // Clear the timer flag - PORTD |= _BV(2); // Diagnostic pin (D2) + DON(4); dds.clockTick(); - if(++tcnt == 1) { - if(radio.afsk.encoder.isSending()) { - radio.afsk.timer(); - } - tcnt = 0; - } - PORTD &= ~(_BV(2)); // Pin D2 off again + DON(5); + radio.afsk.timer(); + DOFF(5); + DOFF(4); } - diff --git a/examples/AX25Receive/AX25Receive.ino b/examples/AX25Receive/AX25Receive.ino index c16576d..cbc6671 100644 --- a/examples/AX25Receive/AX25Receive.ino +++ b/examples/AX25Receive/AX25Receive.ino @@ -1,31 +1,20 @@ -#define DDS_REFCLK_DEFAULT 9600 - #include #include -//#include - -//LiquidCrystal_I2C lcd(0x27,16,2); HamShield radio; DDS dds; -volatile uint8_t adcMax=0, adcMin=255; void setup() { Serial.begin(9600); Wire.begin(); pinMode(2, OUTPUT); pinMode(3, OUTPUT); - /*lcd.init(); - lcd.setCursor(0,0); - lcd.print(F("RSSI:")); - lcd.setCursor(0,1); - lcd.print(F("ADC:"));*/ Serial.println(F("Radio test connection")); Serial.println(radio.testConnection(), DEC); Serial.println(F("Initialize")); delay(100); radio.initialize(); - radio.frequency(144390); + radio.frequency(145010); radio.setVHF(); radio.setSQOff(); I2Cdev::writeWord(A1846S_DEV_ADDR_SENLOW, 0x30, 0x06); @@ -50,7 +39,6 @@ void setup() { pinMode(11, INPUT); // Bodge for now, as pin 3 is hotwired to pin 11 delay(100); dds.setAmplitude(255); - //lcd.backlight(); } uint32_t last = 0; @@ -62,51 +50,18 @@ void loop() { AFSK::Packet *packet = radio.afsk.getRXPacket(); Serial.print(F("Packet: ")); if(packet) { - packet->print(&Serial); + packet->printPacket(&Serial); AFSK::PacketBuffer::freePacket(packet); } } - Serial.println(""); } -/* if(last < millis()) { - uint16_t buf; - lcd.setCursor(6,0); - lcd.print(radio.readRSSI()); - lcd.print(F(" ")); - lcd.setCursor(6,1); - lcd.print(adcMax); - lcd.print(F(" / ")); - lcd.print(adcMin); - lcd.print(F(" ")); - lcd.setCursor(11,0); - lcd.print(radio.afsk.decoder.isReceiving()); - adcMin=255; adcMax=0; - last = millis()+100; - }*/ } -/*ISR(TIMER2_OVF_vect) { - TIFR2 = _BV(TOV2); - static uint8_t tcnt = 0; - if(++tcnt == 8) { - digitalWrite(2, HIGH); - dds.clockTick(); - digitalWrite(2, LOW); - tcnt = 0; - } -}*/ ISR(ADC_vect) { static uint8_t tcnt = 0; TIFR1 = _BV(ICF1); // Clear the timer flag PORTD |= _BV(2); // Diagnostic pin (D2) //dds.clockTick(); radio.afsk.timer(); - if(ADCH>adcMax) { - adcMax = ADCH; - } - if(ADCH