/*
* Use Arduino Leonardo in Arduino IDE to buid for the DFRobot Beetle (DFR0282)
* https://wiki.dfrobot.com/Beetle_SKU_DFR0282
* LCD and Si5351 on SDA and SCL in parallel
* LCD is a 1602 with I2C
* I2C pins are SCL: D3, and SDA: D2
* Rotary encoder A on pin 9
* Rotary encoder B on pin 10
*
* Mode switch to pin 11 (5V Max for HIGH)
*
* TX Interrupt on pin 0 (Serial pin RX) (5V Max for HIGH)
*
* RIT enable to pin 1 (Serial pin TX) (5V Max for HIGH)
*/
#include <LiquidCrystal_I2C.h>
#include <si5351.h>
// Rotary Encoder Inputs, Mode switch and interrupt pint
#define TX 3//0 // TX interrupt pin \\ Leonardo int on 0, 1, 2, 3, or 7; Uno int on 2 or 3
#define RIT 2 //6//1 // RIT On or Off pin
#define CLK 9 // Encoder CLK
#define DT 10 // Encoder Data
#define SW 11 // Mode switch
// Instantiate the Objects
LiquidCrystal_I2C lcd(0x27, 16, 2); // 3F the address of the LCD
Si5351 si5351;
// Variable definitions
static const long bandStart = 7000000; // start of VFO range
static const long bandEnd = 7300000; // end of VFO range
static const long bandInit = 7050000; // where to initially set the frequency
volatile long freq = 7050000; // the current freq
volatile long oldfreq = 0; // place holder for previous frequency on dial change
volatile long currentfreq = 0; // the current CLK frequency to use after dial change
volatile long ritFreq = 0; // RIT frequency offset in Hz
volatile long currentRIT = 0;
volatile long oldRIT = 0;
volatile bool checkRIT = false; // BOOL to determine if the RIT is enabled
volatile long radix = 1000; // How much to change the frequency by, clicking the Up Down switches
volatile long currentRadix = radix;
volatile bool rxtx = false; // false=RX, true=TX
volatile int transmitting = 0; // Check if the first time in transmit loop
volatile int receiving = 0; // Check if we have just reentered the RX mode. Rx is +650 Hz
// Display is -650Hz from Rx freq which is the Tx freq.
// Hold the state of the mode switch
volatile long modeState = 0; // Holds the mode state for the rotary encoder; 0 = freq, 1 = RIT, 2 = radix, 3 = LOCKED
// Rotary encoder variables
volatile int rotState = 0;
volatile int rotated = 0; // Did the encoder rotate? 1 = yes, 0 = no
volatile int changed = 0; // Was the frequency or RIT changed? 1 = yes, 0 = no
volatile int displayUpdate = 0; // If the display needs an update, then update it? 1 = yes, 0 = no
int currentStateCLK;
int lastStateCLK;
String displayRITString = ""; // String for RIT display
String displayRadixString = ""; // String for Radix display
volatile int debugTest1 = 0; // debug testing variable 1
volatile int debugTest2 = 0; // debug testing variable 2
volatile int debugTest3 = 0; // debug testing variable 3
volatile int debugTest4 = 0; // debug testing variable 4
volatile int debugTest5 = 0; // debug testing variable 5
volatile int debugTest6 = 0; // debug testing variable 6
volatile int debugTest7 = 0; // debug testing variable 7
volatile int debugTest8 = 0; // debug testing variable 8
volatile int debugTest9 = 0; // debug testing variable 9
void setup() {
// Set encoder pins as inputs
pinMode(CLK, INPUT);
pinMode(DT, INPUT);
// Set the switch pin
pinMode(SW, INPUT_PULLUP);
// Set the TX interrupt pin
pinMode(TX, INPUT); //_PULLUP);
attachInterrupt(digitalPinToInterrupt(TX), txMode, CHANGE);
// Set the RIT enable pin
pinMode(RIT, INPUT);
attachInterrupt(digitalPinToInterrupt(RIT), ritState, CHANGE);
// Read the initial state of CLK
lastStateCLK = digitalRead(CLK);
// Initialize the Si5351
si5351.init(SI5351_CRYSTAL_LOAD_8PF, 0, 0);
si5351.set_correction(191, SI5351_PLL_INPUT_XO);
si5351.set_pll(SI5351_PLL_FIXED, SI5351_PLLA);
si5351.output_enable(SI5351_CLK0, 1);
si5351.output_enable(SI5351_CLK1, 0);
si5351.output_enable(SI5351_CLK2, 0);
si5351.drive_strength(SI5351_CLK0, SI5351_DRIVE_8MA);
// Initialize the display
lcd.init();
lcd.init();
lcd.backlight();
lcd.noCursor();
// Initalize the Si5351 frequency with no RIT
SendFrequency(0);
// Initalize the LCD display
initDisplayText();
UpdateDisplay();
// // REMOVE // //
Serial.begin(9600);
}
void loop() {
// Check if we are transmitting
//Serial.print("Test");
if (rxtx == true) {
if (transmitting == 0) {
freq = freq - 650;
SendFrequency(freq); // TX frequency is the displayed frequency
transmitting = 1;
receiving = 0;
displayUpdate = 1;
}
else{
// Don't do anything else while transmitting
displayUpdate = 0;
}
//Serial.print("In TX 1");
} // END TX If
else { // We are recieving so check all states and update as needed
// See if we just left transmitting. If so, then update the frequency to +650 HZ
// Keep the display at the Tx frequency.
if (receiving == 0) {
freq = freq + 650; // Receive frequency is 650Hz above the transmit frequncy
receiving = 1;
transmitting = 0;
displayUpdate = 1;
}
// Check the state of the encoder
CheckEncoder();
// Get the current frequency
currentfreq = getfreq(); // Interrupt safe method to get the current frequency
if (rotated == 1) {//0 = freq, 1 = RIT, 2 = radix, 3 = LOCKED
// If the VFO is in lock mode, then only check for a mode change
if (modeState == 3) {
// Locked so skip all other checks
displayUpdate = 0;
} else { // not locked so check for mode FREQ, RIT, or RADIX
if (modeState == 0) { // Update the frequency
UpdateFrequency();
changed = 1;
displayUpdate = 1;
} else if (modeState == 1) { // Update the RIT
UpdateRIT();
changed = 1;
displayUpdate = 1;
} else if (modeState == 2) { // Update the radix
UpdateRadix();
displayUpdate = 1;
}
}
rotated = 0;
}
// Send the frequency to the Si5351, but only if the frequency or RIT has changed
// If the mode state was FREQ or RIT then update the frequency. Check if RIT is ON or OFF
if(changed == 1){
if (checkRIT == true) {
// update the frequency with the RIT value
SendFrequency(ritFreq);
changed = 0;
} else {
SendFrequency(0);
changed = 0;
}
}
// Now check for a mode change
if (digitalRead(SW) == LOW) {
ChangeMode();
}
} // END RX else
if(checkRIT == true){
lcd.setCursor(12, 1);
displayRITString = formatRIT();
lcd.print(displayRITString);
} else {
lcd.setCursor(12, 1);
lcd.print(" ");
}
// Finally update the display, only changing what needs an update
if (displayUpdate == 1) {
UpdateDisplay();
displayUpdate = 0;
}
}
// Interrupt handler
// Checks to see if we are transmitting (TX = true) or receiving (TX = false)
void txMode() {
if (digitalRead(TX) == HIGH) {
rxtx = true;
} else {
rxtx = false;
}
}
// Interrupt handler
void ritState(){
// Check to see if RIT is enabled
if (digitalRead(RIT) == HIGH) {
checkRIT = true;
}
else{
checkRIT = false;
}
}
// Sends the frequency stored in the variable 'freq' to the Si5351 channel 0
void SendFrequency(long RITvalue) {
long sendFreq;
sendFreq = freq + RITvalue;
si5351.set_freq((sendFreq * 100ULL), SI5351_CLK0);
}
// Check the state of the rotary encoder and set the value of 'rotState'
void CheckEncoder() {
// Read the current state of CLK
currentStateCLK = digitalRead(CLK);
// If last and current state of CLK are different, then pulse occurred
// React to only 1 state change to avoid double count
if (currentStateCLK != lastStateCLK && currentStateCLK == 1) {
// If the DT state is different than the CLK state then
// the encoder is rotating CCW so decrement
if (digitalRead(DT) != currentStateCLK) {
rotState = 0;
rotated = 1;
} else {
// Encoder is rotating CW so increment
rotState = 1;
rotated = 1;
}
}
// Remember last CLK state
lastStateCLK = currentStateCLK;
// Put in a slight delay to help debounce the reading
delay(1);
}
// Get the current frequency in an interrupt safe manner
long getfreq() {
long temp_freq;
cli();
temp_freq = freq;
sei();
return temp_freq;
}
// Update the frequency after an encoder rotation
void UpdateFrequency() {
if (rotState == 0) { // CCW rotation so decrement the frequency by the radix
freq = (freq - radix);
if (freq < bandStart)
freq = bandEnd;
} else { // CW rotation so increment the frequency by the radix
freq = (freq + radix);
if (freq > bandEnd)
freq = bandStart;
}
}
// Update the RIT frequency after an encoder rotation
void UpdateRIT() {
if (rotState == 0) { // CCW rotation so decrement the RIT frequency by 1 Hz
ritFreq = ritFreq - 1;
if (ritFreq < -1000)
ritFreq = -1000;
} else { // CW rotation so increment the RIT frequency by 1 Hz
ritFreq = ritFreq + 1;
if (ritFreq > 1000)
ritFreq = 1000;
}
}
// Update the radix value after an encoder rotation
void UpdateRadix() {
if (rotState == 1) { // CW rotation so increment the radix
if (radix == 1)
radix = 10;
else if (radix == 10)
radix = 100;
else if (radix == 100)
radix = 1000;
else if (radix == 1000)
radix = 2500;
else if (radix == 2500)
radix = 5000;
else if (radix == 5000)
radix = 10000;
//else if (radix == 10000)
// radix = 100000; // We don't need 100kHz steps for such a small 10m frequency range
else
radix = 1;
} else { // CCW rotation so decrement the radix
//if (radix == 100000) // We don't need 100kHz steps for such a small 10m frequency range
// radix = 10000;
if (radix == 10000)
radix = 5000;
else if (radix == 5000)
radix = 2500;
else if (radix == 2500)
radix = 1000;
else if (radix == 1000)
radix = 100;
else if (radix == 100)
radix = 10;
else if (radix == 10)
radix = 1;
else
radix = 10000;
}
}
// Handles changing the mode and the state of the display during a mode change
void ChangeMode() {
// Update the mode
if (modeState == 0) {
if(checkRIT == true){
modeState = 1; // 1 = RIT
} else{
modeState = 2; // 2 = radix
}
} else if (modeState == 1) {
modeState = 2; // 2 = radix
} else if (modeState == 2) {
modeState = 3; // 3 = LOCKED
} else {
modeState = 0; // 0 = freq
}
delay(250);
// Now update the display
if (modeState == 1) { // 1 = RIT
lcd.setCursor(7, 1);
lcd.print("RIT ");
lcd.setCursor(13, 1);
lcd.print(formatRIT());
} else if (modeState == 2) { // 2 = radix
lcd.setCursor(7, 1);
lcd.print("Step");
lcd.setCursor(8, 0);
lcd.print(" ");
lcd.setCursor(8, 0);
displayRadixString = formatRadix();
lcd.print(displayRadixString);
} else if (modeState == 3) { // 3 = LOCKED
lcd.setCursor(7, 1);
lcd.print("Lock");
lcd.setCursor(8, 0);
lcd.print(" KD1IN");
} else { // 0 = freq
lcd.setCursor(7, 1);
lcd.print("Freq");
lcd.setCursor(8, 0);
lcd.print(" KD1IN");
}
// FOR TESTING OF MODE STATE
//lcd.setCursor(15, 1);
//lcd.print(modeState);
}
// Updates the display after a change in state.
// In transmit change RX to TX and display the TX frequency.
// In receive change TX to RX and display the TX frequency plus the RIT if enabled.
// Check the modeState and update display as needed.
void UpdateDisplay() {
//
if (rxtx == true) { // transmitting so make sure we display TX and no RIT
lcd.setCursor(0, 0);
lcd.print(freq);
lcd.setCursor(4, 1);
lcd.print("TX");
}
else{ // receiving; see what mode we are in and change the display for that mode as needed
//0 = freq, 1 = RIT, 2 = radix, 3 = LOCKED
lcd.setCursor(4, 1);
lcd.print("RX");
if (modeState == 0) { // Update to the Rx frequency without RIT and do not show 650 Hz offset
lcd.setCursor(0, 0);
lcd.print(String(freq -650));
}
else if (modeState == 2) { // Update to the radix
lcd.setCursor(8, 0);
displayRadixString = formatRadix();
lcd.print(displayRadixString);
}// END modeState == 2
else{
// Do nothing
}
if(checkRIT == true){
lcd.setCursor(12, 1);
displayRITString = formatRIT();
lcd.print(displayRITString);
}
else{
lcd.setCursor(12, 1);
lcd.print(" ");
}
}
}
String formatRIT(){
// format the RIT frequency for display
String theRIT = "";
if(ritFreq < -999){
theRIT = String(ritFreq);
}
else if(ritFreq < -99){
theRIT = String(ritFreq);
}
else if(ritFreq < -9){
theRIT = " " + String(ritFreq);
}
else if(ritFreq < 0){
theRIT = " " + String(ritFreq);
}
else if(ritFreq < 10){
theRIT = " " + String(ritFreq);
}
else if(ritFreq < 100){
theRIT = " " + String(ritFreq);
}
else if(ritFreq < 1000){
theRIT = " " + String(ritFreq);
}
else{
theRIT = String(ritFreq);
}
return theRIT;
}
String formatRadix(){
// format the Radix frequency for display
String theRadix= "";
if(radix < 10){
theRadix = " " + String(radix);
}
else if(radix < 100){
theRadix = " " + String(radix);
}
else if(radix < 1000){
theRadix = " " + String(radix);
}
else if(radix < 10000){
theRadix = " " + String(radix);
}
else{
theRadix = " " + String(radix);
}
return theRadix;
}
void initDisplayText(){
// freq
lcd.setCursor(0, 0);
lcd.print(freq);
// mode
lcd.setCursor(0, 1);
lcd.print("CW");
lcd.setCursor(10, 1);
// PTT
lcd.setCursor(4, 1);
lcd.print("RX");
// callsign
//lcd.setCursor(11, 0);
//lcd.print("KD1IN");
lcd.setCursor(12, 1);
lcd.print(" ");
lcd.setCursor(8, 0);
displayRadixString = formatRadix();
lcd.print(displayRadixString);
}