/*
* Use Arduino Leonardo in Arduino IDE to buid for the DFRobot Beetle (DFR0282)
* https://wiki.dfrobot.com/Beetle_SKU_DFR0282
* OLED and Si5351 on SDA and SCL in parallel
* OLED is a SSD1306 128x32 with I2C
* I2C pins are SCL: D3, and SDA: D2 on the Beetle
* Rotary encoder A on pin 9
* Rotary encoder B on pin 10
*
* Optical encoder used with no encoder switch
* Use momentary switch on pin 11
*
* 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)
*
* RIT potentiometer is on the A0 pin with VCC and GND.
* Use 100 Ohm resistors and 50k Ohm linear taper potentiometer
*
* RX and TX are on si5351 CH2 and CH0 respectively due to ~10 ms frequency switching time
* Must keep them physically seperated to avoid carrier on RX
*
* Adafruit ssd1306 is too large to use with Leonardo
*
*/
#include <si5351.h>
#include "SSD1306Ascii.h"
#include "SSD1306AsciiAvrI2c.h"
// 0X3C+SA0 - 0x3C or 0x3D
#define I2C_ADDRESS 0x3C
// Define proper RST_PIN if required.
#define RST_PIN -1
#define CHAR_1_WIDTH 6 // Width in pixels of char size 1
#define CHAR_1_HEIGHT 10 // Height in pixels of char size 1
#define CHAR_2_WIDTH 12 // Width in pixels of char size 2
#define CHAR_2_HEIGHT 20 // Height in pixels of char size 2
// Rotary Encoder Inputs, Mode switch and interrupt pins
#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
Si5351 si5351;
SSD1306AsciiAvrI2c oled;
// Variable definitions
uint8_t col[3]; // Columns for row 1 values.
uint8_t rows; // Rows per line.
uint8_t rows2; // Rows per line.
volatile int ritPin = A0; // RIT voltage divider pin
volatile int ritValue = 0; // Value at RIT potentiometer pin
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 TX freq
volatile long freqRX = freq + 650; // the current RX 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 int currentRIT = 0;
volatile int oldRIT = 0;
volatile bool checkRIT = false; // BOOL to determine if the RIT is enabled
volatile bool oldCheckRIT = false;
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.
// Hold the state of the mode switch
volatile long modeState = 0; // Holds the mode state for the rotary encoder; 0 = freq, 1 = radix, 2 = 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
void setup() {
// Set encoder pins as inputs
pinMode(CLK, INPUT);
pinMode(DT, INPUT);
// Set the switch pin as input
pinMode(SW, INPUT_PULLUP);
// Setup the TX interrupt pin
pinMode(TX, INPUT); //_PULLUP);
attachInterrupt(digitalPinToInterrupt(TX), txMode, CHANGE);
// Setup the RIT enable pin
pinMode(RIT, INPUT);
attachInterrupt(digitalPinToInterrupt(RIT), ritState, CHANGE);
#if RST_PIN >= 0
oled.begin(&Adafruit128x32, I2C_ADDRESS, RST_PIN);
#else // RST_PIN >= 0
oled.begin(&Adafruit128x32, I2C_ADDRESS);
#endif // RST_PIN >= 0
// Call oled.setI2cClock(frequency) to change from the default frequency.
oled.setFont(Adafruit5x7);
oled.setFont(System5x7);
col[0] = oled.fieldWidth(strlen("7000000 "));
col[1] = oled.fieldWidth(strlen("RX "));
col[2] = oled.fieldWidth(strlen("RX Freq"));
rows = 2 * oled.fontRows();
rows2 = oled.fontRows();
// Read the initial state of CLK
lastStateCLK = digitalRead(CLK);
// Initialize the Si5351, two channels for RX and TX
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, 1);
si5351.drive_strength(SI5351_CLK0, SI5351_DRIVE_8MA);
si5351.drive_strength(SI5351_CLK2, SI5351_DRIVE_8MA);
// Initalize the Si5351 frequency with no RIT
freqRX = freq + 650; // Receive frequency is 650Hz above the transmit frequency
receiving = 1;
transmitting = 0;
displayUpdate = 1;
displayRIT();
changed = 1;
SendFrequency(0);
// Check to see if RIT is enabled
if (digitalRead(RIT) == HIGH) {
checkRIT = true;
}
else{
checkRIT = false;
}
ritValue = analogRead(ritPin);
// Initalize the LCD text
initDisplayText();
UpdateDisplay();
}
void loop() {
// Check if we are transmitting
if (rxtx == true) {
if (transmitting == 0) {
transmitting = 1;
receiving = 0;
displayUpdate = 5;
}
else{
// Don't do anything else while transmitting
displayUpdate = 0;
}
} // 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) {
// Display RX for receiving
oled.clearField(0, rows, 2);
oled.print("RX");
receiving = 1;
transmitting = 0;
changed = 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 = radix, 2 = 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 radix
UpdateRadix();
displayUpdate = 3;
}
}
rotated = 0;
}
if(checkRIT != oldCheckRIT){
//
changed = 1;
displayUpdate = 2;
oldCheckRIT = checkRIT;
}
if(checkRIT == true){
// Check the state of the RIT potentiometer and update if needed
ritValue = analogRead(ritPin);
if(ritValue != oldRIT){
// update the RIT value and set display and send freq
ritFreq = calculateRIT();
changed = 1;
displayUpdate = 2;
oldRIT = ritValue;
}
}
// 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
// Finally update the display, only changing what needs an update
// 1 - Frequency, 2 - RIT, 3 - Radix, 4 - Mode, 5 - TX
if (displayUpdate > 0) {
UpdateDisplay();
displayUpdate = 0;
}
} // END LOOP
// 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 and 2
void SendFrequency(long RITvalue) {
long sendFreq;
sendFreq = freqRX + RITvalue;
si5351.set_freq((sendFreq * 100ULL), SI5351_CLK0); // RX
si5351.set_freq((freq * 100ULL), SI5351_CLK2); // TX
}
// 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;
}
freqRX = freq + 650; // Update the RX frequency
}
// 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) {
modeState = 1; // 1 = radix
} else if (modeState == 1) {
modeState = 2; // 2 = LOCKED
} else {
modeState = 0; // 0 = freq
}
delay(250);
displayUpdate = 4;
}
void displayMode() {
if (modeState == 1) { // 1 = radix
oled.clearField(col[1], rows, 4);
oled.print("Step");
} else if (modeState == 2) { // 2 = LOCKED
oled.clearField(col[1], rows, 4);
oled.print("Lock");
} else { // 0 = freq
oled.clearField(col[1], rows, 4);
oled.print("Freq");
}
}
void displayTX(){
// Display TX inverted for transmitting
oled.setInvertMode(1);
oled.clearField(0, rows, 2);
oled.print("TX");
oled.setInvertMode(0);
}
void displayRIT(){
if(checkRIT == true){
// Display RIT inverted
displayRITString = formatRIT();
oled.clearField(col[0],rows2, 5);
oled.print(displayRITString);
oled.clearField(col[0] + 12, rows, 3);
oled.print("RIT");
} else{
// Blank with filled rectangle
oled.clearField(col[0],rows2, 5);
oled.clearField(col[0] + 12, rows, 3);
}
}
void displayFrequency(int offset){
// Display the frequency 2x size
oled.set2X();
oled.clearField(0, 0, 7);
oled.print(String(freq + offset));
oled.set1X();
}
void displayRadix(){
// Display radix
displayRadixString = formatRadix();
oled.clearField(col[2], rows, 5);
oled.print(displayRadixString);
}
// 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 displayState and update display as needed.
// 1 - Frequency, 2 - RIT, 3 - Step, 4 - Mode, 5 - TX
void UpdateDisplay() {
//
switch(displayUpdate){
case 2:
displayRIT();
break;
case 3:
displayRadix();
break;
case 4:
displayMode();
break;
case 5:
displayTX();
break;
default: // Frequency
displayFrequency(0);
}
}
String formatRIT(){
// format the RIT frequency for display
String theRIT = "";
if(ritFreq < -999){
theRIT = String(ritFreq);
}
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);
}
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;
}
// Startup text to display on OLED
void initDisplayText(){
oled.clearField(0, rows, 2);
oled.print("RX");
displayFrequency(0);
displayRIT();
displayRadix();
displayMode();
}
// Calculate the RIT offset based upon position of the
// potentiometer in the voltage divider as read by
// the analog 10 bit input
// The range of W7EL is 1300 Hz
int calculateRIT(){
// There are 10 bits in an analog read; 0 to 1023
// center is at 511 = 0; 0 = -1024; 1023 = 1022
// Use slope of 2 since we are using an integer
// Resolution is 2 Hz
int ritResolution = 2; // Divide max and min by 2
// gives 1100 Hz range
return (int) ((2 * ritValue) - 1024) / ritResolution;
}