commit b8605a9ad62d9eb611ce498c4f29cfbab5affad4 Author: kk4das <66222670+kk4das@users.noreply.github.com> Date: Thu Mar 11 12:27:23 2021 -0500 Initial Upload diff --git a/ButtonHandler.cpp b/ButtonHandler.cpp new file mode 100644 index 0000000..a630fe5 --- /dev/null +++ b/ButtonHandler.cpp @@ -0,0 +1,362 @@ +// Button handler +#include "RadioControl.h" + +//#define BTN_DEBUG + +////////////////////////////////////////////////////////////////////// +// // +// Button Control Variables // +// // +////////////////////////////////////////////////////////////////////// +byte TuneButtonState = 0; +byte lastTuneButtonState = 0; + +// Encoder button control +byte EncButtonState = 0; +byte lastEncButtonState = 0; + +// Sideband select button control +byte SideBandButtonState = 0; +byte lastSideBandButtonState = 0; + +// Band Switch button control +byte BandButtonState = 0; +byte lastBandButtonState = 0; + +// VFO select button control +byte VFOButtonState = 0; +byte lastVFOButtonState = 0; + +// PTT Control +byte PTTState = 0; +byte lastPTTState = 0; + + +//*********************Check Band ************************************ +void SwapBand() { + + uint32_t freq; + + if (band == BAND20) { // Switch to 40 Meter Band + band=BAND40; + sideband=band40Sideband; + freq=band40Freq; + } else { // Switch to 20 Meter Band + band = BAND20; + sideband=band20Sideband; + freq=band20Freq; + } + + // + // Make sure BFO clock tracks with band change + // + if (sideband == USB) { + bfo = USB_BFO; + } else { + bfo = LSB_BFO; + } + + // + // Change the clock frequency to the new bfo + // + setBFO(bfo); + + // + // Set the active VFO to new frequency + // + switch (active_vfo) { + case VFOA: + vfoAfreq=freq; + setVFO(vfoAfreq); + displayActVFO(vfoAfreq); + break; + case VFOB: + vfoBfreq=freq; + setVFO(vfoBfreq); + displayActVFO(vfoBfreq); + break; + } + + // + // Update the display to show the active mode/sideband + // + displayMode(sideband); + startSettingsTimer(); +} + +void CheckBand() { + BandButtonState = digitalRead(BAND_BTN); + if (BandButtonState != lastBandButtonState) { + + // + // On button press, change band 20/40 + // + if (BandButtonState == LOW) { // if button pressed + +#ifdef BTN_DEBUG + ToggleLED(); + String msg = F("CheckBand"); + displayBanner(msg); +#endif + SwapBand(); + } + + lastBandButtonState = BandButtonState; + Delay(50); + BandButtonState = digitalRead(BAND_BTN); //debounce + } +} + + +//*********************Check Sideband ************************************ +void SwapSB() { + if (sideband == USB) { // Switch to LSB + sideband = LSB; + bfo = LSB_BFO; + } else { // Switch to USB + sideband = USB; + bfo = USB_BFO; + } + + // + // Keep track of which SSB is currently selected for current band + // + if (band == BAND20) { + band20Sideband = sideband; + } else { + band40Sideband = sideband; + } + + // + // Change the clock frequency to the new bfo + // + setBFO(bfo); + + // + // Set the active VFO to adjusted frequency + // + switch (active_vfo) { + case VFOA: + setVFO(vfoAfreq); + vfoASideband=sideband; + break; + case VFOB: + setVFO(vfoBfreq); + vfoBSideband=sideband; + break; + } + + // + // Update the display to show the active mode/sideband + // + displayMode(sideband); + startSettingsTimer(); +} + +void CheckSB() { + SideBandButtonState = digitalRead(SIDEBAND_BTN); + if (SideBandButtonState != lastSideBandButtonState) { + + // + // On button press, change active sideband + // Set the BFO as appropriate + // + if (SideBandButtonState == LOW) { // if button pressed + SwapSB(); + } + + lastSideBandButtonState = SideBandButtonState; + Delay(50); + SideBandButtonState = digitalRead(SIDEBAND_BTN); //debounce + } +} + +/* +//********************* Tune Button Handling ************************************ +void DoTune() { + // + // + // CW experiment - call doCW to turn off BFO, set LO to operating frequencey+700 + // Then activate Tx - should generate a carrier + // + // Does but level is too low to drive the Tx amps + // + + displayTune(true); + + setCW(); + + startTx(PTT_TUNE); + + for (int i = 0; i < 100; i++) { +// tone(TONE_PIN, NOTE_B5); + Delay(50); +// noTone(TONE_PIN); + Delay(50); + } + stopTx(); + displayTune(false); +} +*/ + +//********************* Tune Button Handling ************************************ +void DoTune() { + // + // Temp code - 30 second full duty tone for debugging circuits - cheap tone generator. + // Delay(12); + // tone(TONE_PIN, NOTE_B5); + // Delay(30000); + // noTone(TONE_PIN); + + displayTune(true); + startTx(PTT_TUNE); + + for (int i = 0; i < 100; i++) { + tone(TONE_PIN, NOTE_B5); + Delay(50); + noTone(TONE_PIN); + Delay(50); + } + stopTx(); + displayTune(false); +} + + + +void CheckTune() { + TuneButtonState = digitalRead(TUNE_BTN); // creates a 10 second tuning pulse trani 50% duty cycle and makes TUNE appear on the screen + if (TuneButtonState != lastTuneButtonState) { + if (TuneButtonState == LOW) { + DoTune(); + } + lastTuneButtonState = TuneButtonState; + Delay(50); + } +} + +//*********************VFO switch******* VFO A or B **************** +void SwapVFO() { + + if (active_vfo == VFOA) { + active_vfo = VFOB; // Make VFOB Active + sideband=vfoBSideband; + } else { + active_vfo = VFOA; // Make VFOA Active + sideband=vfoASideband; + } + + // + // Adjust BFO in case sideband has changed + // + if (sideband == USB) { // Switch to LSB + bfo = USB_BFO; + } else { // Switch to USB + bfo = LSB_BFO; + } + setBFO(bfo); + displayMode(sideband); // Change sideband indicator + + +#ifdef BTN_DEBUG + ToggleLED(); + String msg = F("SwapVFO: active_vfo="); + msg += active_vfo; + displayBanner(msg); +#endif + + // + // Swap Active/Alternate frequency displays + // + switch (active_vfo) { + case VFOA: + setVFO(vfoAfreq); + displayActVFO(vfoAfreq); + displayAltVFO(vfoBfreq); + break; + case VFOB: + setVFO(vfoBfreq); + displayActVFO(vfoBfreq); + displayAltVFO(vfoAfreq); + break; + } + displayVFOAB(active_vfo); // Change the A/B indicator + displayMode(sideband); // Change sideband indicator + + startSettingsTimer(); +} + + +void CheckVFO() { + + VFOButtonState = digitalRead(VFO_BTN); + if (VFOButtonState != lastVFOButtonState) { + if (VFOButtonState == LOW) { // button pressed + SwapVFO(); + } + lastVFOButtonState = VFOButtonState; + Delay(50); + VFOButtonState = digitalRead(VFO_BTN); //debounce + } +} + + +// startSplit +// Turn on split mode and update the display +void startSplit() { + split = true; + displaySplit(split); +} + +// Turnb off split mode and update the display +void stopSplit() { + split = false; + displaySplit(split); +} + +// startTx +// if split mode is on swap Act and Alt VFO +// Put the rig in to TX by triggering the PTT pin +// Update the display to Tx +void startTx(byte PTT_source) { + if (split) SwapVFO(); + digitalWrite(PTT,HIGH); + displayTxRx(TX); + TxRxState = TX; + txSource = PTT_source; +} + +// stopTx +// Return the rig to Rx by lowering the PTT pin +// If split mode is on swap Act and Alt VFO +// Update the display to Tx +void stopTx() { + digitalWrite(PTT,LOW); + if (split) SwapVFO(); + displayTxRx(RX); + TxRxState = RX; +} + +//********************* PTT **************************** + +void CheckPTT(){ + + if ((TxRxState==TX) && (txSource != PTT_MIC)) { + return; + } + + TxRxState = digitalRead(PTT_SENSE); + + if(TxRxState != lastTxRxState){ + + if(TxRxState == TX){ + startTx(PTT_MIC); + } else { + if (txSource == PTT_MIC) { + stopTx(); + } + } + lastTxRxState = TxRxState; + Delay(50); + } + +} diff --git a/CAT.cpp b/CAT.cpp new file mode 100644 index 0000000..8e26922 --- /dev/null +++ b/CAT.cpp @@ -0,0 +1,287 @@ +/************************************************************** + F40 CAT Control + 1/24/2021 - KK4DAS Version 1.3 + Changed to IC746 CAT Library + + ***************************************************************/ +#include "RadioControl.h" +#include + +IC746 radio = IC746(); + +//#define CAT_DEBUG + +// radio modes +#define MODE_LSB 00 +#define MODE_USB 01 +#define MODE_CW 02 + +// TX Rx State +// FT857D combine TX and RX status +// 0b abcdefgh +// a = 0 = PTT on +// a = 1 = PTT off +// b = 0 = HI SWR off +// b = 1 = HI SWR on +// c = 0 = split on +// c = 1 = split off +// d = dummy data +// efgh = SWR meter data ?? + + + + +// function to run when we must put radio on TX/RX +// If PTT requests transmit and the rig is not already transmitting, start TX +// If PTT request stop transmit and the current trasnmit source is PTT_CAT then stop TX +// +void catGoPtt(boolean pttf) { + +#ifdef CAT_DEBUG + String msg = F("CatGoPtt "); + msg += pttf; + displayBanner(msg); +#endif + + if ((TxRxState == TX) && (txSource != PTT_CAT)) { + return; + } + + if (pttf) { + // displayDebug("CAT PTT ON "); + if (TxRxState == RX) { + startTx(PTT_CAT); + } + } else { + + if (txSource == PTT_CAT) { + // displayDebug("CAT PTT OFF"); + stopTx(); + } + } +} + +boolean catGetPtt() { +#if defined (DEBUG) + String msg = "GetPTT: "; + if (ptt == PTT_TX) { + msg += "Tx"; + } else { + msg += "Rx"; + } + displayPrintln(msg); +#endif + + if (TxRxState == TX) { + return true; + } else { + return false; + } +} + +void catGoSplit(boolean cat_split) { + +#ifdef CAT_DEBUG + String msg = F("CatGoSplit "); + msg += cat_split; + displayBanner(msg); +#endif + if (cat_split) { + startSplit(); + } else { + stopSplit(); + } +} + +// function to run when VFOs A/B are toggled +void catSwapVfo() { + SwapVFO(); + +#ifdef CAT_DEBUG + String msg = F("catGoToggleVFOs"); + displayBanner(msg); +#endif + +} + +// function to set a freq from CAT +void catSetFreq(long f) { + + if (f == 0) return; // ignore spurious command + + // + // Change the frequency of the current active VFO + // Clock frequency is the operating frequecy plus the BFO + // + if (active_vfo == VFOA) { + vfoAfreq = f; + setVFO(vfoAfreq); + displayActVFO(vfoAfreq); + } else { + vfoBfreq = f; + setVFO(vfoBfreq); + displayActVFO(vfoBfreq); + } + +#ifdef CAT_DEBUG + String msg = F("catsetFreq "); + msg += f; + displayBanner(msg); +#endif + startSettingsTimer(); +} + +// function to set the mode(LSB or USB) from the cat command +void catSetMode(byte m) { + // If the new mode is different from the current sideband, then swap USB/LSB + switch (sideband) { + case USB: + if (m == MODE_LSB) SwapSB(); + break; + case LSB: + if (m == MODE_USB) SwapSB(); + break; + } + +#ifdef CAT_DEBUG + String msg = F("CatSetMode "); + if (sideband == USB ) { + msg += F("USB "); + } else { + msg += F("LSB "); + } + msg += m; + displayBanner(msg); +#endif +} + +// function to pass the freq to the cat library +long catGetFreq() { + // this must return the freq as an unsigned long in Hz, you must prepare it before + long freq; + + // displayPrintln("catGetFreq called"); + if (active_vfo == VFOA) { + freq = vfoAfreq; + } else { + freq = vfoBfreq; + } + +#ifdef CAT_DEBUG + String msg = F("catGetFreq "); + msg += freq; + displayBanner(msg); +#endif + + return freq; +} + +// function to pass the mode to the cat library +byte catGetMode() { + // this must return the mode in the wat the CAT protocol expect it + byte mode; + + if (sideband == USB) { + mode = MODE_USB; + } else { + mode = MODE_LSB; + } + +#ifdef CAT_DEBUG + String msg = F("CatGetMode "); + if (sideband == USB ) { + msg += F("USB "); + } else { + msg += F("LSB "); + } + msg += mode; + displayBanner(msg); +#endif + + return mode; +} + +// function to pass the smeter reading in RX mode +byte catGetSMeter() { + // this must return a byte in with the 4 LSB are the S meter data + // so this procedure must take care of convert your S meter and scale it + // up to just 4 bits + +#ifdef CAT_DEBUG + String msg = F("CatGetSMeter "); + msg += smeter; + displayBanner(msg); +#endif + + return smeter; +} + + +// Function to select the active VFO from the cat command +// If requested VFO is not already active then swap active and alternate +void catSetVFO(byte catVfo) { + if ( ((catVfo == CAT_VFO_A) && (active_vfo == VFOB)) || + ((catVfo == CAT_VFO_B) && (active_vfo == VFOA))) { + SwapVFO(); + } + +#if defined (DEBUG) + String msg = "SetVFO: "; + if (v == CAT_VFO_A) { + msg += "VFO-A"; + } else { + msg += "VFO-B"; + } + displayPrintln(msg); +#endif +} + +// Function to make VFOS the same +void catVfoAtoB() { + + if (active_vfo == VFOA) { + vfoBfreq = vfoAfreq; + vfoBSideband = vfoASideband; + displayAltVFO(vfoBfreq); + } else { + vfoAfreq = vfoBfreq; + vfoASideband = vfoBSideband; + displayAltVFO(vfoAfreq); + } + +#if defined (DEBUG) + String msg = "VfoAtoB"; + displayPrintln(msg); +#endif +} + + +void setupCat() { + + // Setup the CAT control command handlers + radio.addCATPtt(catGoPtt); + radio.addCATGetPtt(catGetPtt); + radio.addCATAtoB(catVfoAtoB); + radio.addCATSwapVfo(catSwapVfo); + radio.addCATsplit(catGoSplit); + radio.addCATFSet(catSetFreq); + radio.addCATMSet(catSetMode); + radio.addCATVSet(catSetVFO); + radio.addCATGetFreq(catGetFreq); + radio.addCATGetMode(catGetMode); + radio.addCATSMeter(catGetSMeter); + + // now we activate the library + + radio.begin(19200, SERIAL_8N1); + +#ifdef CAT_DEBUG + displayBanner(String(F("setupCAT"))); +#endif + +} + + +void CheckCat() { + radio.check(); +} diff --git a/CAT.h b/CAT.h new file mode 100644 index 0000000..315ea3a --- /dev/null +++ b/CAT.h @@ -0,0 +1,16 @@ +#ifndef CAT_h +#define CAT_H +/************************************************************** + F40 CAT Control + + 5/23/202 - KK4DAS Version 0.9 + Incorporated CAT Control Library + FT857D CAT Library, by Pavel Milanes, CO7WT, pavelmc@gmail. + https://github.com/pavelmc/FT857d/ + ***************************************************************/ + +//=============== Function Prototypes ============================================ +extern void setupCat(); +extern void CheckCat(); + +#endif diff --git a/Encoder.cpp b/Encoder.cpp new file mode 100644 index 0000000..89d152a --- /dev/null +++ b/Encoder.cpp @@ -0,0 +1,243 @@ +// +// Encoder fuctions +// +#include "RadioControl.h" + +//#DEFINE DEBUG_ENC + +////////////////////////////////////////////////////////////////////// +// Rotary Enconder // +////////////////////////////////////////////////////////////////////// +Rotary encoder = Rotary(ENCODER_A, ENCODER_B, ENCODER_BTN); // Setup Encoder + +////////////////////////////////////////////////////////////////////// +// // +// Rotary Encoder Variables // +// Number of clockwise and counterclockwise ticks // +// Delta between successive measurements // +// // +////////////////////////////////////////////////////////////////////// +volatile int encoder_count = 0; // count of encoder clicks +1 for CW, -1 for CCW (volatile since used in ISR) +int prev_encoder_count = 0; // used to measure changes over time +int encoder_delta = 0; // differrnce between successive checks of encoder count + +bool incrementChanged = false; + +// when multiplied by tuning increment tells what the frequency change on +// on the active VFO will be +// Encoder button control +extern byte EncButtonState; +extern byte lastEncButtonState; + +/////////////////////////////////////////////////////////// +// ************* ISR **************** // +// Interrupt service routine, called on encoder movement // +// Only interested in completed clicks // +// +1 for Clocwwise // +// -1 for Counter Clockwise // +// Ignore intermediate values // +/////////////////////////////////////////////////////////// + +#if _BOARDTYPE != Every +ISR(PCINT2_vect) { + unsigned char result = encoder.process(); + if (result == DIR_CW) { + encoder_count++; + } else if (result == DIR_CCW) { + encoder_count--; + } +} + +#else +#define PA0_INTERRUPT PORTA.INTFLAGS & PIN0_bm +#define PA0_CLEAR_INTERRUPT_FLAG PORTA.INTFLAGS &= PIN0_bm +#define PF5_INTERRUPT PORTF.INTFLAGS & PIN5_bm +#define PF5_CLEAR_INTERRUPT_FLAG PORTF.INTFLAGS &= PIN5_bm + +ISR(PORTA_PORT_vect) { + unsigned char result = encoder.process(); + + if (PA0_INTERRUPT) + PA0_CLEAR_INTERRUPT_FLAG; + + if (result == DIR_CW) { + encoder_count++; + } else if (result == DIR_CCW) { + encoder_count--; + } +} + +ISR(PORTF_PORT_vect) { + unsigned char result = encoder.process(); + + if (PF5_INTERRUPT) + PF5_CLEAR_INTERRUPT_FLAG; + + if (result == DIR_CW) { + encoder_count++; + } else if (result == DIR_CCW) { + encoder_count--; + } + +} +#endif + + +//*******************Setup Interrupt Service Routine******************** + +void setupEncoderISR() { + cli(); + // Set up for the rotary encoder interrupts +#if _BOARDTYPE != Every + PCICR |= (1 << PCIE2); + PCMSK2 |= (1 << PCINT18) | (1 << PCINT19); +#endif +#if _BOARDTYPE == Every + PORTA.PIN0CTRL |= PORT_PULLUPEN_bm | PORT_ISC_BOTHEDGES_gc; + PORTF.PIN5CTRL |= PORT_PULLUPEN_bm | PORT_ISC_BOTHEDGES_gc; +#endif + sei(); +} + + +//********************CheckEncoder******************************************* + +// roundedF - Calculates new frequency +// After an increment change rounds up or down to the next even increment +// Otherwise just changes increments or decrements the frequency +// +uint32_t roundedF(uint32_t freq, int32_t delta) { + uint32_t f = freq; + + /* if (incrementChanged) { + if (f % increment) { + if (delta > 0 ) { + f = f + increment - (f % increment); + } else { + f = f - (f % increment); + } + } + incrementChanged = false; + } else { + f = f + delta; + } + */ + if (f % increment) { + if (delta > 0 ) { + f = f + increment - (f % increment); + } else { + f = f - (f % increment); + } + } else { + f = f + delta; + } + return f; +} + +void AdjustVFO(long delta) { + + switch (active_vfo) { + case VFOA: + // vfoAfreq = vfoAfreq + delta; + vfoAfreq = roundedF(vfoAfreq, delta); + setVFO(vfoAfreq); + displayActVFO(vfoAfreq); + break; + case VFOB: + // vfoBfreq = vfoBfreq + delta; + vfoBfreq = roundedF(vfoBfreq, delta); + setVFO(vfoBfreq); + displayActVFO(vfoBfreq); + break; + } + startSettingsTimer(); +} + +void CheckEncoder() { + int current_count = encoder_count; // grab the current encoder_count + int32_t encoder_delta = 0; + + if (current_count != prev_encoder_count) { // if there is any change in the encoder coount + +#ifdef DEBUG_ENC + sprintf(debugmsg, "VFOA: %ld", vfoAfreq); + Serial.println(debugmsg); +#endif + + // + // Calculate the delta (how many click positive or negaitve) + // + encoder_delta = current_count - prev_encoder_count; + + // + // Adjust the currently selected VFO + // + AdjustVFO(encoder_delta * increment); + + +#ifdef DEBUG + sprintf(debugmsg, "current_count: %d, New VFOA: %ld", current_count, vfoAfreq); + Serial.println(debugmsg); +#endif + + prev_encoder_count = current_count; // save the current_count for next time around + + } +} + +//********************CheckIncrement******************************************* +// Cycle through tuning increment values on button press +// 10, 100, 1K, 10K, 100K, 1M +//********************CheckIncrement******************************************* +void AdvanceIncrement() { + if (increment == 10) { + increment = 100; + } + else if (increment == 100) { + increment = 1000; + } + else if (increment == 1000) { + increment = 10000; + } + else if (increment == 10000) { + increment = 100000; + } + else if (increment == 100000) { + increment = 1000000; + } + else { + increment = 10; + } + displayIncr(increment); + incrementChanged = true; + startSettingsTimer(); +} + +void CheckIncrement () { + + EncButtonState = encoder.buttonState(); + //EncButtonState = digitalRead(ENCODER_BTN); +#ifdef DEBUG + sprintf(debugmsg, "Encoder button state: %d", EncButtonState); + Serial.println(debugmsg); + Delay(1000); +#endif + if (EncButtonState != lastEncButtonState) { +#ifdef DEBUG + sprintf(debugmsg, "Encoder button state: %d", EncButtonState); + Serial.println(debugmsg); +#endif + if (EncButtonState == LOW) { + AdvanceIncrement(); + + } + lastEncButtonState = EncButtonState; + Delay(50); + EncButtonState = encoder.buttonState(); //debounce + //EncButtonState = digitalRead(ENCODER_BTN); + } +} + +void setupEncoder() { + setupEncoderISR(); +} diff --git a/RadioControl.h b/RadioControl.h new file mode 100644 index 0000000..542b1b6 --- /dev/null +++ b/RadioControl.h @@ -0,0 +1,229 @@ +#ifndef RadioControl_h +#define RadioControl_h + +#define CALLSIGN "KK4DAS" +#define VERSION "1.4" +#define RIGNAME "SimpleSSB" + + +//#define DEBUG +#ifdef DEBUG + extern char debugmsg[]; +#endif + + +//=============================== FEATURE SELECTION ========================= +// Each flag below enables optional features +// DISPLAY_X - uncomment one line depending on the display that is attached +// BFOxXMHS - IF filter center frequency - eitherr 9MHz or 12MHZ +// SMNETER - Uncomment if S-meter sensor circuit is installed +// DUAL_BAND - Uncomment to enable band switching 20/40 if installed +// CW - Uncomment if CW mod installed (future) + +//=============================== DISPLAY TYPE ============================== +#define DISPLAY_LCD //uncomment for 20x4 LCD +//#define DISPLAY_TFT // uncomment for 320x240 TFT +//#define DISPLAY_NEXTION //uncomment for 2.8" Nextion + +//=============================== IF FILTER FREQ ============================ +#define BFO9MHZ // uncomment for 9.0 MHz IF +//#define BFO12MHZ // uncomment for 12.0 MHz IF + +//=============================== S-METER INSTALLED ========================= +//#define SMETER // uncomment if SMETER mod installed + +//=============================== DUAL BAND MOD INSTALLED =================== +//#define DUAL_BAND // uncomment if dual band mod installed 20/40 + +//=============================== DISPLAY TYPE ============================== +//#define CW // uncomment if CW enabled (not complete) + + + + +//============================== BOARD TYPE (Nano, Every) ===================== +#define Nano 0 +#define Every 1 +#define Uno 2 + +#if defined(ARDUINO_AVR_NANO) +#define _BOARDTYPE Nano +#endif +#if defined(ARDUINO_AVR_NANO_EVERY) +#define _BOARDTYPE Every +#endif +#if defined (ARDUINO_AVR_UNO) +#define _BOARDTYPE Uno +#endif + +//============================= INCLUDES ===================================== +#include +#include "Utility.h" // General purpose common functions +#include "SSB_Display.h" // Display handling +#include "RotaryEnc.h" // Rotary encoder handling +#include "si5351.h" // SI 5351 clock +#include "CAT.h" // CAT Control handling +#include "Settings.h" // Get/Store Settings in EEPROM +#include "Smeter.h" // Smeter definitions + + +//============================== Symbolic constants ========================== + + +////////////////////////////////////////////////////////////////////// +// // +// Arduino Pin Definitions // +// // +////////////////////////////////////////////////////////////////////// + +#define ENCODER_A 2 // Rotary Lib Default - Encoder pin A D2 (interrupt pin) +#define ENCODER_B 3 // Rotary Lib Default - Encoder pin B D3 (interrupt pin) +#define PTT_SENSE 4 // Detect Mic PT +#define PTT 5 // LOW=Rx, HIGH=Tx + +// Dual Band Pins (requires dual band mod) +#define BAND_BTN 6 // Band Switch momentary button +#define BAND_PIN 7 // Band Switch Relay - LOW = Band A (40M), HIGH = Band B (20M) + +#define VFO_BTN A0 // VFO A/B button +#define SIDEBAND_BTN A1 // USB/LSB button +#define TUNE_BTN A2 // Tune Button +#define ENCODER_BTN A3 // Rotary Lib Default - Encoder push button +#define I2C_SDA A4 // I2C SDA Pin +#define I2C_SCL A5 // I2C SCL Pin +#define TONE_PIN A6 // Audio out for tune tone + +// Smeter +#define SMETER_PIN A7 // Requires Signal Strength Sensor + + +// +// For Nextion / Nano Every Only +// Pin 0,1 Serial RX/TX + + +// For color TFT Only +// Arduino +// Pin TFT Pin +// -----------|--------- +// 8 | RST - any free Arduino Pin (not used in this sketch) +// 9 | DC - any free Arduino Pin +// 10 | CS - any free Arduino Pin +// 11 | MOSI - fixed +// 12 | MISO - fixed +// 13 | CLK - fixed +// + + +// Tune Tone +#define NOTE_B5 988 // Tune tone + +// +// Dual Band Mode Constants (requires DUAL_BAND) +// +#define BAND20 1 +#define BAND40 2 +#define BAND20_EDGE 14000000 +#define BAND40_EDGE 7000000 + +// VFO, Sideband and TX state are used both here and in the display +// Define here if not already defined + +// VFO selection +#define VFOA 0 +#define VFOB 1 + +// Sideband selection +#define USB 0 +#define LSB 1 + +// Transmit state +#define TX LOW // TX is on when PTT switched to ground +#define RX HIGH + + +// PTT Source +#define PTT_MIC 0 +#define PTT_CAT 1 +#define PTT_TUNE 2 + +//=============== Globals ============================================ + +// Rotary Encoder +extern Rotary encoder; + +// BFO +extern const uint32_t USB_BFO; +extern const uint32_t LSB_BFO; +extern uint32_t bfo; // Startup BFO frequency +extern const uint32_t BFO_DELTA; // Difference between USB and LSB for BFO change + +// VFO A/B frequencies +extern uint32_t vfoAfreq; +extern uint32_t vfoBfreq; +extern byte vfoASideband; +extern byte vfoBSideband; + +// Tuning increment +extern uint32_t increment; + +// Active VFO indicator +extern byte active_vfo; + + +// Active sideband (USB or LSB) +extern byte sideband; + + +// Band specific memory - requires DUAL_BAND +extern uint32_t band20Freq; +extern uint32_t band40Freq; +extern byte band20Sideband; +extern byte band40Sideband; +extern byte band; + + +// Transmit state +extern byte TxRxState; +extern byte lastTxRxState; + +// Transmoit source (mic, CAT) +extern byte txSource; + +// S Meter +extern byte smeter; + +// Split mode +extern bool split; + + +//=============== Function Prototypes ============================================ + +extern void setupEncoder(); +extern void setVFO(uint32_t freq); +extern void setBFO(uint32_t freq); +extern void CheckIncrement(); +extern void AdvanceIncrement(); +extern void CheckEncoder(); +extern void AdjustVFO(long delta); + +extern void CheckSB(); +extern void SwapSB(); +extern void CheckTune(); +extern void DoTune(); +extern void CheckVFO(); +extern void SwapVFO(); +extern void CheckPTT(); +extern void setupPins(); +extern void startTx(byte PTT_source); +extern void stopTx(); +extern void startSplit(); +extern void stopSplit(); +extern void CheckBand(); // Only called if DUAL_BAND enabled +extern void CheckSmeter(); // Only called if SMETER enabled + +#ifdef CW +extern void setCW(); +#endif + +#endif diff --git a/RotaryEnc.cpp b/RotaryEnc.cpp new file mode 100644 index 0000000..c5b0cd5 --- /dev/null +++ b/RotaryEnc.cpp @@ -0,0 +1,122 @@ +/* Rotary encoder handler for arduino. + * + * Revision: 2020 Dean Souleles, KK4DAS, Licensed under the GNU GPL Version 3. + * Contact: kk4das@gmail.com + * Added default pins and button handling + * + * Copyright 2011 Ben Buxton. Licenced under the GNU GPL Version 3. + * Contact: bb@cactii.net + * + */ + +#include "Arduino.h" +#include "RotaryEnc.h" + +/* + * The below state table has, for each state (row), the new state + * to set based on the next encoder output. From left to right in, + * the table, the encoder outputs are 00, 01, 10, 11, and the value + * in that position is the new state to set. + */ + +#define R_START 0x0 + +#ifdef HALF_STEP +// Use the half-step state table (emits a code at 00 and 11) // nulled out to only use full step +#define R_CCW_BEGIN 0x1 +#define R_CW_BEGIN 0x2 +#define R_START_M 0x3 +#define R_CW_BEGIN_M 0x4 +#define R_CCW_BEGIN_M 0x5 +const unsigned char ttable[6][4] = { + // R_START (00) + {R_START_M, R_CW_BEGIN, R_CCW_BEGIN, R_START}, + // R_CCW_BEGIN + {R_START_M | DIR_CCW, R_START, R_CCW_BEGIN, R_START}, + // R_CW_BEGIN + {R_START_M | DIR_CW, R_CW_BEGIN, R_START, R_START}, + // R_START_M (11) + {R_START_M, R_CCW_BEGIN_M, R_CW_BEGIN_M, R_START}, + // R_CW_BEGIN_M + {R_START_M, R_START_M, R_CW_BEGIN_M, R_START | DIR_CW}, + // R_CCW_BEGIN_M + {R_START_M, R_CCW_BEGIN_M, R_START_M, R_START | DIR_CCW}, +}; +#else +// Use the full-step state table (emits a code at 00 only) +#define R_CW_FINAL 0x1 +#define R_CW_BEGIN 0x2 +#define R_CW_NEXT 0x3 +#define R_CCW_BEGIN 0x4 +#define R_CCW_FINAL 0x5 +#define R_CCW_NEXT 0x6 + +const unsigned char ttable[7][4] = { + // R_START + {R_START, R_CW_BEGIN, R_CCW_BEGIN, R_START}, + // R_CW_FINAL + {R_CW_NEXT, R_START, R_CW_FINAL, R_START | DIR_CW}, + // R_CW_BEGIN + {R_CW_NEXT, R_CW_BEGIN, R_START, R_START}, + // R_CW_NEXT + {R_CW_NEXT, R_CW_BEGIN, R_CW_FINAL, R_START}, + // R_CCW_BEGIN + {R_CCW_NEXT, R_START, R_CCW_BEGIN, R_START}, + // R_CCW_FINAL + {R_CCW_NEXT, R_CCW_FINAL, R_START, R_START | DIR_CCW}, + // R_CCW_NEXT + {R_CCW_NEXT, R_CCW_FINAL, R_CCW_BEGIN, R_START}, +}; +#endif + +/* + * Constructor Common + */ +void Rotary::Constructor_Common() { +// Set pins to input. + pinMode(pin1, INPUT); + pinMode(pin2, INPUT); + pinMode(btn, INPUT); +#ifdef ENABLE_PULLUPS + digitalWrite(pin1, HIGH); + digitalWrite(pin2, HIGH); + digitalWrite(btn, HIGH); +#endif + // Initialise state. + state = R_START; + } + +/* + * Constructor. Each arg is the pin number for each encoder contact. + */ +Rotary::Rotary(char _pin1, char _pin2,char _btn) { + // Assign variables. + pin1 = _pin1; + pin2 = _pin2; + btn = _btn; + Constructor_Common(); +} + + +Rotary::Rotary() { + // Assign variables to defaults + pin1 = ENCODER_A; + pin2 = ENCODER_B; + btn = ENCODER_BTN; + Constructor_Common(); +} + + +unsigned char Rotary::process() { + // Grab state of input pins. + unsigned char pinstate = (digitalRead(pin2) << 1) | digitalRead(pin1); + // Determine new state from the pins and state table. + state = ttable[state & 0xf][pinstate]; + // Return emit bits, ie the generated event. + return state & 0x30; +} + +byte Rotary::buttonState() { // returns the state of the Encoder button LOW=pressed +// return byte(digitalRead(btn)); + return digitalRead(btn); +} diff --git a/RotaryEnc.h b/RotaryEnc.h new file mode 100644 index 0000000..d08ffe6 --- /dev/null +++ b/RotaryEnc.h @@ -0,0 +1,67 @@ +/* + * Rotary encoder library for Arduino. + * + * Revision: 2020 Dean Souleles, KK4DAS, Licensed under the GNU GPL Version 3. + * Contact: kk4das@gmail.com + * Added default pins and button handling and setup for clean interrupt handling + * + * Copyright 2011 Ben Buxton. Licenced under the GNU GPL Version 3. + * Contact: bb@cactii.net + * + */ + +#ifndef RotaryEnc_h +#define RotaryEnc_h + + +#include "Arduino.h" + +// Enable this to emit codes twice per step. +//#define HALF_STEP + +// Enable weak pullups +#define ENABLE_PULLUPS + +// Values returned by 'process' +// No complete step yet. +#define DIR_NONE 0x0 +// Clockwise step. +#define DIR_CW 0x10 +// Counter-clockwise step. +#define DIR_CCW 0x20 + + +// +// Default Encoder wiring Pins D2 and D3 for the encoder, A3 for the pushbutton +// +#ifndef ENCODER_A +#define ENCODER_A 2 // Encoder pin A D2 (interrupt pin) +#endif + +#ifndef ENCODER_B +#define ENCODER_B 3 // Encoder pin B D3 (interrupt pin) +#endif + + +#ifndef ENCODER_BTN +#define ENCODER_BTN A3 // Encoder push buttonh +#endif + + +class Rotary +{ + public: + Rotary(char, char, char); + Rotary(); + unsigned char process(); + byte buttonState(); + private: + unsigned char state; + unsigned char pin1; + unsigned char pin2; + unsigned char btn; + void Constructor_Common(); +}; + +#endif + diff --git a/SSB_Display.h b/SSB_Display.h new file mode 100644 index 0000000..6f96d1e --- /dev/null +++ b/SSB_Display.h @@ -0,0 +1,42 @@ +#ifndef SSB_Display_h +#define SSB_Display_h + +/* + * SSB_Display.h + * KK4DAS, Dean Souleles, KK4DAS@gmail.com + * May 30, 2020 + * + * Constants and function prototypes used in the Display Routines + * This file needs to be in included in the main sketch + * + */ +#include "RadioControl.h" + + + +// ===========================Function Prototypes================================== + +extern void displaySMeter(byte level); +extern void displayBanner(String s); +extern void displayActVFO(uint32_t freq); +extern void displayAltVFO(uint32_t freq); +extern void displayVFOAB(int vfo); +extern void displayTxRx(int tx_rx); +extern void displayMode(int mode); +extern void displayIncr(uint32_t increment); +extern void displayTune(boolean on_off); +extern void displaySplit(boolean splt); +extern void displaySetup(String banner, + uint32_t vfoActfreq, uint32_t vfoAltfreq, + uint32_t activeVFO, + int tx_rx, + int sideband, + boolean split, + uint32_t increment, + byte s_meter); + +#ifdef DISPLAY_NEXTION +extern void CheckTouch(); +#endif + +#endif diff --git a/SSB_LCD_Display.cpp b/SSB_LCD_Display.cpp new file mode 100644 index 0000000..e329d7f --- /dev/null +++ b/SSB_LCD_Display.cpp @@ -0,0 +1,280 @@ +#include "RadioControl.h" +#ifdef DISPLAY_LCD + +#include + +LiquidCrystal_I2C lcd(0x27, 20, 4); //0x27 or 0x3F 0r 0x20 //1604 LCD/w I2C + + +////////////////////////////////////////////////////////////////////// +// // +// Display Routines // +// Display routines for 20x4 LCD // +// // +////////////////////////////////////////////////////////////////////// + +////////////////////////////////////////////////////////////////////// +// Display Layout 20x4 // +// 1111111111 // +// 01234567890123456789 // +// 0: A>xX.XXX.XXX xSB // +// 1: B>xX.XXX.XXX SPLIT // +// 2: RX S:123456789+++ // +// 3: CALL xxxx Hz vX.X // +////////////////////////////////////////////////////////////////////// + + + +//************************displayBanner***************************** +// Sets up display line 3 - call sign, increment display and version +// +// Note - Does not use the input String (included for code compatibility +// with other display modules) +// +void displayBanner(String s) { + + // Setup line 3 call and tuning increment + lcd.setCursor(0, 3); + lcd.print(CALLSIGN); + + lcd.setCursor(12, 3); + lcd.print("Hz"); + + lcd.setCursor(16, 3); + lcd.print(F("v")); + lcd.print(VERSION); +} + + +//************************displaySmeter**************************** +void displaySMeter(byte level) { + int i; + lcd.setCursor(6, 2); + lcd.print("S:"); + for (i = 0; i < MAXSLEVELS; i++) { + if (level >= i) { + if (i < 9) { + lcd.print(i + 1); + } else { + lcd.print("+"); + } + } else { + lcd.print(" "); + } + } +} + + +//************************displayVFOAB******************************* +// displayVFOAB(int VFO) +// Updates the display with flag for the currently active VFO - A or B +// +void displayVFOAB(int vfo) { + // Reset the active VFO flag + + // Clear previous flag + lcd.setCursor(1,0); + lcd.print(F(" ")); + lcd.setCursor(1,1); + lcd.print(F(" ")); + + // Set flag by currently active VFO + if (vfo == VFOA) { + lcd.setCursor(1, 0); + } else { + lcd.setCursor(1, 1); + } + lcd.print(F(">")); +} + + +//************************displayVFO******************************* + +// displayVFO(vfo,freq) +// Updates the dispaly for the input vfo +// Displays the frequency subracting the bfo - sideband inversion +// +void displayVFO(int vfo, long freq) { + int row = 0; + + if (vfo == VFOA) { + row = 0; + } else if (vfo == VFOB) { + row = 1; + } + + // Update the frequency + lcd.setCursor(2, row); + if (freq < 10000000) { + lcd.print(" "); + } + lcd.print(int((freq / 1000000))); //millions + lcd.print("."); + lcd.print(((freq / 100000) % 10)); //hundredthousands + lcd.print(((freq / 10000) % 10)); //tenthousands + lcd.print(((freq / 1000) % 10)); //thousands + lcd.print("."); + lcd.print(((freq / 100) % 10)); //hundreds + lcd.print(((freq / 10) % 10)); //tens + lcd.print(((freq / 1) % 10)); //ones + +} + +//************************displayActVFO******************************* +// Updates the frequencey display for the current active VFO A or B +void displayActVFO(uint32_t freq) { + displayVFO(active_vfo, freq); +} + +//************************displayActVFO******************************* +// Updates the frequencey display for the current alternate VFO A or B +extern void displayAltVFO(uint32_t freq) { + if (active_vfo == VFOA) { + displayVFO(VFOB, freq); + } else{ + displayVFO(VFOA, freq); + } +} + + +//************************displayMode******************************* +// +// displaySideband(mode) +// Updates the current mode (sideband) indicator U or L +// +void displayMode(int mode) { + char sb = ' '; + + if (mode == USB) { + sb = 'U'; + } else if (mode == LSB) { + sb = 'L'; + } + lcd.setCursor(13, 0); + lcd.print(sb); +} + +void displayTxRx(int tx_rx) { + lcd.setCursor(0, 2); + if (tx_rx == RX) { + lcd.print(F("RX")); + } else { + lcd.print(F("TX")); + } +} + +//************************displayIncr******************************* +// +// displayIncr(increment) +// Display the tuning increment +// +void displayIncr(unsigned long increment) { + + String hertz = " "; // tune step display + +#ifdef DEBUG + sprintf(debugmsg, "Increment: %ld", increment); + Serial.println(debugmsg); +#endif + + if (increment == 10) { + hertz = F(" 10"); + } else if (increment == 100) { + hertz = F(" 100"); + } else if (increment == 1000) { + hertz = F(" 1K"); + } else if (increment == 10000) { + hertz = F(" 10K"); + } else if (increment == 100000) { + hertz = F("100K"); + } else if (increment == 1000000) { + hertz = F(" 1M"); + } + + lcd.setCursor(7, 3); + lcd.print(hertz); +} + +//************************displayTune******************************* +// +// displayTune(On) +// If On is TRUE displays TUNE message else clears it +// +void displayTune(bool On) { + lcd.setCursor(7, 2); + if (On == true) { + lcd.print(F("TUNE")); + } else { + lcd.print(F(" ")); + } +} + +void displayDebug(String msg) { + lcd.setCursor(7, 2); + lcd.print(msg); +} + +//************************displaySplit******************************* +// +// displayTune(split) +// If split is TRUE displays SPLIT message else clears it +// +void displaySplit(boolean splt) { + lcd.setCursor(14, 1); + if (split == true) { + lcd.print(F("SPLIT")); + } else { + lcd.print(F(" ")); + } +} + + + +/////////////////////////////////////////////////////////////////////////// +// displaySetup() +// Initialze and populate the display +/////////////////////////////////////////////////////////////////////////// +void displaySetup(String banner, + uint32_t vfoActfreq, uint32_t vfoAltfreq, + uint32_t activeVFO, + int tx_rx, + int sideband, + boolean split, + uint32_t increment, + byte s_meter) { + lcd.init(); + lcd.clear(); + delay(100); + lcd.backlight(); + + // Setup fixed screeen elements + + // Line 0 - VFO A + lcd.setCursor(0,0); + lcd.print(F("A")); + + // Line 0 - sideband indicator + lcd.setCursor(14,0); + lcd.print(F("SB")); + + // Line 1 VFO B + lcd.setCursor(0,1); + lcd.print(F("B")); + + // + // Display the intitial values + // + displayBanner(banner); + displayActVFO(vfoActfreq); + displayAltVFO(vfoAltfreq); + displayVFOAB(activeVFO); + displayTxRx(tx_rx); + displayMode(sideband); + displaySplit(split); + displayIncr(increment); +#ifdef SMETER + displaySMeter(s_meter); +#endif + } + +#endif diff --git a/SSB_Nextion_Display.cpp b/SSB_Nextion_Display.cpp new file mode 100644 index 0000000..ff8cee5 --- /dev/null +++ b/SSB_Nextion_Display.cpp @@ -0,0 +1,521 @@ +#include "RadioControl.h" +#ifdef DISPLAY_NEXTION + +//#define NEX_DEBUG // sends debug messages to the banner (bottom of screen) +//#define NEX_DEBUG1 + +// Dean Souleles, KK4DAS +// 7/25/2020 +// +// Nextion Display Module for SSB_Radio_Contrl +// Nextion 2.8 Inch display +// Tested on Arduino Nano +// +// Arduino Nano Every +// PINS / WIRING +// ---------------------- +// Arduino | Nextion +// --------|------------- +// 1 (RX) | TX (Blu) -- pins 0 and 1 are for Serial1 +// 0 (TX) | RX (Yel) +// +5 | +5 (Red) +// Gnd | Gnd (Blk) +//------------------------ +// +// Arduino Nano // deprecated - runs out of memory / stack crash +// PINS / WIRING +// ---------------------- +// Arduino | Nextion +// --------|------------- +// 8 (RX) | TX (Blu) -- pins 8 and 9 are requuired by AltSoftSerial +// 9 (TX) | RX (Yel) -- to use other pins you can use SofwareSerial +// +5 | +5 (Red) -- but that will conflict with the interrupt +// Gnd | Gnd (Blk) -- used by the Rotary Encoder +//------------------------ + + +//////////////////////////////////////////////////////////////////////////////////////////// +// +// We use the AltSoftSerial library to pins (8 and 9) rather than default 0,1 +// AltSoftSerial is used rather than SoftwareSerial to free up the interrupt vector +// required for the digital encoder +// NOTE: +// AltSoftSerial uses only fixed pins 8,9 on an Uno or Nano +// Pin 10 cannot be used for PWM +// For other boards see: https://www.pjrc.com/teensy/td_libs_AltSoftSerial.html +// +// First, apply the serial fixes from Ray Livingston to the Nextion Libarary: +// https://forum.arduino.cc/index.php?topic=620821.0 +// Replace files in the ../libraries/ITEADLIB_Arduino_Nextion folder: +// NexConfig.h +// NexHardware.h +// NexHardware.cpp +// Plus one more patch to fix a bug that was including SoftwareSerial when it should not have +// NexUpload.cpp (change by me, Dean Souleles) +// +// Next we edit NexConfig.h in the ../libraries/ITEADLIB_Arduino_Nextion folder as follows: +// Comment out this line: +// #define nexSerial Serial +// +// Add these three lines: +// #include +// extern AltSoftSerial HMISerial; +// #define nexSerial HMISerial +// +// Add the following to your sketch +// #include +// AltSoftSerial HMISerial; //RX TX - connect to Nextion TX RX - Must use Pins 9,10 on Uno/Nano +// +//////////////////////////////////////////////////////////////////////////////////////////////// + +#include // https://github.com/itead/ITEADLIB_Arduino_Nextion + +//#include +//SoftwareSerial HMISerial(8,9); //RX TX - connect to Nextion TX RX - Pins 8,9 on Uno/Nano + +#if _BOARDTYPE == Nano +#include +AltSoftSerial HMISerial; //RX TX - connect to Nextion TX RX - Must use Pins 8,9 on Uno/Nano +#endif + +// +// Declare Nextion objects +// Use the page ID, component id and component name from the Nextion IDE +// + +// Nextion PAGE +// Current design only uses one page of the display +#define PAGE 0 + + + +// Nextion Component IDs for buttons and text displays +// These ID's must match the ID's Nextion IDE +#define VFO_ID 2 +#define SIDEBAND_ID 5 +#define TUNE_ID 7 +#define INCREMENT_ID 6 +#define ACT_VFO_ID 3 +#define ALT_VFO_ID 4 +#define TX_RX_ID 8 +#define SMETER_ID 10 +#define BANNER_ID 9 +#define TUNE_PLUS_ID 11 +#define TUNE_MINUS_ID 12 +#define TUNE_STATE_ID 15 +#define ATOB_ID 18 // VFO A copy to B button +#define SPLIT_ID 19 +#define VAB_ID 20 // VFO Inidicator + +// Nextion Component names +// These names must match the names in the Nextion IDE +#define VFO_NAME "bVFO" +#define SIDEBAND_NAME "bSideband" +#define TUNE_NAME "bTune" +#define INCREMENT_NAME "bIncr" +#define ACT_VFO_NAME "tActVFO" +#define ALT_VFO_NAME "tAltVFO" +#define BANNER_NAME "tBanner" +#define TX_RX_NAME "tTxRx" +#define SMETER_NAME "pSmeter" +#define TUNE_PLUS_NAME "bPlus" +#define TUNE_MINUS_NAME "bMinus" +#define TUNE_STATE_NAME "tTuneState" +#define ATOB_NAME "bAtoB" +#define SPLIT_NAME "btSplit" +#define VAB_NAME "tVAB" + +// Nextion Buttons +// Each user interface object that the user touches needs to be defined here +// +NexButton bVFO = NexButton(PAGE, VFO_ID, VFO_NAME); +NexButton bSideband = NexButton(PAGE, SIDEBAND_ID, SIDEBAND_NAME); +NexButton bTune = NexButton(PAGE, TUNE_ID, TUNE_NAME); +NexButton bIncrement = NexButton(PAGE, INCREMENT_ID, INCREMENT_NAME); +NexButton bTunePlus = NexButton(PAGE, TUNE_PLUS_ID, TUNE_PLUS_NAME); +NexButton bTuneMinus = NexButton(PAGE, TUNE_MINUS_ID, TUNE_MINUS_NAME); +NexButton bAtoB = NexButton(PAGE, ATOB_ID, ATOB_NAME); +NexDSButton btSplit = NexDSButton(PAGE, SPLIT_ID, SPLIT_NAME); + + +// +// Setup a list of objects to respond to a touch event +// +NexTouch *nex_listen_list[] = { + &bVFO, + &bSideband, + &bTune, + &bIncrement, + &bTunePlus, + &bTuneMinus, + &bAtoB, + &btSplit, + NULL +}; + +/////////////////////////////////////////////////////////////////////////// +// HMI_send_command(cmd) +// Sends one command to the Nextion Display +// +// Example usage: +// String cmd; +// cmd = F("vis "); +// cmd = cmd + F(TUNE_STATE_NAME); +// cmd = cmd + F(","); +// if (on_off) { +// cmd = cmd + F("1"); +// } else { +// cmd = cmd + F("0"); +// } +// HMI_send_command(cmd.c_str()); +// +/////////////////////////////////////////////////////////////////////////// +void HMI_send_command(char* cmd) { + +/* +// Send the command + HMISerial.print(cmd); + +// Send end-of-message per Nextion protocol + HMISerial.write(0xff); + HMISerial.write(0xff); + HMISerial.write(0xff); + +*/ +// Send the command to the Nextion + nexSerial.print(cmd); + +// Send end-of-message per Nextion protocol + nexSerial.write(0xff); + nexSerial.write(0xff); + nexSerial.write(0xff); + +} + +/////////////////////////////////////////////////////////////////////////// +// displayActVFO(uint32_t freq) +// Formats and displays Active and Alternat VFO frequencies +// Legal values: Frequency in Hz +// +// To do - comibine into one function +/////////////////////////////////////////////////////////////////////////// + +void displayActVFO(uint32_t freq) { + String cmd; + String fmt; + char f[11]; + uint32_t mil, hund_thou, ten_thou, thou, hund, tens, ones; + +// Format number as nn.nnn.nnn + mil = freq / 1000000; + hund_thou = (freq/100000)%10; + ten_thou = (freq/10000)%10; + thou = (freq/1000)%10; + hund = (freq/100)%10; + tens = (freq/10)%10; + ones = freq%10; + fmt=F("%2ld.%ld%ld%ld.%ld%ld%ld"); + snprintf(f, sizeof(f),fmt.c_str(),mil, hund_thou, ten_thou,thou, hund, tens, ones); + + cmd = F(ACT_VFO_NAME); + cmd += F(".txt=\""); + cmd += f; + cmd += F("\""); + HMI_send_command(cmd.c_str()); + +} + +void displayAltVFO(uint32_t freq) { + String cmd; + String fmt; + char f[11]; + uint32_t mil, hund_thou, ten_thou, thou, hund, tens, ones; + +// Format number as nn.nnn.nnn + mil = freq / 1000000; + hund_thou = (freq/100000)%10; + ten_thou = (freq/10000)%10; + thou = (freq/1000)%10; + hund = (freq/100)%10; + tens = (freq/10)%10; + ones = freq%10; + fmt=F("%2ld.%ld%ld%ld.%ld%ld%ld"); + snprintf(f, sizeof(f),fmt.c_str(),mil, hund_thou, ten_thou,thou, hund, tens, ones); + + cmd = F(ALT_VFO_NAME); + cmd += F(".txt=\""); + cmd += f; + cmd += F("\""); + HMI_send_command(cmd.c_str()); + +} + +void displaySMeter(byte level) { +// Nextion dipslay bar graph is set by integer percent 0-100 +// Convert the S level into a % and send to display + + String cmd; + float pct; + int scaled_level; + pct = (float(level)/13.0)*100.0; + scaled_level=pct; + + cmd = F(SMETER_NAME); + cmd += F(".val="); + cmd += scaled_level; + + HMI_send_command(cmd.c_str()); + +#ifdef NEX_DEBUG1 + String msg = F("displaySMeter: "); + msg = msg + level; + msg = msg + F(" "); + msg = msg + scaled_level; + displayBanner(msg); +#endif + +} + +void displayBanner(String s) { + String cmd; + cmd = F(BANNER_NAME); + cmd += F(".txt=\""); + cmd += s; + cmd += F("\""); + HMI_send_command(cmd.c_str()); +} + +void displayVFOAB(int vfo) { + String cmd; + cmd = F(VAB_NAME); + cmd += F(".txt=\""); + if (vfo == VFOA) { + cmd += F("A"); + } else { + cmd += F("B"); + } + cmd += F("\""); + HMI_send_command(cmd.c_str()); + +#ifdef NEX_DEBUG + String msg = F("displayVFOAB: "); + msg = msg + vfo; + displayBanner(msg); +#endif + +} + +void displayTxRx(int tx_rx) { + String cmd; + cmd = F(TX_RX_NAME); + cmd += F(".txt=\""); + if (tx_rx == TX) { + cmd += F("TX"); + } else { + cmd += F("RX"); + } + cmd += F("\""); + HMI_send_command(cmd.c_str()); + +#ifdef NEX_DEBUG + String msg = F("displayTxRx: "); + msg = msg + tx_rx; + displayBanner(msg); +#endif + +} + +void displayMode(int mode) { + String modeString; + if (mode == USB) { + modeString = F("USB"); + } else { + modeString = F("LSB"); + } + bSideband.setText(modeString.c_str()); + +#ifdef NEX_DEBUG + String msg = F("displayMode: "); + msg = msg + modeString; + displayBanner(msg); +#endif + +} + +void displayIncr(uint32_t increment) { + String hertz; + switch (increment) { + case 1: hertz = F(" 1"); break; + case 10: hertz = F(" 10"); break; + case 100: hertz = F(" 100"); break; + case 1000: hertz = F(" 1K"); break; + case 10000: hertz = F(" 10K"); break; + case 100000: hertz= F("100K"); break; + case 1000000:hertz = F(" 1M"); + } + bIncrement.setText(hertz.c_str()); + +#ifdef NEX_DEBUG + String msg = F("displayIncr: "); + msg = msg + hertz; + displayBanner(msg); +#endif + +} + +void displayTune(boolean on_off) { +// Send visibility command to Nextion +// Format: vis + String cmd; + cmd = F("vis "); + cmd += F(TUNE_STATE_NAME); + cmd += F(","); + cmd += on_off; + HMI_send_command(cmd.c_str()); + +#ifdef NEX_DEBUG + displayBanner(cmd); +#endif +} + + +void displaySplit(boolean split) { +// The split button is a dual state button +// State 0 = blue = ff +// State 1 = green = on +// To change the state and color set the val property to 0/1 +// This mimics a button press on the display + + String cmd =F(SPLIT_NAME); + cmd += F(".val="); + if (split) { + cmd += 1; + } else { + cmd += 0; + } + HMI_send_command(cmd.c_str()); + +#ifdef NEX_DEBUG + String msg = F("displaySplit: "); + msg = msg + split; + displayBanner(msg); +#endif +} + +// +// Button Callback Functions +// Called whenever a button is pressed and released +// + +// VFO A/B Button +void bVFOPopCallback(void *ptr) { + SwapVFO(); // call the VFO switch button handler +} + +// LSB/USB Button +void bSidebandPopCallback(void *ptr) { + SwapSB(); +} + +// Tune Plus (increase VFO) +void bTunePlusPushCallback(void *ptr) { + AdjustVFO(increment); +} + +// Tune Minus (decrease VFO) +void bTuneMinusPushCallback(void *ptr) { + AdjustVFO(-1 * increment); +} + +// Tune Tone +void bTunePopCallback(void *ptr) { + DoTune(); +} + +// Tuning Increment Change +void bIncrementPopCallback(void *ptr) { + AdvanceIncrement(); +} + +// Split on/off +void btSplitPopCallback(void *ptr) { + uint32_t split_val; + + btSplit.getValue(&split_val); + if (split_val==1) { + startSplit(); + } else { + stopSplit(); + } + +#ifdef NEX_DEBUG + String msg = F("btSplitPopCallback: "); + msg = msg + split_val; + displayBanner(msg); +#endif +} + +// Make Act and Alt VFO the same +void bAtoBPopCallback(void *ptr) { + + if (active_vfo == VFOA) { + vfoBfreq = vfoAfreq; + } else { + vfoAfreq = vfoBfreq; + } + displayAltVFO(vfoAfreq); // update the Alt VFO display + +} + +// +// Setup +// Called once at startup +// +void displaySetup(String banner, + uint32_t vfoActfreq, uint32_t vfoAltfreq, + uint32_t activeVFO, + int tx_rx, + int sideband, + boolean split, + uint32_t increment, + byte s_meter) { + + nexInit(9600); // Initialize the Nextion library + + // + // Attach callback routines for each button + // attachPop will set the library to invoke the specified funtion each time a button is released + // + bVFO.attachPop(bVFOPopCallback, &bVFO); // VFO A/B button + bSideband.attachPop(bSidebandPopCallback, &bSideband); // LSB/USB button + bTune.attachPop(bTunePopCallback, &bTune); // Tune + bIncrement.attachPop(bIncrementPopCallback, &bIncrement); // Change Incrmement + btSplit.attachPop(btSplitPopCallback, &btSplit); // Split On/Off + bAtoB.attachPop(bAtoBPopCallback, &bAtoB); // Make Alt VFO = Act VFO + + bTunePlus.attachPush(bTunePlusPushCallback, &bTunePlus); // Tune up + bTuneMinus.attachPush(bTuneMinusPushCallback, &bTuneMinus); // Tune down + + // + // Display the intitial values + // + displayBanner(banner); + displayActVFO(vfoActfreq); + displayAltVFO(vfoAltfreq); + displayVFOAB(activeVFO); + displayTxRx(tx_rx); + displayMode(sideband); + displaySplit(split); + displayIncr(increment); + displaySMeter(s_meter); +} + + + +void CheckTouch() { + // Call the Nextion check function to look for activites on your listen list + nexLoop(nex_listen_list); +} + + +#endif diff --git a/SSB_Radio_Control.HMI b/SSB_Radio_Control.HMI new file mode 100644 index 0000000..d665843 Binary files /dev/null and b/SSB_Radio_Control.HMI differ diff --git a/SSB_Radio_Control.ino b/SSB_Radio_Control.ino new file mode 100644 index 0000000..1a79182 --- /dev/null +++ b/SSB_Radio_Control.ino @@ -0,0 +1,375 @@ +/* + SSB_Radio_Control + Dean Souleles, KK4DAS, contact kk4das@gmail.com + + This sketch implement s basic SSB radio control panel with the following features: + Dual VFO A/B + Rotary encoder for tuning, push button to change tuning increment + SI5351 Clock generator for the LO and BFO + CAT Control (emulates an ICOM IC-746) + Split mode support (Split from CAT, manual split requres Nextion touch screen) + Settings memory (last frequency and mode are saved) + Optional S-Meter + + Controls + * Rotary encoder to change frequence + * Rotary encode button changes tuning increment + * VFO A/B Select toggle + * Mode select USB/LSB toggle + * Tune button (emits 10 second pulsed tone at 800Hz for tuning) + * MOX toggle - puts rig in to Tx/Rx + * Optional dual-band support 20/40 + + Modules for different display types + * 20x4 LCD + * 320x240 TFT color display + * 2.8" Nextion Touch Screen + + Display features + * Dual VFOS + * Mode indicator SSB/LSB + * Tx/Rx ndicator + * TuningStep Inidicator + * Optional S Meter + * Banner including callsign + + Additional controls with the Nextion display + * Continuous scanning + * Split mode + + Version 1.4 + March 9 2021 + Rstored LCD Display Option + Compile time selection of S-Meter and Dual Band mods + Refactoring of encoder handling + + Version 1.3 + Jan 25, 2021 + Changed CAT module to IC-746 + + Version 1.2 + Dec 14, 2020 + Dual Band 20/40 Support + S-Meter + + Version 1.1 + Dec 13. 2020 + Ported to Nano Every for more sweet SRAM + * * Updated interrupt handling in Encoder.cpp to work with Nano Every (as well as UNO/Nano) + * * Replaced SoftwareSerial connections to Nextion with Hardware Serial - Serial1 + + Version 1.0 + Aug 13, 2020 + Adapted from SimpleSSB Sketch by N6QW, Pete Juliano and others + + + NOTE TO BUILDERS + This is a reference implementation of an SSB radio control program for a homebrew SSB transceiver. + It is a fully functioning SSB radio control program. While it has been built for my particular hardware + configuration every attempt has been made to make it modular in design so that the builder can swap out + modules at will. It should be fairly straight forward to adapt to the builder's hardware selection + of switches, buttons and knobs or even alternate displays. + +*/ + +#include "RadioControl.h" + +#ifdef DEBUG +char debugmsg[25]; +#endif + +//=============== Globals ============================================ + +////////////////////////////////////////////////////////////////////// +// // +// si5351 Clock Module // +// // +////////////////////////////////////////////////////////////////////// +Si5351 si5351; + +// Calibration offest - adjust for variability in the SI5351 module +// crystal - must be set for the particular SI5351 installed +//#define CALIBRATION_OFFSET 1190 // Calibration for the SI-5351 +#define CALIBRATION_OFFSET 880 // Calibration for the SI-5351 + +////////////////////////////////////////////////////////////////////// +// // +// BFO and VFO Constants and Variables // +// // +////////////////////////////////////////////////////////////////////// + +#ifdef BFO12MHZ +const uint32_t USB_BFO = 12001600L; +const uint32_t LSB_BFO = 11998600L; +#endif + +#ifdef BFO9MHZ +const uint32_t USB_BFO = 9001500L; +const uint32_t LSB_BFO = 8998500L; +#endif + +uint32_t bfo = LSB_BFO; // Startup BFO frequency +const uint32_t BFO_DELTA = USB_BFO - LSB_BFO; // Difference between USB and LSB for BFO change + +// +// Startup VFO A/B frequencies +// +uint32_t vfoAfreq = 7200000L; // 7.200.000 +uint32_t vfoBfreq = 7074000L; // FT-8 7.074.000 +byte vfoASideband = LSB; +byte vfoBSideband = USB; + +uint32_t increment = 1000; // startup VFO tuning increment in HZ. + + +////////////////////////////////////////////////////////////////////// +// // +// Active VFO // +// // +////////////////////////////////////////////////////////////////////// +byte active_vfo = VFOA; // startup on VFOA + + +////////////////////////////////////////////////////////////////////// +// // +// Sideband Selection // +// // +////////////////////////////////////////////////////////////////////// +byte sideband = LSB; // startup in LSB + +////////////////////////////////////////////////////////////////////// +// // +// Sideband Selection // +// // +///////////////////////////////////////////////////////////////////// +uint32_t band20Freq = 14200000L; // 14.200.000 +uint32_t band40Freq = 7200000L; // 7.200.000 +byte band20Sideband = USB; +byte band40Sideband = LSB; +byte band = BAND40; + +////////////////////////////////////////////////////////////////////// +// // +// Transmit / Receive Indicators // +// // +////////////////////////////////////////////////////////////////////// +byte TxRxState = RX; // startup in RX +byte lastTxRxState = RX; // previous TxRxState +byte txSource = PTT_MIC; // transmit source - Mic, Tune, CAT + +////////////////////////////////////////////////////////////////////// +// // +// S Meter 0-9 +10, +20 +30 // +// // +////////////////////////////////////////////////////////////////////// +byte smeter = 0; // startup s_meter reading (requires SMETER) + + +////////////////////////////////////////////////////////////////////// +// // +// Split Mode On/Off // +// // +////////////////////////////////////////////////////////////////////// +bool split = false; + + + + +/////////////////////////////////////////////////////////// +// setBandFilters(band) // +// For 20 meters turn relay ON (NO) // +// For 40 meters turn relay OFF (NC) // +/////////////////////////////////////////////////////////// +void setBandFilters(int band) { +#ifdef DUAL_BAND + switch (band) { + case BAND20: + digitalWrite(BAND_PIN, HIGH); + break; + case BAND40: + digitalWrite(BAND_PIN, LOW); + break; + } +#endif +} + +#ifdef CW +// +// setCW() +// +// Experimental code to generate CW tone on key down at 700Hz above the dial frequency +// Needs a bunch of scaffolding to implement CW mode +// +// Turns off the BFO, sets the LO to the VFO frequency + 700 +// +// After testing - +// tone produced OK but needs an amplifier to get significant power out +// +void setCW() { + si5351.set_freq(0, 0, SI5351_CLK2); // turn off BFO + si5351.set_freq(vfoAfreq+700L , SI5351_PLL_FIXED, SI5351_CLK0); // set LO to operating freq +} +#endif + +//********************setVFO****************************************** +void setVFO(uint32_t freq) { + // + // Set CLK0 to the to input frequency adjusted for the current BFO frequency + +#ifdef DUAL_BAND + // + // Set filters for the band based on frequency + // Save frequency and sideband for band switching + // + if (freq >= BAND20_EDGE) { + setBandFilters(BAND20); + band20Freq = freq; + band20Sideband = sideband; + band = BAND20; + } else if (freq >= BAND40_EDGE) { + setBandFilters(BAND40); + band40Freq = freq; + band40Sideband = sideband; + band=BAND40; + } +#endif + + si5351.set_freq(freq + bfo, SI5351_PLL_FIXED, SI5351_CLK0); + startSettingsTimer(); // start timer to save current settings +} + +void setBFO(uint32_t freq) { + // + // Set CLK2 to the to input frequency + // + + si5351.set_freq(freq, 0, SI5351_CLK2); +} + + +//*********************Setup Arduino Pins***************************** + +void setupPins() { + // + // Set the control buttons to INPUT_PULLUP + // Button state will be HIGH when open and LOW when pressed + // + pinMode(TUNE_BTN, INPUT_PULLUP); // Tune - momentary button + pinMode(VFO_BTN, INPUT_PULLUP); // VFO A/B Select - momentary button + pinMode(SIDEBAND_BTN, INPUT_PULLUP); // Upper/lower SB Select - momentary button + pinMode(BAND_BTN, INPUT_PULLUP); // Band Switch 20/40 - momentary button + pinMode(PTT_SENSE, INPUT_PULLUP); // Mic PTT swtich + pinMode(PTT, OUTPUT); digitalWrite(PTT, LOW); // HIGH to enable TX + pinMode(BAND_PIN, OUTPUT); digitalWrite(BAND_PIN, LOW); // Band Switch Relay (LOW = NC = 40m, HIGH = NO = 20m) + + pinMode(LED_BUILTIN, OUTPUT); + +} + + +//**********************Initialize SI5351****************************** + +void setupSI5351() { + si5351.init(SI5351_CRYSTAL_LOAD_8PF); + si5351.set_correction(CALIBRATION_OFFSET); // calibration offset + si5351.set_pll(SI5351_PLL_FIXED, SI5351_PLLA); + si5351.drive_strength(SI5351_CLK0, SI5351_DRIVE_8MA); // Higher Drive since it is a ADE-1 DBM + si5351.drive_strength(SI5351_CLK2, SI5351_DRIVE_8MA); + si5351.set_freq(bfo, 0, SI5351_CLK2); // Initialize the bfo + + // + // Initialize the VFO + // + switch (active_vfo) { + case VFOA: + setVFO(vfoAfreq); + break; + case VFOB: + setVFO(vfoBfreq); + break; + } +} + + +////////////////////////////////////////////////////////////////////// +// Setup // +// // +// Called once by the Arduino operating system at startup // +// All initialization code goes here // +// // +////////////////////////////////////////////////////////////////////// +void setup() { + + uint32_t vfoActfreq; + uint32_t vfoAltfreq; + +#ifdef DEBUG + Serial.begin(57600); +#endif + + setupPins(); // Initialize arduino pins + setupEncoder(); // Initialize interrupt service for rotary encoder + setupSettings(); // Retrive settings from EEPROM + setupSI5351(); + + + if (active_vfo == VFOA) { + vfoActfreq = vfoAfreq; + vfoAltfreq = vfoBfreq; + } else { + vfoActfreq = vfoBfreq; + vfoAltfreq = vfoAfreq; + } + + + // Initialize the display with startup values + Delay(500); // short delay to let the display initialize - needed for Nextion + + // Construct banner for TFT or Nextion display + // "Vx.x RIGNAME CALLSIGN" + String banner; + banner = F("V"); + banner += F(VERSION); + banner += F(" "); + banner += F(RIGNAME); + banner += F(" "); + banner += F(CALLSIGN); + + displaySetup(banner, // version number. call sign + vfoActfreq, vfoAltfreq, // Initial active and alternate VFO + active_vfo, // VFO A/B indicator + TxRxState, // TX/RX indicator + sideband, // LSB/USB, + split, // Split mode on/off + increment, // Tuning increment + smeter); // S Meter + + setupCat(); +} + +void loop() { + + CheckEncoder(); //VFO frequency changes + CheckIncrement(); // Encoder Button + +#ifdef DUAL_BAND + CheckBand(); // Band Switch 20/40 +#endif + + CheckVFO(); // VFO A/B change + CheckSB(); // USB/LSB change + CheckPTT(); // Check for Mic PTT + CheckTune(); // Check for Tune button press + +#ifdef SMETER + CheckSmeter(); // Signal strength +#endif + + CheckCat(); // CAT Control + CheckSettings(); // Update EEPROM on Settings Change + +#ifdef DISPLAY_NEXTION + CheckTouch(); // Check for touch screen action +#endif + +} diff --git a/SSB_Radio_Control.tft b/SSB_Radio_Control.tft new file mode 100644 index 0000000..30934f0 Binary files /dev/null and b/SSB_Radio_Control.tft differ diff --git a/SSB_TFT_Display.cpp b/SSB_TFT_Display.cpp new file mode 100644 index 0000000..771b7ba --- /dev/null +++ b/SSB_TFT_Display.cpp @@ -0,0 +1,551 @@ +#include "RadioControl.h" +#ifdef DISPLAY_TFT + +// +// 7/31/202 - Dean Souleles +// Added stubbed out Split indicator function to stay consistent with main sketch +// TO DO: +// ADD constants and code for Split indicator +// + +/* SSB_TFT_Display + * KK4DAS, Dean Souleles, KK4DAS@gmail.com + * May 30, 2020 + * + * Basic radio display panel for a SSB transsceiver + * Designed for a 320x240 Color TFT (non touch) + * Tested with an ILI9341 display + * Requires the following libraries be installed: + * Adafruit_GFX + * Adafruit_ILI9341 + * + * Implements a basic SSB display console with the following features + * Dual VFO A/B + * Mode indicator SSB/LSB + * Tx/Rx ndicator + * TuningStep Inidicator + * S Meter + * Banner including Call sign + * + * Fully customizable. Fast display makins use of minimal resources./ + * Room is left on the screen for additional features + * There is room on the screen for another row of features + * + * Easily change colors, font sizes and layout + * + * Default Screeen Layout + * A 7.200.000 LSB -- VFO A/B indicator, Active VFO Freq, LSB/USB inidicator + * Rx 7.048.000 100K -- Rx/Tx indicator, Alternate VFO Freq, Tuning Increment + * + * S |_|_|_|_|_|_|_|_|_|_|_|_| -- S Meter + * 1 3 5 7 9 + * + * AGC SPL RIT -- (Planned) AGC on/of, Split On/Off, RIT On/OFF + * + * Ver Rig Name Call + * + * This module provides the following radio console dsiplayfunctions: + * displaySetup - initialize the display and displays the startup values - call once from your setup function + * displayBanner - Displays a text banner across the bottom of the screen + * displayActVFO - Displays the frequency of the Active VFO + * displayAltVFO - Displays the frequency of the Alternate VFO + * displayVFOAB - Displays the indicator which VFO is active (A or B) + * displayTxRx - Displays whether the rig is in (Tx or Rx) + * displayMode - Displays the whiuch sideband is selected (LSB or USB) + * displayIncr - Displays the tuning increment (10, 100, 1K 10K, 100K, 1M) + * displaySMeter - Displays the S Meter (1-9 are gray, +10 +20 and +30 are red + * + * This module also provides the following general purpose displauy functions: + * displayClearScreen - fills the screen with the selected backgrond color + * displayPrintat - prints text or nubmers on the screen at a specific location + * displayDrawBoundingBox - draw a box on the screen and fills it with a background color + * displayDrawTextBox - displays text inside a boundig box + * + * Design notes and how to use the code + * + * NOTE TO BUILDERS + * This is not a complete radio control sketch. It is the Display software only. In the spirit of modular design + * it is stand-alone and not dependent on using an SI-5351 or any other specific hardware, or on my particular + * hardware selection of switches, buttons and knobs. The demonstration sketch shows how to update the display, + * but you need to provide the code to determine what the actual values should be. You will likely need other + * libraries like the Si5351 and a Rotary encoder library aside from the GFX and the ILI9341. + * + * DESIGN PRINCIPLES + * Good software design principles are to use as few hard-coded numbers as possible. + * Wherever possible I have used #defines for any number that will be used more than one place in the code. + * For example #define DSP_VFO_ACT_X 60 defines the X coordinate (how far from the left of the screen) + * of the Active VFO frequency display. You will see multiple references to DSP_VFO_ACT_X throughout the code, + * but I never use the hardcoded number 60 again. Change it once – and it is changed throughout. + * + * Taking the S-Meter as an example: + * + * To update the S-Meter display you make a call to displaySMeter(n); where n is an integer from 1 to 12 + * (representing S1-9, +10, +20 +30). Your sketch will need a way of monitoring signal strength (an analog input + * pin on the Arduino attached to an appropriate place on your rig) and converting it to the logarithmic S scale. + * + * SCREEN COORDINATES + * Coordinates work differently on displays than a typical graph where the origin 0,0 is in the middle abd positive + * and negative values move you away from the origin. For displays 0,0, the origin, is always upper left hand corner + * of the display and you only use positive numbers for the coordinates +X is pixels from the left edge, +Y is pixels + * down from the top. This particular example based on a 320x240 display but should be easily portable to other + * display sizes – but you have to keep in mind how the coordinate system works. + * + * SCREEN LAYOUT + * Here are a few notes about how the demonstration display is laid out. This should help you understand the design + * concept and allow you to begin to modify it. + * + * The VFO display is setup for a dual VFO rig. The currently Active VFO is always on the top and the alternate VFO + * is just below it. Your code will need to keep track of whether VFO A or VFO B is currently selected and call the + * display routines to update the display. I’ll describe how the VFO displays are are defined and that will give you + * an idea how you might modify or enhance the display. + * + * Active VFO - top center of the screen + * #define DSP_VFO_ACT_X 60 // Active VFO begins 60 pixels from the left hand edge (I picked 60 by experimenting) + * #define DSP_VFO_ACT_Y 30 // Active VFO box starts 30 pixels down from the top of the screen (Try changing it to 50 + * // and see what happens) + * #define DSP_VFO_ACT_COLOR ILI9341_GREEN // This sets the text color for the Frequency display + * // use whatever colors you like + * #define DSP_VFO_ACT_BK ILI9341_BLACK // This sets the background color for the Active VFO + * #define DSP_VFO_ACT_SZ 3 // This is text size from Arduino TFT, values 1-5 1 is small 5 is large + * // (2 was too small, 4 was too large, 3 was just right) + * + * Alternate VFO – the second VFO is placed directly below the Active VFO on the screen. There are a couple of things of + * interest here. For the X coordinate, instead of putting in a hard coded number I refer back to the #define that I used + * for the Active VFO (DSP_VFO_ACT). That way, if I want to move VFO section to a different part of the screen I only need + * to change one number DSP_VFO_ACT_X, and the alternate VFO will move as well. Figuring out the Y coordinate for the + * alternate VFO is a little more challenging. Some math is involved. Starting with the Y coordinate of the Active VFO + * I need to calculate where how far down the display I need to go to place the second VFO. To do that I need calculate + * how many pixels tall the text characters in the Active VFO are and use that as an offset. It turns out we have everything + * we need already defined. CH_W and CH_H are #defines that specify the height and width of a text character in pixels for + * TFT font size 1. Size 2 through 5 are even multiples of that – so font height for size 2 is 2*CH_H pixels and font width + * for size 4 is 4*CH_W pixels and so on. so we have everything we need to calculate how many pixels the Active VFO takes + * on the screen – we multiply the font size by the character height and add 16 pixels offset. The 16 was determined by + * experimentation for something that looked good. The code looks like this: + * + * #define DSP_VFO_ALT_X DSP_VFO_ACT_X + * #define DSP_VFO_ALT_Y DSP_VFO_ACT_Y + (DSP_VFO_ACT_SZ * CH_H) + 16 + * + * Take a look the other sections of the display code and you will see similar references and calculations. The VFO A//B + * indicator and LSB/USB mode indicator, for example are similarly “pinned” to the Active VFO display, so if you move the + * Active VFO display to another screen location they will move also. + * + * In summary - each object is on the display is defined by a set of constants that indicate the X,Y coordinates + * of the object on the screen and various other attributes like text size and color. The basic user interface display + * object is a bounded/filed text box. You can control the text size and color, and the box fill color. With this basic + * set of features you can implement a wide variety of user interface elements. The S-meter, for example, is a row of + * filled boxes. + * + * HARDWARE NOTES + * My test sketch uses an Arduino Nano. The display is an HiLetgo 2.2 Inch ILI9341 SPI TFT LCD Display 240x320 ILI9341, + * but any ILI9341 display should work. There are many sources. Please note that the Arduino has 5V logic levels, + * but the display requires 3.3V - so you need some sort of level shifter. I used the"HiLetgo 10pcs 4 Channels IIC I2C + * Logic Level Converter Bi-Directional 3.3V-5V Shifter Module for Arduino" I used hardware SPI and the pinouts are + * standard as follows: + * + * Arduino + * Pin TFT Pin + * -----------|--------- + * 8 | RST - any free Arduino Pin (not used in this sketch) + * 9 | DC - any free Arduino Pin + * 10 | CS - any free Arduino Pin + * 11 | MOSI - fixed + * 12 | MISO - fixed + * 13 | CLK - fixed + * + * That is all the wiring you need for the demonstration sketch. + * + * + * BUILDING THE DEMONSTRATION SKETCH + * + * Create a folder called SSB_TFT_Display_Demo + * + * Copy all three files to that folder + * SSB_TFT_Display_Demo.ino + * SSB_TFT_Display.h + * SSB_TFT_Display.cpp + * + * Use the Arduino IDE library manager to install teh following libraries + * Adafruit_GFX + * Adafruit_ILI9341 + * + * Compile and upload the sketch + ****************************************************************************************************************** +*/ + +#include +#include + + +// For the Adafruit shield, these are the default. +#define TFT_DC 9 +#define TFT_CS 10 +#define TFT_MOSI 11 +#define TFT_CLK 13 +#define TFT_RST 8 +#define TFT_MISO 12 + +// Use hardware SPI (on Uno, #13, #12, #11) and the above for CS/DC +Adafruit_ILI9341 tft = Adafruit_ILI9341(TFT_CS, TFT_DC); + +#define CH_W 6 // default TFT Character width in pixels +#define CH_H 8 // default TFT Charachter height in pixels + + + +#define DSP_BG_COLOR ILI9341_NAVY + +// Banner - Version, Rig Name, Call sign +#define DSP_BANNER_X 10 +#define DSP_BANNER_Y 220 +#define DSP_BANNER_COLOR ILI9341_BLACK +#define DSP_BANNER_BK ILI9341_WHITE +#define DSP_BANNER_SZ 2 + + +// Active VFO - top center of the screen +#define DSP_VFO_ACT_X 60 +#define DSP_VFO_ACT_Y 30 +#define DSP_VFO_ACT_COLOR ILI9341_GREEN +#define DSP_VFO_ACT_BK ILI9341_BLACK +#define DSP_VFO_ACT_SZ 3 + +// Alternate VFO - directly below the Active VFO +#define DSP_VFO_ALT_X DSP_VFO_ACT_X +#define DSP_VFO_ALT_Y DSP_VFO_ACT_Y + (DSP_VFO_ACT_SZ * CH_H) + 16 +#define DSP_VFO_ALT_COLOR ILI9341_WHITE +#define DSP_VFO_ALT_BK DSP_VFO_ACT_BK +#define DSP_VFO_ALT_SZ 3 + +// VFP A/B indidcator - to the left of the Active VFO +#define DSP_VFO_AB_X DSP_VFO_ACT_X-40 +#define DSP_VFO_AB_Y DSP_VFO_ACT_Y + 6 +#define DSP_VFO_AB_COLOR DSP_VFO_ACT_COLOR +#define DSP_VFO_AB_BK DSP_VFO_ACT_BK +#define DSP_VFO_AB_SZ 2 + +// Tx/Rx Indicator - to the left of the Alternate VFO +#define DSP_RX_TX_X DSP_VFO_ACT_X-45 +#define DSP_RX_TX_Y DSP_VFO_ALT_Y + 4 +#define DSP_RX_COLOR DSP_VFO_ACT_COLOR +#define DSP_TX_COLOR ILI9341_RED +#define DSP_RX_TX_BK DSP_VFO_ACT_BK +#define DSP_RX_TX_SZ 2 + +// Mode (LSB/USB) inidcator - to the right of the Active VFO +#define DSP_MODE_X DSP_VFO_ACT_X+205 +#define DSP_MODE_Y DSP_VFO_ACT_Y + 6 +#define DSP_MODE_COLOR DSP_VFO_ACT_COLOR +#define DSP_MODE_BK DSP_VFO_ACT_BK +#define DSP_MODE_SZ 2 + +// Tuning Increment - to the right of teh Alternate VFO +#define DSP_INCR_X DSP_VFO_ACT_X+200 +#define DSP_INCR_Y DSP_VFO_ALT_Y + 4 +#define DSP_INCR_COLOR DSP_VFO_ACT_COLOR +#define DSP_INCR_BK DSP_VFO_ACT_BK +#define DSP_INCR_SZ 2 + +// S Meter - below the the Alt VFO (1/2 way down the screen) +#define DSP_S_METER_X 56 +#define DSP_S_METER_Y 120 +#define DSP_S_METER_SEGMENTS 12 +#define DSP_S_METER_UNIT_SIZE 20 +#define DSP_S_METER_LOW_COLOR ILI9341_DARKGREY //below S9 +#define DSP_S_METER_HI_COLOR ILI9341_RED //above S9 + +// S Meter Label "S" to the left of the S Meter +#define DSP_S_LABEL_X DSP_S_METER_X - 18 +#define DSP_S_LABEL_Y DSP_S_METER_Y + 4 +#define DSP_S_LABEL_COLOR ILI9341_BLACK +#define DSP_S_LABEL_SZ 2 + +// S Meter Scale immediately belwo the S Meter +#define DSP_S_METER_SCALE_X DSP_S_METER_X + 5 +#define DSP_S_METER_SCALE_Y DSP_S_METER_Y + DSP_S_METER_UNIT_SIZE + 5 +#define DSP_S_METER_SCALE_COLOR DSP_S_LABEL_COLOR +#define DSP_S_METER_SCALE_SZ 2 + + +/////////////////////////////////////////////////////////////////////////// +// displayPrintln(s) +// Prints one line of text at a time on the display - for debug messages +/////////////////////////////////////////////////////////////////////////// +#ifdef DEBUG +int ln=0; +void displayPrintln(String s ) { + if (ln==14) { + tft.fillScreen(ILI9341_BLACK); + tft.setCursor(0, 0); + ln=0; + } + tft.println(s); + ln++; +} +#endif + +/////////////////////////////////////////////////////////////////////////// +// displayClearScreen() +// Fills the screen with selected bacground color +/////////////////////////////////////////////////////////////////////////// +void displayClearScreen() { + tft.fillScreen(DSP_BG_COLOR); +} + +/////////////////////////////////////////////////////////////////////////// +// displayPrintat String +// Prints a string on the display +// x,y: upper left pixel coordinates +// fontsize: Arduino Font size (1-5) +// color: text color +/////////////////////////////////////////////////////////////////////////// +void displayPrintat(String s, int x, int y, int fontsize, int color) { + tft.setCursor(x,y); + tft.setTextSize(fontsize); + tft.setTextColor(color); + tft.print(s); +} + +/////////////////////////////////////////////////////////////////////////// +// displayPrintat Integer +// Prints a whole number on the display +// x,y: upper left pixel coordinates +// fontsize: Arduino Font size (1-5) +// color: text color +/////////////////////////////////////////////////////////////////////////// +void displayPrintat(int i, int x, int y, int fontsize, int color) { + tft.setCursor(x,y); + tft.setTextSize(fontsize); + tft.setTextColor(color); + tft.print(i); +} + +/////////////////////////////////////////////////////////////////////////// +// displayDrawBoundingBox +// Draw and fill a bounding box for text +// Draw a recrtanglie slightly larger than the text +// Fill it with the color - leave the border white +// +// len - length of the box in characxters +// x - x coord +// y - y coord +// fontsiez - font size - fontsize*CH_W is the width of a character, fontsize*CH_H is the height +// fillcolor - color +/////////////////////////////////////////////////////////////////////////// +void displayDrawBoundingBox(int len, int x, int y, int fontsize, int fillcolor) { + + tft.drawRect(x-8, y-6, ((len*fontsize*CH_W))+16, ((fontsize*CH_H)+8), ILI9341_WHITE); + tft.fillRect(x-6, y-4, ((len*fontsize*CH_W))+12, ((fontsize*CH_H)+4), fillcolor); +} + +/////////////////////////////////////////////////////////////////////////// +// displayDrawTextBox +// Display text in an outlined and filled box +// +// s: text to display +// x,y: upper left pixel coordinates +// fontsize: ARduino font size (1-5) +// fillcolor: color to fill the box with +/////////////////////////////////////////////////////////////////////////// +void displayDrawTextBox(String s, int x, int y, int fontsize, int color, int fillcolor) { + displayDrawBoundingBox(s.length(), x, y, fontsize, fillcolor); // draw the box + displayPrintat(s, x, y, fontsize, color); // display the text +} + +/////////////////////////////////////////////////////////////////////////// +// displaySMeter +// Display the S meter as a line of filled squares - up to the input level (1-12) S1-S0, +10dB, +20dB, +30dB +// S 1-9 are filled with the Low color S9+ is filled with the High color +/////////////////////////////////////////////////////////////////////////// +void displaySMeter(byte level) { + int i; + int color; + + displayPrintat(F("S"), DSP_S_LABEL_X, DSP_S_LABEL_Y, DSP_S_LABEL_SZ, DSP_S_LABEL_COLOR); + + for (i=0; i8 && level>9) { + color = DSP_S_METER_HI_COLOR; + } + } + tft.drawRect(DSP_S_METER_X+(i*DSP_S_METER_UNIT_SIZE), DSP_S_METER_Y, DSP_S_METER_UNIT_SIZE, DSP_S_METER_UNIT_SIZE, ILI9341_WHITE); + tft.fillRect(DSP_S_METER_X+(i*DSP_S_METER_UNIT_SIZE)+2, DSP_S_METER_Y+2, DSP_S_METER_UNIT_SIZE-4, DSP_S_METER_UNIT_SIZE-4, color); + + // Print scale odd numbers up to 9 + if ((i % 2) == 0 && i<9) { + displayPrintat(i+1, DSP_S_METER_SCALE_X+(i*DSP_S_METER_UNIT_SIZE), DSP_S_METER_SCALE_Y, DSP_S_METER_SCALE_SZ, DSP_S_METER_SCALE_COLOR); + } + } +} + +/////////////////////////////////////////////////////////////////////////// +// displayBanner +// Displays a banner acroos the bottom of the display +/////////////////////////////////////////////////////////////////////////// +void displayBanner(String s) { + displayDrawTextBox(s,DSP_BANNER_X, DSP_BANNER_Y, DSP_BANNER_SZ, DSP_BANNER_COLOR, DSP_BANNER_BK); +} + + +/////////////////////////////////////////////////////////////////////////// +// displayActVFO(uint32_t freq) +// displayAltVFO(uint32_t freq +// Formats and displays Active and Alternat VFO frequencies +// Legal values: Frequency in Hz +// +// To do - comibine into one function +/////////////////////////////////////////////////////////////////////////// +void displayActVFO(uint32_t freq) { + char f[11]; + uint32_t mil, hund_thou, ten_thou, thou, hund, tens, ones; + +// Format number as nn.nnn.nnn + mil = freq / 1000000; + hund_thou = (freq/100000)%10; + ten_thou = (freq/10000)%10; + thou = (freq/1000)%10; + hund = (freq/100)%10; + tens = (freq/10)%10; + ones = freq%10; + snprintf(f, sizeof(f),"%2ld.%ld%ld%ld.%ld%ld%ld",mil, hund_thou, ten_thou,thou, hund, tens, ones); + displayDrawTextBox(f,DSP_VFO_ACT_X, DSP_VFO_ACT_Y, DSP_VFO_ACT_SZ, DSP_VFO_ACT_COLOR, DSP_VFO_ACT_BK); +} + +void displayAltVFO(uint32_t freq) { + char f[11]; + uint32_t mil, hund_thou, ten_thou, thou, hund, tens, ones; + +// Format number as nn.nnn.nnn + mil = freq / 1000000; + hund_thou = (freq/100000)%10; + ten_thou = (freq/10000)%10; + thou = (freq/1000)%10; + hund = (freq/100)%10; + tens = (freq/10)%10; + ones = freq%10; + snprintf(f, sizeof(f),"%2ld.%ld%ld%ld.%ld%ld%ld",mil, hund_thou, ten_thou,thou, hund, tens, ones); + displayDrawTextBox(f,DSP_VFO_ALT_X, DSP_VFO_ALT_Y, DSP_VFO_ALT_SZ, DSP_VFO_ALT_COLOR, DSP_VFO_ALT_BK); +} + +/////////////////////////////////////////////////////////////////////////// +// displayVFOAB(int vfo) +// Displays which VFO is currently active A or B +// Legal values are VFOA and VFOB +/////////////////////////////////////////////////////////////////////////// +void displayVFOAB(int vfo) { + String vfo_str; + if (vfo == VFOA) { + vfo_str = F("A"); + } else { + vfo_str = F("B"); + } + displayDrawTextBox(vfo_str,DSP_VFO_AB_X, DSP_VFO_AB_Y, DSP_VFO_AB_SZ, DSP_VFO_AB_COLOR, DSP_VFO_AB_BK); +} + +/////////////////////////////////////////////////////////////////////////// +// displayTxRx(int tx_rx +// Displays whether the radio is currenlty transmitting or receiveing +// Legal values are TX and RX +/////////////////////////////////////////////////////////////////////////// +void displayTxRx(int tx_rx) { + String tx_rx_str; + int color; + if (tx_rx == RX) { + tx_rx_str = F("Rx"); + color = DSP_RX_COLOR; + } else { + tx_rx_str = F("Tx"); + color = DSP_TX_COLOR; + } + displayDrawTextBox(tx_rx_str,DSP_RX_TX_X, DSP_RX_TX_Y, DSP_RX_TX_SZ, color, DSP_RX_TX_BK); +} + +////////////////////////////////////////////////////////////////////////// +// displayMode(int mode) +// Displays whether the radio is LSB or USB +// Legal values are LSB and USB +/////////////////////////////////////////////////////////////////////////// +void displayMode(int mode) { + String mode_str; + if (mode == LSB) { + mode_str = F("LSB"); + } else { + mode_str = F("USB"); + } + displayDrawTextBox(mode_str,DSP_MODE_X, DSP_MODE_Y, DSP_MODE_SZ, DSP_MODE_COLOR, DSP_MODE_BK); +} + +/////////////////////////////////////////////////////////////////////////// +// displayIncr(uint32_t increment) +// Changes display of the tuning incrementindicator +// Legal values in Hz are 1, 10, 100, 1000, 10000, 100000, 1000000 +/////////////////////////////////////////////////////////////////////////// +void displayIncr(uint32_t increment) { + String hertz; + switch (increment) { + case 1: hertz = F(" 1"); break; + case 10: hertz = F(" 10"); break; + case 100: hertz = F(" 100"); break; + case 1000: hertz = F(" 1K"); break; + case 10000: hertz = F(" 10K"); break; + case 100000: hertz= F("100K"); break; + case 1000000:hertz = F(" 1M"); + } + displayDrawTextBox(hertz,DSP_INCR_X, DSP_INCR_Y, DSP_INCR_SZ, DSP_INCR_COLOR, DSP_INCR_BK); +} + +/////////////////////////////////////////////////////////////////////////// +// displaySplit(boolean split) +// Turns split mode indicator on/off +/////////////////////////////////////////////////////////////////////////// +void displaySplit(boolean splt) { + if (split) { + // todo + } else { + // todo + } +} + +/////////////////////////////////////////////////////////////////////////// +// displaySplit(boolean split) +// Turns split mode indicator on/off +/////////////////////////////////////////////////////////////////////////// +void displayTune(boolean on_off) { +// todo +} + + +/////////////////////////////////////////////////////////////////////////// +// displaySetup() +// Initialze and populate the display +/////////////////////////////////////////////////////////////////////////// +void displaySetup(String banner, + uint32_t vfoActfreq, uint32_t vfoAltfreq, + uint32_t activeVFO, + int tx_rx, + int sideband, + boolean split, + uint32_t increment, + byte s_meter) { + + tft.begin(); // Initialize the TFT + tft.setRotation(1); // Set landscape orientation + displayClearScreen(); // Fill teh screen with the background color + + // + // Display the intitial values + // + displayBanner(banner); + displayActVFO(vfoActfreq); + displayAltVFO(vfoAltfreq); + displayVFOAB(activeVFO); + displayTxRx(tx_rx); + displayMode(sideband); + displaySplit(split); + displayIncr(increment); + displaySMeter(s_meter); + } + +#endif diff --git a/Settings.cpp b/Settings.cpp new file mode 100644 index 0000000..5970cb6 --- /dev/null +++ b/Settings.cpp @@ -0,0 +1,104 @@ +#include "RadioControl.h" +#include + +#define EE_MAGIC_ADDR 0 +#define EE_SETTINGS_ADDR 4 +#define SETTINGS_TIMER 3000 // time to wait after a settings change before writing to EEPROM + +const byte magic[4] = {'4', 'D', 'A', 'S'}; + + +struct Settings_struct { + uint32_t vfoAfreq; + uint32_t vfoBfreq; + uint32_t increment; + byte active_vfo; + byte sideband; + // Band specific memory + uint32_t band20Freq; + uint32_t band40Freq; + byte band20Sideband; + byte band40Sideband; +}; + +// Settings Change +unsigned long settings_time; +bool settings_changed; + + +void readSettings() { + Settings_struct settings; + EEPROM.get(EE_SETTINGS_ADDR, settings); + vfoAfreq = settings.vfoAfreq; + vfoBfreq = settings.vfoBfreq; + increment = settings.increment; + active_vfo = settings.active_vfo; + sideband = settings.sideband; + band20Freq = settings.band20Freq; + band40Freq = settings.band40Freq; + band20Sideband = settings.band20Sideband; + band40Sideband = settings.band40Sideband; + + if (sideband == LSB) { + bfo = LSB_BFO; + } else { + bfo = USB_BFO; + } + +} + +void writeSettings() { + Settings_struct settings; + settings.vfoAfreq = vfoAfreq; + settings.vfoBfreq = vfoBfreq; + settings.increment = increment; + settings.active_vfo = active_vfo; + settings.sideband = sideband; + settings.band20Freq = band20Freq; + settings.band40Freq = band40Freq; + settings.band20Sideband = band20Sideband; + settings.band40Sideband = band40Sideband; + EEPROM.put(EE_SETTINGS_ADDR, settings); +} + +void initSettings() { + EEPROM.put(EE_MAGIC_ADDR, magic); + writeSettings(); +} + +void setupSettings() { + byte buff[4]; + bool magic_ok = true; + + EEPROM.get(EE_MAGIC_ADDR, buff); + + for (int i = 0; i < 4; i++) { + if (buff[i] != magic[i]) { + magic_ok = false; + break; + } + } + + if (magic_ok) { + readSettings(); + } else { + initSettings(); + } + + settings_time = millis(); + settings_changed = false; +} + +void CheckSettings() { + + if (settings_changed && (millis() - SETTINGS_TIMER) > settings_time) { + writeSettings(); + settings_time = millis(); + settings_changed = false; + } +} + +void startSettingsTimer() { + settings_time = millis(); + settings_changed = true; +} diff --git a/Settings.h b/Settings.h new file mode 100644 index 0000000..30ef9c2 --- /dev/null +++ b/Settings.h @@ -0,0 +1,13 @@ +#ifndef Settings_h +#define Settings_h + +// Function prototypes + +extern void setupSettings(); +extern void initSettings(); +extern void readSettings(); +extern void writeSettings(); +extern void startSettingsTimer(); +extern void CheckSettings(); + +#endif diff --git a/Smeter.cpp b/Smeter.cpp new file mode 100644 index 0000000..aa4aec3 --- /dev/null +++ b/Smeter.cpp @@ -0,0 +1,57 @@ +// +// S Meter +// +#include "RadioControl.h" + + +//#define S_DEBUG + +const int slevels[MAXSLEVELS] = {25, 100, 200, 375, 500, 575, 650, 775, 900, 950, 975, 1000}; + +//byte smeter = 0; +unsigned long smeter_raw = 0; +unsigned long smeter_sample_count = 0; +unsigned long smeter_time = 0; + +//***************************************** +void CheckSmeter() { + + int i; + int smeter_avg; + + if (TxRxState == TX) { + return; + } + smeter_raw = smeter_raw + analogRead(SMETER_PIN); + smeter_sample_count++; + + + if ((millis() - SMETER_SAMPLE_TIMER) > smeter_time) { + /* + lcd.setCursor(12, 1); + lcd.print(" "); + lcd.setCursor(12, 1); + lcd.print(smeter_raw); + */ + + smeter_avg = int(smeter_raw / smeter_sample_count); + + #ifdef S_DEBUG + String msg = F("S raw:"); + msg+=smeter_avg; + displayBanner(msg); + #endif + + smeter = 0; + for (i = 0; i < MAXSLEVELS; i++) { + if (smeter_avg >= slevels[i]) { + smeter = byte(i); + } + + } + displaySMeter(smeter); + smeter_raw = 0; + smeter_sample_count = 0; + smeter_time = millis(); + } +} diff --git a/Smeter.h b/Smeter.h new file mode 100644 index 0000000..9b5b0d5 --- /dev/null +++ b/Smeter.h @@ -0,0 +1,12 @@ +#ifndef Smeter_h +#define Smeter_h +/* + * SMeter + * Constants defining and controlling Smeter collection and display + */ + + +#define SMETER_SAMPLE_TIMER 250 // Sample time +#define MAXSLEVELS 12 // S Levels 1-0, S9+10m S9+20, S9+30 + +#endif diff --git a/Utility.cpp b/Utility.cpp new file mode 100644 index 0000000..dd83611 --- /dev/null +++ b/Utility.cpp @@ -0,0 +1,37 @@ +/* Utility Functions + * + * Delay(interval) - non-blocking delahy + */ + +#include "Arduino.h" +#include "Utility.h" + +/* + * "Delay" is a non-blocking delay function. The standard Arduino "delay" function + * turns off interrupts, which prevents the Serial I/O functions from working. That + * sometimes causes characters to be missed and junk commands to show up. + */ + +void Delay ( unsigned long interval ) +{ +unsigned long currentTime = 0UL; + +unsigned long startTime = millis(); + + while ( startTime + interval > currentTime ) + currentTime = millis(); +} + + +/* + * ToggleLED - toggles built-in LED On/Off - useful for debug + */ +byte LEDState=LOW; +void ToggleLED(){ + if (LEDState == LOW){ + LEDState = HIGH; + } else { + LEDState = LOW; + } + digitalWrite(LED_BUILTIN, LEDState); +} diff --git a/Utility.h b/Utility.h new file mode 100644 index 0000000..d7ca857 --- /dev/null +++ b/Utility.h @@ -0,0 +1,10 @@ +#ifndef UTILITY_h +#define UTILITY_h +/************************************************************** + Uttility functions - general purpose + ***************************************************************/ + +//=============== Function Prototypes ============================================ +extern void Delay (unsigned long interval); // Non-blocking delay +extern void ToggleLED(); // Toggle built-in LED +#endif diff --git a/si5351.cpp b/si5351.cpp new file mode 100644 index 0000000..5e8803e --- /dev/null +++ b/si5351.cpp @@ -0,0 +1,770 @@ +/* + * si5351.cpp - Si5351 library for Arduino + * + * Copyright (C) 2014 Jason Milldrum + * + * Some tuning algorithms derived from clk-si5351.c in the Linux kernel. + * Sebastian Hesselbarth + * Rabeeh Khoury + * + * rational_best_approximation() derived from lib/rational.c in + * the Linux kernel. + * Copyright (C) 2009 emlix GmbH, Oskar Schirmer + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include +#include + +#include "Arduino.h" +#include "Wire.h" +#include "si5351.h" + +uint32_t EEMEM ee_ref_correction = 0; + +/********************/ +/* Public functions */ +/********************/ + +Si5351::Si5351(void) +{ +} + +/* + * init(uint8_t xtal_load_c) + * + * Setup communications to the Si5351 and set the crystal + * load capacitance. + * + * xtal_load_c - Crystal load capacitance. Use the SI5351_CRYSTAL_LOAD_*PF + * defines in the header file + * + */ +void Si5351::init(uint8_t xtal_load_c) +{ + // Start I2C comms + Wire.begin(); + + // Set crystal load capacitance + si5351_write(SI5351_CRYSTAL_LOAD, xtal_load_c); + + // Get the correction factor from EEPROM + ref_correction = eeprom_read_dword(&ee_ref_correction); +} + +/* + * set_freq(uint32_t freq, uint32_t pll_freq, enum si5351_clock output) + * + * Sets the clock frequency of the specified CLK output + * + * freq - Output frequency in Hz + * pll_freq - Frequency of the PLL driving the Multisynth + * Use a 0 to have the function choose a PLL frequency + * clk - Clock output + * (use the si5351_clock enum) + */ +void Si5351::set_freq(uint32_t freq, uint32_t pll_freq, enum si5351_clock clk) +{ + struct Si5351RegSet ms_reg, pll_reg; + enum si5351_pll target_pll; + uint8_t set_pll = 0; + + /* Calculate the synth parameters */ + /* If pll_freq is 0, let the algorithm pick a PLL frequency */ + if(pll_freq == 0) + { + pll_freq = multisynth_calc(freq, &ms_reg); + set_pll = 1; + } + /* TODO: bounds checking */ + else + { + multisynth_recalc(freq, pll_freq, &ms_reg); + set_pll = 0; + } + + /* Determine which PLL to use */ + /* CLK0 gets PLLA, CLK1 gets PLLB */ + /* CLK2 gets PLLB if necessary */ + /* Only good for Si5351A3 variant at the moment */ + if(clk == SI5351_CLK0) + { + target_pll = SI5351_PLLA; + si5351_set_ms_source(SI5351_CLK0, SI5351_PLLA); + plla_freq = pll_freq; + } + else if(clk == SI5351_CLK1) + { + target_pll = SI5351_PLLB; + si5351_set_ms_source(SI5351_CLK1, SI5351_PLLB); + pllb_freq = pll_freq; + } + else + { + /* need to account for CLK2 set before CLK1 */ + if(pllb_freq == 0) + { + target_pll = SI5351_PLLB; + si5351_set_ms_source(SI5351_CLK2, SI5351_PLLB); + pllb_freq = pll_freq; + } + else + { + target_pll = SI5351_PLLB; + si5351_set_ms_source(SI5351_CLK2, SI5351_PLLB); + pll_freq = pllb_freq; + multisynth_recalc(freq, pll_freq, &ms_reg); + } + } + + pll_calc(pll_freq, &pll_reg, ref_correction); + + /* Derive the register values to write */ + + /* Prepare an array for parameters to be written to */ + uint8_t *params = new uint8_t[20]; + uint8_t i = 0; + uint8_t temp; + + /* PLL parameters first */ + + if(set_pll == 1) + { + /* Registers 26-27 */ + temp = ((pll_reg.p3 >> 8) & 0xFF); + params[i++] = temp; + + temp = (uint8_t)(pll_reg.p3 & 0xFF); + params[i++] = temp; + + /* Register 28 */ + temp = (uint8_t)((pll_reg.p1 >> 16) & 0x03); + params[i++] = temp; + + /* Registers 29-30 */ + temp = (uint8_t)((pll_reg.p1 >> 8) & 0xFF); + params[i++] = temp; + + temp = (uint8_t)(pll_reg.p1 & 0xFF); + params[i++] = temp; + + /* Register 31 */ + temp = (uint8_t)((pll_reg.p3 >> 12) & 0xF0); + temp += (uint8_t)((pll_reg.p2 >> 16) & 0x0F); + params[i++] = temp; + + /* Registers 32-33 */ + temp = (uint8_t)((pll_reg.p2 >> 8) & 0xFF); + params[i++] = temp; + + temp = (uint8_t)(pll_reg.p2 & 0xFF); + params[i++] = temp; + + /* Write the parameters */ + if(target_pll == SI5351_PLLA) + { + si5351_write_bulk(SI5351_PLLA_PARAMETERS, i + 1, params); + } + else if(target_pll == SI5351_PLLB) + { + si5351_write_bulk(SI5351_PLLB_PARAMETERS, i + 1, params); + } + } + + delete params; + + /* Now the multisynth parameters */ + params = new uint8_t[20]; + i = 0; + + /* Registers 42-43 */ + temp = (uint8_t)((ms_reg.p3 >> 8) & 0xFF); + params[i++] = temp; + + temp = (uint8_t)(ms_reg.p3 & 0xFF); + params[i++] = temp; + + /* Register 44 */ + /* TODO: add code for output divider */ + temp = (uint8_t)((ms_reg.p1 >> 16) & 0x03); + params[i++] = temp; + + /* Registers 45-46 */ + temp = (uint8_t)((ms_reg.p1 >> 8) & 0xFF); + params[i++] = temp; + + temp = (uint8_t)(ms_reg.p1 & 0xFF); + params[i++] = temp; + + /* Register 47 */ + temp = (uint8_t)((ms_reg.p3 >> 12) & 0xF0); + temp += (uint8_t)((ms_reg.p2 >> 16) & 0x0F); + params[i++] = temp; + + /* Registers 48-49 */ + temp = (uint8_t)((ms_reg.p2 >> 8) & 0xFF); + params[i++] = temp; + + temp = (uint8_t)(ms_reg.p2 & 0xFF); + params[i++] = temp; + + /* Write the parameters */ + switch(clk) + { + case SI5351_CLK0: + si5351_write_bulk(SI5351_CLK0_PARAMETERS, i + 1, params); + si5351_set_ms_source(clk, target_pll); + break; + case SI5351_CLK1: + si5351_write_bulk(SI5351_CLK1_PARAMETERS, i + 1, params); + si5351_set_ms_source(clk, target_pll); + break; + case SI5351_CLK2: + si5351_write_bulk(SI5351_CLK2_PARAMETERS, i + 1, params); + si5351_set_ms_source(clk, target_pll); + break; + case SI5351_CLK3: + si5351_write_bulk(SI5351_CLK3_PARAMETERS, i + 1, params); + si5351_set_ms_source(clk, target_pll); + break; + case SI5351_CLK4: + si5351_write_bulk(SI5351_CLK4_PARAMETERS, i + 1, params); + si5351_set_ms_source(clk, target_pll); + break; + case SI5351_CLK5: + si5351_write_bulk(SI5351_CLK5_PARAMETERS, i + 1, params); + si5351_set_ms_source(clk, target_pll); + break; + case SI5351_CLK6: + si5351_write_bulk(SI5351_CLK6_PARAMETERS, i + 1, params); + si5351_set_ms_source(clk, target_pll); + break; + case SI5351_CLK7: + si5351_write_bulk(SI5351_CLK7_PARAMETERS, i + 1, params); + si5351_set_ms_source(clk, target_pll); + break; + } + + delete params; +} + +/* + * set_pll(uint32_t pll_freq, enum si5351_pll target_pll) + * + * Set the specified PLL to a specific oscillation frequency + * + * pll_freq - Desired PLL frequency + * target_pll - Which PLL to set + * (use the si5351_pll enum) + */ +void Si5351::set_pll(uint32_t pll_freq, enum si5351_pll target_pll) +{ + struct Si5351RegSet pll_reg; + + pll_calc(pll_freq, &pll_reg, ref_correction); + + /* Derive the register values to write */ + + /* Prepare an array for parameters to be written to */ + uint8_t *params = new uint8_t[20]; + ; + uint8_t i = 0; + uint8_t temp; + + /* Registers 26-27 */ + temp = ((pll_reg.p3 >> 8) & 0xFF); + params[i++] = temp; + + temp = (uint8_t)(pll_reg.p3 & 0xFF); + params[i++] = temp; + + /* Register 28 */ + temp = (uint8_t)((pll_reg.p1 >> 16) & 0x03); + params[i++] = temp; + + /* Registers 29-30 */ + temp = (uint8_t)((pll_reg.p1 >> 8) & 0xFF); + params[i++] = temp; + + temp = (uint8_t)(pll_reg.p1 & 0xFF); + params[i++] = temp; + + /* Register 31 */ + temp = (uint8_t)((pll_reg.p3 >> 12) & 0xF0); + temp += (uint8_t)((pll_reg.p2 >> 16) & 0x0F); + params[i++] = temp; + + /* Registers 32-33 */ + temp = (uint8_t)((pll_reg.p2 >> 8) & 0xFF); + params[i++] = temp; + + temp = (uint8_t)(pll_reg.p2 & 0xFF); + params[i++] = temp; + + /* Write the parameters */ + if(target_pll == SI5351_PLLA) + { + si5351_write_bulk(SI5351_PLLA_PARAMETERS, i + 1, params); + } + else if(target_pll == SI5351_PLLB) + { + si5351_write_bulk(SI5351_PLLB_PARAMETERS, i + 1, params); + } + + delete params; +} + +/* + * clock_enable(enum si5351_clock clk, uint8_t enable) + * + * Enable or disable a chosen clock + * clk - Clock output + * (use the si5351_clock enum) + * enable - Set to 1 to enable, 0 to disable + */ +void Si5351::clock_enable(enum si5351_clock clk, uint8_t enable) +{ + uint8_t reg_val; + + reg_val = si5351_read(SI5351_OUTPUT_ENABLE_CTRL); + + if(enable == 1) + { + reg_val &= ~(1<<(uint8_t)clk); + } + else + { + reg_val |= (1<<(uint8_t)clk); + } + + si5351_write(SI5351_OUTPUT_ENABLE_CTRL, reg_val); +} + +/* + * drive_strength(enum si5351_clock clk, enum si5351_drive drive) + * + * Sets the drive strength of the specified clock output + * + * clk - Clock output + * (use the si5351_clock enum) + * drive - Desired drive level + * (use the si5351_drive enum) + */ +void Si5351::drive_strength(enum si5351_clock clk, enum si5351_drive drive) +{ + uint8_t reg_val; + const uint8_t mask = 0x03; + + reg_val = si5351_read(SI5351_CLK0_CTRL + (uint8_t)clk); + + switch(drive) + { + case SI5351_DRIVE_2MA: + reg_val &= ~(mask); + reg_val |= 0x00; + break; + case SI5351_DRIVE_4MA: + reg_val &= ~(mask); + reg_val |= 0x01; + break; + case SI5351_DRIVE_6MA: + reg_val &= ~(mask); + reg_val |= 0x02; + break; + case SI5351_DRIVE_8MA: + reg_val &= ~(mask); + reg_val |= 0x03; + break; + default: + break; + } + + si5351_write(SI5351_CLK0_CTRL + (uint8_t)clk, reg_val); +} + +/* + * update_status(void) + * + * Call this to update the status structs, then access them + * via the dev_status and dev_int_status global variables. + * + * See the header file for the struct definitions. These + * correspond to the flag names for registers 0 and 1 in + * the Si5351 datasheet. + */ +void Si5351::update_status(void) +{ + si5351_update_sys_status(&dev_status); + si5351_update_int_status(&dev_int_status); +} + +/* + * set_correction(int32_t corr) + * + * Use this to set the oscillator correction factor to + * EEPROM. This value is a signed 32-bit integer of the + * parts-per-10 million value that the actual oscillation + * frequency deviates from the specified frequency. + * + * The frequency calibration is done as a one-time procedure. + * Any desired test frequency within the normal range of the + * Si5351 should be set, then the actual output frequency + * should be measured as accurately as possible. The + * difference between the measured and specified frequencies + * should be calculated in Hertz, then multiplied by 10 in + * order to get the parts-per-10 million value. + * + * Since the Si5351 itself has an intrinsic 0 PPM error, this + * correction factor is good across the entire tuning range of + * the Si5351. Once this calibration is done accurately, it + * should not have to be done again for the same Si5351 and + * crystal. The library will read the correction factor from + * EEPROM during initialization for use by the tuning + * algorithms. + */ +void Si5351::set_correction(int32_t corr) +{ + eeprom_write_dword(&ee_ref_correction, corr); + ref_correction = corr; +} + +/* + * get_correction(void) + * + * Returns the oscillator correction factor stored + * in EEPROM. + */ +int32_t Si5351::get_correction(void) +{ + return eeprom_read_dword(&ee_ref_correction); +} + + +uint8_t Si5351::si5351_write_bulk(uint8_t addr, uint8_t bytes, uint8_t *data) +{ + Wire.beginTransmission(SI5351_BUS_BASE_ADDR); + Wire.write(addr); + for(int i = 0; i < bytes; i++) + { + Wire.write(data[i]); + } + Wire.endTransmission(); +} + +uint8_t Si5351::si5351_write(uint8_t addr, uint8_t data) +{ + Wire.beginTransmission(SI5351_BUS_BASE_ADDR); + Wire.write(addr); + Wire.write(data); + Wire.endTransmission(); +} + +uint8_t Si5351::si5351_read(uint8_t addr) +{ + Wire.beginTransmission(SI5351_BUS_BASE_ADDR); + Wire.write(addr); + Wire.endTransmission(); + + Wire.requestFrom(SI5351_BUS_BASE_ADDR, 1); + + return Wire.read(); +} + +/*********************/ +/* Private functions */ +/*********************/ + +/* + * Calculate best rational approximation for a given fraction + * taking into account restricted register size, e.g. to find + * appropriate values for a pll with 5 bit denominator and + * 8 bit numerator register fields, trying to set up with a + * frequency ratio of 3.1415, one would say: + * + * rational_best_approximation(31415, 10000, + * (1 << 8) - 1, (1 << 5) - 1, &n, &d); + * + * you may look at given_numerator as a fixed point number, + * with the fractional part size described in given_denominator. + * + * for theoretical background, see: + * http://en.wikipedia.org/wiki/Continued_fraction + */ + +void Si5351::rational_best_approximation( + unsigned long given_numerator, unsigned long given_denominator, + unsigned long max_numerator, unsigned long max_denominator, + unsigned long *best_numerator, unsigned long *best_denominator) +{ + unsigned long n, d, n0, d0, n1, d1; + n = given_numerator; + d = given_denominator; + n0 = d1 = 0; + n1 = d0 = 1; + for (;;) { + unsigned long t, a; + if ((n1 > max_numerator) || (d1 > max_denominator)) { + n1 = n0; + d1 = d0; + break; + } + if (d == 0) + break; + t = d; + a = n / d; + d = n % d; + n = t; + t = n0 + a * n1; + n0 = n1; + n1 = t; + t = d0 + a * d1; + d0 = d1; + d1 = t; + } + *best_numerator = n1; + *best_denominator = d1; +} + +uint32_t Si5351::pll_calc(uint32_t freq, struct Si5351RegSet *reg, int32_t correction) +{ + uint32_t ref_freq = SI5351_XTAL_FREQ; + uint32_t rfrac, denom, a, b, c, p1, p2, p3; + uint64_t lltmp; + + /* Factor calibration value into nominal crystal frequency */ + /* Measured in parts-per-ten million */ + ref_freq += (uint32_t)((double)(correction / 10000000.0) * (double)ref_freq); + + /* PLL bounds checking */ + if (freq < SI5351_PLL_VCO_MIN) + freq = SI5351_PLL_VCO_MIN; + if (freq > SI5351_PLL_VCO_MAX) + freq = SI5351_PLL_VCO_MAX; + + /* Determine integer part of feedback equation */ + a = freq / ref_freq; + + if (a < SI5351_PLL_A_MIN) + freq = ref_freq * SI5351_PLL_A_MIN; + if (a > SI5351_PLL_A_MAX) + freq = ref_freq * SI5351_PLL_A_MAX; + + /* find best approximation for b/c = fVCO mod fIN */ + denom = 1000L * 1000L; + lltmp = freq % ref_freq; + lltmp *= denom; + do_div(lltmp, ref_freq); + rfrac = (uint32_t)lltmp; + + b = 0; + c = 1; + if (rfrac) + rational_best_approximation(rfrac, denom, + SI5351_PLL_B_MAX, SI5351_PLL_C_MAX, &b, &c); + + /* calculate parameters */ + p3 = c; + p2 = (128 * b) % c; + p1 = 128 * a; + p1 += (128 * b / c); + p1 -= 512; + + /* recalculate rate by fIN * (a + b/c) */ + lltmp = ref_freq; + lltmp *= b; + do_div(lltmp, c); + + freq = (uint32_t)lltmp; + freq += ref_freq * a; + + reg->p1 = p1; + reg->p2 = p2; + reg->p3 = p3; + + return freq; +} + +uint32_t Si5351::multisynth_calc(uint32_t freq, struct Si5351RegSet *reg) +{ + uint32_t pll_freq; + uint64_t lltmp; + uint32_t a, b, c, p1, p2, p3; + uint8_t divby4; + + /* Multisynth bounds checking */ + if (freq > SI5351_MULTISYNTH_MAX_FREQ) + freq = SI5351_MULTISYNTH_MAX_FREQ; + if (freq < SI5351_MULTISYNTH_MIN_FREQ) + freq = SI5351_MULTISYNTH_MIN_FREQ; + + divby4 = 0; + if (freq > SI5351_MULTISYNTH_DIVBY4_FREQ) + divby4 = 1; + + /* Find largest integer divider for max */ + /* VCO frequency and given target frequency */ + if (divby4 == 0) + { + lltmp = SI5351_PLL_VCO_MAX; + do_div(lltmp, freq); + a = (uint32_t)lltmp; + } + else + a = 4; + + b = 0; + c = 1; + pll_freq = a * freq; + + /* Recalculate output frequency by fOUT = fIN / (a + b/c) */ + lltmp = pll_freq; + lltmp *= c; + do_div(lltmp, a * c + b); + freq = (unsigned long)lltmp; + + /* Calculate parameters */ + if (divby4) + { + p3 = 1; + p2 = 0; + p1 = 0; + } + else + { + p3 = c; + p2 = (128 * b) % c; + p1 = 128 * a; + p1 += (128 * b / c); + p1 -= 512; + } + + reg->p1 = p1; + reg->p2 = p2; + reg->p3 = p3; + + return pll_freq; +} + +uint32_t Si5351::multisynth_recalc(uint32_t freq, uint32_t pll_freq, struct Si5351RegSet *reg) +{ + uint64_t lltmp; + uint32_t rfrac, denom, a, b, c, p1, p2, p3; + uint8_t divby4; + + /* Multisynth bounds checking */ + if (freq > SI5351_MULTISYNTH_MAX_FREQ) + freq = SI5351_MULTISYNTH_MAX_FREQ; + if (freq < SI5351_MULTISYNTH_MIN_FREQ) + freq = SI5351_MULTISYNTH_MIN_FREQ; + + divby4 = 0; + if (freq > SI5351_MULTISYNTH_DIVBY4_FREQ) + divby4 = 1; + + /* Determine integer part of feedback equation */ + a = pll_freq / freq; + + /* TODO: not sure this is correct */ + if (a < SI5351_MULTISYNTH_A_MIN) + freq = pll_freq / SI5351_MULTISYNTH_A_MIN; + if (a > SI5351_MULTISYNTH_A_MAX) + freq = pll_freq / SI5351_MULTISYNTH_A_MAX; + + /* find best approximation for b/c */ + denom = 1000L * 1000L; + lltmp = pll_freq % freq; + lltmp *= denom; + do_div(lltmp, freq); + rfrac = (uint32_t)lltmp; + + b = 0; + c = 1; + if (rfrac) + rational_best_approximation(rfrac, denom, + SI5351_MULTISYNTH_B_MAX, SI5351_MULTISYNTH_C_MAX, &b, &c); + + /* Recalculate output frequency by fOUT = fIN / (a + b/c) */ + lltmp = pll_freq; + lltmp *= c; + do_div(lltmp, a * c + b); + freq = (unsigned long)lltmp; + + /* Calculate parameters */ + if (divby4) + { + p3 = 1; + p2 = 0; + p1 = 0; + } + else + { + p3 = c; + p2 = (128 * b) % c; + p1 = 128 * a; + p1 += (128 * b / c); + p1 -= 512; + } + + reg->p1 = p1; + reg->p2 = p2; + reg->p3 = p3; + + return freq; +} + +void Si5351::si5351_update_sys_status(struct Si5351Status *status) +{ + uint8_t reg_val = 0; + + reg_val = si5351_read(SI5351_DEVICE_STATUS); + + /* Parse the register */ + status->SYS_INIT = (reg_val >> 7) & 0x01; + status->LOL_B = (reg_val >> 6) & 0x01; + status->LOL_A = (reg_val >> 5) & 0x01; + status->LOS = (reg_val >> 4) & 0x01; + status->REVID = reg_val & 0x03; +} + +void Si5351::si5351_update_int_status(struct Si5351IntStatus *int_status) +{ + uint8_t reg_val = 0; + + reg_val = si5351_read(SI5351_DEVICE_STATUS); + + /* Parse the register */ + int_status->SYS_INIT_STKY = (reg_val >> 7) & 0x01; + int_status->LOL_B_STKY = (reg_val >> 6) & 0x01; + int_status->LOL_A_STKY = (reg_val >> 5) & 0x01; + int_status->LOS_STKY = (reg_val >> 4) & 0x01; +} + +void Si5351::si5351_set_ms_source(enum si5351_clock clk, enum si5351_pll pll) +{ + uint8_t reg_val = 0x0c; + uint8_t reg_val2; + + reg_val2 = si5351_read(SI5351_CLK0_CTRL + (uint8_t)clk); + + if(pll == SI5351_PLLA) + { + reg_val &= ~(SI5351_CLK_PLL_SELECT); + } + else if(pll == SI5351_PLLB) + { + reg_val |= SI5351_CLK_PLL_SELECT; + } + si5351_write(SI5351_CLK0_CTRL + (uint8_t)clk, reg_val); +} diff --git a/si5351.h b/si5351.h new file mode 100644 index 0000000..165a13a --- /dev/null +++ b/si5351.h @@ -0,0 +1,289 @@ +/* + * si5351.h - Si5351 library for Arduino + * + * Copyright (C) 2014 Jason Milldrum + * + * Many defines derived from clk-si5351.h in the Linux kernel. + * Sebastian Hesselbarth + * Rabeeh Khoury + * + * do_div() macro derived from /include/asm-generic/div64.h in + * the Linux kernel. + * Copyright (C) 2003 Bernardo Innocenti + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef SI5351_H_ +#define SI5351_H_ + +#include "Arduino.h" +#include "Wire.h" +#include +#include + +/* Define definitions */ + +#define SI5351_BUS_BASE_ADDR 0x60 +#define SI5351_XTAL_FREQ 25000000 +#define SI5351_PLL_FIXED 900000000 + +#define SI5351_PLL_VCO_MIN 600000000 +#define SI5351_PLL_VCO_MAX 900000000 +#define SI5351_MULTISYNTH_MIN_FREQ 1000000 +#define SI5351_MULTISYNTH_DIVBY4_FREQ 150000000 +#define SI5351_MULTISYNTH_MAX_FREQ 160000000 +#define SI5351_MULTISYNTH67_MAX_FREQ SI5351_MULTISYNTH_DIVBY4_FREQ +#define SI5351_CLKOUT_MIN_FREQ 8000 +#define SI5351_CLKOUT_MAX_FREQ SI5351_MULTISYNTH_MAX_FREQ +#define SI5351_CLKOUT67_MAX_FREQ SI5351_MULTISYNTH67_MAX_FREQ + +#define SI5351_PLL_A_MIN 15 +#define SI5351_PLL_A_MAX 90 +#define SI5351_PLL_B_MAX (SI5351_PLL_C_MAX-1) +#define SI5351_PLL_C_MAX 1048575 +#define SI5351_MULTISYNTH_A_MIN 6 +#define SI5351_MULTISYNTH_A_MAX 1800 +#define SI5351_MULTISYNTH67_A_MAX 254 +#define SI5351_MULTISYNTH_B_MAX (SI5351_MULTISYNTH_C_MAX-1) +#define SI5351_MULTISYNTH_C_MAX 1048575 +#define SI5351_MULTISYNTH_P1_MAX ((1<<18)-1) +#define SI5351_MULTISYNTH_P2_MAX ((1<<20)-1) +#define SI5351_MULTISYNTH_P3_MAX ((1<<20)-1) + +#define SI5351_DEVICE_STATUS 0 +#define SI5351_INTERRUPT_STATUS 1 +#define SI5351_INTERRUPT_MASK 2 +#define SI5351_STATUS_SYS_INIT (1<<7) +#define SI5351_STATUS_LOL_B (1<<6) +#define SI5351_STATUS_LOL_A (1<<5) +#define SI5351_STATUS_LOS (1<<4) +#define SI5351_OUTPUT_ENABLE_CTRL 3 +#define SI5351_OEB_PIN_ENABLE_CTRL 9 +#define SI5351_PLL_INPUT_SOURCE 15 +#define SI5351_CLKIN_DIV_MASK (3<<6) +#define SI5351_CLKIN_DIV_1 (0<<6) +#define SI5351_CLKIN_DIV_2 (1<<6) +#define SI5351_CLKIN_DIV_4 (2<<6) +#define SI5351_CLKIN_DIV_8 (3<<6) +#define SI5351_PLLB_SOURCE (1<<3) +#define SI5351_PLLA_SOURCE (1<<2) + +#define SI5351_CLK0_CTRL 16 +#define SI5351_CLK1_CTRL 17 +#define SI5351_CLK2_CTRL 18 +#define SI5351_CLK3_CTRL 19 +#define SI5351_CLK4_CTRL 20 +#define SI5351_CLK5_CTRL 21 +#define SI5351_CLK6_CTRL 22 +#define SI5351_CLK7_CTRL 23 +#define SI5351_CLK_POWERDOWN (1<<7) +#define SI5351_CLK_INTEGER_MODE (1<<6) +#define SI5351_CLK_PLL_SELECT (1<<5) +#define SI5351_CLK_INVERT (1<<4) +#define SI5351_CLK_INPUT_MASK (3<<2) +#define SI5351_CLK_INPUT_XTAL (0<<2) +#define SI5351_CLK_INPUT_CLKIN (1<<2) +#define SI5351_CLK_INPUT_MULTISYNTH_0_4 (2<<2) +#define SI5351_CLK_INPUT_MULTISYNTH_N (3<<2) +#define SI5351_CLK_DRIVE_STRENGTH_MASK (3<<0) +#define SI5351_CLK_DRIVE_STRENGTH_2MA (0<<0) +#define SI5351_CLK_DRIVE_STRENGTH_4MA (1<<0) +#define SI5351_CLK_DRIVE_STRENGTH_6MA (2<<0) +#define SI5351_CLK_DRIVE_STRENGTH_8MA (3<<0) + +#define SI5351_CLK3_0_DISABLE_STATE 24 +#define SI5351_CLK7_4_DISABLE_STATE 25 +#define SI5351_CLK_DISABLE_STATE_MASK 3 +#define SI5351_CLK_DISABLE_STATE_LOW 0 +#define SI5351_CLK_DISABLE_STATE_HIGH 1 +#define SI5351_CLK_DISABLE_STATE_FLOAT 2 +#define SI5351_CLK_DISABLE_STATE_NEVER 3 + +#define SI5351_PARAMETERS_LENGTH 8 +#define SI5351_PLLA_PARAMETERS 26 +#define SI5351_PLLB_PARAMETERS 34 +#define SI5351_CLK0_PARAMETERS 42 +#define SI5351_CLK1_PARAMETERS 50 +#define SI5351_CLK2_PARAMETERS 58 +#define SI5351_CLK3_PARAMETERS 66 +#define SI5351_CLK4_PARAMETERS 74 +#define SI5351_CLK5_PARAMETERS 82 +#define SI5351_CLK6_PARAMETERS 90 +#define SI5351_CLK7_PARAMETERS 91 +#define SI5351_CLK6_7_OUTPUT_DIVIDER 92 +#define SI5351_OUTPUT_CLK_DIV_MASK (7 << 4) +#define SI5351_OUTPUT_CLK6_DIV_MASK (7 << 0) +#define SI5351_OUTPUT_CLK_DIV_SHIFT 4 +#define SI5351_OUTPUT_CLK_DIV6_SHIFT 0 +#define SI5351_OUTPUT_CLK_DIV_1 0 +#define SI5351_OUTPUT_CLK_DIV_2 1 +#define SI5351_OUTPUT_CLK_DIV_4 2 +#define SI5351_OUTPUT_CLK_DIV_8 3 +#define SI5351_OUTPUT_CLK_DIV_16 4 +#define SI5351_OUTPUT_CLK_DIV_32 5 +#define SI5351_OUTPUT_CLK_DIV_64 6 +#define SI5351_OUTPUT_CLK_DIV_128 7 +#define SI5351_OUTPUT_CLK_DIVBY4 (3<<2) + +#define SI5351_SSC_PARAM0 149 +#define SI5351_SSC_PARAM1 150 +#define SI5351_SSC_PARAM2 151 +#define SI5351_SSC_PARAM3 152 +#define SI5351_SSC_PARAM4 153 +#define SI5351_SSC_PARAM5 154 +#define SI5351_SSC_PARAM6 155 +#define SI5351_SSC_PARAM7 156 +#define SI5351_SSC_PARAM8 157 +#define SI5351_SSC_PARAM9 158 +#define SI5351_SSC_PARAM10 159 +#define SI5351_SSC_PARAM11 160 +#define SI5351_SSC_PARAM12 161 + +#define SI5351_VXCO_PARAMETERS_LOW 162 +#define SI5351_VXCO_PARAMETERS_MID 163 +#define SI5351_VXCO_PARAMETERS_HIGH 164 + +#define SI5351_CLK0_PHASE_OFFSET 165 +#define SI5351_CLK1_PHASE_OFFSET 166 +#define SI5351_CLK2_PHASE_OFFSET 167 +#define SI5351_CLK3_PHASE_OFFSET 168 +#define SI5351_CLK4_PHASE_OFFSET 169 +#define SI5351_CLK5_PHASE_OFFSET 170 + +#define SI5351_PLL_RESET 177 +#define SI5351_PLL_RESET_B (1<<7) +#define SI5351_PLL_RESET_A (1<<5) + +#define SI5351_CRYSTAL_LOAD 183 +#define SI5351_CRYSTAL_LOAD_MASK (3<<6) +#define SI5351_CRYSTAL_LOAD_6PF (1<<6) +#define SI5351_CRYSTAL_LOAD_8PF (2<<6) +#define SI5351_CRYSTAL_LOAD_10PF (3<<6) + +#define SI5351_FANOUT_ENABLE 187 +#define SI5351_CLKIN_ENABLE (1<<7) +#define SI5351_XTAL_ENABLE (1<<6) +#define SI5351_MULTISYNTH_ENABLE (1<<4) + +/* Macro definitions */ + +/* + * Based on former asm-ppc/div64.h and asm-m68knommu/div64.h + * + * The semantics of do_div() are: + * + * uint32_t do_div(uint64_t *n, uint32_t base) + * { + * uint32_t remainder = *n % base; + * *n = *n / base; + * return remainder; + * } + * + * NOTE: macro parameter n is evaluated multiple times, + * beware of side effects! + */ + +# define do_div(n,base) ({ \ + uint32_t __base = (base); \ + uint32_t __rem; \ + __rem = ((uint64_t)(n)) % __base; \ + (n) = ((uint64_t)(n)) / __base; \ + __rem; \ + }) + +/* Enum definitions */ + +/* + * enum si5351_variant - SiLabs Si5351 chip variant + * @SI5351_VARIANT_A: Si5351A (8 output clocks, XTAL input) + * @SI5351_VARIANT_A3: Si5351A MSOP10 (3 output clocks, XTAL input) + * @SI5351_VARIANT_B: Si5351B (8 output clocks, XTAL/VXCO input) + * @SI5351_VARIANT_C: Si5351C (8 output clocks, XTAL/CLKIN input) + */ +enum si5351_variant { + SI5351_VARIANT_A = 1, + SI5351_VARIANT_A3 = 2, + SI5351_VARIANT_B = 3, + SI5351_VARIANT_C = 4, +}; + +enum si5351_clock {SI5351_CLK0, SI5351_CLK1, SI5351_CLK2, SI5351_CLK3, + SI5351_CLK4, SI5351_CLK5, SI5351_CLK6, SI5351_CLK7}; + +enum si5351_pll {SI5351_PLLA, SI5351_PLLB}; + +enum si5351_drive {SI5351_DRIVE_2MA, SI5351_DRIVE_4MA, SI5351_DRIVE_6MA, SI5351_DRIVE_8MA}; + +/* Struct definitions */ + +struct Si5351RegSet +{ + uint32_t p1; + uint32_t p2; + uint32_t p3; +}; + +struct Si5351Status +{ + uint8_t SYS_INIT; + uint8_t LOL_B; + uint8_t LOL_A; + uint8_t LOS; + uint8_t REVID; +}; + +struct Si5351IntStatus +{ + uint8_t SYS_INIT_STKY; + uint8_t LOL_B_STKY; + uint8_t LOL_A_STKY; + uint8_t LOS_STKY; +}; + +class Si5351 +{ +public: + Si5351(void); + void init(uint8_t); + void set_freq(uint32_t, uint32_t, enum si5351_clock); + void set_pll(uint32_t, enum si5351_pll); + void clock_enable(enum si5351_clock, uint8_t); + void drive_strength(enum si5351_clock, enum si5351_drive); + void update_status(void); + void set_correction(int32_t); + int32_t get_correction(void); + uint8_t si5351_write_bulk(uint8_t, uint8_t, uint8_t *); + uint8_t si5351_write(uint8_t, uint8_t); + uint8_t si5351_read(uint8_t); + struct Si5351Status dev_status; + struct Si5351IntStatus dev_int_status; +private: + void rational_best_approximation( + unsigned long, unsigned long, + unsigned long, unsigned long, + unsigned long *, unsigned long *); + uint32_t pll_calc(uint32_t, struct Si5351RegSet *, int32_t); + uint32_t multisynth_calc(uint32_t, struct Si5351RegSet *); + uint32_t multisynth_recalc(uint32_t, uint32_t,struct Si5351RegSet *); + void si5351_update_sys_status(struct Si5351Status *); + void si5351_update_int_status(struct Si5351IntStatus *); + void si5351_set_ms_source(enum si5351_clock, enum si5351_pll); + uint32_t ee_ref_correction; + int32_t ref_correction; + uint32_t plla_freq; + uint32_t pllb_freq; +}; + +#endif /* SI5351_H_ */