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() );
}
LIBRARIESPut 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);
};
#endifAD9851.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