Here's the 24 Aug 2017 update of all my current sketches and libraries. Mainly tidying up and checking functionality on both AD9851 & Si5351 synthesiser platforms.
Download
Thursday, 24 August 2017
LATEST Sketches and libraries
Tuesday, 22 August 2017
NEW NEW NEW!!! New GPS read code, display Lat/Lon or Maidenhead Locator, set RTC
I have a couple of new Arduino code sketches for GPS. The problem with my previous code was that it repeatedly read the GPS time, and set the RTC. This took too long and so the time display of the seconds jumped to every two seconds!
So I have completely re-written the code, it now waits for the GPS "fix", programs the RTC, gets the lat & Lon and calculates the Maidenhead Locator, then displays these, but displays the time from the programmed RTC, which then goes by seconds. Phew!!
My header file "Oled_128X64_I2C.h" has been published previously, look back and you will find it.
Just to remind you I use a VK-163 GPS (look on Amazon or ebay) which connects by a 4-way 3.5mm jack, and provides NMEA GPS data at 9600 baud. The block digram of the "VFO" I use is below, it uses an Arduino UNO with an AD9851 synthesiser and MMIC output amplifier giving up to 50mW output. The Arduino also drives an OLED 128x64 pixel display, using a U8g2lib library (look up on GitHUb). This is a great box of tricks and I can use it for many different sketches from GPS to WSPR, JT65, RTTY, PSK31, QRSS and DFCW...
Download here
CODE LAT & LON DISPLAY
// GPS_DATE_TIME_LATLON
// V1.0 22-8-17 after fix, set RTC, then display RTC Time & Date
// jack
// TIP +5V
// ring 13 (RX LOW, FROMGPS)
// ring 12 (TX LOW, TOGPS)
// GND
// HEADRERS & LIBRARIES
#include "Oled_128X64_I2C.h"
#include "SoftwareSerial.h"
// CONNECTIONS
#define FROMGPS 13
#define TOGPS 12
#define SW 4
// I2C COMMS DATA
// RTC address
#define RTCADDR 0x68
// OBJECTS
SoftwareSerial gps(FROMGPS, TOGPS);
// GLOBAL VARIABLES
char gpsbuf[200]; // GPS input buffer
char tm[20]; // time HHMMSS
char fix[5]; // fix A|V, init void
char dt[20]; // date YYMMDD
char la[15]; // latitude
char ns[2]; // NS
char lo[15]; // longitude
char ew[2]; // EW
// data converted to numeric (decimal) bytes
byte hrs, mns, sec;
byte yr, mth, dy;
byte dow;
double lat, lon;
// Maidenhead Locator
char mh[10] = "";
// SETUP
void setup() {
// pin modes
pinMode(FROMGPS, INPUT);
pinMode(TOGPS, OUTPUT);
pinMode(SW, INPUT_PULLUP);
// OLED init, I2C addr 0x3C
oled.begin();
// GPS serial init
gps.begin(9600);
strcpy(fix, "V"); // init no fix
}
// LOOP
void loop() {
dispUpdate(); // init display AWTG GPS
do { // read sentence until fix confirmed
getGPS("$GPRMC", gpsbuf); // get $GPRMC
fieldsGPS(gpsbuf); // dig out ASCII fields
} while (strcmp(fix, "A") != 0);
convert(); // convert Time & Date to numeric values
dow = calcDow(yr, mth, dy); // calulate day of week
setRTC(); // program RTC with GPS time
lat = convertPos(la, ns); // convert Lat & Lon to numeric
lon = convertPos(lo, ew);
findMH(mh, lat, lon); // find Maidenhead Locator
while (!button()) { // update Time & Date display from RTC
readRTC(); // read RTC
dispUpdate();
}
strcpy(fix, "V"); // button pressed restart
}
// button pressed?
bool button() {
if (digitalRead(SW) == LOW) { // button pressed?
while (!digitalRead(SW)); // wait for release
return true;
}
else {
return false;
}
}
// GPS FUNCTIONS
// get sentence into buffer, for example
// $GPRMC,081836,A,3751.65,S,14507.36,E,000.0,360.0,130998,011.3,E*62
void getGPS(char *sntc, char *buf) {
do { // find sntc
getline(buf);
} while (strncmp(buf, sntc, 6) != 0);
}
// get a line from the GPS, inc /r/n, add /0
void getline(char *line) {
char c;
int p;
p = 0; // buffer pointer
do {
if (gps.available() > 0) { // data?
c = gps.read(); // read character
line[p++] = c; // put in buffer
}
} while ( c != '\n' ); // stop on /n
line[p] = '\0'; // terminate string
}
// find GPS fields
void fieldsGPS(char *buf) {
// extract strings from $GPRMC fields
xtract(buf, 1, tm); // time HHMMSS
xtract(buf, 2, fix); // fix A or V
xtract(buf, 9, dt); // date YYMMDD
xtract(buf, 3, la); // latitude
xtract(buf, 4, ns); // NS
xtract(buf, 5, lo); // longitude
xtract(buf, 6, ew); // EW
}
// extract field and return string in outbuf
void xtract(char *in, int field, char *out) {
int ip = 0; // input buffer pointer
int op = 0; // output buffer pointer
int f = 0; // field counter
while (f < field) { // find start of field, ip
while (in[ip++] != ',');
f++;
}
while (in[ip] != ',') { // scan to next ','
out[op++] = in[ip++]; // copy in to out
}
out[op] = '\0'; // terminate out string
}
// convert GPS ASCII Time and Date to decimal bytes
void convert() {
hrs = strtob(tm, 0); // HH....
mns = strtob(tm, 2); // ..MM..
sec = strtob(tm, 4); // ....SS
dy = strtob(dt, 0); // DD....
mth = strtob(dt, 2); // ..MM..
yr = strtob(dt, 4); // ....YY
}
// convert ASCII field starting at field pointer, to byte
byte strtob(char *field, int fp) {
char out[20];
strncpy(out, field + fp, 2); // copy 2 char
return (byte)atoi(out); // return byte
}
// convert Lat, Lon strings to decimal +/-NS|EW
double convertPos(char *pos, char *d) {
double pp, mm, ans;
int dd;
pp = atof(pos); // get in decimal ddmm.mmmmmmm
dd = (int)pp / 100; // get degrees part
mm = pp - (100 * dd); // get minutes
ans = dd + (double)mm / 60.0; // calc decimal degrees
if (strcmp(d, "N") == 0 || strcmp(d, "E") == 0) // if positive
return ans;
else
return - ans; // negative
}
// DATE OF WEEK CALC
// calc dow, return decimal byte, Sun = 0
byte calcDow(byte year, byte month, byte day)
{
unsigned long days;
unsigned int febs;
unsigned int months[] =
{
0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334, 365 // days until 1st of month
};
days = year * 365; // days up to year
febs = year;
if (month > 2) febs++; // number of completed Februaries
// add in the leap days
days += ((febs + 3) / 4);
days -= ((febs + 99) / 100);
days += ((febs + 399) / 400);
days += months[month - 1] + day;
return (byte)(((days + 5) % 7)); // sun = 0
}
// FIND MAIDENHEAD LOCATOR
void findMH(char *dst, double fa, double fo) {
int a1, a2, a3;
int o1, o2, o3;
double rd;
// Latitude
rd = fa + 90.0;
a1 = (int)(rd / 10.0);
rd = rd - (double)a1 * 10.0;
a2 = (int)(rd);
rd = rd - (double)a2;
a3 = (int)(24.0 * rd);
// Longitude
rd = fo + 180.0;
o1 = (int)(rd / 20.0);
rd = rd - (double)o1 * 20.0;
o2 = (int)(rd / 2.0);
rd = rd - 2.0 * (double)o2;
o3 = (int)(12.0 * rd);
dst[0] = (char)o1 + 'A';
dst[1] = (char)a1 + 'A';
dst[2] = (char)o2 + '0';
dst[3] = (char)a2 + '0';
dst[4] = (char)o3 + 'A';
dst[5] = (char)a3 + 'A';
dst[6] = '\0';
}
// SET RTC
// set date and time bytes to RTC BCD
void setRTC() {
// program RTC
Wire.beginTransmission(RTCADDR);
Wire.write(0); // next input at sec register
Wire.write(decToBcd(sec)); // set seconds
Wire.write(decToBcd(mns)); // set minutes
Wire.write(decToBcd(hrs)); // set hours
Wire.write(decToBcd(dow)); // set day of week
Wire.write(decToBcd(dy)); // set date (1 to 31)
Wire.write(decToBcd(mth)); // set month (1-12)
Wire.write(decToBcd(yr)); // set year (0 to 99)
Wire.endTransmission();
}
// Convert decimal to BCD
byte decToBcd(byte dec)
{
return ( (dec / 10 * 16) + (dec % 10) );
}
// 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
}
// Convert BCD to decimal numbers
byte bcdToDec(byte val) {
return ( (val / 16 * 10) + (val % 16) );
}
// PICTURE LOOP
// this version displays lat/lon
void dispUpdate() {
oled.firstPage();
do {
dispMsg(30, 0, "GPS Lat/Lon");
if (strcmp(fix, "A") != 0) { // no fix?
dispMsgL(30, 15, "AWTG GPS");
}
else {
dispNum(10, 15, lat, 2);
dispMsg(50, 15, ns);
dispNum(70, 15, lon, 2);
dispMsg(110, 15, ew);
dispDate(15, 32, dow, dy, mth, yr);
dispTimeL(25, 47, hrs, mns, sec);
}
} while ( oled.nextPage() );
}
CODE MAIDENHEAD LOCATOR DISPLAY
// GPS_DATE_TIME_MH
// V1.0 22-8-17 after fix, set RTC, then display RTC Time & Date
// jack
// TIP +5V
// ring 13 (RX LOW, FROMGPS)
// ring 12 (TX LOW, TOGPS)
// GND
// HEADERS & LIBRARIES
#include "Oled_128X64_I2C.h"
#include "SoftwareSerial.h"
// CONNECTIONS
#define FROMGPS 13
#define TOGPS 12
#define SW 4
// I2C COMMS DATA
// RTC address
#define RTCADDR 0x68
// OBJECTS
SoftwareSerial gps(FROMGPS, TOGPS);
// GLOBAL VARIABLES
char gpsbuf[200]; // GPS input buffer
char tm[20]; // time HHMMSS
char fix[5]; // fix A|V, init void
char dt[20]; // date YYMMDD
char la[15]; // latitude
char ns[2]; // NS
char lo[15]; // longitude
char ew[2]; // EW
// data converted to numeric (decimal) bytes
byte hrs, mns, sec;
byte yr, mth, dy;
byte dow;
double lat, lon;
// Maidenhead Locator
char mh[10] = "";
// SETUP
void setup() {
// pin modes
pinMode(FROMGPS, INPUT);
pinMode(TOGPS, OUTPUT);
pinMode(SW, INPUT_PULLUP);
// OLED init, I2C addr 0x3C
oled.begin();
// GPS serial init
gps.begin(9600);
strcpy(fix, "V"); // init no fix
}
// LOOP
void loop() {
dispUpdate(); // init display AWTG GPS
do { // read sentence until fix confirmed
getGPS("$GPRMC", gpsbuf); // get $GPRMC
fieldsGPS(gpsbuf); // dig out ASCII fields
} while (strcmp(fix, "A") != 0);
convert(); // convert Time & Date to numeric values
dow = calcDow(yr, mth, dy); // calulate day of week
setRTC(); // program RTC with GPS time
lat = convertPos(la, ns); // convert Lat & Lon to numeric
lon = convertPos(lo, ew);
findMH(mh, lat, lon); // find Maidenhead Locator
while (!button()) { // update Time & Date display from RTC
readRTC(); // read RTC
dispUpdate();
}
strcpy(fix, "V"); // button pressed restart
}
// button pressed?
bool button() {
if (digitalRead(SW) == LOW) { // button pressed?
while (!digitalRead(SW)); // wait for release
return true;
}
else {
return false;
}
}
// GPS FUNCTIONS
// get sentence into buffer, for example
// $GPRMC,081836,A,3751.65,S,14507.36,E,000.0,360.0,130998,011.3,E*62
void getGPS(char *sntc, char *buf) {
do { // find sntc
getline(buf);
} while (strncmp(buf, sntc, 6) != 0);
}
// get a line from the GPS, inc /r/n, add /0
void getline(char *line) {
char c;
int p;
p = 0; // buffer pointer
do {
if (gps.available() > 0) { // data?
c = gps.read(); // read character
line[p++] = c; // put in buffer
}
} while ( c != '\n' ); // stop on /n
line[p] = '\0'; // terminate string
}
// find GPS fields
void fieldsGPS(char *buf) {
// extract strings from $GPRMC fields
xtract(buf, 1, tm); // time HHMMSS
xtract(buf, 2, fix); // fix A or V
xtract(buf, 9, dt); // date YYMMDD
xtract(buf, 3, la); // latitude
xtract(buf, 4, ns); // NS
xtract(buf, 5, lo); // longitude
xtract(buf, 6, ew); // EW
}
// extract field and return string in outbuf
void xtract(char *in, int field, char *out) {
int ip = 0; // input buffer pointer
int op = 0; // output buffer pointer
int f = 0; // field counter
while (f < field) { // find start of field, ip
while (in[ip++] != ',');
f++;
}
while (in[ip] != ',') { // scan to next ','
out[op++] = in[ip++]; // copy in to out
}
out[op] = '\0'; // terminate out string
}
// convert GPS ASCII Time and Date to decimal bytes
void convert() {
hrs = strtob(tm, 0); // HH....
mns = strtob(tm, 2); // ..MM..
sec = strtob(tm, 4); // ....SS
dy = strtob(dt, 0); // DD....
mth = strtob(dt, 2); // ..MM..
yr = strtob(dt, 4); // ....YY
}
// convert ASCII field starting at field pointer, to byte
byte strtob(char *field, int fp) {
char out[20];
strncpy(out, field + fp, 2); // copy 2 char
return (byte)atoi(out); // return byte
}
// convert Lat, Lon strings to decimal +/-NS|EW
double convertPos(char *pos, char *d) {
double pp, mm, ans;
int dd;
pp = atof(pos); // get in decimal ddmm.mmmmmmm
dd = (int)pp / 100; // get degrees part
mm = pp - (100 * dd); // get minutes
ans = dd + (double)mm / 60.0; // calc decimal degrees
if (strcmp(d, "N") == 0 || strcmp(d, "E") == 0) // if positive
return ans;
else
return - ans; // negative
}
// DATE OF WEEK CALC
// calc dow, return decimal byte, Sun = 0
byte calcDow(byte year, byte month, byte day)
{
unsigned long days;
unsigned int febs;
unsigned int months[] =
{
0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334, 365 // days until 1st of month
};
days = year * 365; // days up to year
febs = year;
if (month > 2) febs++; // number of completed Februaries
// add in the leap days
days += ((febs + 3) / 4);
days -= ((febs + 99) / 100);
days += ((febs + 399) / 400);
days += months[month - 1] + day;
return (byte)(((days + 5) % 7)); // sun = 0
}
// FIND MAIDENHEAD LOCATOR
void findMH(char *dst, double fa, double fo) {
int a1, a2, a3;
int o1, o2, o3;
double rd;
// Latitude
rd = fa + 90.0;
a1 = (int)(rd / 10.0);
rd = rd - (double)a1 * 10.0;
a2 = (int)(rd);
rd = rd - (double)a2;
a3 = (int)(24.0 * rd);
// Longitude
rd = fo + 180.0;
o1 = (int)(rd / 20.0);
rd = rd - (double)o1 * 20.0;
o2 = (int)(rd / 2.0);
rd = rd - 2.0 * (double)o2;
o3 = (int)(12.0 * rd);
dst[0] = (char)o1 + 'A';
dst[1] = (char)a1 + 'A';
dst[2] = (char)o2 + '0';
dst[3] = (char)a2 + '0';
dst[4] = (char)o3 + 'A';
dst[5] = (char)a3 + 'A';
dst[6] = '\0';
}
// SET RTC
// set date and time bytes to RTC BCD
void setRTC() {
// program RTC
Wire.beginTransmission(RTCADDR);
Wire.write(0); // next input at sec register
Wire.write(decToBcd(sec)); // set seconds
Wire.write(decToBcd(mns)); // set minutes
Wire.write(decToBcd(hrs)); // set hours
Wire.write(decToBcd(dow)); // set day of week
Wire.write(decToBcd(dy)); // set date (1 to 31)
Wire.write(decToBcd(mth)); // set month (1-12)
Wire.write(decToBcd(yr)); // set year (0 to 99)
Wire.endTransmission();
}
// Convert decimal to BCD
byte decToBcd(byte dec)
{
return ( (dec / 10 * 16) + (dec % 10) );
}
// 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
}
// Convert BCD to decimal numbers
byte bcdToDec(byte val) {
return ( (val / 16 * 10) + (val % 16) );
}
// PICTURE LOOP
// this version displays MH
// this version displays lat/lon
void dispUpdate() {
oled.firstPage();
do {
dispMsg(40, 0, "GPS MH");
if (strcmp(fix, "A") != 0) { // no fix?
dispMsgL(30, 15, "AWTG GPS");
}
else {
dispMsgL(35, 15, mh);
dispDate(15, 32, dow, dy, mth, yr);
dispTimeL(25, 47, hrs, mns, sec);
}
} while ( oled.nextPage() );
}
Saturday, 19 August 2017
New! ADS9851 library update, QRSS & QRSS DFCW for AD9851 VFO
Been a bit busy today with some updates and a couple of re-writes. First off I noticed a problem with the library ADS9851 which I wrote for the AD9851 Analog Digital Synthesiser. When the "down" function was called it did not completely turn off the output! It continued to output a low level signal 500Hz below the programmed one... I think this was due to the reset pulse applied being too short - it has to be at least 5 system cycles long. So I have included a small 2us delay in the "pulse" function to give a longer LOW-HIGH-LOW pulse. The same pulse is used for "init" so will serve both functions.
Here's the ".h" and ".cpp" code for the updated library,
// Arduino Library for AD9851 frequency synthesiser module, with 30MHz clock
// V1.1 18-8-17 Antony Watts, M0IFA
// frequency in Hz and cHz
// W_CLK, FQ_UD, DATA, RESET to any pins
// 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)
// void calibrate(double calHz); change xtal frequency from standard 125MHz to new value
// void down(); power down, power up with setFreq()
// phase coding, 0-180 in 11.25deg steps 0x00, 0x01, 0x02, 0x04, 0x08, 0x10
// REFCLK = 1 for x6 multiplier
#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);
void init();
};
#endifAnd the CODE for ADS9851.cpp
// Arduino Library for AD9851 frequency synthesiser module, with 30MHz clock
// V1.1 19-8-17 Antony Watts, M0IFA, pulser code update
// frequency in Hz and cHz
// W_CLK, FQ_UD, DATA, RESET to any pins
// void begin(int W_CLK, int FQ_UD, int DATA, int RESET); init, reset, serial mode
// void setFreq(double Hz, double Chz, uint8_t p); set f(Hz) and cHz(Chz), phase
// void calibrate(double calHz); change xtal frequency from standard 180MHz (30MHz x6)
// void down(); power down, power up with setFreq()
// phase in steps 0x00, 0x01, 0x02, 0x04, 0x08, 0x10 (11.5, 22.5, 45. 90, 180deg)
// REFCLK = 1 for x6 multiplier
#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
pulse(_FQ_UD);
init(); // clear freq/phase registers, REFCLK=1 (x6 en), PD=1 (pwd dn)
}
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 freq byte
}
shiftOut(_DATA, _W_CLK, LSBFIRST, cP); // output control & phase byte
pulse(_FQ_UD);
}
// calculate 4 freq bytes, convert double to to uint32_t
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); // REFCLK on, = ppppp001
update(delta, p);
}
// turn off, zero freq
void ADS9851::down() {
update(0, 0);
}
// clear freq to zero, set PD bit, set REFCLK bit
void ADS9851::init() {
pulse(_FQ_UD);
update(0, 0b00000101); // fW=0, PD=1 (pwr dwn), REFCLK=1 (on)
}
// pulse a pin LOW-HIGH-LOW
void ADS9851::pulse(int _pin) {
digitalWrite(_pin, LOW);
digitalWrite(_pin, HIGH);
delayMicroseconds(2); // 2us pulse for init & down
digitalWrite(_pin, LOW);
}
// load a new value for _calFreq
void ADS9851::calibrate(double calXtal) {
_calFreq = calXtal;
}
NEW QRSS_CW and QRSS_DFCW sketches
Two very interesting slow CW modes are QRSS and QRSS DFCW. QRSS sends morse code at a very low speed, in this case it is 3sec/dot known as QRSS3. The DFCW mode sends both dots and dashes with the same lenght, but at different frequencies, in this case 5Hz apart, dash higher, dot lower.
CODE for QRSS_CW
// QRSS_CW, sends 40m QRSS3 message from the KB
// V1.0 18-8-17
// AD9851
// RESET 8
// DATA 9
// FQ_UD 10
// W_CLK 11
// OLED 128x64
// SDA = A4
// SCL = A5
// OLED, AD9851 libraries
#include "Oled_128X64_I2C.h"
#include "ADS9851.h"
// AD9851 pins
#define RESET 8
#define DATA 9
#define FQ_UD 10
#define W_CLK 11
// 3sec dot time
#define DOT 3000
// xtal calibration (30MHz external x6 REFCLK = 180MHz internal
#define CALIBRATE 180002300 // cal against SDR (cal at 7070 against CORRA)
// ASCII input
char msg[30];
// frequency settings - fixed for now
volatile double freqHz = 7000800; // (Hz) start frequency 7000.8kHz
volatile double freqChz = 0; // (cHz) additional 0cHz
uint8_t phase = 0; // init phase
// ads (analog-output digital synthesiser) object
ADS9851 ads;
// morse code strings, _ = dot space, 0-9 numbers, 10-36 A..Z
// table from 0 - 36
char morse[][8] = {
"-----_", // 0
".----_", // 1-9
"..---_",
"...--_",
"....-_",
"....._",
"-...._",
"--..._",
"---.._",
"----._",
".-_", // A
"-..._", // B
"-.-._", // C
"-.._", // D
"._", // E
"..-._", // F
"--._", // G
"...._", // H
".._", // I
".---_", // J
"-.-_", // K
".-.._", // L
"--_", // M
"-._", // N
"---_", // O
".--._", // P
"--.-_", // Q
".-._", // R
"..._", // S
"-_", // T
"..-_", // U
"...-_", // V
".--_", // W
"-..-_", // X
"-.--_", // Y
"--.._", // Z
"__", // word space
};
void setup() {
Serial.begin(9600);
// oled init, sets I2C addr to 0x3C
oled.begin();
// init ads, executes down() to flush buffers
ads.begin(W_CLK, FQ_UD, DATA, RESET);
// calibrate to xtal actual frequency
ads.calibrate(CALIBRATE);
ads.down();
msg[0] = '\0'; // init message
dispUpdate(); // init display
}
void loop()
{
// get message, send
if (getMsg(msg)) {
qrssOut(msg);
}
}
// get input message
bool getMsg(char *m)
{
char ch;
int n;
n = 0;
if (Serial.available() > 0) { // if input
while (Serial.available() > 0) { // get input
ch = Serial.read(); // get char
if (ch == '\n' || ch == '\r') ch = '\0'; // end of text
m[n++] = ch;
delay(20); // let USB catch up
}
return true; // got input
}
return false; // no input
}
// look up morse string, send char by char
void qrssOut(char *m)
{
static byte ndx;
byte n;
char c;
dispUpdate(); // display msg
// step along msg chraracters
ndx = 0;
while (m[ndx] != '\0')
{
// convert SPACE
if (m[ndx] == 32)
c = m[ndx] + 4;
// convert ASCII
else if (m[ndx] >= 48 && m[ndx] <= 57) // table 0-9
c = m[ndx] - 48;
else if (m[ndx] >= 65 && m[ndx] <= 90) // table A-Z (uc)
c = m[ndx] - 55;
else if (msg[ndx] >= 97 && msg[ndx] <= 122) // table a-z (lc)
c = m[ndx] - 87;
// output morse, up to SPACE
n = 0;
while (morse[c][n] != '_')
{
if (morse[c][n] == '.') dotOut(); // dot out
else if (morse[c][n] == '-') dashOut(); // dash out
n++;
}
spaceOut(); // end of char
ndx++;
}
m[0] = '\0'; // clear message
dispUpdate();
}
// send a dot for DOT time
void dotOut()
{
unsigned long t;
ads.setFreq(freqHz, freqChz, phase);
t = millis();
while (millis() < t + DOT);
ads.down();
t = millis();
while (millis() < t + DOT);
}
// send a dash for 3* DOT time
void dashOut()
{
unsigned long t;
ads.setFreq(freqHz, freqChz, phase);
t = millis();
while (millis() < t + DOT * 3);
ads.down();
t = millis();
while (millis() < t + DOT);
}
// word space for 2 * DOT time (each character has its own one DOT space
void spaceOut()
{
unsigned long t;
t = millis();
while (millis() < t + DOT * 2);
}
// picture loop, display init data
void dispUpdate() {
oled.firstPage();
do {
dispMsg(50, 0, "QRSS3");
dispFreq(15, 20, freqHz, freqChz, 2);
dispMsg(5, 40, msg);
} while ( oled.nextPage() );
}
And the CODE for QRSS_DFCW
// QRSS_DFCW sends a DFCW3 message from the KB
// V1.0 18-8-17 like QRSS but with equal time for dot and dash
// with DFCW dot/dash spacing of 10Hz
// tunable in 10Hz steps
// AD9851
// RESET 8
// DATA 9
// FQ_UD 10
// W_CLK 11
// OLED 128x64
// SDA = A4
// SCL = A5
// rotary encoder pins
// CLK = 2
// DT = 3
// SW = 4
// OLED, AD9851, Rotary Encoder libraries
#include "Oled_128X64_I2C.h"
#include "ADS9851.h"
#include "Rotary.h"
// AD9851 pins
#define RESET 8
#define DATA 9
#define FQ_UD 10
#define W_CLK 11
// encoder
#define CLK 2
#define DT 3
// 3sec dot time
#define DOT 3000
// xtal calibration (30MHz external x6 REFCLK = 180MHz internal
#define CALIBRATE 180002300 // cal against SDR (cal at 7070 against CORRA)
// ads (analog-output digital synthesiser) object
ADS9851 ads;
// Encoder object
Rotary enc = Rotary(DT, CLK);
// frequency settings
volatile double freqHz = 7000800; // (Hz) start frequency 7000.8kHz
volatile double dotChz = 0; // (cHz) zero
volatile double dashChz = 500; // (cHz) additional 5Hz
volatile double freqStep = 10; // (Hz) freqHz +/- step
uint8_t phase = 0; // init phase
// ASCII input
char msg[30];
// morse code strings, _ = dot space, 0-9 numbers, 10-36 A..Z
// table from 0 - 36
char morse[][8] = {
"-----_", // 0
".----_", // 1-9
"..---_",
"...--_",
"....-_",
"....._",
"-...._",
"--..._",
"---.._",
"----._",
".-_", // A
"-..._", // B
"-.-._", // C
"-.._", // D
"._", // E
"..-._", // F
"--._", // G
"...._", // H
".._", // I
".---_", // J
"-.-_", // K
".-.._", // L
"--_", // M
"-._", // N
"---_", // O
".--._", // P
"--.-_", // Q
".-._", // R
"..._", // S
"-_", // T
"..-_", // U
"...-_", // V
".--_", // W
"-..-_", // X
"-.--_", // Y
"--.._", // Z
"__", // word space
};
// interrupt freq change flag
bool freqChange;
void setup() {
// encoder
pinMode(DT, INPUT_PULLUP);
pinMode(CLK, INPUT_PULLUP);
// for msg input/display
Serial.begin(9600);
// setup interrupts from DT or CLK for tuning
attachInterrupt(digitalPinToInterrupt(DT), freqTune, CHANGE);
attachInterrupt(digitalPinToInterrupt(CLK), freqTune, CHANGE);
interrupts(); // enable
// oled init, sets I2C addr to 0x3C
oled.begin();
// init ads, executes down() to flush buffers
ads.begin(W_CLK, FQ_UD, DATA, RESET);
// calibrate to xtal actual frequency
ads.calibrate(CALIBRATE);
ads.down();
msg[0] = '\0'; // init message
dispUpdate();
}
void loop()
{
if (freqChange) {
freqChange = false;
dispUpdate();
}
if (getMsg(msg)) {
dfcwOut(msg);
}
}
// get input message
bool getMsg(char *m)
{
char ch;
int n;
n = 0;
if (Serial.available() > 0) { // if input
while (Serial.available() > 0) { // get input
ch = Serial.read(); // get char
if (ch == '\n' || ch == '\r') ch = '\0'; // end of text
m[n++] = ch;
delay(20); // let USB catch up
}
return true; // got input
}
return false; // no input
}
// ISR - encoder interrupt service routine
void freqTune() {
unsigned char result;
result = enc.process();
if (result == DIR_CW ) {
freqHz += freqStep;
freqChange = true;
}
else if (result == DIR_CCW) {
freqHz -= freqStep;
freqChange = true;
}
}
// look up morse string, send char by char
void dfcwOut(char *m)
{
static byte ndx;
byte n;
char c;
dispUpdate();
// step along msg chraracters
ndx = 0;
while (m[ndx] != '\0')
{
// convert to position in morse table
// convert SPACE
if (msg[ndx] == 32)
c = m[ndx] + 4;
// convert ASCII
else if (m[ndx] >= 48 && m[ndx] <= 57) // table 0-9
c = m[ndx] - 48;
else if (m[ndx] >= 65 && m[ndx] <= 90) // table A-Z (uc)
c = m[ndx] - 55;
else if (m[ndx] >= 97 && m[ndx] <= 122) // table a-z (lc)
c = m[ndx] - 87;
// output morse, up to SPACE
n = 0;
while (morse[c][n] != '_')
{
if (morse[c][n] == '.') dotOut(); // dot out
else if (morse[c][n] == '-') dashOut(); // dash out
n++;
}
spaceOut(); // end of char
ndx++;
}
m[0] = '\0';
dispUpdate();
}
// send a dot for DOT time
void dotOut()
{
unsigned long t;
ads.setFreq(freqHz, dotChz, phase); // send dot
t = millis();
while (millis() < t + DOT);
ads.down(); // small space
t = millis();
while (millis() < t + DOT / 2);
}
// send a dash for 3* DOT time
void dashOut()
{
unsigned long t;
ads.setFreq(freqHz, dashChz, phase); // send dash, 3x dot
t = millis();
while (millis() < t + DOT);
ads.down(); // small space
t = millis();
while (millis() < t + DOT / 2);
}
// word space for 2 * DOT time (each character has its own one DOT space
void spaceOut()
{
unsigned long t;
t = millis();
while (millis() < t + DOT * 2);
}
// picture loop, display init data
void dispUpdate() {
oled.firstPage();
do {
dispMsg(50, 0, "DFCW3");
dispFreq(15, 20, freqHz, dotChz, 2);
dispMsg(5, 40, msg);
} while ( oled.nextPage() );
}
This is the transmitter and the received signal in DFCW using the ARGO software,
Wednesday, 16 August 2017
Some Arduino routines for GPS
There are some very cheap GPS receivers on the market today, notably GPS originally designed for plugging into dash-cams. These receivers have a 4-way 3.5mm jack connection, and send out 9600 baud serial data in ASCII format.
You can also buy very cheap Arduino Nano boards and cheap OLED 128x64 pixel displays.
Putting this all together you can have a useful tool for amateur radio to display your latitude, longitude, Maidenhead Locator, date and time. Here are few of the code snippets useful for handling the data
HEADER definitions, libraries, defines and variables
#include "Oled_128X64_I2C.h" #include "SoftwareSerial.h" #include "Wire.h" // GPS connections, #define FROMGPS 12 #define TOGPS 13 #define SW 4 // GPS data buffer char gpsbuf[200]; // data extracted from $GPRMC, ACSII char tm[20]; // time HHMMSS char fix[5]; // fix A|V, init void char dt[20]; // date YYMMDD char la[15]; // latitude char ns[2]; // NS char lo[15]; // longitude char ew[2]; // EW // Maidenhead Locator char mh[10] = ""; // Date, Time Lat & lon decimal byte hrs, mns, sec; byte yr, mth, dy; byte dow; double lat, lon; // Serial object RX TX SoftwareSerial gps(FROMGPS, TOGPS);
CODE for setup
void setup() {
// pins
pinMode(FROMGPS, INPUT);
pinMode(TOGPS, OUTPUT);
pinMode(SW, INPUT_PULLUP);
// I2C init
Wire.begin();
// OLED init, I2C addr 0x3C
oled.begin();
// GPS serial init
gps.begin(9600);
strcpy(fix, "V");
dispUpdate();
}The basic loop CODE
void loop() {
getGPS(); // get GPS, extract data
if (strcmp(fix, "A") == 0) { // when GPS Aquired
getDateTime();
getMH();
dispUpdate();
}
}And finally the functions
// get RMC line data
void getGPS() {
do {
getline(gpsbuf);
} while (strncmp(gpsbuf, "$GPRMC", 6) != 0);
// extract strings from $GPRMC fields
xtract(gpsbuf, 1, tm); // time HHMMSS
xtract(gpsbuf, 2, fix); // fix A or V
xtract(gpsbuf, 9, dt); // date YYMMDD
xtract(gpsbuf, 3, la); // latitude
xtract(gpsbuf, 4, ns); // NS
xtract(gpsbuf, 5, lo); // longitude
xtract(gpsbuf, 6, ew); // EW
}
// get a line from the GPS, inc /r/n, add /0
void getline(char *out) {
char c;
int p;
p = 0; // buffer pointer
do {
if (gps.available() > 0) { // data?
c = gps.read(); // read character
out[p++] = c; // put in buffer
}
} while ( c != '\n' ); // stop on /n
out[p] = '\0'; // terminate string
}
// extract field and return string in outbuf
void xtract(char *in, int field, char *out) {
int ip = 0; // input buffer pointer
int op = 0; // output buffer pointer
int f = 0; // field counter
while (f < field) { // find start of field, ip
while (in[ip++] != ',');
f++;
}
while (in[ip] != ',') { // scan to next ','
out[op++] = in[ip++]; // copy in to out
}
out[op] = '\0'; // terminate out string
}
// ================ Date & Time, Dow =================
void getDateTime() {
// get GPS data in bytes, calc dow
hrs = strtob(tm, 0); // HH....
mns = strtob(tm, 2); // ..MM..
sec = strtob(tm, 4); // ....SS
dy = strtob(dt, 0); // DD....
mth = strtob(dt, 2); // ..MM..
yr = strtob(dt, 4); // ....YY
dow = calcDow(yr, mth, dy);
}
// convert ASCII (0-99), starting at bp, to byte
byte strtob(char *in, int bp) {
char out[20];
strncpy(out, in + bp, 2); // copy 2 char
return (byte)atoi(out); // return byte
}
// calc dow
byte calcDow(byte year, byte month, byte day)
{
unsigned long days;
unsigned int febs;
unsigned int months[] =
{
0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334, 365 // days until 1st of month
};
days = year * 365; // days up to year
febs = year;
if (month > 2) febs++; // number of completed Februaries
// add in the leap days
days += ((febs + 3) / 4);
days -= ((febs + 99) / 100);
days += ((febs + 399) / 400);
days += months[month - 1] + day;
return (byte)(((days + 5) % 7)); // sun = 0
}
// ====================Maidenhead functions================
// calculate maideng=head locator from lat & lon
void getMH() {
// extract lat * lon from GPS data
xtract(gpsbuf, 3, la);
xtract(gpsbuf, 4, ns);
xtract(gpsbuf, 5, lo);
xtract(gpsbuf, 6, ew);
lat = convertPos(la, ns);
lon = convertPos(lo, ew);
calcMH(mh, lat, lon);
}
// convert Lat, Lon strings to decimal +/-NS|EW
double convertPos(char *pos, char *d) {
double pp, mm, ans;
int dd;
pp = atof(pos); // get in decimal ddmm.mmmmmmm
dd = (int)pp / 100; // get degrees part
mm = pp - (100 * dd); // get minutes
ans = dd + (double)mm / 60.0; // calc decimal degrees
if (strcmp(d, "N") == 0 || strcmp(d, "E") == 0) // if positive
return ans;
else
return - ans; // negative
}
// calc MH from lat & lon
void calcMH(char *dst, double fa, double fo) {
int a1, a2, a3;
int o1, o2, o3;
double rd;
// Latitude
rd = fa + 90.0;
a1 = (int)(rd / 10.0);
rd = rd - (double)a1 * 10.0;
a2 = (int)(rd);
rd = rd - (double)a2;
a3 = (int)(24.0 * rd);
// Longitude
rd = fo + 180.0;
o1 = (int)(rd / 20.0);
rd = rd - (double)o1 * 20.0;
o2 = (int)(rd / 2.0);
rd = rd - 2.0 * (double)o2;
o3 = (int)(12.0 * rd);
dst[0] = (char)o1 + 'A';
dst[1] = (char)a1 + 'A';
dst[2] = (char)o2 + '0';
dst[3] = (char)a2 + '0';
dst[4] = (char)o3 + 'A';
dst[5] = (char)a3 + 'A';
dst[6] = '\0';
}
// ============Picture Display ===============
// picture loop
void dispUpdate() {
oled.firstPage();
do {
dispMsg(55, 0, "GPS");
if (strcmp(fix, "V") == 0) {
dispMsgL(10, 25, "NO GPS");
}
else if (strcmp(fix, "A") == 0) {
dispMsg(10, 12, ns);
dispNum(25, 12, lat, 2);
dispMsg(75, 12, ew);
dispNum(90, 12, lon, 2);
dispMsg(45, 25, mh);
dispDate(15, 37, dow, dy, mth, yr);
dispTime(35, 52, hrs, mns, sec);
}
} while ( oled.nextPage() );
}