/*
Arduino sketch for DDS-60 controller
Initial tuning rate 1 kHz/step
*/
#include <EEPROM.h>
// define our event codes
const byte BUTTON_PUSH_EVENT = 1; // push a button
const byte BUTTON_HOLD_EVENT = 2; // hold a button down for 5 seconds
const byte ENCODER_TURN_EVENT = 3; // the encoder was turned
// these are the event code and parameter generated by our I/O code
byte _event = 0;
int _eventParam;
// our main timing variable instead of using millis() directly. See comments in main loop.
int _ms;
volatile byte _encoderCount = 0; // used by interupt code to communicate encoder rotation to main loop
// encoder speed setting. 1 = maximum speed, 2 = half speed etc. This should only be set to a power of 2.
byte _encoderSpeed = 4;
const byte MEMORY_CHANNEL_MAX = 9; // lets have 9 memory channels
// addresses used in EEPROM
const int EE_FREQUENCY = 0; // last frequency VFO A
const int EE_FREQUENCYB = 4;
const int EE_CALIBRATION = 8; // calibration offset
const int EE_MODE = 10; // mode
const int EE_MEMORY_CHANNELS = 1024 - (MEMORY_CHANNEL_MAX * 4); // put the memory channels at the top of the EEPROM
// frequency range
const long FREQ_MIN = 1000000; // 1 MHz
const long FREQ_MAX = 30000000; // 30 MHz
// the limits of the LSB of the clock value.
// 180,000,000 is ABA9500 hex. We make use of the convenient fact that + or - 100 ppm (18 KHz) only changes the 2 least significant bytes.
// This means that we can do a simple and quick OR with CLOCK_BASE to get the final clock to calculate the delta to send to the DDS
const unsigned long CLOCK_LSB_MIN = 0x4EB0;
const unsigned long CLOCK_LSB_MAX = 0xDB50;
// 64 bit 2 to the power of 32.
// note that this is a 64 bit integer using the int64_t type.
const int64_t TWO_E32 = 0x100000000LL;
const long CLOCK_BASE = 179985700 & 0xFFFF0000; // Upper bits of 180 MHz. ORed with _clockLSB when programming DDS
// modes
const byte NORMAL_MODE = 0;
const byte MEMORY_MODE = 1;
const byte MEMORY_CONFIRM_MODE = 2;
const byte DUMMY_MODE = 3;
const byte CALIBRATE_MODE =4;
const byte SPLIT_MODE = 5;
byte _mode; // the current mode
unsigned int _clockLSB; // calibration value. 0x9500 for clock = exactly 180 MHz
unsigned int _saveClockLSB; // save _clockLSB in case we don't commit to calibration adjustment
long _frequency; // current frequency in Hz
long _frequencyB;
long _frequencyStep = 1000; // selected frequency step in Hz
boolean _frequencyHasChanged = false; // set of the frequency has changed. Used for saving frequency to EEPROM
boolean _modeHasChanged = false;
boolean _set40 = true; // assume we start on 40m
byte _memoryChannel = 1; // selected memory channel
long _memoryFrequency; // frequency of selected memory channel
byte _buttonPorts[4]; // holds the port numbers of the push buttons.
//--------------------------------------------------------------------------------------------------------------------------
// Setup
void setup()
{
LCDInitialize(); // initialize the LCD for 4 bit data mode
// Set push button port numbers. The array index starts at zero but I find it easier to think of the buttons as 1, 2 and 3.
// We use the Arduino digital port functions for the (slow) buttons so it's easy to add more buttons to the 3 spare ports
// but, to get good performance, we write directly to the port registers for the DDS, LCD and encoder.
_buttonPorts[0] = 8; // button 1
_buttonPorts[1] = 9; // button 2
_buttonPorts[2] = 10; // button 3
_buttonPorts[3] = 11; // button 4
// initialize the button ports
for (int i = 0; i < sizeof(_buttonPorts); i++)
{
pinMode(_buttonPorts[i], INPUT); // set port as input
digitalWrite(_buttonPorts[i], HIGH); // enable pull-up resistor
}
// setup ports with |= and &= to avoid disturbing serial / USB ports 0 and 1
DDRD &= B11110011; // encoder ports as inputs
DDRD |= B01110000; // set DDS ports as outputs
PORTD |= B00001100; // set encoder inputs to use pull-up resistors
PORTD &= B10001111; // set DDS ports low
// read calibration value from EEPROM
_clockLSB = ReadIntFromEEPROM(EE_CALIBRATION);
// if it is outside + or - 9999 Hz then set it from 0 Hz, i.e., clock assumed to be exactly 180 MHz
if (_clockLSB < CLOCK_LSB_MIN || _clockLSB > CLOCK_LSB_MAX)
{
_clockLSB = 0x9500;
SaveToEEPROM(_clockLSB, EE_CALIBRATION);
}
// write 0 to the DDS. This might not be necessary but it keeps it off until we set it to a known frequency
WriteToDDS(0);
// enable interupts and attach our interupt routines. Set to trigger on every transition of the inputs
attachInterrupt(0, Interrupt0, CHANGE);
attachInterrupt(1, Interrupt1, CHANGE);
// delay 1/4 second. This seems to help ensure a clean power-up
delay(250);
// throw away any initial interupts on power up
ReadEncoder();
// get freqency from EEPROM
_frequency = ReadLongFromEEPROM(EE_FREQUENCY);
_frequencyB = ReadLongFromEEPROM(EE_FREQUENCYB);
// set it to some favorite frequency if it is out of range. This should really only happen on the first power-up
if (_frequency < FREQ_MIN || _frequency > FREQ_MAX)
{
_frequency = 16123408;
_frequencyB = 16123408;
}
PrintFrequency(_frequency - 8998408);
SetDDSFrequency(); // set the DDS to the selected frequency
_event = 0; // no events are happening yet
_mode = EEPROM.read(EE_MODE); // read the last mode setting from EEPROM
// start in NORMAL
SetMode(NORMAL_MODE);
}
// ---------------------------------------------------------------------------------------------------------------
// Main loop
void loop()
{
// We need to keep track of elapsed time for the various delays. millis() returns a 32 bit long.
// If we only use the lower 16 bits then it will cycle around (overflow) every 65 seconds or so. That's plenty for us.
// Since _ms is an int, our other time variables only need to be ints rather than longs
// With the usual 2s complement arithmetic, "elapsed" calculations work fine without us needing to do anything special for overflow.
_ms = millis() & 0xFFFF;
ReadButtons(); // read the buttons
if (!_event) // read the encoder if no button was pushed
{
ReadEncoder();
}
if (_event && _mode != DUMMY_MODE) // was there an event?
{
switch(_mode) // yes, so lets direct it to the appropriate mode routine
{
case NORMAL_MODE:
NormalMode();
break;
case MEMORY_MODE:
MemoryMode();
break;
case MEMORY_CONFIRM_MODE:
MemoryConfirmMode();
break;
case CALIBRATE_MODE:
CalibrateMode();
break;
case SPLIT_MODE:
SplitMode();
break;
case DUMMY_MODE:
break;
}
_event = 0; // The event has been processed
}
// Save the frequency and mode in EEPROM if these have changed.
// Because of the save delay to protect the EEPROM from excessive writes, this is called every time whether or not an event happened.
SaveStateToEEPROM();
}
//---------------------------------------------------------------------------------------------------------------------------------------
// NORMAL mode. This is the normal "turn the dial and the frequency changes" mode
//
void NormalMode()
{
switch(_event)
{
case ENCODER_TURN_EVENT: // encoder was turned
_frequency += ((long) _eventParam) * _frequencyStep; // adjust the frequency by the amount the encoder moved
_frequency = RestrictFrequency(_frequency); // restrict the frequency to the allowable range
SetDDSFrequency(); // set the DDS to the new frequency
PrintFrequency(_frequency - 8998408); // print the new frequency
_frequencyHasChanged = true; // remember that the frequency has changed so we can save it to EEPROM later
break;
case BUTTON_PUSH_EVENT: // a button was pressed
switch(_eventParam)
{
case 1:
SetMode(MEMORY_MODE); // move to memory mode
break;
case 2:
SetMode(SPLIT_MODE); // move to split mode VFO A & B
break;
case 3:
IncrementFrequencyStep(); // change & print the frequency step
PrintFrequencyStep();
break;
case 4:
if (_frequency > 16000000)
{
_frequency = 5201592;
SetDDSFrequency(); // set the DDS to the new frequency
PrintFrequency(_frequency + 8998408);
}
else if (_frequency < 5500000)
{
_frequency = 1612340;
SetDDSFrequency(); // set the DDS to the new frequency
PrintFrequency(_frequency - 8998408);
}
break;
}
break;
}
}
//---------------------------------------------------------------------------------------------------------------------------------------
// MEMORY mode. Save to or retrieve from chosen memory channel.
//
void MemoryMode()
{
if (ENCODER_TURN_EVENT) // the encoder was turned
{
_memoryChannel += _eventParam; // move the memory channel up or down
if (_memoryChannel < 1) // cycle around if we go outside the range
{
_memoryChannel = MEMORY_CHANNEL_MAX;
}
else if (_memoryChannel > MEMORY_CHANNEL_MAX)
{
_memoryChannel = 1;
}
GetMemoryChannel(); // get the frequency of the new channel and display
}
if (_event == BUTTON_HOLD_EVENT && _eventParam == 1)
{
// button 1 held so go to Calibrate mode
SetMode(CALIBRATE_MODE);
}
else if (_event == BUTTON_PUSH_EVENT && _eventParam == 1)
{
// button 1 quick push, so Normal Mode
SetMode(NORMAL_MODE);
}
else if (_event == BUTTON_PUSH_EVENT && _eventParam == 2)
// if the selected channel is not "empty" then read memory and set the DDS to the new frequency, then return to NORMAL mode
if (_memoryFrequency)
{
_frequency = _memoryFrequency;
SetDDSFrequency();
_frequencyHasChanged = true;
// memory was empty so go back to Normal mode
SetMode(NORMAL_MODE);
}
else if (_event == BUTTON_PUSH_EVENT && _eventParam == 3)
{
SetMode(MEMORY_CONFIRM_MODE); // we have chosen to save into this channel so move to confirm mode
}
}
//---------------------------------------------------------------------------------------------------------------------------------
// MEMORY CONFIRM mode. Button 3 confirms write to memory channel chosen above
//
void MemoryConfirmMode()
{
// the display is saving "Save ?" and waiting for a button push
if (_event == BUTTON_HOLD_EVENT && _eventParam == 3)
{
// button 3 held so erase that memory channel
_memoryFrequency = 0;
SaveToEEPROM(_memoryFrequency, EE_MEMORY_CHANNELS + ((_memoryChannel - 1) * 4));
ClearLowerRight();
PrintFrequency(_memoryFrequency);
SetMode(MEMORY_MODE);
}
else if (_event == BUTTON_PUSH_EVENT && _eventParam == 3)
{
// button 3 pressed so save to that memory channel
SaveToEEPROM(_frequency, EE_MEMORY_CHANNELS + ((_memoryChannel - 1) * 4));
SetMode(NORMAL_MODE);
}
else
{
// return to normal mode without changing memory channel
ClearLowerRight();
SetMode(NORMAL_MODE);
}
}
// Calibrate Mode
//
void CalibrateMode()
{
switch(_event)
{
case ENCODER_TURN_EVENT: // the encoder was turned
_clockLSB += ((long) _eventParam) * _frequencyStep; // move the _clockLSB calibration value up or down
RestrictClock(); // restrict to + or - 9999
PrintClock(); // print the new calibration value
SetDDSFrequency(); // set the DDS frequency using the new calibration value
break;
case BUTTON_PUSH_EVENT:
switch(_eventParam)
{
case 1: // button 1 pressed. Return to normal mode without saving calibration value
_clockLSB = _saveClockLSB;
SetDDSFrequency();
SetMode(NORMAL_MODE);
break;
case 2: // button 2 pressed. Save new calibration value
SaveToEEPROM(_clockLSB, EE_CALIBRATION);
SetMode(NORMAL_MODE);
break;
case 3: // button 3 pressed. Change increment.
IncrementFrequencyStep();
PrintFrequencyStep();
break;
}
}
}
//------------------------------------------------------------------------
// Split Mode VFO A & B
void SplitMode()
{
switch(_event)
{
case ENCODER_TURN_EVENT: // encoder was turned
_frequencyB += ((long) _eventParam) * _frequencyStep; // adjust the frequency by the amount the encoder moved
_frequencyB = RestrictFrequency(_frequencyB); // restrict the frequency to the allowable range
SetDDSFrequencyB(); // set the DDS to the new frequency
PrintFrequencyB(_frequencyB - 8998408); // print the new frequency
_frequencyHasChanged = true; // remember that the frequency has changed so we can save it to EEPROM later
break;
case BUTTON_PUSH_EVENT: // a button was pressed
switch(_eventParam)
{
case 1:
SetMode(NORMAL_MODE); // return to Normal mode
break;
case 2:
_frequencyB = _frequency; // copy A to B
PrintFrequencyB(_frequencyB - 8998408);
SetDDSFrequencyB(); // set the DDS to the B frequency
break;
case 3:
IncrementFrequencyStep();
PrintFrequencyStep();
break;
}
break;
}
}
//----------------------------------------------------------------------------
// setup for new mode
void SetMode(byte mode)
{
_mode = mode;
switch(mode)
{
case NORMAL_MODE:
LCDClear();
LCDPrint("A",0,0);
_encoderSpeed = 4;
SetDDSFrequency();
ReadEncoder(); // throw away any extra encoder movement
PrintFrequency(_frequency - 8998408);
PrintFrequencyStep();
break;
case MEMORY_MODE:
_encoderSpeed = 4;
PrintMode(" Mem");
GetMemoryChannel();
break;
case MEMORY_CONFIRM_MODE:
LCDPrint("Save?", 11, 1);
break;
case CALIBRATE_MODE:
_saveClockLSB = _clockLSB;
_encoderSpeed = 1;
PrintMode("Calib");
PrintFrequencyStep();
PrintClock();
break;
case SPLIT_MODE:
LCDClear();
LCDPrint("A",0,0);
LCDPrint("B",0,1);
_encoderSpeed = 4;
SetDDSFrequencyB();
ReadEncoder(); // throw away any extra encoder movement
PrintFrequency(_frequency - 8998408);
PrintFrequencyB(_frequencyB - 8998408);
PrintFrequencyStep();
break;
}
_modeHasChanged = true;
}
//------------------------------------------------------------------------------
// retrieve and print the memory channel in _memoryChannel
void GetMemoryChannel()
{
char str[3];
sprintf(str, "M%1i", _memoryChannel); // format as M nn where nn is memory channel
LCDPrint(str, 0, 1);
_memoryFrequency = ReadLongFromEEPROM(EE_MEMORY_CHANNELS + ((_memoryChannel - 1) * 4));
// restrict frequency if out of range
if (_memoryFrequency < FREQ_MIN || _memoryFrequency > FREQ_MAX)
{
_memoryFrequency = 0;
}
if (_memoryFrequency != 0)
{
PrintFrequency(_memoryFrequency - 8998408);
}
else if (_memoryFrequency == 0)
{
PrintFrequency(_memoryFrequency);
}
}
//--------------------------------------------------------------------------------
// save current frequency to EEPROM
void SaveStateToEEPROM()
{
static int lastSaveMS; // remember when we last did this. Note: a static variables retains its value on each call of this routine
int elapsed = _ms - lastSaveMS;
// we only do this every 5 seconds
if (elapsed >= 5000)
{
// only save if the frequency has actually changed
if (_frequencyHasChanged)
{
SaveToEEPROM(_frequency, EE_FREQUENCY);
SaveToEEPROM(_frequencyB, EE_FREQUENCYB);
_frequencyHasChanged = false;
}
if (_modeHasChanged)
{
if (EEPROM.read(EE_MODE) != _mode)
{
EEPROM.write(EE_MODE, _mode);
}
_modeHasChanged = false;
}
lastSaveMS = _ms;
}
}
//--------------------------------------------------------------------------------------------
// cycle the frequency step around 10 KHz, 1 KHz, 100 Hz
void IncrementFrequencyStep()
{
if (_frequencyStep == 100)
{
_frequencyStep = 10000;
}
else
{
_frequencyStep = _frequencyStep / 10;
}
}
// -------------------------------------------------------------------------------------------
// Read buttons
void ReadButtons()
{
// define some sub modes
const byte WAIT_FOR_DOWN = 0; // wait for a button press
const byte WAIT_FOR_UP_OR_TIME = 1; // wait for button to be released or for it to be pressed long enough to be considered "held".
const byte WAIT_FOR_ALL_UP = 2; // waiting for all buttons released
static int lastReadMS = 0; // remember when we last did this
static int downTimeMS = 0; // how long has the button been held down
static byte state = WAIT_FOR_DOWN; // start by waiting for a button press
static byte activeButton; // the button that was pushed or held last time
int elapsed = _ms - lastReadMS; // how long since we last did this?
// only check the buttons every 100 ms (i.e, 10 times a second).
// this is how we debounce the button contacts
if (elapsed < 100)
{
return;
}
lastReadMS = _ms;
// Is there a button currently pressed?
// We only want to know about one button down at the time. i.e, we're not implementing any "shift" keys.
// Note that the other side of the buttons are grounded so the port reads zero when a button is pressed.
// My buttons are all on PORTB so the code could be simplified and made slightly faster by reading PORTB directly in one operation
// but this is more generic and makes it simple to change the port assignments or add more buttons.
byte buttonDown = 0;
for (int i = 0; !buttonDown && i < sizeof(_buttonPorts); i++)
{
if (!digitalRead(_buttonPorts[i]))
{
buttonDown = i + 1;
}
}
// at this point, buttonDown > 0 means that a button is pressed
switch(state)
{
case WAIT_FOR_DOWN:
if (buttonDown) // a button is pressed
{
downTimeMS = _ms; // start the hold counter with the current value of millis
state = WAIT_FOR_UP_OR_TIME; // wait for release or timeout
activeButton = buttonDown; // remember which button is active for next time around the loop
_event = BUTTON_PUSH_EVENT; // raise button push event
_eventParam = activeButton; // tell other code which button was pressed
}
break;
case WAIT_FOR_UP_OR_TIME:
if (buttonDown != activeButton)
{
// the button that was pressed is now up
if (buttonDown)
{
// we prefer all buttons to be up now. i.e, we're do cope with two buttons pressed at the same time
state = WAIT_FOR_ALL_UP;
}
else
{
state = WAIT_FOR_DOWN; // nothing pressed now so go back to waiting for the next button press
}
}
else
{
// button is still pressed
elapsed = _ms - downTimeMS; // how long has it been pressed?
if (elapsed >= 5000) // consider it "held" if it has been pressed for 5 seconds
{
_event = BUTTON_HOLD_EVENT; // raise button hold event
_eventParam = activeButton; // tell other code which button was held
state = WAIT_FOR_ALL_UP; // wait for all released
}
}
break;
case WAIT_FOR_ALL_UP:
if (!buttonDown)
{
state = WAIT_FOR_DOWN; // all released so wait for next press
}
break;
}
}
// --------------------------------------------------------------------------------------------------------------------------
// Read encoder
void ReadEncoder()
{
// the interupt codes moves _encoderCount up or down.
static int prevCount;
// read the count into another variable. It's a single byte so it's safe to read without stopping interupts
byte count = _encoderCount;
int diff = count - prevCount; // how much has it shifted
// this handles overflow or underflow of _encoderCount
if (diff < -100)
{
diff += 256;
}
else if (diff > 100)
{
diff -= 256;
}
if (_encoderSpeed > 1) // reduce the speed if necessary
{
diff = diff / _encoderSpeed;
}
// if diff is > 0 then we moved clockwise, if diff is < 0 then we moved counterclockwise
if (diff != 0)
{
// the encoder moved
prevCount = count; // remember for next time
_event = ENCODER_TURN_EVENT; // raise event
_eventParam = diff; // turn other code how far we moved and in what direction
}
}
// ----------------------------------------------------------------------------------------------------------------------
// Interupts. We want to do as little as possible here.
// By knowing which pin caused the interupt and the state of both pins, we can figure out which direction it moved
//
void Interrupt0()
{
// this is called whenever pin A of the encoder changes
byte inpB = PIND & 8;
if (PIND & 4)
{
if (inpB)
{
_encoderCount--;
}
else
{
_encoderCount++;
}
}
else
{
if (inpB)
{
_encoderCount++;
}
else
{
_encoderCount--;
}
}
}
void Interrupt1()
{
byte inpA = PIND & 4;
if (PIND & 8)
{
if (inpA)
{
_encoderCount++;
}
else
{
_encoderCount--;
}
}
else
{
if (inpA)
{
_encoderCount--;
}
else
{
_encoderCount++;
}
}
}
// -----------------------------------------------------------------------------------------------------------------------------
// Printing VFO A and B
void PrintFrequency(long frequency)
{
if (frequency == 0) // an "empty" memory channel returns frequency == 0
{
LCDPrint(" Empty ", 0, 0);
}
else
{
// this formats the frequency in the typical transceiver format with two decimal points separating MHz and KHz
char str[11];
sprintf(str, "%010lu", frequency);
str[0] = str[2];
str[1] = str[3];
str[2] = '.';
str[3] = str[4];
str[4] = str[5];
str[5] = str[6];
str[6] = '.';
if (frequency < 10000000)
{
str[0] = ' ';
}
LCDPrint(str, 2, 0);
char strr[2]; //blank the Hz digit on line 1
{
sprintf(strr, " ");
}
LCDPrint(strr, 11, 0);
}
}
void PrintFrequencyB(long frequencyB)
{
// this formats the frequency in the typical transceiver format with two decimal points separating MHz and KHz
char str[11];
sprintf(str, "%010lu", frequencyB);
str[0] = str[2];
str[1] = str[3];
str[2] = '.';
str[3] = str[4];
str[4] = str[5];
str[5] = str[6];
str[6] = '.';
if (frequencyB < 10000000)
{
str[0] = ' ';
}
LCDPrint(str, 2, 1);
char strr[2]; //blank the Hz digit on line 2
{
sprintf(strr, " ");
}
LCDPrint(strr, 11, 1);
}
//------------------------------------------------------------------------------------
// print the mode in the top left
void PrintMode(char str[6])
{
LCDPrint(str, 12, 0);
}
void ClearLowerRight()
{
LCDClearArea(9, 1, 7);
}
// print the frequency step in the lower right
void PrintFrequencyStep()
{
char str[2];
if (_frequencyStep == 10000)
{
sprintf(str, "F");
}
else if (_frequencyStep == 1000)
{
sprintf(str, "M");
}
else if (_frequencyStep == 100)
{
sprintf(str, "S");str;
}
else if (_frequencyStep == 10)
{
sprintf(str, "V");str;
}
else
{
sprintf(str, "X");str;
}
LCDPrint(str, 15, 1);
}
// print the calibration value
void PrintClock()
{
// if _clockLSB == 0x9500 then the offset is zero
int offset = _clockLSB - 0x9500;
char str[7];
sprintf(str, "%+6i", offset);
LCDPrint(str, 0, 1);
}
//----------------------------------------------------------------------------------------------------------------------------
// Set the DDS frequency for VFO A & B
void SetDDSFrequency()
{
SetDDSFrequency(_frequency);
}
void SetDDSFrequency(long frequency)
{
long clock = CLOCK_BASE | _clockLSB; // get the clock frequency to use. if _clockLSB is 0x9500 then this is exactly 180 MHz
// calculate and write the required delta to the DDS
// the formula is delta = frequency * (2 to the power of 32) / clock frequency
WriteToDDS((frequency * TWO_E32) / clock);
}
void SetDDSFrequencyB()
{
SetDDSFrequency(_frequencyB);
}
void SetDDSFrequencyB(long frequencyB)
{
long clock = CLOCK_BASE | _clockLSB; // get the clock frequency to use. if _clockLSB is 0x9500 then this is exactly 180 MHz
// calculate and write the required delta to the DDS
// the formula is delta = frequency * (2 to the power of 32) / clock frequency
WriteToDDS((frequencyB * TWO_E32) / clock);
}
//-----------------------------------------------------------------------------------------------------------------------------
// Write to DDS
void WriteToDDS(long delta)
{
// we shift 5 bytes, i.e, 40 bits LSB first
PORTD &= B10111111; // load line low
WriteByteToDDS(delta); // shift out first byte
// now do the other 3 bytes of the delta value
for (int i = 0; i < 3; i++)
{
delta = delta >> 8;
WriteByteToDDS(delta);
}
// this follows example in AD9841 data sheet
WriteByteToDDS(0x09);
PORTD |= B01000000; // load line high
}
// Serially shift 8 bits out to the DDS
// this is faster than using the Arduino ShiftOut function
void WriteByteToDDS(byte data)
{
byte mask = 1; // mask to pick out the bit to send
for (int i = 0; i < 8; i++)
{
if (data & mask)
{
PORTD |= B00010000; // sending 1 so set data bit high
PORTD &= B11011111; // set clock low
}
else
{
PORTD &= B11001111; // sending 0 so set data and clock both low
}
// 1us delays are much longer than we need. The AD9851 datasheet indicates that only a few ns are necessary
// but it seems foolish to push our luck with long wires to the display etc.
// with 1us delays we still shift out 40 bits in about 130us which is plenty fast enough
// and much faster than using ShiftOut which takes about 620us.
delayMicroseconds(1); // let data settle
PORTD |= B00100000; // set clock high
delayMicroseconds(1); // data hold delay
mask = mask << 1; // shift for next bit
}
}
long RestrictFrequency(long frequency)
{
if (frequency < FREQ_MIN)
{
frequency = FREQ_MIN;
}
else if (frequency > FREQ_MAX)
{
frequency = FREQ_MAX;
}
return frequency;
}
void RestrictClock()
{
if (_clockLSB < CLOCK_LSB_MIN)
{
_clockLSB = CLOCK_LSB_MIN;
}
else if (_clockLSB > CLOCK_LSB_MAX)
{
_clockLSB = CLOCK_LSB_MAX;
}
}
// --------------------------------------------------------
// EEPROM
// --------------------------------------------------------
void SaveToEEPROM(unsigned int value, int address)
{
// save a 2 byte int to EEPROM. The MSB is saved to the lowest address
address += 1;
for (int i = 0; i < 2; i++)
{
byte toSave = value & 0xFF;
if (EEPROM.read(address) != toSave) // only write if we really need to so we avoid excessive writes
{
EEPROM.write(address, toSave);
}
value = value >> 8;
address--;
}
}
void SaveToEEPROM(long value, int address)
{
// save a 4 byte long to EEPROM. The MSB is saved to the lowest address
address += 3;
for (int i = 0; i < 4; i++)
{
byte toSave = value & 0xFF;
if (EEPROM.read(address) != toSave) // only write if we really need to so we avoid excessive writes
{
EEPROM.write(address, toSave);
}
value = value >> 8;
address--;
}
}
// read 2 byte int from EEPROM
unsigned int ReadIntFromEEPROM(int address)
{
unsigned int value = EEPROM.read(address);
value = value << 8;
return value | EEPROM.read(address + 1);
}
// read 4 byte long from EEPROM
long ReadLongFromEEPROM(int address)
{
long value = 0;
for (int i = 0; i < 4; i++)
{
value = value | EEPROM.read(address);
if (i < 3)
{
value = value << 8;
address++;
}
}
return value;
}
// ---------------------------------------------------------------------------------------------------------------------------------
// LCD driver
//
void LCDInitialize()
{
DDRC = B00111111;
PORTC = 0;
// setup 4 bit mode following page 46 of the HD44780 datasheet
delayMicroseconds(50000); // wait 50 ms at power-up
LCDWrite4Bits(0x03, false);
delayMicroseconds(5000);
LCDWrite4Bits(0x03, false);
delayMicroseconds(200);
LCDWrite4Bits(0x03, false);
delayMicroseconds(200);
LCDWrite4Bits(0x02, false);
delayMicroseconds(5000);
// we are now in 4 bit mode
LCDWrite(0x28, false); // 2 lines
LCDWrite(0x08, false); // display off, cursor off
LCDClear(); // clear the display
LCDWrite(0x06, false); // increment cursor after writing. Don't shift display
LCDWrite(0x0C, false); // move cursor, not the display. Move to right
}
// print at string at specified position
void LCDPrint(char *str, byte column, byte row)
{
LCDSetCursor(column, row);
LCDPrint(str);
}
// print at string cursor position
void LCDPrint(char *str)
{
while (*str)
{
LCDWrite(*str++, true);
}
}
// clear an area of the display. This is probably more efficient and wastes
// less space than potentially having " " of varying lengths throughout the code
void LCDClearArea(byte column, byte row, byte length)
{
LCDSetCursor(column, row);
for (int i = 0; i < length; i++)
{
LCDWrite(' ', true);
}
}
// clear display. Note the 2 ms delay
void LCDClear()
{
LCDWrite(0x01, false);
delayMicroseconds(2000);
}
// set the cursor position
void LCDSetCursor(byte column, byte row)
{
byte bits = row ? 0xC0 : 0x80;
LCDWrite(bits | column, false);
}
// write a byte as 2 writes of 4 bits each
void LCDWrite(byte value, boolean rs)
{
LCDWrite4Bits(value >> 4, rs);
LCDWrite4Bits(value & 0x0F, rs);
}
// write 4 bits to the display
void LCDWrite4Bits(byte value, boolean rs)
{
if (rs)
{
value |= 0x20; // set RS bit
}
PORTC = value; // write data
delayMicroseconds(1); // wait 1 us to settle
PORTC |= 0x10; // set E high
delayMicroseconds(1); // 1 us delay
PORTC &= 0x2F; // set E low
delayMicroseconds(50); // wait 50 us
}