I have, finally, got to the point of putting my projects in boxes. Here you can see, from top to bottom,
The Elektor SDR, with OLED display and a rotary encoder to select 80, 40 & 20m centre frequencies in 50kHz steps.
The RF meter, displaying the RF output from the VFO, it can display 0.1uW up to 10W power and has a switchable 50R dummy load inside.
The VFO, which is programmable as a simple VFO 1-70MHz, in steps of 10Hz to 1MHz. But can also be programmed with various Arduino sketches as a Beacon for CW, PSK & RTTY, as a JT65 transmistter and a WSPR transmitter. The output is -5 to +15dBm adjustable.
Box stack
Sketches
The current range of sketches I have are:
These sketches all use the OLED display, and are written for the AD9851 synthesiser - except the SDR which uses the Si5351 sythesiser.
From the top these are for
BCN_CW_OLED - a CW beacon transmitting a short programable message at regular selectable intervals, uses an OLED display
Argo view on PC
BCN_PSK_OLED - same for PSK31
PSK31 Beacon running on VFO
PSK reception on HDSDR on MacBook
PSK31 decode on MultiMode Cocoa on MacBook
BNC_RTTY_OLED - same for RTTY
DATE_TIME_OLED - a simple date and time display that runs on the Arduino in any of the boxes as they have the same OLED connections and display libraries.
DATE_TIME_SET_OLED - an important sketch to set the time of the RTC in the VFO, this has to be set to 1sec accuracy for WSPR & JT65 transmissions
HELL_FELD_7x14_OLED - Hellschreiber message transmitter, with a standard 7x14 font
HELL_S-MT_5x7_OLED - another version of Hellschreiber using sequencial multi tone transmission and a 5x7 font
Argo on PC
JT65_ADS51_TEXT_OLED - JT65 transmission of simple messages, input via a serial comms program over USB
VFO running JT65
HDSDR on PC
WSJT-X on PC (Mac version crashes... need to sort out memory shared size)
RF_METER_OLED - a sketch for the RF Meter box, displaying RF bar graph 0.1uW to 10W, mV, dBm and power (when internal 50R dummy load active)
SDR_ELEKTOR_TUNE_OLED - a sketch for the Si5351 synthesiser used on the Elektor SDR board, with frequency selection in 50kHz steps over the 80, 40 & 20m bands
VFO_ADS51_OLED - a general purpose VFO 1-70MHz in steps of 10Hz to 1MHz.
VOLT_OLED - not a sketch for these boxes, but a simple DC voltmeter (0-5V) usng the Arduino analog input A0, and displaying on the OLED display. It also calculates the dB of the input and reads dBm if measurements are made across a 50R load. Good for a very low cost voltmeter using an Arduino Nano...
WSPR_ADS51_OLED - programmable WSPR transmitter, currently set to 40m (7040.1kHz, dial 7038,6kHz reception).
That's a full 2 years on and off work but I am nearing completion of my station. Planned are two more boxes - a complete QRP SDR transceiver and a 2-3W PA with 80, 40, 20m LPF and SWR display.
All Code and Libraries download.
Wednesday, 26 April 2017
BASIC Tech Group - MyNews - 42 Boxing them up
Wednesday, 19 April 2017
BASIC Tech Group - MyNews - 41 Moving from LCD to OLED displays - a new header file
I am planning to mount a number of my projects in some, quite small, instrument boxes. I will build a VFO, an RF METER, an SDR, and a PA/LPF/SWR. These boxes will use 1.3" OLED displays. (Note: works also for the smaller 0.96" OLEDs).
I plan to tidy up the sketches i have for driving OLED displays and consolidate them in a new header file, I call Oled_128X64_I2C.h. This header includes six functions used for displaying the data I need across many applications. They are
dispBar - display a bar graph at x, y, height h and length l, with a surrounding box
dispFreq - display, in a larger font, the frequency at x, y, in d decimal places
dispMsg - display a general text message at x, y
dispNum - display a general number at x, y, d decimal places
dispDate- display the date day-of-week, day, month, year
dispTime - display the time at x, y, hours, minute, seconds
dispStep - display the VFO frequency step when tuning, at x, y
The variables used are shown in the functions' outlines above.
Example
Here is an example of the use of the new header file
// test OLED_128x64_I2C with OLED SH1106 display #include "Oled_128X64_I2C.h" // display values - global values used to pass them to dispUpdate() function double a = 7100000; // freq double b = 0; // centi freq unsigned int c = 1000; // step byte hr = 12; // time byte mn = 3; byte sc = 33; double n = 345.6; // a number byte hT = 5; // bar height and length byte bL = 45; void setup() { oled.begin(); } void loop() { // put your main code here, to run repeatedly: dispUpdate(); } // picture loop, display definition data, what, where void dispUpdate() { oled.firstPage(); do { dispMsg(0, 0, "RF"); dispBar(15, 3, hT, bL); dispFreq(10, 15, a, b, 2); dispNum(45, 35, n, 2); dispTime(10, 50 , hr, mn, sc); dispStep(80, 50, c); } while ( oled.nextPage() ); }You can see how simple it makes writing the sketch. Every thing needed, including the constructor for the 1.3" I2C OLED display is in the header file.
The result of the code above.
My desk full of OLED displays for my projects.
Code
// oled_128X64_I2C.h // V1.5 21-4-17 added small & large versions, and date display // defines oled pins, creates "oled" object for 128x64 SH1106 display // step now an unsigned int // functions usage // void dispBar(u8g2_uint_t x, u8g2_uint_t y, u8g2_uint_t h, u8g2_uint_t l) // void dispFreq(u8g2_uint_t x, u8g2_uint_t y, double f, double cf, uint8_t d) // void dispMsgS(u8g2_uint_t x, u8g2_uint_t y, char *m) // void dispMsg(u8g2_uint_t x, u8g2_uint_t y, char *m) // void dispMsgL(u8g2_uint_t x, u8g2_uint_t y, char *m) // void dispNum(u8g2_uint_t x, u8g2_uint_t y, double n, uint8_t d) // void dispNumL(u8g2_uint_t x, u8g2_uint_t y, double n, uint8_t d) // void dispDate(u8g2_uint_t x, u8g2_uint_t y, byte dw, byte da, byte mo, byte yr) // void dispTime(u8g2_uint_t x, u8g2_uint_t y, byte h, byte m, byte s) // void dispTimeL(u8g2_uint_t x, u8g2_uint_t y, byte h, byte m, byte s) // void dispStep(u8g2_uint_t x, u8g2_uint_t y, unsigned int s) #include "U8g2lib.h" #include "Wire.h" // oled object, SH1106 controller, 128X64, HW I2C and normal orientation R0 U8G2_SH1106_128X64_NONAME_1_HW_I2C oled(U8G2_R0); //=====FUNCTIONS // 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); } } // display freq at x, y, f (Hz), cf (cHz), d)ecimal places (max 3) void dispFreq(u8g2_uint_t x, u8g2_uint_t y, double f, double cf, byte d) { // sets font, cursor position and displays freq 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"); } // fonts github.com/olikraus/u8g2/wiki/fntgrpx11 // 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) { // sets font, cursor position and displays message oled.setFont(u8g2_font_10x20_tf); // font oled.setFontPosTop(); oled.setCursor(x, y); oled.print(m); } // 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) { // sets font, cursor position and displays number 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) { // sets font, cursor position and displays number oled.setFont(u8g2_font_10x20_tf); // font oled.setFontPosTop(); oled.setCursor(x, y); oled.print(n, d); } // display date void dispDate(u8g2_uint_t x, u8g2_uint_t y, byte dw, byte da, byte mo, byte yr) { // sets font, cursor position and displays message 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); } // display time HH:MM:SS at x), y) void dispTime(u8g2_uint_t x, u8g2_uint_t y, byte h, byte m, byte s) { // sets font, cursor position and displays message 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) { // sets font, cursor position and displays message 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); } // display step at x) y) s)tep void dispStep(u8g2_uint_t x, u8g2_uint_t y, unsigned int s) { // set font, cursor position and display step 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; } }
Sunday, 16 April 2017
BASIC Tech Group - MyNews 40 - VFO using AD9851 with OLED display
This code has been updated - see later posting!!
Here's the code for a VFO using the AD9851, up to 70MHz. The AD9851 code is here
// VFO_ADS51_OLED // V1.0 16-4-17 VFO for the AD9851, 30MHz external xtal, x6 180MHz internal clock // freq steps 10Hz, 100Hz, 1kHz, 10kHz, 100kHz, 1MHz // 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 & I2C 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 // xtal calibration (30MHz external x6 REFCLK = 180MHz internal #define CALIBRATE 180002300 // cal against SDR (cal at 7070 against CORRA) // encoder #define CLK 2 #define DT 3 #define SW 4 // ads (analog-output digital synthesiser) object ADS9851 ads; // Encoder object Rotary enc = Rotary(DT, CLK); // phase coding, 0-180 [0-5] uint8_t ph[] = {0x00, 0x01, 0x02, 0x04, 0x08, 0x10}; // initial settings volatile double freqHz = 7100000; // (Hz) start frequency 7.1MHz volatile double freqChz = 0; // (cHz) additional 0cHz volatile double freqStep = 10; // (Hz) init 10Hz freqStep uint8_t phase = ph[0]; // init phase // freq change flag volatile bool freqChange; void setup() { // encoder, button pins pinMode(DT, INPUT_PULLUP); pinMode(CLK, INPUT_PULLUP); pinMode(SW, INPUT_PULLUP); // 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.setFreq(freqHz, freqChz, phase); freqChange = false; dispUpdate(); } // check button & freq tune, update display void loop() { if (button()) { dispUpdate(); } if (freqChange) { // freq updated ads.setFreq(freqHz, freqChz, phase); freqChange = false; dispUpdate(); } } // 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; } } // change freqStep, 10Hz to 1MHz bool button() { if (digitalRead(SW) == LOW) { // button pressed? while (!digitalRead(SW)); // wait for release if (freqStep == 1000000) freqStep = 10; // back to 10Hz else freqStep = freqStep * 10; // or increase by x10 return true; } else { return false; } } // picture loop, display init data void dispUpdate() { oled.firstPage(); do { dispMsg(50, 0, "VFO"); dispFreq(15, 25, freqHz, freqChz, 2); dispMsg(15, 50, "<--"); dispStep(45, 50, freqStep); dispMsg(95, 50, "-->"); } while ( oled.nextPage() ); }
Getting it straight - AD985x IC Arduino library "ADS9850 & 51"
I’ve seen a lot of libraries on the web for the AD9850 and AD9851 Analog Digital Synthesisers. And as far as I can see none of them meet the data sheet description of how to program the devices.
Most people use the AD985x devices mounted on modules made in China.
These all seem to have the same circuit diagram. The device is wired for either parallel or serial data inputs, and two sine and two square wave outputs.
Large board
Small board
But ONLY one sine output has a filter to filter the DAC jitters, a 70MHz LPF, this is on the IOUT pin. So this is the output you should use.
ARDUINOLets start with the Arduino, and assume we are going to send data to the device in serial mode. We need 4 pins for this, for the signals W_CLK, FQ_UP, DATA and RESET. So we set up 4 Arduino pins in OUTPUT mode. On the Arduino this defaults the output to LOW level, which is what we want.
The first thing to do is to reset the device, this is a bit difficult as it starts in parallel mode and could cause a problem. With the device in parallel mode a reset (a LOW-HIGH-LOW pulse on RESET) will zero the phase accumulator & offset, set the output IOUT to zero mA, set the internal address pointer to the first of the five programming bytes, set power down PD off and, for the AD9851 set the internal x6 xtal frequency REFCLK multiplier off. But it won’t clear the frequency registers or the control and phase set registers. So they could contain random bits… this is chance you have to take. They will anyway be overwritten by the following code, but could briefly put the device in an illegal factory control mode...
The large modules from China are hard wired with pins D0 & D1 to VCC and D2 via a jumper J1 to GND, but the small modules should be wired externally to be safe. This gives the input code required to the parallel input D0-D7 of xxxxx011 which will switch the device to serial mode by a pulse on W_CLK followed by FQ_UD.
Next thing to do for the AD985x is to place the device in power down mode, which will clear all the registers, and for the AD9851 set the PD and the REFCLK registers. We need the REFCLK set to ‘1’ for the AD9851 to enable the x6 xtal frequency multiplier, as all AD9851 modules from China seem to come with a lower cost 30MHz xtal, not an external 180MHz one. But we need an internal 180MHz for correct frequency outputs. (This seems to be a BIG mistake many users make as AD9850 modules come with a 125MHz xtal...).
So the first call is to the library function “begin":
// init calFreq, pins, reset & serial mode void ADS9850::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 d0-d2 pulse(_FQ_UD); down(); // clear freq and phase registers, REFCLK=1 (x6 en), PD=1 (pwd dn) }
To set an output frequency we need to calculate the 32 bit value to serially transfer into the first 4 bytes of the 40 bit control register. This we do like this for the AD9850:
// calculate 4 freq bytes, convert double to to uint32_t void ADS9850::setFreq(double f, double cf, uint8_t p) { uint32_t delta; delta = (uint32_t)((f + cf/100.0) * 4294967296.0 / _calFreq); p = p << 3; // = ppppp000 update(delta, p); }or for the AD9851
// 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 setBit(p, 0); // REFCLK on, = ppppp001 update(delta, p); }We start with frequencies f (Hz) and cf (cHz) in double floating point, calculate the "delta" value then convert the result to a 32 bit uint32_t value. And we calculate the phase register contents, setting the REFCLK flag for the AD9851
Next we have to transfer the uint32_t frequency, and the byte p for the control and phase of the ouptut. This is done by the function update().
The update is made like this (same for the AD9851):
// load the 4 delta & the control/phase registers void ADS9850::update(uint32_t d, uint8_t c) { for (int i=0; i <4 ; i++, d >>= 8) { shiftOut(_DATA, _W_CLK, LSBFIRST, d); // output freq byte } shiftOut(_DATA, _W_CLK, LSBFIRST, c); // output control & phase byte pulse(_FQ_UD); }
First "shiftOut" the four delta frequency bytes then the control/phase byte with the previous settings for PD and REFCLK. The function "shiftOut" generates the W_CLK signal for the transfer.
So here are the complete libraries.
Study them both to understand the differences between the AD9850 and 51.Monday, 10 April 2017
BASIC Tech Group - MyNews 39 - Updated VFO with OLED display
Here’s the updated sketch for my AD9850 based VFO, to use an OLED display. The VFO tunes in 10Hz to 1MHz steps. I have in development a buffer amplifier - probably it will be based on an MMIC (ERA2-SM)maybe to give up to +10dBm output.
Code
// VFO_ADS_OLED
// V1.5 9-4-17 update to U8g2lib
// output G0
// freq steps 10Hz, 100Hz, 1kHz, 10kHz, 100kHz, 1MHz
// AD9850
// W_CLK 8
// FQ_UD 9
// DATA 10
// RESET 11
// OLED 128x64
// SDA = A4
// SCL = A5
// rotary encoder pins
// DT = 2
// CLK = 3
// SW = 4
// ADS9850, OLED and Rotary Encoder, I2C libraries
#include "ADS9850.h"
#include "U8g2lib.h"
#include "Rotary.h"
#include "Wire.h"
// AD9850 pins
#define W_CLK 8
#define FQ_UD 9
#define DATA 10
#define RESET 11
// chose pin 2,3 or 3,2 for encoder
#define DT 2
//#define DT 3
#define CLK 3
//#define CLK 2
#define SW 4
// xtal calibration
#define CALIBRATE 125000000
// ads (analog-output digital synthesiser) object
ADS9850 ads;
// oled object
U8G2_SH1106_128X64_NONAME_1_HW_I2C oled(U8G2_R0, U8X8_PIN_NONE, SCL, SDA);
// Encoder object
Rotary enc = Rotary(DT, CLK);
// phase coding, 0-180 [0-5] in 11.25deg steps
uint8_t ph[] = {0x00, 0x01, 0x02, 0x04, 0x08, 0x10};
// initial settings
volatile double freqHz = 7100000; // (Hz) start frequency 7.1MHz
volatile double freqChz = 0; // (cHz) additional 0cHz
volatile double freqStep = 10; // (Hz) init 10Hz freqStep
uint8_t phase = ph[0]; // init phase
// freq change flag
volatile bool freqChange;
void setup() {
// encoder, button pins
pinMode(DT, INPUT_PULLUP);
pinMode(CLK, INPUT_PULLUP);
pinMode(SW, INPUT_PULLUP);
// 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 AD9850
ads.begin(W_CLK, FQ_UD, DATA, RESET);
// calibrate to xtal actual frequency
ads.calibrate(CALIBRATE);
ads.setFreq(freqHz, freqChz, phase);
freqChange = false;
dispUpdate();
}
// check button & freq tune, update display
void loop() {
if (button()) {
dispUpdate();
}
if (freqChange) {
// freq updated
ads.setFreq(freqHz, freqChz, phase);
freqChange = false;
dispUpdate();
}
}
// 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;
}
}
// change freqStep, 10Hz to 1MHz
bool button() {
if (digitalRead(SW) == LOW) { // button pressed?
while (!digitalRead(SW)); // wait for release
if (freqStep == 1000000) freqStep = 10; // back to 10Hz
else freqStep = freqStep * 10; // or increase by x10
return true;
}
else {
return false;
}
}
// picture loop, display init data
void dispUpdate() {
oled.firstPage();
do {
dispMsg(40, 0, "VFO_ADS");
dispFreq(15, 25, freqHz, freqChz, 2);
dispMsg(30, 50, "Step");
dispStep(70, 50, freqStep);
} while ( oled.nextPage() );
}
// display freq at x, y, f (Hz), cf (cHz), d)ecimal places
void dispFreq(u8g2_uint_t x, u8g2_uint_t y, double f, double cf, uint8_t d) {
double fd;
char buf[100];
// sets font, cursor position and displays freq
oled.setFont(u8g2_font_10x20_tf); // font
oled.setFontPosTop(); // origin top
fd = f + cf / 100; // calc freq
oled.setCursor(x, y);
oled.print(fd / 1000, d);
oled.print("kHz");
}
void dispStep(u8g2_uint_t x, u8g2_uint_t y, uint64_t s) {
// set font, cursor position and display step
oled.setFont(u8g2_font_7x13_tf); // font
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;
}
}
// 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);
}
BASIC Tech Group - MyNews 38 - Updated SDR ELEKTOR with OLED display
Here’s the code for an update to the sketch for the Elektor SDR RX. It updates the display to OLED and adds a tuning capability to select 50kHZ segments of 80, 40 & 20m bands.
Code
// SDR_ELEKTOR_OLED_TUNE
// V4.5 9-4-17 update to U8g2lib
// Output 80, 40, 20m in 50kHz steps x4 CLK1
// Si5351 I2C bus
// SDA = A4
// SCL = A5
// OLED 128x64
// SDA = A4
// SCL = A5
// Si5351 V2, LCD libraries
#include "si5351.h"
#include "U8g2lib.h"
#include "Rotary.h"
// chose pin 2,3 or 3,2 for encoder
#define DT 2
//#define DT 3
#define CLK 3
//#define CLK 2
#define SW 4
// xtal correction for Elektor DDS
#define CORRE 180000
// dds object
Si5351 dds;
// oled object
U8G2_SH1106_128X64_NONAME_1_HW_I2C oled(U8G2_R0, U8X8_PIN_NONE, SCL, SDA);
// Encoder object
Rotary enc = Rotary(DT, CLK);
// table of tuning frequencies
uint64_t freqTable[] = {
350000000, 355000000, 360000000, 365000000, 370000000, 375000000,
705000000, 710000000, 715000000,
1405000000, 1410000000, 1415000000, 1420000000, 1425000000
};
// index into freq table, startup freq
byte ndx = 7;
// initial frequency
uint64_t freq = freqTable[ndx];
// freq change flag
volatile bool freqChange;
void setup() {
// encoder, button pins
pinMode(DT, INPUT_PULLUP);
pinMode(CLK, INPUT_PULLUP);
pinMode(SW, INPUT_PULLUP);
// setup interrupts from DT or CLK for tuning
attachInterrupt(digitalPinToInterrupt(DT), freqTune, CHANGE);
attachInterrupt(digitalPinToInterrupt(CLK), freqTune, CHANGE);
// enable interrupts
interrupts();
// oled init, sets I2C addr to 0x3C
oled.begin();
// init dds si5351 module, "0" = default 25MHz XTAL, correction
dds.init(SI5351_CRYSTAL_LOAD_8PF, 0, CORRE);
// set 8mA output drive
dds.drive_strength(SI5351_CLK1, SI5351_DRIVE_8MA);
// enable VFO output CLK1, disable CLK0 & 2
dds.output_enable(SI5351_CLK0, 0);
dds.output_enable(SI5351_CLK1, 1);
dds.output_enable(SI5351_CLK2, 0);
freqOut(freq); // cHz, output freq
freqChange = false;
dispUpdate();
}
void loop() {
if (freqChange) {
freqOut(freq);
freqChange = false;
dispUpdate();
}
}
// ISR - encoder interrupt service routine
void freqTune() {
unsigned char result;
result = enc.process();
if (result == DIR_CW && ndx < 13) {
freq = freqTable[++ndx];
freqChange = true;
}
else if (result == DIR_CCW && ndx > 0) {
freq = freqTable[--ndx];;
freqChange = true;
}
}
// picture loop, display init data
void dispUpdate() {
oled.firstPage();
do {
dispMsg(25, 0, "SDR_ELEKTOR");
dispFreq(25, 25, 0, freq, 0); // freq is in cHz
dispMsg(20, 50, "<-- Freq -->");
} while ( oled.nextPage() );
}
// Output Freq for Elektor SDR, f (cHz) x 4 on CLK1
void freqOut(uint64_t f) {
dds.set_freq(f * 4ULL, SI5351_CLK1);
}
// display freq at x, y, f (Hz), cf (cHz), d)ecimal places
void dispFreq(u8g2_uint_t x, u8g2_uint_t y, double f, double cf, uint8_t d) {
double fd;
char buf[100];
// sets font, cursor position and displays freq
oled.setFont(u8g2_font_10x20_tf); // font
oled.setFontPosTop(); // origin top
fd = f + cf / 100; // calc freq
oled.setCursor(x, y);
oled.print(fd / 1000, d);
oled.print("kHz");
}
// 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);
}
BASIC Tech Group - MyNews 37 - Updated JT65 with OLED display
Here’s the code for JT65 with OLED display and text input via a USB connected terminal program, such as MacOS iSerialTerm or the Arduino IDE Serial Monitor window.
Code
// JT65_ADS_TEXT_OLED // V2.5 9-4-17 serial input/output, update to U8g2lib // Code based on Feld Hell beacon for Arduino by K6HX // Timer setup code by LA3PNA. // TX 7060000, Dial 7074830 // AD9850 // W_CLK 8 // FQ_UD 9 // DATA 10 // RESET 11 // OLED 128x64 // SDA = A4 // SCL = A5 // RTC I2C bus // SDA = A4 // SCL = A5 #include "ADS9850.h" #include "U8g2lib.h" #include "Wire.h" // RTC address #define RTCADDR 0x68 // Stuff specific to the general (integer) version of the Reed-Solomon codecs #define MODNN(x) modnn(rs,x) #define MM (rs->mm) #define NN (rs->nn) #define ALPHA_TO (rs->alpha_to) #define INDEX_OF (rs->index_of) #define GENPOLY (rs->genpoly) #define NROOTS (rs->nroots) #define FCR (rs->fcr) #define PRIM (rs->prim) #define IPRIM (rs->iprim) #define PAD (rs->pad) #define A0 (NN) #define TONE_SPACING 269 // ~2.6917 Hz #define SUBMODE_A 5812 // CTC value for JT65A #define SYMBOL_COUNT 126 // AD9850 pins #define W_CLK 8 #define FQ_UD 9 #define DATA 10 #define RESET 11 typedef unsigned int data_t; /* Reed-Solomon codec control block */ struct rs { int mm; /* Bits per symbol */ int nn; /* Symbols per block (= (1<<mm)-1) */ data_t *alpha_to; /* log lookup table */ data_t *index_of; /* Antilog lookup table */ data_t *genpoly; /* Generator polynomial */ int nroots; /* Number of generator roots = number of parity symbols */ int fcr; /* First consecutive root, index form */ int prim; /* Primitive element, index form */ int iprim; /* prim-th root of 1, index form */ int pad; /* Padding bytes in shortened block */ }; // RTC time byte sec, mns, hrs; // trigger TX (sec) uint8_t trigger; bool tx; // Frequency variables double freqHz = 7076000; // nominal dial is 1270Hz lower double freqChz = 0; uint8_t phase = 0; // message to send char message[20] = ""; // encode variables uint8_t tx_buffer[SYMBOL_COUNT]; static void *rs; // timer flag volatile bool proceed = false; // AD9850 object ADS9850 ads; // oled object U8G2_SH1106_128X64_NONAME_1_HW_I2C oled(U8G2_R0, U8X8_PIN_NONE, SCL, SDA); void setup() { Serial.begin(9600); // init I2C Wire.begin(); // oled init, sets I2C addr to 0x3C oled.begin(); // init AD9850, init freq and off to start ads.begin(W_CLK, FQ_UD, DATA, RESET); ads.setFreq(freqHz, freqChz, phase); ads.down(); // Set up Timer1 for interrupts every symbol period. noInterrupts(); // Turn off interrupts. TCCR1A = 0; // Set entire TCCR1A register to 0; disconnects // interrupt output pins, sets normal waveform // mode. We're just using Timer1 as a counter. TCNT1 = 0; // Initialize counter value to 0. TCCR1B = (1 << CS12) | // Set CS12 and CS10 bit to set prescale (1 << CS10) | // to /1024 (1 << WGM12); // turn on CTC // which gives, 64us ticks TIMSK1 = (1 << OCIE1A); // Enable timer compare interrupt. OCR1A = SUBMODE_A; // Set up interrupt trigger count; interrupts(); // Re-enable interrupts. trigger = 1; // trigger on each minute tx = false; // Initialize the Reed-Solomon encoder rs = (struct rs *)(intptr_t)init_rs_int(6, 0x43, 3, 1, 51, 0); } void loop() { // get time getRTC(); dispUpdate(); // message? if (getMsg(message) == true) { // echo message Serial.println(message); // display message & time, trigger time? do { getRTC(); dispUpdate(); } while (mns % trigger != 0 || sec != 0); // send tx = true; // indicate TX on display encode(message); tx = false; // clear message & buffer clearBuf(message); } } // picture loop, display init data void dispUpdate() { oled.firstPage(); do { dispMsg(15, 0, "JT65_ADS_TEXT"); dispMsg(15, 15, message); dispFreq(15, 30, freqHz, freqChz, 2); if (tx == false) dispTime(30, 50); else dispMsg(40, 50, "TX"); } while ( oled.nextPage() ); } // Loop through the string, transmitting one character at a time. void encode(char * tx_string) { uint8_t i; // encode the message jt65_encode(tx_string, tx_buffer); // Now do the rest of the message for (i = 0; i < SYMBOL_COUNT; i++) { ads.setFreq(freqHz, freqChz + (tx_buffer[i] * TONE_SPACING), phase); proceed = false; while (!proceed); } // Turn off the output ads.down(); } static inline int modnn(struct rs *rs, int x) { while (x >= rs->nn) { x -= rs->nn; x = (x >> rs->mm) + (x & rs->nn); } return x; } uint8_t jt_code(char c) { /* Validate the input then return the proper integer code */ // Return 255 as an error code if the char is not allowed if (isdigit(c)) { return (uint8_t)(c - 48); } else if (c >= 'A' && c <= 'Z') { return (uint8_t)(c - 55); } else if (c == ' ') { return 36; } else if (c == '+') { return 37; } else if (c == '-') { return 38; } else if (c == '.') { return 39; } else if (c == '/') { return 40; } else if (c == '?') { return 41; } else { return 255; } } // timer interrupt veector ISR(TIMER1_COMPA_vect) { proceed = true; } // Reed Soloman encoder by KA9Q void encode_rs_int(void *p, data_t *data, data_t *parity) { struct rs *rs = (struct rs *)p; #undef A0 #define A0 (NN) /* Special reserved value encoding zero in index form */ int i, j; data_t feedback; memset(parity, 0, NROOTS * sizeof(data_t)); for (i = 0; i < NN - NROOTS - PAD; i++) { feedback = INDEX_OF[data[i] ^ parity[0]]; if (feedback != A0) { /* feedback term is non-zero */ #ifdef UNNORMALIZED /* This line is unnecessary when GENPOLY[NROOTS] is unity, as it must always be for the polynomials constructed by init_rs() */ feedback = MODNN(NN - GENPOLY[NROOTS] + feedback); #endif for (j = 1; j < NROOTS; j++) parity[j] ^= ALPHA_TO[MODNN(feedback + GENPOLY[NROOTS - j])]; } /* Shift */ memmove(&parity[0], &parity[1], sizeof(data_t) * (NROOTS - 1)); if (feedback != A0) parity[NROOTS - 1] = ALPHA_TO[MODNN(feedback + GENPOLY[0])]; else parity[NROOTS - 1] = 0; } } void free_rs_int(void *p) { struct rs *rs = (struct rs *)p; free(rs->alpha_to); free(rs->index_of); free(rs->genpoly); free(rs); } // init RS enc, symb size, poly coeff, first root, primative, no roots, padding void *init_rs_int(int symsize, int gfpoly, int fcr, int prim, int nroots, int pad) { struct rs *rs; //#undef NULL //#define NULL ((void *)0) int i, j, sr, root, iprim; rs = ((struct rs *)0); /* Check parameter ranges */ if (symsize < 0 || symsize > 8 * sizeof(data_t)) { goto done; } if (fcr < 0 || fcr >= (1 << symsize)) goto done; if (prim <= 0 || prim >= (1 << symsize)) goto done; if (nroots < 0 || nroots >= (1 << symsize)) goto done; /* Can't have more roots than symbol values! */ if (pad < 0 || pad >= ((1 << symsize) - 1 - nroots)) goto done; /* Too much padding */ rs = (struct rs *)calloc(1, sizeof(struct rs)); if (rs == NULL) goto done; rs->mm = symsize; rs->nn = (1 << symsize) - 1; rs->pad = pad; rs->alpha_to = (data_t *)malloc(sizeof(data_t) * (rs->nn + 1)); if (rs->alpha_to == NULL) { free(rs); rs = ((struct rs *)0); goto done; } rs->index_of = (data_t *)malloc(sizeof(data_t) * (rs->nn + 1)); if (rs->index_of == NULL) { free(rs->alpha_to); free(rs); rs = ((struct rs *)0); goto done; } /* Generate Galois field lookup tables */ rs->index_of[0] = A0; /* log(zero) = -inf */ rs->alpha_to[A0] = 0; /* alpha**-inf = 0 */ sr = 1; for (i = 0; i < rs->nn; i++) { rs->index_of[sr] = i; rs->alpha_to[i] = sr; sr <<= 1; if (sr & (1 << symsize)) sr ^= gfpoly; sr &= rs->nn; } if (sr != 1) { /* field generator polynomial is not primitive! */ free(rs->alpha_to); free(rs->index_of); free(rs); rs = ((struct rs *)0); goto done; } /* Form RS code generator polynomial from its roots */ rs->genpoly = (data_t *)malloc(sizeof(data_t) * (nroots + 1)); if (rs->genpoly == NULL) { free(rs->alpha_to); free(rs->index_of); free(rs); rs = ((struct rs *)0); goto done; } rs->fcr = fcr; rs->prim = prim; rs->nroots = nroots; /* Find prim-th root of 1, used in decoding */ for (iprim = 1; (iprim % prim) != 0; iprim += rs->nn) ; rs->iprim = iprim / prim; rs->genpoly[0] = 1; for (i = 0, root = fcr * prim; i < nroots; i++, root += prim) { rs->genpoly[i + 1] = 1; /* Multiply rs->genpoly[] by @**(root + x) */ for (j = i; j > 0; j--) { if (rs->genpoly[j] != 0) rs->genpoly[j] = rs->genpoly[j - 1] ^ rs->alpha_to[modnn(rs, rs->index_of[rs->genpoly[j]] + root)]; else rs->genpoly[j] = rs->genpoly[j - 1]; } /* rs->genpoly[0] can never be zero */ rs->genpoly[0] = rs->alpha_to[modnn(rs, rs->index_of[rs->genpoly[0]] + root)]; } /* convert rs->genpoly[] to index form for quicker encoding */ for (i = 0; i <= nroots; i++) rs->genpoly[i] = rs->index_of[rs->genpoly[i]]; done:; return rs; } uint8_t gray_code(uint8_t c) { return (c >> 1) ^ c; } void rs_encode(uint8_t * data, uint8_t * symbols) { unsigned int dat1[12]; unsigned int b[51]; unsigned int i; // Reverse data order for the Karn codec. for (i = 0; i < 12; i++) { dat1[i] = data[11 - i]; } // Compute the parity symbols encode_rs_int(rs, dat1, b); // Move parity symbols and data into symbols array, in reverse order. for (i = 0; i < 51; i++) { symbols[50 - i] = b[i]; } for (i = 0; i < 12; i++) { symbols[i + 51] = dat1[11 - i]; } } void jt65_encode(char * message, uint8_t symbols[SYMBOL_COUNT]) { uint8_t i, j, k; // Convert all chars to uppercase for (i = 0; i < 13; i++) { if (islower(message[i])) { message[i] = toupper(message[i]); } } // Collapse multiple spaces down to one // Pad the message with trailing spaces uint8_t len = strlen(message); if (len < 13) { for (i = len; i < 13; i++) { message[i] = ' '; } } // Bit packing // ----------- uint8_t c[12]; uint32_t n1, n2, n3; // Find the N values n1 = jt_code(message[0]); n1 = n1 * 42 + jt_code(message[1]); n1 = n1 * 42 + jt_code(message[2]); n1 = n1 * 42 + jt_code(message[3]); n1 = n1 * 42 + jt_code(message[4]); n2 = jt_code(message[5]); n2 = n2 * 42 + jt_code(message[6]); n2 = n2 * 42 + jt_code(message[7]); n2 = n2 * 42 + jt_code(message[8]); n2 = n2 * 42 + jt_code(message[9]); n3 = jt_code(message[10]); n3 = n3 * 42 + jt_code(message[11]); n3 = n3 * 42 + jt_code(message[12]); // Pack bits 15 and 16 of N3 into N1 and N2, // then mask reset of N3 bits n1 = (n1 << 1) + ((n3 >> 15) & 1); n2 = (n2 << 1) + ((n3 >> 16) & 1); n3 = n3 & 0x7fff; // Set the freeform message flag n3 += 32768; c[0] = (n1 >> 22) & 0x003f; c[1] = (n1 >> 16) & 0x003f; c[2] = (n1 >> 10) & 0x003f; c[3] = (n1 >> 4) & 0x003f; c[4] = ((n1 & 0x000f) << 2) + ((n2 >> 26) & 0x0003); c[5] = (n2 >> 20) & 0x003f; c[6] = (n2 >> 14) & 0x003f; c[7] = (n2 >> 8) & 0x003f; c[8] = (n2 >> 2) & 0x003f; c[9] = ((n2 & 0x0003) << 4) + ((n3 >> 12) & 0x000f); c[10] = (n3 >> 6) & 0x003f; c[11] = n3 & 0x003f; // Reed-Solomon encoding // --------------------- uint8_t s[63]; k = 0; rs_encode(c, s); // Interleaving // ------------ uint8_t d[63]; uint8_t d1[7][9]; // Fill temp d1 array for (i = 0; i < 9; i++) { for (j = 0; j < 7; j++) { d1[i][j] = s[(i * 7) + j]; } } // Interleave and translate back to 1D destination array for (i = 0; i < 7; i++) { for (j = 0; j < 9; j++) { d[(i * 9) + j] = d1[j][i]; } } // Gray Code // --------- uint8_t g[63]; for (i = 0; i < 63; i++) { g[i] = gray_code(d[i]); } // Merge with sync vector // ---------------------- const uint8_t sync_vector[126] = { 1, 0, 0, 1, 1, 0, 0, 0, 1, 1, 1, 1, 1, 1, 0, 1, 0, 1, 0, 0, 0, 1, 0, 1, 1, 0, 0, 1, 0, 0, 0, 1, 1, 1, 0, 0, 1, 1, 1, 1, 0, 1, 1, 0, 1, 1, 1, 1, 0, 0, 0, 1, 1, 0, 1, 0, 1, 0, 1, 1, 0, 0, 1, 1, 0, 1, 0, 1, 0, 1, 0, 0, 1, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 1, 0, 0, 1, 0, 1, 1, 0, 1, 0, 1, 0, 1, 0, 0, 1, 1, 0, 0, 1, 0, 0, 1, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1 }; j = 0; for (i = 0; i < 126; i++) { if (sync_vector[i]) { symbols[i] = 0; } else { symbols[i] = g[j] + 2; j++; } } } // get time from RTC, convert bcd to decimal void getRTC() { // Reset the register pointer Wire.beginTransmission(RTCADDR); byte zero = 0x00; Wire.write(zero); Wire.endTransmission(); // request 1st 3 bytes from the RTC address Wire.requestFrom(RTCADDR, 3); // get the s/m/h time data sec = bcdToDec(Wire.read()); mns = bcdToDec(Wire.read()); hrs = bcdToDec(Wire.read() & 0b111111); // mask 24 hour time bit } // Convert binary coded decimal to normal decimal numbers byte bcdToDec(byte val) { return ( (val / 16 * 10) + (val % 16) ); } // get input message[] U.C. 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(); // use upper case as input if (ch == '\n') ch = '\0'; // end of text m[n++] = ch; delay(20); // let USB catch up } return true; // got input } return false; // no input } // clear msg and buffer void clearBuf(char *m) { m[0] = '\0'; while (Serial.available() > 0) Serial.read(); } // display freq at x, y, f (Hz), cf (cHz), d)ecimal places void dispFreq(u8g2_uint_t x, u8g2_uint_t y, double f, double cf, uint8_t d) { double fd; char buf[100]; // sets font, cursor position and displays freq oled.setFont(u8g2_font_10x20_tf); // font oled.setFontPosTop(); // origin top fd = f + cf / 100; // calc freq oled.setCursor(x, y); oled.print(fd / 1000, d); oled.print("kHz"); } // 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 time HH:MM:SS at x), y) void dispTime(u8g2_uint_t x, u8g2_uint_t y) { // sets font, cursor position and displays message oled.setFont(u8g_font_7x14); // fix font for now oled.setFontPosTop(); oled.setCursor(x, y); if (hrs < 10) oled.print("0"); oled.print(hrs); oled.print(":"); if (mns < 10) oled.print("0"); oled.print(mns); oled.print(":"); if (sec < 10) oled.print("0"); oled.print(sec); }
BASIC Tech Group - MyNews 36 - Updated RF METER with OLED display
I have updated the code for my RF Meter to use an OLED display. This is to make it possible to mount the system in a small instrument case...
Code
// RF_METER_OLED
// V1.5 9-4-17 update to U8g2lib
// RF Meter with OLED display
// inputs
// VIN A3
// AREF 3v3
#include "U8g2lib.h"
// Analog input pin, ref (measured) mV
// AREF can vary between Arduino, measure the 3.3V line and put AREF value here.
// scale for 80dB range to fill bar 0-1023
#define DCIN A3
#define AREF 3300.0
#define BARSCALE (100 / 80)
// intercept (dBm), slope (mw/dB), input impedance, attenuator (dB).
// input attenuator is 4k7/51R
#define INTERCEPT -84.0
#define SLOPE 25.0
#define IMP 50.0
#define ATTN 40.0
// bar length
u8g2_uint_t bL;
double vR, dB, wA; // display values
uint8_t vD, dD, wD; // decimal places
double mW, mV, dBm, Vrms; // calculations
// oled object, OELDADDR 0x3C
//U8GLIB_SH1106_128X64 oled(U8G_I2C_OPT_NONE);
U8G2_SH1106_128X64_NONAME_1_HW_I2C oled(U8G2_R0, U8X8_PIN_NONE, SCL, SDA);
//======SETUP
void setup() {
// oled init, sets I2C addr to 0x3C
oled.begin();
// 3.3V connected to AREF
analogReference(EXTERNAL);
}
//======LOOP
void loop() {
int Ain; // int = signed 16 bit
// calculations for mV input, dBm, mW and Volts, AREF in mV
Ain = analogRead(DCIN); // returns an int 0-1023
mV = (double)(Ain * AREF / 1023); // AREF in mV, calculate & convert to double
dBm = (mV / SLOPE) + INTERCEPT + ATTN; // in doubles
mW = pow(10.0, (dBm / 10.0)); // in double, out double, 0dBm = 1mW
Vrms = sqrt((mW / 1000.0) * IMP); // in double, out double,
bL = (dBm + 40); // -40 to +40dBm
// setup display values
// Volts
dispUpdate(); // display bar, Vrms, dBm and Pwr
}
//=====PICTURE LOOP, layout display positions and contant
// global variables, bH, bL, vR, dB, wA
void dispUpdate() {
oled.firstPage();
do {
dispBar(15, 5, 10, bL);
//--VOLTS--
if (Vrms < 1.0) { // millivolts
vR = Vrms * 1000.0;
vD = 0;
dispMsg(75, 20, "mV");
}
else { // volts
vR = Vrms;
vD = 1;
dispMsg(75, 20, "V");
}
dispNum(30, 20, vR, vD);
//--DBM--
dB = dBm; //dBm
dD = 0;
dispNum(30, 35, dB, dD);
dispMsg(75, 35, "dBm");
//--PWR--
if (mW > 1000) { // watts
wA = mW / 1000;;
wD = 1;
dispMsg(75, 50, "W");
}
else { // milliwatts
wA = mW;
wD = 2;
dispMsg(75, 50, "mW");
}
dispNum(30, 50, wA, wD);
} while (oled.nextPage());
}
//=======OLED DISPLAYS
// display bar at x, y, h)eight, l)ength (0-128 pixels)
void dispBar(u8g2_uint_t x, u8g2_uint_t y, u8g2_uint_t h, u8g2_uint_t l) {
u8g2_uint_t n;
oled.drawFrame(x, y, 100, 11);
for ( n = 0; n < l; n++) {
oled.drawLine(x + n, y, x + n, y + h);
}
}
// 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_8x13B_tf); // font
oled.setFontPosTop();
oled.setCursor(x, y);
oled.print(m);
}
// display number at x), y), n)umber (double), d)ecimal places
void dispNum(u8g2_uint_t x, u8g2_uint_t y, double n, uint8_t d) {
// sets font, cursor position and displays number
oled.setFont(u8g2_font_8x13B_tf); // font
oled.setFontPosTop();
oled.setCursor(x, y);
oled.print(n, d);
}
Converting to OLED displays
But I did find some nice, small "instrument cases" on Amazon and bought 4 of them. What I found was that the 16x2 LCD displays that I have been using are too large... and I have chosen to switch to 1.3" OLED displays. Lot's of new learning about U8g2lib.h library and re-writing of code... but I have my wide band SDR, my wide range VFO and my RF power meter ready to go.
I now have to cut the rectanglar holes for the Arduino UNO USB connector and te 1.3" OLED displays. Fortunately my son is production director at a small engineering company and can do this using his CNC milling machines. So there is hope.
Monday, 3 April 2017
BASIC Tech Group - MyNews 35 - OLED display for Elektor SDR
This is a version of the simple Arduino sketch which displays a single coded SDR centre frequency on an OLED 1.3" display. To use it install the library "u8g2" using the Arduino Library Manager (Sketch > Include Library > Manage Libraries ... and search for “u8g2", then select the latest version and Install).
Code
// SDR_ELEKTOR_OLED_TUNE
// V4.5 9-4-17 update to U8g2lib
// Output 80, 40, 20m in 50kHz steps x4 CLK1
// Si5351 I2C bus
// SDA = A4
// SCL = A5
// OLED 128x64
// SDA = A4
// SCL = A5
// Si5351 V2, LCD libraries
#include "si5351.h"
#include "U8g2lib.h"
#include "Rotary.h"
// chose pin 2,3 or 3,2 for encoder
#define DT 2
//#define DT 3
#define CLK 3
//#define CLK 2
#define SW 4
// xtal correction for Elektor DDS
#define CORRE 180000
// dds object
Si5351 dds;
// oled object
U8G2_SH1106_128X64_NONAME_1_HW_I2C oled(U8G2_R0, U8X8_PIN_NONE, SCL, SDA);
// Encoder object
Rotary enc = Rotary(DT, CLK);
// table of tuning frequencies
uint64_t freqTable[] = {
350000000, 355000000, 360000000, 365000000, 370000000, 375000000,
705000000, 710000000, 715000000,
1405000000, 1410000000, 1415000000, 1420000000, 1425000000
};
// index into freq table, startup freq
byte ndx = 7;
// initial frequency
uint64_t freq = freqTable[ndx];
// freq change flag
volatile bool freqChange;
void setup() {
// encoder, button pins
pinMode(DT, INPUT_PULLUP);
pinMode(CLK, INPUT_PULLUP);
pinMode(SW, INPUT_PULLUP);
// setup interrupts from DT or CLK for tuning
attachInterrupt(digitalPinToInterrupt(DT), freqTune, CHANGE);
attachInterrupt(digitalPinToInterrupt(CLK), freqTune, CHANGE);
// enable interrupts
interrupts();
// oled init, sets I2C addr to 0x3C
oled.begin();
// init dds si5351 module, "0" = default 25MHz XTAL, correction
dds.init(SI5351_CRYSTAL_LOAD_8PF, 0, CORRE);
// set 8mA output drive
dds.drive_strength(SI5351_CLK1, SI5351_DRIVE_8MA);
// enable VFO output CLK1, disable CLK0 & 2
dds.output_enable(SI5351_CLK0, 0);
dds.output_enable(SI5351_CLK1, 1);
dds.output_enable(SI5351_CLK2, 0);
freqOut(freq); // cHz, output freq
freqChange = false;
dispUpdate();
}
void loop() {
if (freqChange) {
freqOut(freq);
freqChange = false;
dispUpdate();
}
}
// ISR - encoder interrupt service routine
void freqTune() {
unsigned char result;
result = enc.process();
if (result == DIR_CW && ndx < 13) {
freq = freqTable[++ndx];
freqChange = true;
}
else if (result == DIR_CCW && ndx > 0) {
freq = freqTable[--ndx];;
freqChange = true;
}
}
// picture loop, display init data
void dispUpdate() {
oled.firstPage();
do {
dispMsg(25, 0, "SDR_ELEKTOR");
dispFreq(25, 25, 0, freq, 0); // freq is in cHz
dispMsg(20, 50, "<-- Freq -->");
} while ( oled.nextPage() );
}
// Output Freq for Elektor SDR, f (cHz) x 4 on CLK1
void freqOut(uint64_t f) {
dds.set_freq(f * 4ULL, SI5351_CLK1);
}
// display freq at x, y, f (Hz), cf (cHz), d)ecimal places
void dispFreq(u8g2_uint_t x, u8g2_uint_t y, double f, double cf, uint8_t d) {
double fd;
char buf[100];
// sets font, cursor position and displays freq
oled.setFont(u8g2_font_10x20_tf); // font
oled.setFontPosTop(); // origin top
fd = f + cf / 100; // calc freq
oled.setCursor(x, y);
oled.print(fd / 1000, d);
oled.print("kHz");
}
// 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);
}