My code for using an AD9851 synthesiser, with a DS3231 Real Time Clock, to send WSPR messages has always been a bit messy. Now I have tidied it up.
Note that my code sends out signals on Arduino pins A0 & A1 which are used to control the LPF band switching in a PA. You can see the codes sent over a 4 pin 3.5mm jack in the code. At the PA these switch the LPF relays to chose the correct band, as well as switching from RX/TX modes.
The code uses four libraries, my own AD9851.h and Oled.h, and public Rotary.h & WsprMessage.h.
My libraries are listed below. All my sketches and libraries can be downloaded from the blogpost above.
CODE
// WSPR with AD9851 synth // V4.0 18-4-15 simplify code to remove wspr message copy // enter callsign, MH locator and Power in the code below // HEADERS & LIBRARIES #include "Oled.h" #include "ADS9851.h" #include "Rotary.h" #include "WsprMessage.h" // CONNECTIONS #define RESET 8 // AD9851 #define DATA 9 #define FQ_UD 10 #define W_CLK 11 #define CLK 2 // ENC #define DT 3 #define SW 4 // button // PARAMETERS // RTC I2C address #define RTCADDR 0x68 // PA modes #define RX 0 #define TX40 1 #define TX30 2 #define TX20 3 // xtal calibration, WSPR symbollength adj, orginal 682 #define CALIBRATE 180000000 #define SYMBOLLENGTH 684 // OBJECTS // create AD9851 object ADS9851 ads; // Encoder object Rotary enc = Rotary(DT, CLK); // GLOBAL VARIABLES // RTC Data, Decimal byte sec, mns, hrs; byte dow; byte dy, mth, yr; // repeat TX interval (sec) uint8_t repeat; // WSPR data char callsign[10] = " M0IFA"; // your callsign 6 char (SP first if needed) char loc_short[5] = "IO92"; // your short locator (read from EEPROM) char power[5] = "30"; // TX power in dBm, 30 = 1W // band byte band = 1; // band 1 = 40m // table of band frequencies 0 RX, 1-3 TX40, 30, 20 double freqTable[] = { 0, 7040100, 10140200, 14097100}; // WSPR cHz Freq for symbols 0, 1, 2, 3. // = 145.48 x 0, 1, 2 and 3 double freqChz[] = {0.0, 146.0, 291.0, 437.0}; // WSPR Frequency & phase double freqHz = freqTable[band]; volatile double freqStep = 10; // (Hz) freqStep 10Hz uint8_t phase = 0; // phase // TX flag bool tx; // freq change flag bool freqChange; // Data from WsprMessage unsigned char *sym; // ptr to symbol vector in WsprMessage.h // SETUP void setup() { Serial.begin(9600); // encoder pinMode(DT, INPUT_PULLUP); pinMode(CLK, INPUT_PULLUP); // button pinMode(SW, INPUT_PULLUP); // jack PA control outputs pinMode(A0, OUTPUT); pinMode(A1, OUTPUT); // setup interrupts from DT or CLK for tuning PCICR |= (1 << PCIE2); PCMSK2 |= (1 << PCINT18) | (1 << PCINT19); sei(); // oled init, sets I2C addr to 0x3C oled.begin(); // ads begin ads.begin(W_CLK, FQ_UD, DATA, RESET); // calibrate to xtal actual frequency ads.calibrate(CALIBRATE); // create WSPR message, define MSG_SIZE and get pointer to symbols WsprMessage WsprMessage(callsign, loc_short, atoi(power)); sym = WsprMessage.symbols; // get address ptr to symbols repeat = 4; // repeat every 4 min, must be even number // Should be random 4-20 or so tx = false; // display flag // PA init TX40 setPA(band); // PA S/W requires that band must be initialised by TX command, // followed by return to passive RX delay(1000); setPA(RX); freqChange = false; } void loop() { // get time & display readRTC(); dispUpdate(); if (button()) { // button updates band choice if (band < 3) band += 1; else band = 1; if (band == 1) setPA(TX40); // 40m if (band == 2) setPA(TX30); // 30m if (band == 3) setPA(TX20); // 20m delay(500); setPA(RX); freqHz = freqTable[band]; dispUpdate(); } if (freqChange) { freqChange = false; dispUpdate(); } // send WSPR? if (mns % repeat == 0 && sec == 0) { // send tx = true; // display msgTxt if (band == 1) setPA(TX40); // 40m if (band == 2) setPA(TX30); // 30m if (band == 3) setPA(TX20); // 20m dispUpdate(); sendWspr(SYMBOLLENGTH); tx = false; // display time setPA(RX); } } // BUTTON bool button() { if (digitalRead(SW) == LOW) { // button pressed? while (!digitalRead(SW)); // wait for release return true; } else { return false; } } // ENCODER/INTERRUPT // ISR - encoder interrupt service routine ISR(PCINT2_vect) { unsigned char result; result = enc.process(); if (result == DIR_CW ) { freqHz += freqStep; freqChange = true; } else if (result == DIR_CCW) { freqHz -= freqStep; freqChange = true; } } //send the WSPR msg void sendWspr(int SymbolLength) { // Send WSPR Message for (int s = 0; s < MSG_SIZE; s++) { // For all symbols, MSG_SIZE in WsprMessage.h ads.setFreq(freqHz, freqChz[sym[s]], phase); // transmit frequency delay(SymbolLength); // symbol timing } ads.down(); // disable output } // Convert BCD to decimal numbers byte bcdToDec(byte val) { return ( (val / 16 * 10) + (val % 16) ); } // READ RTC void readRTC() { // Reset the RTC register pointer Wire.beginTransmission(RTCADDR); Wire.write(0x00); Wire.endTransmission(); // request 7 bytes from the RTC address Wire.requestFrom(RTCADDR, 7); // get the time date sec = bcdToDec(Wire.read()); // 0 - 59 mns = bcdToDec(Wire.read()); // 0 - 59 hrs = bcdToDec(Wire.read() & 0b111111); // mask 12/24 bit dow = bcdToDec(Wire.read()); // 0 = Sunday dy = bcdToDec(Wire.read()); // 1 - 31 mth = bcdToDec(Wire.read()); // 0 = jan yr = bcdToDec(Wire.read()); // ..yy } // SET PA MODE // modes 0, 1, 2, 3 output on A1, A0, active LOW void setPA(int m) { // set mode switch (m) { case 0: digitalWrite(A1, HIGH); digitalWrite(A0, HIGH); // RX break; case 1: digitalWrite(A1, HIGH); digitalWrite(A0, LOW); // TX40 break; case 2: digitalWrite(A1, LOW); digitalWrite(A0, HIGH); // TX30 break; case 3: digitalWrite(A1, LOW); digitalWrite(A0, LOW); // TX20 break; } } // picture loop, display init data void dispUpdate() { oled.firstPage(); do { // initial display dispMsg(50, 0, "WSPR"); dispFreq(15, 15, freqHz, freqChz[0], 2); // display frequency if (tx == true) { dispMsg(10, 35, callsign); dispMsg(55, 35, loc_short); dispMsg(85, 35, power); } else { dispTime(40, 35, hrs, mns, sec); } dispMsg(20, 50, "Every"); dispNum(70, 50, repeat, 0); dispMsg(85, 50, "min"); } while ( oled.nextPage() ); }LIBRARIES
Put AD9851.h & AD9851.cpp in a folder named AD9851, and put Oled.h in a folder named Oled both in your Arduino "libraries" folder
AD9851.h
// Arduino Library for AD9851 frequency synthesiser module, with 30MHz clock // V1.2 4-9-17 Antony Watts, M0IFA, code update // frequency in Hz and cHz // functions // void begin(int W_CLK, int FQ_UD, int DATA, int RESET); intialise pins and reset AD9850 // void setFreq(double Hz, double Chz, uint8_t p); set frequency(Hz) and centi-freq(Chz) // phase coding, 0-180 in 11.25deg steps 0x00, 0x01, 0x02, 0x04, 0x08, 0x10 // void calibrate(double calHz); change xtal frequency from standard 125MHz to new value // void down(); power down on, x6 remains on b00000101 #ifndef ADS9851_H #define ADS9851_H #include "Arduino.h" #define ADS_XTAL 180000000.0 class ADS9851 { public: ADS9851(); void begin(int W_CLK, int FQ_UD, int DATA, int RESET); void setFreq(double Hz, double Chz, uint8_t phase); void calibrate(double calHz); void down(); private: int _W_CLK; int _FQ_UD; int _DATA; int _RESET; double _calFreq; void update(uint32_t d, uint8_t p); void pulse(int _pin); }; #endif
AD9851.cpp
// Arduino Library for AD9851 frequency synthesiser module, with 30MHz clock // V1.2 4-9-17 Antony Watts, M0IFA, pulser code update // frequency in Hz and cHz // W_CLK, FQ_UD, DATA, RESET to (e.g.) D11, D10, D9 & D8 // functions // void begin(int W_CLK, int FQ_UD, int DATA, int RESET); reset, serial, down // void setFreq(double Hz, double Chz, uint8_t p); set f(Hz) and cHz(Chz), // p)hase in steps 0x00, 0x01, 0x02, 0x04, 0x08, 0x10 (11.5, 22.5, 45. 90, 180deg) // void calibrate(double calHz); correct xtal frequency from standard 180MHz (30MHz x6) // void down(); freq zero, power down (power up with setFreq()) #include "Arduino.h" #include "ADS9851.h" // constructor ADS9851::ADS9851() { } // init calFreq, pins, reset & serial mode void ADS9851::begin(int W_CLK, int FQ_UD, int DATA, int RESET) { _W_CLK = W_CLK; _FQ_UD = FQ_UD; _DATA = DATA; _RESET = RESET; _calFreq = ADS_XTAL; pinMode(_W_CLK, OUTPUT); // outputs default to LOW pinMode(_FQ_UD, OUTPUT); pinMode(_DATA, OUTPUT); pinMode(_RESET, OUTPUT); pulse(_RESET); // reset, parallel mode, ptr to W0 pulse(_W_CLK); // switch to serial mode, xxxxx011 wired on d2-d0 down(); // init/clear freq & phase registers, power down } // write 40 bits, 4x8 freq + 8 control & phase void ADS9851::update(uint32_t fW, uint8_t cP) { for (int i=0; i <4 ; i++, fW >>= 8) { shiftOut(_DATA, _W_CLK, LSBFIRST, fW); // output 32 freq bits } shiftOut(_DATA, _W_CLK, LSBFIRST, cP); // output 8 control & phase bits pulse(_FQ_UD); } // calculate 32 freq bits, convert double to to uint32_t, set PD off & x6, update void ADS9851::setFreq(double f, double cf, uint8_t p) { uint32_t delta; delta = (uint32_t)((f + cf/100.0) * 4294967296.0 / _calFreq); p = p << 3; // PD off = ppppp000 bitSet(p, 0); // ppppp001 set x6 REFCLK on update(delta, p); } // clear freq & phase registers void ADS9851::down() { update(0x00000000, 0x04); // freq zero, ppppp100 set PD on, x6 REFCLK off } // pulse a pin LOW-HIGH-LOW void ADS9851::pulse(int _pin) { digitalWrite(_pin, LOW); digitalWrite(_pin, HIGH); digitalWrite(_pin, LOW); } // load a new value for _calFreq void ADS9851::calibrate(double calXtal) { _calFreq = calXtal; }
OLED<br />
// Oled.h // V2.0 18-4-5 added UL numbers // defines oled pins, creates "oled" object for 128x64 SH1106 display // fonts github.com/olikraus/u8g2/wiki // HEADERS & LIBRARIES #include "U8g2lib.h" #include "Wire.h" // OBJECTS // oled object, SH1106 controller, 128X64, HW I2C and normal orientation R0 U8G2_SH1106_128X64_NONAME_1_HW_I2C oled(U8G2_R0); //FUNCTIONS // BAR // display bar at x, y, h)eight, l)ength (0-128 pixels) void dispBar(u8g2_uint_t x, u8g2_uint_t y, byte h, byte l) { byte n; oled.drawFrame(x, y, 100, h+1); for ( n = 0; n < l; n++) { oled.drawLine(x + n, y, x + n, y + h); } } // FREQ // display freq at x, y, f (Hz) plus cf (cHz), d)ecimal places (max 3) void dispFreq(u8g2_uint_t x, u8g2_uint_t y, double f, double cf, byte d) { oled.setFont(u8g2_font_10x20_tf); // font oled.setFontPosTop(); // origin top f = f / 1000.0; cf = cf / 100000.0; oled.setCursor(x, y); oled.print(f + cf, d); oled.print("kHz"); } //MSG // display small message at at x), y), *m)essage void dispMsgS(u8g2_uint_t x, u8g2_uint_t y, char *m) { // sets font, cursor position and displays message oled.setFont(u8g2_font_5x8_tf); // font oled.setFontPosTop(); oled.setCursor(x, y); oled.print(m); } // display message at at x), y), *m)essage void dispMsg(u8g2_uint_t x, u8g2_uint_t y, char *m) { // sets font, cursor position and displays message oled.setFont(u8g2_font_7x13_tf); // font oled.setFontPosTop(); oled.setCursor(x, y); oled.print(m); } // display large message at at x), y), *m)essage void dispMsgL(u8g2_uint_t x, u8g2_uint_t y, char *m) { oled.setFont(u8g2_font_10x20_tf); // font oled.setFontPosTop(); oled.setCursor(x, y); oled.print(m); } // display ultra large message at at x), y), *m)essage void dispMsgUL(u8g2_uint_t x, u8g2_uint_t y, char *m) { oled.setFont(u8g2_font_logisoso30_tf); // font oled.setFontPosTop(); oled.setCursor(x, y); oled.print(m); } // NUM // display number at x), y), n)umber (double), d)ecimal places void dispNum(u8g2_uint_t x, u8g2_uint_t y, double n, byte d) { oled.setFont(u8g_font_7x14); // fix font for now oled.setFontPosTop(); oled.setCursor(x, y); oled.print(n, d); } // display number large at x), y), n)umber (double), d)ecimal places void dispNumL(u8g2_uint_t x, u8g2_uint_t y, double n, byte d) { oled.setFont(u8g2_font_10x20_tf); // font oled.setFontPosTop(); oled.setCursor(x, y); oled.print(n, d); } // display number ultra large at x), y), n)umber (double), d)ecimal places void dispNumUL(u8g2_uint_t x, u8g2_uint_t y, double n, byte d) { oled.setFont(u8g2_font_logisoso24_tf); // font oled.setFontPosTop(); oled.setCursor(x, y); oled.print(n, d); } // DATE // display date void dispDate(u8g2_uint_t x, u8g2_uint_t y, byte dw, byte da, byte mo, byte yr) { oled.setFont(u8g_font_7x14); // fix font for now oled.setFontPosTop(); oled.setCursor(x, y); switch (dw) { case 1: oled.print("Mon"); break; case 2: oled.print("Tue"); break; case 3: oled.print("Wed"); break; case 4: oled.print("Thu"); break; case 5: oled.print("Fri"); break; case 6: oled.print("Sat"); break; case 7: oled.print("Sun"); break; } oled.print(" "); oled.print(da); oled.print(" "); switch (mo) { case 1: oled.print("Jan"); break; case 2: oled.print("Feb"); break; case 3: oled.print("Mar"); break; case 4: oled.print("Apr"); break; case 5: oled.print("May"); break; case 6: oled.print("Jun"); break; case 7: oled.print("Jul"); break; case 8: oled.print("Aug"); break; case 9: oled.print("Sep"); break; case 10: oled.print("Oct"); break; case 11: oled.print("Nov"); break; case 12: oled.print("Dec"); break; } oled.print(" "); oled.print("20"); oled.print(yr); } // TIME // display time HH:MM:SS at x), y) void dispTime(u8g2_uint_t x, u8g2_uint_t y, byte h, byte m, byte s) { oled.setFont(u8g_font_7x14); // fix font for now oled.setFontPosTop(); oled.setCursor(x, y); if (h < 10) oled.print("0"); oled.print(h); oled.print(":"); if (m < 10) oled.print("0"); oled.print(m); oled.print(":"); if (s < 10) oled.print("0"); oled.print(s); } // display time HH:MM:SS at x), y) void dispTimeL(u8g2_uint_t x, u8g2_uint_t y, byte h, byte m, byte s) { oled.setFont(u8g2_font_10x20_tf); // font oled.setFontPosTop(); oled.setCursor(x, y); if (h < 10) oled.print("0"); oled.print(h); oled.print(":"); if (m < 10) oled.print("0"); oled.print(m); oled.print(":"); if (s < 10) oled.print("0"); oled.print(s); } // STEP // display step at x) y) s)tep void dispStep(u8g2_uint_t x, u8g2_uint_t y, unsigned int s) { oled.setFont(u8g_font_7x14); // fix font for now oled.setFontPosTop(); oled.setCursor(x, y); switch (s) // display freqStep { case 10: oled.print(" 10Hz"); break; case 100: oled.print("100Hz"); break; case 1000: oled.print(" 1kHz"); break; case 10000: oled.print(" 10kHz"); break; case 100000: oled.print("100kHz"); break; case 1000000: oled.print(" 1MHz"); break; } }Note: Oled.h uses the public u8glib.
No comments:
Post a Comment