Skip to content

Instantly share code, notes, and snippets.

@ednisley
Last active May 31, 2017 15:32
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save ednisley/7b268d716574dbc492547eb36985ae2a to your computer and use it in GitHub Desktop.
Save ednisley/7b268d716574dbc492547eb36985ae2a to your computer and use it in GitHub Desktop.
Arduino source code: OLED display with U8x8 driver for AD9850 DDS
// OLED display test for 60 kHz crystal tester
#include <avr/pgmspace.h>
//#include <SPI.h>
#include <U8g2lib.h>
#include <U8x8lib.h>
// Turn off DDS SPI for display checkout
#define DOSPI 0
//---------------------
// Pin locations
// SPI uses hardware support: those pins are predetermined
#define PIN_HEARTBEAT 9
#define PIN_DDS_RESET 7
#define PIN_DDS_LATCH 8
#define PIN_DISP_SEL 4
#define PIN_DISP_DC 5
#define PIN_DISP_RST 6
#define PIN_SCK 13
#define PIN_MISO 12
#define PIN_MOSI 11
#define PIN_SS 10
char Buffer[10+1+10+1]; // string buffer for long long conversions
#define GIGA 1000000000LL
#define MEGA 1000000LL
#define KILO 1000LL
struct ll_fx {
uint32_t low; // fractional part
uint32_t high; // integer part
};
union ll_u {
uint64_t fx_64;
struct ll_fx fx_32;
};
union ll_u CtPerHz; // will be 2^32 / 125 MHz
union ll_u HzPerCt; // will be 125 MHz / 2^32
union ll_u One; // 1.0 as fixed point
union ll_u Tenth; // 0.1 as fixed point
union ll_u TenthHzCt; // 0.1 Hz in counts
// All nominal values are integers for simplicity
#define OSC_NOMINAL (125 * MEGA)
#define OSC_OFFSET_NOMINAL (-344LL)
union ll_u OscillatorNominal; // nominal oscillator frequency
union ll_u OscOffset; // ... and offset, which will be signed 64-bit value
union ll_u Oscillator; // true oscillator frequency with offset
union ll_u CenterFreq; // center of scan width
#define SCAN_WIDTH 6
#define SCAN_SETTLE 2000
union ll_u ScanFrom, ScanTo, ScanFreq, ScanStep; // frequency scan settings
uint8_t ScanStepCounter;
union ll_u TestFreq,TestCount; // useful variables
//U8X8_SH1106_128X64_NONAME_4W_HW_SPI u8x8(PIN_DISP_SEL, PIN_DISP_DC , PIN_DISP_RST);
U8X8_SH1106_128X64_NONAME_4W_SW_SPI u8x8(PIN_SCK, PIN_MOSI, PIN_DISP_SEL, PIN_DISP_DC , PIN_DISP_RST);
//U8X8_SH1106_128X64_NONAME_HW_I2C u8x8(U8X8_PIN_NONE);
#define HEARTBEAT_MS 3000
unsigned long MillisNow,MillisThen;
//-----------
// Useful functions
// Pin twiddling
void TogglePin(char bitpin) {
digitalWrite(bitpin,!digitalRead(bitpin)); // toggle the bit based on previous output
}
void PulsePin(char bitpin) {
TogglePin(bitpin);
TogglePin(bitpin);
}
// SPI I/O
void EnableSPI(void) {
digitalWrite(PIN_SS,HIGH); // set SPI into Master mode
SPCR |= 1 << SPE;
}
void DisableSPI(void) {
SPCR &= ~(1 << SPE);
}
void WaitSPIF(void) {
while (! (SPSR & (1 << SPIF))) {
TogglePin(PIN_HEARTBEAT);
TogglePin(PIN_HEARTBEAT);
continue;
}
}
byte SendRecSPI(byte Dbyte) { // send one byte, get another in exchange
SPDR = Dbyte;
WaitSPIF();
return SPDR; // SPIF will be cleared
}
// DDS module
void EnableDDS(void) {
digitalWrite(PIN_DDS_LATCH,LOW); // ensure proper startup
digitalWrite(PIN_DDS_RESET,HIGH); // minimum reset pulse 40 ns, not a problem
digitalWrite(PIN_DDS_RESET,LOW);
delayMicroseconds(1); // max latency 100 ns, not a problem
DisableSPI(); // allow manual control of outputs
digitalWrite(PIN_SCK,LOW); // ensure clean SCK pulse
PulsePin(PIN_SCK); // ... to latch hardwired config bits
PulsePin(PIN_DDS_LATCH); // load hardwired config bits = begin serial mode
EnableSPI(); // turn on hardware SPI controls
SendRecSPI(0x00); // shift in serial config bits
PulsePin(PIN_DDS_LATCH); // load serial config bits
}
// Write delta phase count to DDS
// This comes from the integer part of a 64-bit scaled value
void WriteDDS(uint32_t DeltaPhase) {
SendRecSPI((byte)DeltaPhase); // low-order byte first
SendRecSPI((byte)(DeltaPhase >> 8));
SendRecSPI((byte)(DeltaPhase >> 16));
SendRecSPI((byte)(DeltaPhase >> 24));
SendRecSPI(0x00); // 5 MSBs = phase = 0, 3 LSBs must be zero
PulsePin(PIN_DDS_LATCH); // write data to DDS
}
//-----------
// Round scaled fixed point to specific number of decimal places: 0 through 8
// You should display the value with only Decimals characters beyond the point
// Must calculate rounding value as separate variable to avoid mystery error
uint64_t RoundFixedPt(union ll_u TheNumber,unsigned Decimals) {
union ll_u Rnd;
Rnd.fx_64 = (One.fx_64 / 2) / (pow(10LL,Decimals));
TheNumber.fx_64 = TheNumber.fx_64 + Rnd.fx_64;
return TheNumber.fx_64;
}
//-----------
// Multiply two unsigned scaled fixed point numbers without overflowing a 64 bit value
// The product of the two integer parts mut be < 2^32
uint64_t MultiplyFixedPt(union ll_u Mcand, union ll_u Mplier) {
union ll_u Result;
Result.fx_64 = ((uint64_t)Mcand.fx_32.high * (uint64_t)Mplier.fx_32.high) << 32; // integer parts (clear fract)
Result.fx_64 += ((uint64_t)Mcand.fx_32.low * (uint64_t)Mplier.fx_32.low) >> 32; // fraction parts (always < 1)
Result.fx_64 += (uint64_t)Mcand.fx_32.high * (uint64_t)Mplier.fx_32.low; // cross products
Result.fx_64 += (uint64_t)Mcand.fx_32.low * (uint64_t)Mplier.fx_32.high;
return Result.fx_64;
}
//-----------
// Long long print-to-buffer helpers
// Assumes little-Endian layout
void PrintHexLL(char *pBuffer,union ll_u FixedPt) {
sprintf(pBuffer,"%08lx %08lx",FixedPt.fx_32.high,FixedPt.fx_32.low);
}
// converts all 9 decimal digits of fraction, which should suffice
void PrintFractionLL(char *pBuffer,union ll_u FixedPt) {
union ll_u Fraction;
Fraction.fx_64 = FixedPt.fx_32.low; // copy 32 fraction bits, high order = 0
Fraction.fx_64 *= GIGA; // times 10^9 for conversion
Fraction.fx_64 >>= 32; // align integer part in low long
sprintf(pBuffer,"%09lu",Fraction.fx_32.low); // convert low long to decimal
}
void PrintIntegerLL(char *pBuffer,union ll_u FixedPt) {
sprintf(pBuffer,"%lu",FixedPt.fx_32.high);
}
void PrintFixedPt(char *pBuffer,union ll_u FixedPt) {
PrintIntegerLL(pBuffer,FixedPt); // do the integer part
pBuffer += strlen(pBuffer); // aim pointer beyond integer
*pBuffer++ = '.'; // drop in the decimal point, tick pointer
PrintFractionLL(pBuffer,FixedPt);
}
void PrintFixedPtRounded(char *pBuffer,union ll_u FixedPt,unsigned Decimals) {
char *pDecPt;
FixedPt.fx_64 = RoundFixedPt(FixedPt,Decimals);
PrintIntegerLL(pBuffer,FixedPt); // do the integer part
pBuffer += strlen(pBuffer); // aim pointer beyond integer
pDecPt = pBuffer; // save the point location
*pBuffer++ = '.'; // drop in the decimal point, tick pointer
PrintFractionLL(pBuffer,FixedPt); // do the fraction
if (Decimals == 0)
*pDecPt = 0; // 0 places means discard the decimal point
else
*(pDecPt + Decimals + 1) = 0; // truncate string to leave . and Decimals chars
}
//-----------
// Calculate useful "constants" from oscillator info
// Args are integer constants in Hz
void CalcOscillator(uint32_t Base,uint32_t Offset) {
union ll_u Temp;
Oscillator.fx_32.high = Base + Offset; // get true osc frequency from integers
Oscillator.fx_32.low = 0;
HzPerCt.fx_32.low = Oscillator.fx_32.high; // divide oscillator by 2^32 with simple shifting
HzPerCt.fx_32.high = 0;
CtPerHz.fx_64 = -1; // Compute (2^32 - 1) / oscillator
CtPerHz.fx_64 /= (uint64_t)Oscillator.fx_32.high; // remove 2^32 scale factor from divisor
TenthHzCt.fx_64 = MultiplyFixedPt(Tenth,CtPerHz); // 0.1 Hz as delta-phase count
#if 0
printf("Inputs: %ld = %ld%+ld\n",Base+Offset,Base,Offset);
PrintFixedPt(Buffer,Oscillator);
printf("Osc freq: %s\n",Buffer);
PrintFixedPt(Buffer,HzPerCt);
printf("Hz/Ct: %s\n",Buffer);
PrintFixedPt(Buffer,CtPerHz);
printf("Ct/Hz: %s\n",Buffer);
PrintFixedPt(Buffer,TenthHzCt);
printf("0.1 Hz Ct: %s",Buffer);
#endif
}
//-- Helper routine for printf()
int s_putc(char c, FILE *t) {
Serial.write(c);
}
//-----------
void setup ()
{
pinMode(PIN_HEARTBEAT,OUTPUT);
digitalWrite(PIN_HEARTBEAT,HIGH); // show we got here
Serial.begin (115200);
fdevopen(&s_putc,0); // set up serial output for printf()
Serial.println (F("DDS OLED exercise"));
Serial.println (F("Ed Nisley - KE4ZNU - May 2017\n"));
// DDS module controls
pinMode(PIN_DDS_LATCH,OUTPUT);
digitalWrite(PIN_DDS_LATCH,LOW);
pinMode(PIN_DDS_RESET,OUTPUT);
digitalWrite(PIN_DDS_RESET,HIGH);
// Light up the display
Serial.println("Initialize OLED");
u8x8.begin();
u8x8.setPowerSave(0);
u8x8.setFont(u8x8_font_pxplusibmcga_f);
u8x8.draw2x2String(0,0,"OLEDTest");
u8x8.drawString(0,2,"Ed Nisley");
u8x8.drawString(0,3," KE4ZNU");
u8x8.drawString(0,4,"May 2017");
// configure SPI hardware
#if DOSPI
SPCR = B01110001; // Auto SPI: no int, enable, LSB first, master, + edge, leading, f/16
SPSR = B00000000; // not double data rate
pinMode(PIN_SS,OUTPUT);
digitalWrite(PIN_SCK,HIGH);
pinMode(PIN_SCK,OUTPUT);
digitalWrite(PIN_SCK,LOW);
pinMode(PIN_MOSI,OUTPUT);
digitalWrite(PIN_MOSI,LOW);
pinMode(PIN_MISO,INPUT_PULLUP);
#endif
TogglePin(PIN_HEARTBEAT); // show we got here
// Calculate useful constants
One.fx_64 = 1LL << 32; // Set up 1.0, a very useful constant
Tenth.fx_64 = One.fx_64 / 10; // Likewise, 0.1
// Set oscillator "constants"
CalcOscillator(OSC_NOMINAL,OSC_OFFSET_NOMINAL);
TogglePin(PIN_HEARTBEAT); // show we got here
// Set the crystal-under-test nominal frequency
CenterFreq.fx_64 = One.fx_64 * (60 * KILO);
#if 1
PrintFixedPtRounded(Buffer,CenterFreq,1);
printf("Center: %s\n",Buffer);
#endif
// Set up scan limits based on center frequency
ScanFrom.fx_64 = CenterFreq.fx_64 - SCAN_WIDTH * (One.fx_64 >> 1);
ScanTo.fx_64 = CenterFreq.fx_64 + SCAN_WIDTH * (One.fx_64 >> 1);
ScanFreq = ScanFrom; // start scan at lower limit
// ScanStep.fx_64 = One.fx_64 / 4; // 0.25 Hz = 8 or 9 tuning register steps
ScanStep.fx_64 = One.fx_64 / 10; // 0.1 Hz = 3 or 4 tuning register steps
// ScanStep.fx_64 = One.fx_64 / 20; // 0.05 Hz = 2 or 3 tuning register steps
// ScanStep = HzPerCt; // smallest possible frequency step
#if 1
Serial.println("\nScan limits");
PrintFixedPtRounded(Buffer,ScanFrom,1);
printf(" from: %11s\n",Buffer);
PrintFixedPtRounded(Buffer,ScanFreq,1);
printf(" at: %11s\n",Buffer);
PrintFixedPtRounded(Buffer,ScanTo,1);
printf(" to: %11s\n",Buffer);
PrintFixedPtRounded(Buffer,ScanStep,3);
printf(" step: %s\n",Buffer);
#endif
// Wake up and load the DDS
#if DOSPI
TestCount.fx_64 = MultiplyFixedPt(ScanFreq,CtPerHz);
EnableDDS();
WriteDDS(TestCount.fx_32.high);
#endif
delay(2000);
u8x8.clearDisplay();
u8x8.setFont(u8x8_font_artossans8_r);
Serial.println("\nStartup done!");
MillisThen = millis();
}
//-----------
void loop () {
MillisNow = millis();
if ((MillisNow - MillisThen) >= SCAN_SETTLE) {
TogglePin(PIN_HEARTBEAT);
MillisThen = MillisNow;
PrintFixedPtRounded(Buffer,ScanFreq,2);
TestCount.fx_64 = MultiplyFixedPt(ScanFreq,CtPerHz);
// printf("%12s -> %9ld\n",Buffer,TestCount.fx_32.high);
#if DOSPI
WriteDDS(TestCount.fx_32.high);
#endif
TestCount.fx_32.low = 0; // truncate to integer
TestFreq.fx_64 = MultiplyFixedPt(TestCount,HzPerCt); // recompute frequency
PrintFixedPtRounded(Buffer,TestFreq,2);
int ln = 0;
u8x8.draw2x2String(0,ln,Buffer);
ln += 2;
TestFreq.fx_64 = ScanTo.fx_64 - ScanFrom.fx_64;
PrintFixedPtRounded(Buffer,TestFreq,1);
u8x8.draw2x2String(0,ln,"W ");
u8x8.draw2x2String(2*(8-strlen(Buffer)),ln,Buffer);
ln += 2;
PrintFixedPtRounded(Buffer,ScanStep,3);
u8x8.draw2x2String(0,ln,"S ");
u8x8.draw2x2String(2*(8-strlen(Buffer)),ln,Buffer);
ln += 2;
TestFreq.fx_32.high = SCAN_SETTLE; // milliseconds
TestFreq.fx_32.low = 0;
TestFreq.fx_64 /= KILO; // to seconds
PrintFixedPtRounded(Buffer,TestFreq,3);
u8x8.draw2x2String(0,ln,"T ");
u8x8.draw2x2String(2*(8-strlen(Buffer)),ln,Buffer);
ln += 2;
ScanFreq.fx_64 += ScanStep.fx_64;
if (ScanFreq.fx_64 > (ScanTo.fx_64 + ScanStep.fx_64 / 2)) {
ScanFreq = ScanFrom;
}
}
}
@ednisley
Copy link
Author

More details on my blog at http://wp.me/poZKh-6O5

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment