├── ButtonHandler.cpp ├── CAT.cpp ├── CAT.h ├── Encoder.cpp ├── LICENSE.txt ├── README.md ├── RadioControl.h ├── RotaryEnc.cpp ├── RotaryEnc.h ├── SSB_Display.h ├── SSB_LCD_Display.cpp ├── SSB_Nextion_Display.cpp ├── SSB_Radio_Control.HMI ├── SSB_Radio_Control.ino ├── SSB_Radio_Control.tft ├── SSB_TFT_Display.cpp ├── Settings.cpp ├── Settings.h ├── Smeter.cpp ├── Smeter.h ├── Utility.cpp ├── Utility.h ├── si5351.cpp └── si5351.h /ButtonHandler.cpp: -------------------------------------------------------------------------------- 1 | // Button handler 2 | #include "RadioControl.h" 3 | 4 | //#define BTN_DEBUG 5 | 6 | ////////////////////////////////////////////////////////////////////// 7 | // // 8 | // Button Control Variables // 9 | // // 10 | ////////////////////////////////////////////////////////////////////// 11 | byte TuneButtonState = 0; 12 | byte lastTuneButtonState = 0; 13 | 14 | // Encoder button control 15 | byte EncButtonState = 0; 16 | byte lastEncButtonState = 0; 17 | 18 | // Sideband select button control 19 | byte SideBandButtonState = 0; 20 | byte lastSideBandButtonState = 0; 21 | 22 | // Band Switch button control 23 | byte BandButtonState = 0; 24 | byte lastBandButtonState = 0; 25 | 26 | // VFO select button control 27 | byte VFOButtonState = 0; 28 | byte lastVFOButtonState = 0; 29 | 30 | // PTT Control 31 | byte PTTState = 0; 32 | byte lastPTTState = 0; 33 | 34 | 35 | //*********************Check Band ************************************ 36 | void SwapBand() { 37 | 38 | uint32_t freq; 39 | 40 | if (band == BAND20) { // Switch to 40 Meter Band 41 | band=BAND40; 42 | sideband=band40Sideband; 43 | freq=band40Freq; 44 | } else { // Switch to 20 Meter Band 45 | band = BAND20; 46 | sideband=band20Sideband; 47 | freq=band20Freq; 48 | } 49 | 50 | // 51 | // Make sure BFO clock tracks with band change 52 | // 53 | if (sideband == USB) { 54 | bfo = USB_BFO; 55 | } else { 56 | bfo = LSB_BFO; 57 | } 58 | 59 | // 60 | // Change the clock frequency to the new bfo 61 | // 62 | setBFO(bfo); 63 | 64 | // 65 | // Set the active VFO to new frequency 66 | // 67 | switch (active_vfo) { 68 | case VFOA: 69 | vfoAfreq=freq; 70 | setVFO(vfoAfreq); 71 | displayActVFO(vfoAfreq); 72 | break; 73 | case VFOB: 74 | vfoBfreq=freq; 75 | setVFO(vfoBfreq); 76 | displayActVFO(vfoBfreq); 77 | break; 78 | } 79 | 80 | // 81 | // Update the display to show the active mode/sideband 82 | // 83 | displayMode(sideband); 84 | startSettingsTimer(); 85 | } 86 | 87 | void CheckBand() { 88 | BandButtonState = digitalRead(BAND_BTN); 89 | if (BandButtonState != lastBandButtonState) { 90 | 91 | // 92 | // On button press, change band 20/40 93 | // 94 | if (BandButtonState == LOW) { // if button pressed 95 | 96 | #ifdef BTN_DEBUG 97 | ToggleLED(); 98 | String msg = F("CheckBand"); 99 | displayBanner(msg); 100 | #endif 101 | SwapBand(); 102 | } 103 | 104 | lastBandButtonState = BandButtonState; 105 | Delay(50); 106 | BandButtonState = digitalRead(BAND_BTN); //debounce 107 | } 108 | } 109 | 110 | 111 | //*********************Check Sideband ************************************ 112 | void SwapSB() { 113 | if (sideband == USB) { // Switch to LSB 114 | sideband = LSB; 115 | bfo = LSB_BFO; 116 | } else { // Switch to USB 117 | sideband = USB; 118 | bfo = USB_BFO; 119 | } 120 | 121 | // 122 | // Keep track of which SSB is currently selected for current band 123 | // 124 | if (band == BAND20) { 125 | band20Sideband = sideband; 126 | } else { 127 | band40Sideband = sideband; 128 | } 129 | 130 | // 131 | // Change the clock frequency to the new bfo 132 | // 133 | setBFO(bfo); 134 | 135 | // 136 | // Set the active VFO to adjusted frequency 137 | // 138 | switch (active_vfo) { 139 | case VFOA: 140 | setVFO(vfoAfreq); 141 | vfoASideband=sideband; 142 | break; 143 | case VFOB: 144 | setVFO(vfoBfreq); 145 | vfoBSideband=sideband; 146 | break; 147 | } 148 | 149 | // 150 | // Update the display to show the active mode/sideband 151 | // 152 | displayMode(sideband); 153 | startSettingsTimer(); 154 | } 155 | 156 | void CheckSB() { 157 | SideBandButtonState = digitalRead(SIDEBAND_BTN); 158 | if (SideBandButtonState != lastSideBandButtonState) { 159 | 160 | // 161 | // On button press, change active sideband 162 | // Set the BFO as appropriate 163 | // 164 | if (SideBandButtonState == LOW) { // if button pressed 165 | SwapSB(); 166 | } 167 | 168 | lastSideBandButtonState = SideBandButtonState; 169 | Delay(50); 170 | SideBandButtonState = digitalRead(SIDEBAND_BTN); //debounce 171 | } 172 | } 173 | 174 | /* 175 | //********************* Tune Button Handling ************************************ 176 | void DoTune() { 177 | // 178 | // 179 | // CW experiment - call doCW to turn off BFO, set LO to operating frequencey+700 180 | // Then activate Tx - should generate a carrier 181 | // 182 | // Does but level is too low to drive the Tx amps 183 | // 184 | 185 | displayTune(true); 186 | 187 | setCW(); 188 | 189 | startTx(PTT_TUNE); 190 | 191 | for (int i = 0; i < 100; i++) { 192 | // tone(TONE_PIN, NOTE_B5); 193 | Delay(50); 194 | // noTone(TONE_PIN); 195 | Delay(50); 196 | } 197 | stopTx(); 198 | displayTune(false); 199 | } 200 | */ 201 | 202 | //********************* Tune Button Handling ************************************ 203 | void DoTune() { 204 | // 205 | // Temp code - 30 second full duty tone for debugging circuits - cheap tone generator. 206 | // Delay(12); 207 | // tone(TONE_PIN, NOTE_B5); 208 | // Delay(30000); 209 | // noTone(TONE_PIN); 210 | 211 | displayTune(true); 212 | startTx(PTT_TUNE); 213 | 214 | for (int i = 0; i < 100; i++) { 215 | tone(TONE_PIN, NOTE_B5); 216 | Delay(50); 217 | noTone(TONE_PIN); 218 | Delay(50); 219 | } 220 | stopTx(); 221 | displayTune(false); 222 | } 223 | 224 | 225 | 226 | void CheckTune() { 227 | TuneButtonState = digitalRead(TUNE_BTN); // creates a 10 second tuning pulse trani 50% duty cycle and makes TUNE appear on the screen 228 | if (TuneButtonState != lastTuneButtonState) { 229 | if (TuneButtonState == LOW) { 230 | DoTune(); 231 | } 232 | lastTuneButtonState = TuneButtonState; 233 | Delay(50); 234 | } 235 | } 236 | 237 | //*********************VFO switch******* VFO A or B **************** 238 | void SwapVFO() { 239 | 240 | if (active_vfo == VFOA) { 241 | active_vfo = VFOB; // Make VFOB Active 242 | sideband=vfoBSideband; 243 | } else { 244 | active_vfo = VFOA; // Make VFOA Active 245 | sideband=vfoASideband; 246 | } 247 | 248 | // 249 | // Adjust BFO in case sideband has changed 250 | // 251 | if (sideband == USB) { // Switch to LSB 252 | bfo = USB_BFO; 253 | } else { // Switch to USB 254 | bfo = LSB_BFO; 255 | } 256 | setBFO(bfo); 257 | displayMode(sideband); // Change sideband indicator 258 | 259 | 260 | #ifdef BTN_DEBUG 261 | ToggleLED(); 262 | String msg = F("SwapVFO: active_vfo="); 263 | msg += active_vfo; 264 | displayBanner(msg); 265 | #endif 266 | 267 | // 268 | // Swap Active/Alternate frequency displays 269 | // 270 | switch (active_vfo) { 271 | case VFOA: 272 | setVFO(vfoAfreq); 273 | displayActVFO(vfoAfreq); 274 | displayAltVFO(vfoBfreq); 275 | break; 276 | case VFOB: 277 | setVFO(vfoBfreq); 278 | displayActVFO(vfoBfreq); 279 | displayAltVFO(vfoAfreq); 280 | break; 281 | } 282 | displayVFOAB(active_vfo); // Change the A/B indicator 283 | displayMode(sideband); // Change sideband indicator 284 | 285 | startSettingsTimer(); 286 | } 287 | 288 | 289 | void CheckVFO() { 290 | 291 | VFOButtonState = digitalRead(VFO_BTN); 292 | if (VFOButtonState != lastVFOButtonState) { 293 | if (VFOButtonState == LOW) { // button pressed 294 | SwapVFO(); 295 | } 296 | lastVFOButtonState = VFOButtonState; 297 | Delay(50); 298 | VFOButtonState = digitalRead(VFO_BTN); //debounce 299 | } 300 | } 301 | 302 | 303 | // startSplit 304 | // Turn on split mode and update the display 305 | void startSplit() { 306 | split = true; 307 | displaySplit(split); 308 | } 309 | 310 | // Turnb off split mode and update the display 311 | void stopSplit() { 312 | split = false; 313 | displaySplit(split); 314 | } 315 | 316 | // startTx 317 | // if split mode is on swap Act and Alt VFO 318 | // Put the rig in to TX by triggering the PTT pin 319 | // Update the display to Tx 320 | void startTx(byte PTT_source) { 321 | if (split) SwapVFO(); 322 | digitalWrite(PTT,HIGH); 323 | displayTxRx(TX); 324 | TxRxState = TX; 325 | txSource = PTT_source; 326 | } 327 | 328 | // stopTx 329 | // Return the rig to Rx by lowering the PTT pin 330 | // If split mode is on swap Act and Alt VFO 331 | // Update the display to Tx 332 | void stopTx() { 333 | digitalWrite(PTT,LOW); 334 | if (split) SwapVFO(); 335 | displayTxRx(RX); 336 | TxRxState = RX; 337 | } 338 | 339 | //********************* PTT **************************** 340 | 341 | void CheckPTT(){ 342 | 343 | if ((TxRxState==TX) && (txSource != PTT_MIC)) { 344 | return; 345 | } 346 | 347 | TxRxState = digitalRead(PTT_SENSE); 348 | 349 | if(TxRxState != lastTxRxState){ 350 | 351 | if(TxRxState == TX){ 352 | startTx(PTT_MIC); 353 | } else { 354 | if (txSource == PTT_MIC) { 355 | stopTx(); 356 | } 357 | } 358 | lastTxRxState = TxRxState; 359 | Delay(50); 360 | } 361 | 362 | } 363 | -------------------------------------------------------------------------------- /CAT.cpp: -------------------------------------------------------------------------------- 1 | /************************************************************** 2 | F40 CAT Control 3 | 1/24/2021 - KK4DAS Version 1.3 4 | Changed to IC746 CAT Library 5 | 6 | ***************************************************************/ 7 | #include "RadioControl.h" 8 | #include 9 | 10 | IC746 radio = IC746(); 11 | 12 | //#define CAT_DEBUG 13 | 14 | // radio modes 15 | #define MODE_LSB 00 16 | #define MODE_USB 01 17 | #define MODE_CW 02 18 | 19 | 20 | // function to run when we must put radio on TX/RX 21 | // If PTT requests transmit and the rig is not already transmitting, start TX 22 | // If PTT request stop transmit and the current trasnmit source is PTT_CAT then stop TX 23 | // 24 | void catGoPtt(boolean pttf) { 25 | 26 | #ifdef CAT_DEBUG 27 | String msg = F("CatGoPtt "); 28 | msg += pttf; 29 | displayBanner(msg); 30 | #endif 31 | 32 | if ((TxRxState == TX) && (txSource != PTT_CAT)) { 33 | return; 34 | } 35 | 36 | if (pttf) { 37 | // displayDebug("CAT PTT ON "); 38 | if (TxRxState == RX) { 39 | startTx(PTT_CAT); 40 | } 41 | } else { 42 | 43 | if (txSource == PTT_CAT) { 44 | // displayDebug("CAT PTT OFF"); 45 | stopTx(); 46 | } 47 | } 48 | } 49 | 50 | boolean catGetPtt() { 51 | #if defined (DEBUG) 52 | String msg = "GetPTT: "; 53 | if (ptt == PTT_TX) { 54 | msg += "Tx"; 55 | } else { 56 | msg += "Rx"; 57 | } 58 | displayPrintln(msg); 59 | #endif 60 | 61 | if (TxRxState == TX) { 62 | return true; 63 | } else { 64 | return false; 65 | } 66 | } 67 | 68 | void catGoSplit(boolean cat_split) { 69 | 70 | #ifdef CAT_DEBUG 71 | String msg = F("CatGoSplit "); 72 | msg += cat_split; 73 | displayBanner(msg); 74 | #endif 75 | if (cat_split) { 76 | startSplit(); 77 | } else { 78 | stopSplit(); 79 | } 80 | } 81 | 82 | // function to run when VFOs A/B are toggled 83 | void catSwapVfo() { 84 | SwapVFO(); 85 | 86 | #ifdef CAT_DEBUG 87 | String msg = F("catGoToggleVFOs"); 88 | displayBanner(msg); 89 | #endif 90 | 91 | } 92 | 93 | // function to set a freq from CAT 94 | void catSetFreq(long f) { 95 | 96 | if (f == 0) return; // ignore spurious command 97 | 98 | // 99 | // Change the frequency of the current active VFO 100 | // Clock frequency is the operating frequecy plus the BFO 101 | // 102 | if (active_vfo == VFOA) { 103 | vfoAfreq = f; 104 | setVFO(vfoAfreq); 105 | displayActVFO(vfoAfreq); 106 | } else { 107 | vfoBfreq = f; 108 | setVFO(vfoBfreq); 109 | displayActVFO(vfoBfreq); 110 | } 111 | 112 | #ifdef CAT_DEBUG 113 | String msg = F("catsetFreq "); 114 | msg += f; 115 | displayBanner(msg); 116 | #endif 117 | startSettingsTimer(); 118 | } 119 | 120 | // function to set the mode(LSB or USB) from the cat command 121 | void catSetMode(byte m) { 122 | // If the new mode is different from the current sideband, then swap USB/LSB 123 | switch (sideband) { 124 | case USB: 125 | if (m == MODE_LSB) SwapSB(); 126 | break; 127 | case LSB: 128 | if (m == MODE_USB) SwapSB(); 129 | break; 130 | } 131 | 132 | #ifdef CAT_DEBUG 133 | String msg = F("CatSetMode "); 134 | if (sideband == USB ) { 135 | msg += F("USB "); 136 | } else { 137 | msg += F("LSB "); 138 | } 139 | msg += m; 140 | displayBanner(msg); 141 | #endif 142 | } 143 | 144 | // function to pass the freq to the cat library 145 | long catGetFreq() { 146 | // this must return the freq as an unsigned long in Hz, you must prepare it before 147 | long freq; 148 | 149 | // displayPrintln("catGetFreq called"); 150 | if (active_vfo == VFOA) { 151 | freq = vfoAfreq; 152 | } else { 153 | freq = vfoBfreq; 154 | } 155 | 156 | #ifdef CAT_DEBUG 157 | String msg = F("catGetFreq "); 158 | msg += freq; 159 | displayBanner(msg); 160 | #endif 161 | 162 | return freq; 163 | } 164 | 165 | // function to pass the mode to the cat library 166 | byte catGetMode() { 167 | // this must return the mode in the wat the CAT protocol expect it 168 | byte mode; 169 | 170 | if (sideband == USB) { 171 | mode = MODE_USB; 172 | } else { 173 | mode = MODE_LSB; 174 | } 175 | 176 | #ifdef CAT_DEBUG 177 | String msg = F("CatGetMode "); 178 | if (sideband == USB ) { 179 | msg += F("USB "); 180 | } else { 181 | msg += F("LSB "); 182 | } 183 | msg += mode; 184 | displayBanner(msg); 185 | #endif 186 | 187 | return mode; 188 | } 189 | 190 | // function to pass the smeter reading in RX mode 191 | byte catGetSMeter() { 192 | // this must return a byte in with the 4 LSB are the S meter data 193 | // so this procedure must take care of convert your S meter and scale it 194 | // up to just 4 bits 195 | 196 | #ifdef CAT_DEBUG 197 | String msg = F("CatGetSMeter "); 198 | msg += smeter; 199 | displayBanner(msg); 200 | #endif 201 | 202 | return smeter; 203 | } 204 | 205 | 206 | // Function to select the active VFO from the cat command 207 | // If requested VFO is not already active then swap active and alternate 208 | void catSetVFO(byte catVfo) { 209 | if ( ((catVfo == CAT_VFO_A) && (active_vfo == VFOB)) || 210 | ((catVfo == CAT_VFO_B) && (active_vfo == VFOA))) { 211 | SwapVFO(); 212 | } 213 | 214 | #if defined (DEBUG) 215 | String msg = "SetVFO: "; 216 | if (v == CAT_VFO_A) { 217 | msg += "VFO-A"; 218 | } else { 219 | msg += "VFO-B"; 220 | } 221 | displayPrintln(msg); 222 | #endif 223 | } 224 | 225 | // Function to make VFOS the same 226 | void catVfoAtoB() { 227 | 228 | if (active_vfo == VFOA) { 229 | vfoBfreq = vfoAfreq; 230 | vfoBSideband = vfoASideband; 231 | displayAltVFO(vfoBfreq); 232 | } else { 233 | vfoAfreq = vfoBfreq; 234 | vfoASideband = vfoBSideband; 235 | displayAltVFO(vfoAfreq); 236 | } 237 | 238 | #if defined (DEBUG) 239 | String msg = "VfoAtoB"; 240 | displayPrintln(msg); 241 | #endif 242 | } 243 | 244 | 245 | void setupCat() { 246 | 247 | // Setup the CAT control command handlers 248 | radio.addCATPtt(catGoPtt); 249 | radio.addCATGetPtt(catGetPtt); 250 | radio.addCATAtoB(catVfoAtoB); 251 | radio.addCATSwapVfo(catSwapVfo); 252 | radio.addCATsplit(catGoSplit); 253 | radio.addCATFSet(catSetFreq); 254 | radio.addCATMSet(catSetMode); 255 | radio.addCATVSet(catSetVFO); 256 | radio.addCATGetFreq(catGetFreq); 257 | radio.addCATGetMode(catGetMode); 258 | radio.addCATSMeter(catGetSMeter); 259 | 260 | // now we activate the library 261 | 262 | radio.begin(19200, SERIAL_8N1); 263 | 264 | #ifdef CAT_DEBUG 265 | displayBanner(String(F("setupCAT"))); 266 | #endif 267 | 268 | } 269 | 270 | 271 | void CheckCat() { 272 | radio.check(); 273 | } 274 | -------------------------------------------------------------------------------- /CAT.h: -------------------------------------------------------------------------------- 1 | #ifndef CAT_h 2 | #define CAT_H 3 | /************************************************************** 4 | F40 CAT Control 5 | 6 | 5/23/202 - KK4DAS Version 0.9 7 | Incorporated CAT Control Library 8 | FT857D CAT Library, by Pavel Milanes, CO7WT, pavelmc@gmail. 9 | https://github.com/pavelmc/FT857d/ 10 | ***************************************************************/ 11 | 12 | //=============== Function Prototypes ============================================ 13 | extern void setupCat(); 14 | extern void CheckCat(); 15 | 16 | #endif 17 | -------------------------------------------------------------------------------- /Encoder.cpp: -------------------------------------------------------------------------------- 1 | // 2 | // Encoder fuctions 3 | // 4 | #include "RadioControl.h" 5 | 6 | //#DEFINE DEBUG_ENC 7 | 8 | ////////////////////////////////////////////////////////////////////// 9 | // Rotary Enconder // 10 | ////////////////////////////////////////////////////////////////////// 11 | Rotary encoder = Rotary(ENCODER_A, ENCODER_B, ENCODER_BTN); // Setup Encoder 12 | 13 | ////////////////////////////////////////////////////////////////////// 14 | // // 15 | // Rotary Encoder Variables // 16 | // Number of clockwise and counterclockwise ticks // 17 | // Delta between successive measurements // 18 | // // 19 | ////////////////////////////////////////////////////////////////////// 20 | volatile int encoder_count = 0; // count of encoder clicks +1 for CW, -1 for CCW (volatile since used in ISR) 21 | int prev_encoder_count = 0; // used to measure changes over time 22 | int encoder_delta = 0; // differrnce between successive checks of encoder count 23 | 24 | bool incrementChanged = false; 25 | 26 | // when multiplied by tuning increment tells what the frequency change on 27 | // on the active VFO will be 28 | // Encoder button control 29 | extern byte EncButtonState; 30 | extern byte lastEncButtonState; 31 | 32 | /////////////////////////////////////////////////////////// 33 | // ************* ISR **************** // 34 | // Interrupt service routine, called on encoder movement // 35 | // Only interested in completed clicks // 36 | // +1 for Clocwwise // 37 | // -1 for Counter Clockwise // 38 | // Ignore intermediate values // 39 | /////////////////////////////////////////////////////////// 40 | 41 | #if _BOARDTYPE != Every 42 | ISR(PCINT2_vect) { 43 | unsigned char result = encoder.process(); 44 | if (result == DIR_CW) { 45 | encoder_count++; 46 | } else if (result == DIR_CCW) { 47 | encoder_count--; 48 | } 49 | } 50 | 51 | #else 52 | #define PA0_INTERRUPT PORTA.INTFLAGS & PIN0_bm 53 | #define PA0_CLEAR_INTERRUPT_FLAG PORTA.INTFLAGS &= PIN0_bm 54 | #define PF5_INTERRUPT PORTF.INTFLAGS & PIN5_bm 55 | #define PF5_CLEAR_INTERRUPT_FLAG PORTF.INTFLAGS &= PIN5_bm 56 | 57 | ISR(PORTA_PORT_vect) { 58 | unsigned char result = encoder.process(); 59 | 60 | if (PA0_INTERRUPT) 61 | PA0_CLEAR_INTERRUPT_FLAG; 62 | 63 | if (result == DIR_CW) { 64 | encoder_count++; 65 | } else if (result == DIR_CCW) { 66 | encoder_count--; 67 | } 68 | } 69 | 70 | ISR(PORTF_PORT_vect) { 71 | unsigned char result = encoder.process(); 72 | 73 | if (PF5_INTERRUPT) 74 | PF5_CLEAR_INTERRUPT_FLAG; 75 | 76 | if (result == DIR_CW) { 77 | encoder_count++; 78 | } else if (result == DIR_CCW) { 79 | encoder_count--; 80 | } 81 | 82 | } 83 | #endif 84 | 85 | 86 | //*******************Setup Interrupt Service Routine******************** 87 | 88 | void setupEncoderISR() { 89 | cli(); 90 | // Set up for the rotary encoder interrupts 91 | #if _BOARDTYPE != Every 92 | PCICR |= (1 << PCIE2); 93 | PCMSK2 |= (1 << PCINT18) | (1 << PCINT19); 94 | #endif 95 | #if _BOARDTYPE == Every 96 | PORTA.PIN0CTRL |= PORT_PULLUPEN_bm | PORT_ISC_BOTHEDGES_gc; 97 | PORTF.PIN5CTRL |= PORT_PULLUPEN_bm | PORT_ISC_BOTHEDGES_gc; 98 | #endif 99 | sei(); 100 | } 101 | 102 | 103 | //********************CheckEncoder******************************************* 104 | 105 | // roundedF - Calculates new frequency 106 | // After an increment change rounds up or down to the next even increment 107 | // Otherwise just changes increments or decrements the frequency 108 | // 109 | uint32_t roundedF(uint32_t freq, int32_t delta) { 110 | uint32_t f = freq; 111 | 112 | /* if (incrementChanged) { 113 | if (f % increment) { 114 | if (delta > 0 ) { 115 | f = f + increment - (f % increment); 116 | } else { 117 | f = f - (f % increment); 118 | } 119 | } 120 | incrementChanged = false; 121 | } else { 122 | f = f + delta; 123 | } 124 | */ 125 | if (f % increment) { 126 | if (delta > 0 ) { 127 | f = f + increment - (f % increment); 128 | } else { 129 | f = f - (f % increment); 130 | } 131 | } else { 132 | f = f + delta; 133 | } 134 | return f; 135 | } 136 | 137 | void AdjustVFO(long delta) { 138 | 139 | switch (active_vfo) { 140 | case VFOA: 141 | // vfoAfreq = vfoAfreq + delta; 142 | vfoAfreq = roundedF(vfoAfreq, delta); 143 | setVFO(vfoAfreq); 144 | displayActVFO(vfoAfreq); 145 | break; 146 | case VFOB: 147 | // vfoBfreq = vfoBfreq + delta; 148 | vfoBfreq = roundedF(vfoBfreq, delta); 149 | setVFO(vfoBfreq); 150 | displayActVFO(vfoBfreq); 151 | break; 152 | } 153 | startSettingsTimer(); 154 | } 155 | 156 | void CheckEncoder() { 157 | int current_count = encoder_count; // grab the current encoder_count 158 | int32_t encoder_delta = 0; 159 | 160 | if (current_count != prev_encoder_count) { // if there is any change in the encoder coount 161 | 162 | #ifdef DEBUG_ENC 163 | sprintf(debugmsg, "VFOA: %ld", vfoAfreq); 164 | Serial.println(debugmsg); 165 | #endif 166 | 167 | // 168 | // Calculate the delta (how many click positive or negaitve) 169 | // 170 | encoder_delta = current_count - prev_encoder_count; 171 | 172 | // 173 | // Adjust the currently selected VFO 174 | // 175 | AdjustVFO(encoder_delta * increment); 176 | 177 | 178 | #ifdef DEBUG 179 | sprintf(debugmsg, "current_count: %d, New VFOA: %ld", current_count, vfoAfreq); 180 | Serial.println(debugmsg); 181 | #endif 182 | 183 | prev_encoder_count = current_count; // save the current_count for next time around 184 | 185 | } 186 | } 187 | 188 | //********************CheckIncrement******************************************* 189 | // Cycle through tuning increment values on button press 190 | // 10, 100, 1K, 10K, 100K, 1M 191 | //********************CheckIncrement******************************************* 192 | void AdvanceIncrement() { 193 | if (increment == 10) { 194 | increment = 100; 195 | } 196 | else if (increment == 100) { 197 | increment = 1000; 198 | } 199 | else if (increment == 1000) { 200 | increment = 10000; 201 | } 202 | else if (increment == 10000) { 203 | increment = 100000; 204 | } 205 | else if (increment == 100000) { 206 | increment = 1000000; 207 | } 208 | else { 209 | increment = 10; 210 | } 211 | displayIncr(increment); 212 | incrementChanged = true; 213 | startSettingsTimer(); 214 | } 215 | 216 | void CheckIncrement () { 217 | 218 | EncButtonState = encoder.buttonState(); 219 | //EncButtonState = digitalRead(ENCODER_BTN); 220 | #ifdef DEBUG 221 | sprintf(debugmsg, "Encoder button state: %d", EncButtonState); 222 | Serial.println(debugmsg); 223 | Delay(1000); 224 | #endif 225 | if (EncButtonState != lastEncButtonState) { 226 | #ifdef DEBUG 227 | sprintf(debugmsg, "Encoder button state: %d", EncButtonState); 228 | Serial.println(debugmsg); 229 | #endif 230 | if (EncButtonState == LOW) { 231 | AdvanceIncrement(); 232 | 233 | } 234 | lastEncButtonState = EncButtonState; 235 | Delay(50); 236 | EncButtonState = encoder.buttonState(); //debounce 237 | //EncButtonState = digitalRead(ENCODER_BTN); 238 | } 239 | } 240 | 241 | void setupEncoder() { 242 | setupEncoderISR(); 243 | } 244 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | GNU GENERAL PUBLIC LICENSE 2 | Version 3, 29 June 2007 3 | 4 | Copyright (C) 2007 Free Software Foundation, Inc. 5 | Everyone is permitted to copy and distribute verbatim copies 6 | of this license document, but changing it is not allowed. 7 | 8 | Preamble 9 | 10 | The GNU General Public License is a free, copyleft license for 11 | software and other kinds of works. 12 | 13 | The licenses for most software and other practical works are designed 14 | to take away your freedom to share and change the works. By contrast, 15 | the GNU General Public License is intended to guarantee your freedom to 16 | share and change all versions of a program--to make sure it remains free 17 | software for all its users. We, the Free Software Foundation, use the 18 | GNU General Public License for most of our software; it applies also to 19 | any other work released this way by its authors. You can apply it to 20 | your programs, too. 21 | 22 | When we speak of free software, we are referring to freedom, not 23 | price. Our General Public Licenses are designed to make sure that you 24 | have the freedom to distribute copies of free software (and charge for 25 | them if you wish), that you receive source code or can get it if you 26 | want it, that you can change the software or use pieces of it in new 27 | free programs, and that you know you can do these things. 28 | 29 | To protect your rights, we need to prevent others from denying you 30 | these rights or asking you to surrender the rights. Therefore, you have 31 | certain responsibilities if you distribute copies of the software, or if 32 | you modify it: responsibilities to respect the freedom of others. 33 | 34 | For example, if you distribute copies of such a program, whether 35 | gratis or for a fee, you must pass on to the recipients the same 36 | freedoms that you received. You must make sure that they, too, receive 37 | or can get the source code. And you must show them these terms so they 38 | know their rights. 39 | 40 | Developers that use the GNU GPL protect your rights with two steps: 41 | (1) assert copyright on the software, and (2) offer you this License 42 | giving you legal permission to copy, distribute and/or modify it. 43 | 44 | For the developers' and authors' protection, the GPL clearly explains 45 | that there is no warranty for this free software. For both users' and 46 | authors' sake, the GPL requires that modified versions be marked as 47 | changed, so that their problems will not be attributed erroneously to 48 | authors of previous versions. 49 | 50 | Some devices are designed to deny users access to install or run 51 | modified versions of the software inside them, although the manufacturer 52 | can do so. This is fundamentally incompatible with the aim of 53 | protecting users' freedom to change the software. The systematic 54 | pattern of such abuse occurs in the area of products for individuals to 55 | use, which is precisely where it is most unacceptable. Therefore, we 56 | have designed this version of the GPL to prohibit the practice for those 57 | products. If such problems arise substantially in other domains, we 58 | stand ready to extend this provision to those domains in future versions 59 | of the GPL, as needed to protect the freedom of users. 60 | 61 | Finally, every program is threatened constantly by software patents. 62 | States should not allow patents to restrict development and use of 63 | software on general-purpose computers, but in those that do, we wish to 64 | avoid the special danger that patents applied to a free program could 65 | make it effectively proprietary. To prevent this, the GPL assures that 66 | patents cannot be used to render the program non-free. 67 | 68 | The precise terms and conditions for copying, distribution and 69 | modification follow. 70 | 71 | TERMS AND CONDITIONS 72 | 73 | 0. Definitions. 74 | 75 | "This License" refers to version 3 of the GNU General Public License. 76 | 77 | "Copyright" also means copyright-like laws that apply to other kinds of 78 | works, such as semiconductor masks. 79 | 80 | "The Program" refers to any copyrightable work licensed under this 81 | License. Each licensee is addressed as "you". "Licensees" and 82 | "recipients" may be individuals or organizations. 83 | 84 | To "modify" a work means to copy from or adapt all or part of the work 85 | in a fashion requiring copyright permission, other than the making of an 86 | exact copy. The resulting work is called a "modified version" of the 87 | earlier work or a work "based on" the earlier work. 88 | 89 | A "covered work" means either the unmodified Program or a work based 90 | on the Program. 91 | 92 | To "propagate" a work means to do anything with it that, without 93 | permission, would make you directly or secondarily liable for 94 | infringement under applicable copyright law, except executing it on a 95 | computer or modifying a private copy. Propagation includes copying, 96 | distribution (with or without modification), making available to the 97 | public, and in some countries other activities as well. 98 | 99 | To "convey" a work means any kind of propagation that enables other 100 | parties to make or receive copies. Mere interaction with a user through 101 | a computer network, with no transfer of a copy, is not conveying. 102 | 103 | An interactive user interface displays "Appropriate Legal Notices" 104 | to the extent that it includes a convenient and prominently visible 105 | feature that (1) displays an appropriate copyright notice, and (2) 106 | tells the user that there is no warranty for the work (except to the 107 | extent that warranties are provided), that licensees may convey the 108 | work under this License, and how to view a copy of this License. If 109 | the interface presents a list of user commands or options, such as a 110 | menu, a prominent item in the list meets this criterion. 111 | 112 | 1. Source Code. 113 | 114 | The "source code" for a work means the preferred form of the work 115 | for making modifications to it. "Object code" means any non-source 116 | form of a work. 117 | 118 | A "Standard Interface" means an interface that either is an official 119 | standard defined by a recognized standards body, or, in the case of 120 | interfaces specified for a particular programming language, one that 121 | is widely used among developers working in that language. 122 | 123 | The "System Libraries" of an executable work include anything, other 124 | than the work as a whole, that (a) is included in the normal form of 125 | packaging a Major Component, but which is not part of that Major 126 | Component, and (b) serves only to enable use of the work with that 127 | Major Component, or to implement a Standard Interface for which an 128 | implementation is available to the public in source code form. A 129 | "Major Component", in this context, means a major essential component 130 | (kernel, window system, and so on) of the specific operating system 131 | (if any) on which the executable work runs, or a compiler used to 132 | produce the work, or an object code interpreter used to run it. 133 | 134 | The "Corresponding Source" for a work in object code form means all 135 | the source code needed to generate, install, and (for an executable 136 | work) run the object code and to modify the work, including scripts to 137 | control those activities. However, it does not include the work's 138 | System Libraries, or general-purpose tools or generally available free 139 | programs which are used unmodified in performing those activities but 140 | which are not part of the work. For example, Corresponding Source 141 | includes interface definition files associated with source files for 142 | the work, and the source code for shared libraries and dynamically 143 | linked subprograms that the work is specifically designed to require, 144 | such as by intimate data communication or control flow between those 145 | subprograms and other parts of the work. 146 | 147 | The Corresponding Source need not include anything that users 148 | can regenerate automatically from other parts of the Corresponding 149 | Source. 150 | 151 | The Corresponding Source for a work in source code form is that 152 | same work. 153 | 154 | 2. Basic Permissions. 155 | 156 | All rights granted under this License are granted for the term of 157 | copyright on the Program, and are irrevocable provided the stated 158 | conditions are met. This License explicitly affirms your unlimited 159 | permission to run the unmodified Program. The output from running a 160 | covered work is covered by this License only if the output, given its 161 | content, constitutes a covered work. This License acknowledges your 162 | rights of fair use or other equivalent, as provided by copyright law. 163 | 164 | You may make, run and propagate covered works that you do not 165 | convey, without conditions so long as your license otherwise remains 166 | in force. You may convey covered works to others for the sole purpose 167 | of having them make modifications exclusively for you, or provide you 168 | with facilities for running those works, provided that you comply with 169 | the terms of this License in conveying all material for which you do 170 | not control copyright. Those thus making or running the covered works 171 | for you must do so exclusively on your behalf, under your direction 172 | and control, on terms that prohibit them from making any copies of 173 | your copyrighted material outside their relationship with you. 174 | 175 | Conveying under any other circumstances is permitted solely under 176 | the conditions stated below. Sublicensing is not allowed; section 10 177 | makes it unnecessary. 178 | 179 | 3. Protecting Users' Legal Rights From Anti-Circumvention Law. 180 | 181 | No covered work shall be deemed part of an effective technological 182 | measure under any applicable law fulfilling obligations under article 183 | 11 of the WIPO copyright treaty adopted on 20 December 1996, or 184 | similar laws prohibiting or restricting circumvention of such 185 | measures. 186 | 187 | When you convey a covered work, you waive any legal power to forbid 188 | circumvention of technological measures to the extent such circumvention 189 | is effected by exercising rights under this License with respect to 190 | the covered work, and you disclaim any intention to limit operation or 191 | modification of the work as a means of enforcing, against the work's 192 | users, your or third parties' legal rights to forbid circumvention of 193 | technological measures. 194 | 195 | 4. Conveying Verbatim Copies. 196 | 197 | You may convey verbatim copies of the Program's source code as you 198 | receive it, in any medium, provided that you conspicuously and 199 | appropriately publish on each copy an appropriate copyright notice; 200 | keep intact all notices stating that this License and any 201 | non-permissive terms added in accord with section 7 apply to the code; 202 | keep intact all notices of the absence of any warranty; and give all 203 | recipients a copy of this License along with the Program. 204 | 205 | You may charge any price or no price for each copy that you convey, 206 | and you may offer support or warranty protection for a fee. 207 | 208 | 5. Conveying Modified Source Versions. 209 | 210 | You may convey a work based on the Program, or the modifications to 211 | produce it from the Program, in the form of source code under the 212 | terms of section 4, provided that you also meet all of these conditions: 213 | 214 | a) The work must carry prominent notices stating that you modified 215 | it, and giving a relevant date. 216 | 217 | b) The work must carry prominent notices stating that it is 218 | released under this License and any conditions added under section 219 | 7. This requirement modifies the requirement in section 4 to 220 | "keep intact all notices". 221 | 222 | c) You must license the entire work, as a whole, under this 223 | License to anyone who comes into possession of a copy. This 224 | License will therefore apply, along with any applicable section 7 225 | additional terms, to the whole of the work, and all its parts, 226 | regardless of how they are packaged. This License gives no 227 | permission to license the work in any other way, but it does not 228 | invalidate such permission if you have separately received it. 229 | 230 | d) If the work has interactive user interfaces, each must display 231 | Appropriate Legal Notices; however, if the Program has interactive 232 | interfaces that do not display Appropriate Legal Notices, your 233 | work need not make them do so. 234 | 235 | A compilation of a covered work with other separate and independent 236 | works, which are not by their nature extensions of the covered work, 237 | and which are not combined with it such as to form a larger program, 238 | in or on a volume of a storage or distribution medium, is called an 239 | "aggregate" if the compilation and its resulting copyright are not 240 | used to limit the access or legal rights of the compilation's users 241 | beyond what the individual works permit. Inclusion of a covered work 242 | in an aggregate does not cause this License to apply to the other 243 | parts of the aggregate. 244 | 245 | 6. Conveying Non-Source Forms. 246 | 247 | You may convey a covered work in object code form under the terms 248 | of sections 4 and 5, provided that you also convey the 249 | machine-readable Corresponding Source under the terms of this License, 250 | in one of these ways: 251 | 252 | a) Convey the object code in, or embodied in, a physical product 253 | (including a physical distribution medium), accompanied by the 254 | Corresponding Source fixed on a durable physical medium 255 | customarily used for software interchange. 256 | 257 | b) Convey the object code in, or embodied in, a physical product 258 | (including a physical distribution medium), accompanied by a 259 | written offer, valid for at least three years and valid for as 260 | long as you offer spare parts or customer support for that product 261 | model, to give anyone who possesses the object code either (1) a 262 | copy of the Corresponding Source for all the software in the 263 | product that is covered by this License, on a durable physical 264 | medium customarily used for software interchange, for a price no 265 | more than your reasonable cost of physically performing this 266 | conveying of source, or (2) access to copy the 267 | Corresponding Source from a network server at no charge. 268 | 269 | c) Convey individual copies of the object code with a copy of the 270 | written offer to provide the Corresponding Source. This 271 | alternative is allowed only occasionally and noncommercially, and 272 | only if you received the object code with such an offer, in accord 273 | with subsection 6b. 274 | 275 | d) Convey the object code by offering access from a designated 276 | place (gratis or for a charge), and offer equivalent access to the 277 | Corresponding Source in the same way through the same place at no 278 | further charge. You need not require recipients to copy the 279 | Corresponding Source along with the object code. If the place to 280 | copy the object code is a network server, the Corresponding Source 281 | may be on a different server (operated by you or a third party) 282 | that supports equivalent copying facilities, provided you maintain 283 | clear directions next to the object code saying where to find the 284 | Corresponding Source. Regardless of what server hosts the 285 | Corresponding Source, you remain obligated to ensure that it is 286 | available for as long as needed to satisfy these requirements. 287 | 288 | e) Convey the object code using peer-to-peer transmission, provided 289 | you inform other peers where the object code and Corresponding 290 | Source of the work are being offered to the general public at no 291 | charge under subsection 6d. 292 | 293 | A separable portion of the object code, whose source code is excluded 294 | from the Corresponding Source as a System Library, need not be 295 | included in conveying the object code work. 296 | 297 | A "User Product" is either (1) a "consumer product", which means any 298 | tangible personal property which is normally used for personal, family, 299 | or household purposes, or (2) anything designed or sold for incorporation 300 | into a dwelling. In determining whether a product is a consumer product, 301 | doubtful cases shall be resolved in favor of coverage. For a particular 302 | product received by a particular user, "normally used" refers to a 303 | typical or common use of that class of product, regardless of the status 304 | of the particular user or of the way in which the particular user 305 | actually uses, or expects or is expected to use, the product. A product 306 | is a consumer product regardless of whether the product has substantial 307 | commercial, industrial or non-consumer uses, unless such uses represent 308 | the only significant mode of use of the product. 309 | 310 | "Installation Information" for a User Product means any methods, 311 | procedures, authorization keys, or other information required to install 312 | and execute modified versions of a covered work in that User Product from 313 | a modified version of its Corresponding Source. The information must 314 | suffice to ensure that the continued functioning of the modified object 315 | code is in no case prevented or interfered with solely because 316 | modification has been made. 317 | 318 | If you convey an object code work under this section in, or with, or 319 | specifically for use in, a User Product, and the conveying occurs as 320 | part of a transaction in which the right of possession and use of the 321 | User Product is transferred to the recipient in perpetuity or for a 322 | fixed term (regardless of how the transaction is characterized), the 323 | Corresponding Source conveyed under this section must be accompanied 324 | by the Installation Information. But this requirement does not apply 325 | if neither you nor any third party retains the ability to install 326 | modified object code on the User Product (for example, the work has 327 | been installed in ROM). 328 | 329 | The requirement to provide Installation Information does not include a 330 | requirement to continue to provide support service, warranty, or updates 331 | for a work that has been modified or installed by the recipient, or for 332 | the User Product in which it has been modified or installed. Access to a 333 | network may be denied when the modification itself materially and 334 | adversely affects the operation of the network or violates the rules and 335 | protocols for communication across the network. 336 | 337 | Corresponding Source conveyed, and Installation Information provided, 338 | in accord with this section must be in a format that is publicly 339 | documented (and with an implementation available to the public in 340 | source code form), and must require no special password or key for 341 | unpacking, reading or copying. 342 | 343 | 7. Additional Terms. 344 | 345 | "Additional permissions" are terms that supplement the terms of this 346 | License by making exceptions from one or more of its conditions. 347 | Additional permissions that are applicable to the entire Program shall 348 | be treated as though they were included in this License, to the extent 349 | that they are valid under applicable law. If additional permissions 350 | apply only to part of the Program, that part may be used separately 351 | under those permissions, but the entire Program remains governed by 352 | this License without regard to the additional permissions. 353 | 354 | When you convey a copy of a covered work, you may at your option 355 | remove any additional permissions from that copy, or from any part of 356 | it. (Additional permissions may be written to require their own 357 | removal in certain cases when you modify the work.) You may place 358 | additional permissions on material, added by you to a covered work, 359 | for which you have or can give appropriate copyright permission. 360 | 361 | Notwithstanding any other provision of this License, for material you 362 | add to a covered work, you may (if authorized by the copyright holders of 363 | that material) supplement the terms of this License with terms: 364 | 365 | a) Disclaiming warranty or limiting liability differently from the 366 | terms of sections 15 and 16 of this License; or 367 | 368 | b) Requiring preservation of specified reasonable legal notices or 369 | author attributions in that material or in the Appropriate Legal 370 | Notices displayed by works containing it; or 371 | 372 | c) Prohibiting misrepresentation of the origin of that material, or 373 | requiring that modified versions of such material be marked in 374 | reasonable ways as different from the original version; or 375 | 376 | d) Limiting the use for publicity purposes of names of licensors or 377 | authors of the material; or 378 | 379 | e) Declining to grant rights under trademark law for use of some 380 | trade names, trademarks, or service marks; or 381 | 382 | f) Requiring indemnification of licensors and authors of that 383 | material by anyone who conveys the material (or modified versions of 384 | it) with contractual assumptions of liability to the recipient, for 385 | any liability that these contractual assumptions directly impose on 386 | those licensors and authors. 387 | 388 | All other non-permissive additional terms are considered "further 389 | restrictions" within the meaning of section 10. If the Program as you 390 | received it, or any part of it, contains a notice stating that it is 391 | governed by this License along with a term that is a further 392 | restriction, you may remove that term. If a license document contains 393 | a further restriction but permits relicensing or conveying under this 394 | License, you may add to a covered work material governed by the terms 395 | of that license document, provided that the further restriction does 396 | not survive such relicensing or conveying. 397 | 398 | If you add terms to a covered work in accord with this section, you 399 | must place, in the relevant source files, a statement of the 400 | additional terms that apply to those files, or a notice indicating 401 | where to find the applicable terms. 402 | 403 | Additional terms, permissive or non-permissive, may be stated in the 404 | form of a separately written license, or stated as exceptions; 405 | the above requirements apply either way. 406 | 407 | 8. Termination. 408 | 409 | You may not propagate or modify a covered work except as expressly 410 | provided under this License. Any attempt otherwise to propagate or 411 | modify it is void, and will automatically terminate your rights under 412 | this License (including any patent licenses granted under the third 413 | paragraph of section 11). 414 | 415 | However, if you cease all violation of this License, then your 416 | license from a particular copyright holder is reinstated (a) 417 | provisionally, unless and until the copyright holder explicitly and 418 | finally terminates your license, and (b) permanently, if the copyright 419 | holder fails to notify you of the violation by some reasonable means 420 | prior to 60 days after the cessation. 421 | 422 | Moreover, your license from a particular copyright holder is 423 | reinstated permanently if the copyright holder notifies you of the 424 | violation by some reasonable means, this is the first time you have 425 | received notice of violation of this License (for any work) from that 426 | copyright holder, and you cure the violation prior to 30 days after 427 | your receipt of the notice. 428 | 429 | Termination of your rights under this section does not terminate the 430 | licenses of parties who have received copies or rights from you under 431 | this License. If your rights have been terminated and not permanently 432 | reinstated, you do not qualify to receive new licenses for the same 433 | material under section 10. 434 | 435 | 9. Acceptance Not Required for Having Copies. 436 | 437 | You are not required to accept this License in order to receive or 438 | run a copy of the Program. Ancillary propagation of a covered work 439 | occurring solely as a consequence of using peer-to-peer transmission 440 | to receive a copy likewise does not require acceptance. However, 441 | nothing other than this License grants you permission to propagate or 442 | modify any covered work. These actions infringe copyright if you do 443 | not accept this License. Therefore, by modifying or propagating a 444 | covered work, you indicate your acceptance of this License to do so. 445 | 446 | 10. Automatic Licensing of Downstream Recipients. 447 | 448 | Each time you convey a covered work, the recipient automatically 449 | receives a license from the original licensors, to run, modify and 450 | propagate that work, subject to this License. You are not responsible 451 | for enforcing compliance by third parties with this License. 452 | 453 | An "entity transaction" is a transaction transferring control of an 454 | organization, or substantially all assets of one, or subdividing an 455 | organization, or merging organizations. If propagation of a covered 456 | work results from an entity transaction, each party to that 457 | transaction who receives a copy of the work also receives whatever 458 | licenses to the work the party's predecessor in interest had or could 459 | give under the previous paragraph, plus a right to possession of the 460 | Corresponding Source of the work from the predecessor in interest, if 461 | the predecessor has it or can get it with reasonable efforts. 462 | 463 | You may not impose any further restrictions on the exercise of the 464 | rights granted or affirmed under this License. For example, you may 465 | not impose a license fee, royalty, or other charge for exercise of 466 | rights granted under this License, and you may not initiate litigation 467 | (including a cross-claim or counterclaim in a lawsuit) alleging that 468 | any patent claim is infringed by making, using, selling, offering for 469 | sale, or importing the Program or any portion of it. 470 | 471 | 11. Patents. 472 | 473 | A "contributor" is a copyright holder who authorizes use under this 474 | License of the Program or a work on which the Program is based. The 475 | work thus licensed is called the contributor's "contributor version". 476 | 477 | A contributor's "essential patent claims" are all patent claims 478 | owned or controlled by the contributor, whether already acquired or 479 | hereafter acquired, that would be infringed by some manner, permitted 480 | by this License, of making, using, or selling its contributor version, 481 | but do not include claims that would be infringed only as a 482 | consequence of further modification of the contributor version. For 483 | purposes of this definition, "control" includes the right to grant 484 | patent sublicenses in a manner consistent with the requirements of 485 | this License. 486 | 487 | Each contributor grants you a non-exclusive, worldwide, royalty-free 488 | patent license under the contributor's essential patent claims, to 489 | make, use, sell, offer for sale, import and otherwise run, modify and 490 | propagate the contents of its contributor version. 491 | 492 | In the following three paragraphs, a "patent license" is any express 493 | agreement or commitment, however denominated, not to enforce a patent 494 | (such as an express permission to practice a patent or covenant not to 495 | sue for patent infringement). To "grant" such a patent license to a 496 | party means to make such an agreement or commitment not to enforce a 497 | patent against the party. 498 | 499 | If you convey a covered work, knowingly relying on a patent license, 500 | and the Corresponding Source of the work is not available for anyone 501 | to copy, free of charge and under the terms of this License, through a 502 | publicly available network server or other readily accessible means, 503 | then you must either (1) cause the Corresponding Source to be so 504 | available, or (2) arrange to deprive yourself of the benefit of the 505 | patent license for this particular work, or (3) arrange, in a manner 506 | consistent with the requirements of this License, to extend the patent 507 | license to downstream recipients. "Knowingly relying" means you have 508 | actual knowledge that, but for the patent license, your conveying the 509 | covered work in a country, or your recipient's use of the covered work 510 | in a country, would infringe one or more identifiable patents in that 511 | country that you have reason to believe are valid. 512 | 513 | If, pursuant to or in connection with a single transaction or 514 | arrangement, you convey, or propagate by procuring conveyance of, a 515 | covered work, and grant a patent license to some of the parties 516 | receiving the covered work authorizing them to use, propagate, modify 517 | or convey a specific copy of the covered work, then the patent license 518 | you grant is automatically extended to all recipients of the covered 519 | work and works based on it. 520 | 521 | A patent license is "discriminatory" if it does not include within 522 | the scope of its coverage, prohibits the exercise of, or is 523 | conditioned on the non-exercise of one or more of the rights that are 524 | specifically granted under this License. You may not convey a covered 525 | work if you are a party to an arrangement with a third party that is 526 | in the business of distributing software, under which you make payment 527 | to the third party based on the extent of your activity of conveying 528 | the work, and under which the third party grants, to any of the 529 | parties who would receive the covered work from you, a discriminatory 530 | patent license (a) in connection with copies of the covered work 531 | conveyed by you (or copies made from those copies), or (b) primarily 532 | for and in connection with specific products or compilations that 533 | contain the covered work, unless you entered into that arrangement, 534 | or that patent license was granted, prior to 28 March 2007. 535 | 536 | Nothing in this License shall be construed as excluding or limiting 537 | any implied license or other defenses to infringement that may 538 | otherwise be available to you under applicable patent law. 539 | 540 | 12. No Surrender of Others' Freedom. 541 | 542 | If conditions are imposed on you (whether by court order, agreement or 543 | otherwise) that contradict the conditions of this License, they do not 544 | excuse you from the conditions of this License. If you cannot convey a 545 | covered work so as to satisfy simultaneously your obligations under this 546 | License and any other pertinent obligations, then as a consequence you may 547 | not convey it at all. For example, if you agree to terms that obligate you 548 | to collect a royalty for further conveying from those to whom you convey 549 | the Program, the only way you could satisfy both those terms and this 550 | License would be to refrain entirely from conveying the Program. 551 | 552 | 13. Use with the GNU Affero General Public License. 553 | 554 | Notwithstanding any other provision of this License, you have 555 | permission to link or combine any covered work with a work licensed 556 | under version 3 of the GNU Affero General Public License into a single 557 | combined work, and to convey the resulting work. The terms of this 558 | License will continue to apply to the part which is the covered work, 559 | but the special requirements of the GNU Affero General Public License, 560 | section 13, concerning interaction through a network will apply to the 561 | combination as such. 562 | 563 | 14. Revised Versions of this License. 564 | 565 | The Free Software Foundation may publish revised and/or new versions of 566 | the GNU General Public License from time to time. Such new versions will 567 | be similar in spirit to the present version, but may differ in detail to 568 | address new problems or concerns. 569 | 570 | Each version is given a distinguishing version number. If the 571 | Program specifies that a certain numbered version of the GNU General 572 | Public License "or any later version" applies to it, you have the 573 | option of following the terms and conditions either of that numbered 574 | version or of any later version published by the Free Software 575 | Foundation. If the Program does not specify a version number of the 576 | GNU General Public License, you may choose any version ever published 577 | by the Free Software Foundation. 578 | 579 | If the Program specifies that a proxy can decide which future 580 | versions of the GNU General Public License can be used, that proxy's 581 | public statement of acceptance of a version permanently authorizes you 582 | to choose that version for the Program. 583 | 584 | Later license versions may give you additional or different 585 | permissions. However, no additional obligations are imposed on any 586 | author or copyright holder as a result of your choosing to follow a 587 | later version. 588 | 589 | 15. Disclaimer of Warranty. 590 | 591 | THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY 592 | APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT 593 | HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY 594 | OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, 595 | THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 596 | PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM 597 | IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF 598 | ALL NECESSARY SERVICING, REPAIR OR CORRECTION. 599 | 600 | 16. Limitation of Liability. 601 | 602 | IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING 603 | WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS 604 | THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY 605 | GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE 606 | USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF 607 | DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD 608 | PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), 609 | EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF 610 | SUCH DAMAGES. 611 | 612 | 17. Interpretation of Sections 15 and 16. 613 | 614 | If the disclaimer of warranty and limitation of liability provided 615 | above cannot be given local legal effect according to their terms, 616 | reviewing courts shall apply local law that most closely approximates 617 | an absolute waiver of all civil liability in connection with the 618 | Program, unless a warranty or assumption of liability accompanies a 619 | copy of the Program in return for a fee. 620 | 621 | END OF TERMS AND CONDITIONS 622 | 623 | How to Apply These Terms to Your New Programs 624 | 625 | If you develop a new program, and you want it to be of the greatest 626 | possible use to the public, the best way to achieve this is to make it 627 | free software which everyone can redistribute and change under these terms. 628 | 629 | To do so, attach the following notices to the program. It is safest 630 | to attach them to the start of each source file to most effectively 631 | state the exclusion of warranty; and each file should have at least 632 | the "copyright" line and a pointer to where the full notice is found. 633 | 634 | 635 | Copyright (C) 636 | 637 | This program is free software: you can redistribute it and/or modify 638 | it under the terms of the GNU General Public License as published by 639 | the Free Software Foundation, either version 3 of the License, or 640 | (at your option) any later version. 641 | 642 | This program is distributed in the hope that it will be useful, 643 | but WITHOUT ANY WARRANTY; without even the implied warranty of 644 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 645 | GNU General Public License for more details. 646 | 647 | You should have received a copy of the GNU General Public License 648 | along with this program. If not, see . 649 | 650 | Also add information on how to contact you by electronic and paper mail. 651 | 652 | If the program does terminal interaction, make it output a short 653 | notice like this when it starts in an interactive mode: 654 | 655 | Copyright (C) 656 | This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. 657 | This is free software, and you are welcome to redistribute it 658 | under certain conditions; type `show c' for details. 659 | 660 | The hypothetical commands `show w' and `show c' should show the appropriate 661 | parts of the General Public License. Of course, your program's commands 662 | might be different; for a GUI interface, you would use an "about box". 663 | 664 | You should also get your employer (if you work as a programmer) or school, 665 | if any, to sign a "copyright disclaimer" for the program, if necessary. 666 | For more information on this, and how to apply and follow the GNU GPL, see 667 | . 668 | 669 | The GNU General Public License does not permit incorporating your program 670 | into proprietary programs. If your program is a subroutine library, you 671 | may consider it more useful to permit linking proprietary applications with 672 | the library. If this is what you want to do, use the GNU Lesser General 673 | Public License instead of this License. But first, please read 674 | . 675 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | SSB_Radio_Control 2 | Dean Souleles, KK4DAS, contact kk4das@gmail.com 3 | 4 | This sketch implement s basic SSB radio control panel with the following features: 5 | Dual VFO A/B 6 | Rotary encoder for tuning, push button to change tuning increment 7 | SI5351 Clock generator for the LO and BFO 8 | CAT Control (emulates an ICOM IC-746) 9 | Split mode support (Split from CAT, manual split requres Nextion touch screen) 10 | Settings memory (last frequency and mode are saved) 11 | Optional S-Meter 12 | 13 | Controls 14 | * Rotary encoder to change frequence 15 | * Rotary encode button changes tuning increment 16 | * VFO A/B Select toggle 17 | * Mode select USB/LSB toggle 18 | * Tune button (emits 10 second pulsed tone at 800Hz for tuning) 19 | * MOX toggle - puts rig in to Tx/Rx 20 | * Optional dual-band support 20/40 21 | 22 | Modules for different display types 23 | * 20x4 LCD 24 | * 320x240 TFT color display 25 | * 2.8" Nextion Touch Screen 26 | 27 | Display features 28 | * Dual VFOS 29 | * Mode indicator SSB/LSB 30 | * Tx/Rx ndicator 31 | * TuningStep Inidicator 32 | * Optional S Meter 33 | * Banner including callsign 34 | 35 | Additional controls with the Nextion display 36 | * Continuous scanning 37 | * Split mode 38 | 39 | Version 1.4 40 | March 9 2021 41 | Rstored LCD Display Option 42 | Compile time selection of S-Meter and Dual Band mods 43 | Refactoring of encoder handling 44 | 45 | Version 1.3 46 | Jan 25, 2021 47 | Changed CAT module to IC-746 48 | 49 | Version 1.2 50 | Dec 14, 2020 51 | Dual Band 20/40 Support 52 | S-Meter 53 | 54 | Version 1.1 55 | Dec 13. 2020 56 | Ported to Nano Every for more sweet SRAM 57 | * * Updated interrupt handling in Encoder.cpp to work with Nano Every (as well as UNO/Nano) 58 | * * Replaced SoftwareSerial connections to Nextion with Hardware Serial - Serial1 59 | 60 | Version 1.0 61 | Aug 13, 2020 62 | Adapted from SimpleSSB Sketch by N6QW, Pete Juliano and others 63 | 64 | 65 | NOTE TO BUILDERS 66 | This is a reference implementation of an SSB radio control program for a homebrew SSB transceiver. 67 | It is a fully functioning SSB radio control program. While it has been built for my particular hardware 68 | configuration every attempt has been made to make it modular in design so that the builder can swap out 69 | modules at will. It should be fairly straight forward to adapt to the builder's hardware selection 70 | of switches, buttons and knobs or even alternate displays. 71 | 72 | REQUIRED EXTERNAL LIBRARIES: 73 | * IC746CAT - https://github.com/kk4das/IC746CAT 74 | * For LCD - LiquidCrystal_I2C 75 | * For TFT - Adafruit_GFX.h, and Adafruit_ILI9341.h 76 | * For Nextion - https://github.com/itead/ITEADLIB_Arduino_Nextion (with updates - see notes) 77 | -------------------------------------------------------------------------------- /RadioControl.h: -------------------------------------------------------------------------------- 1 | #ifndef RadioControl_h 2 | #define RadioControl_h 3 | 4 | #define CALLSIGN "KK4DAS" 5 | #define VERSION "1.4" 6 | #define RIGNAME "SimpleSSB" 7 | 8 | 9 | //#define DEBUG 10 | #ifdef DEBUG 11 | extern char debugmsg[]; 12 | #endif 13 | 14 | 15 | //=============================== FEATURE SELECTION ========================= 16 | // Each flag below enables optional features 17 | // DISPLAY_X - uncomment one line depending on the display that is attached 18 | // BFOxXMHS - IF filter center frequency - eitherr 9MHz or 12MHZ 19 | // SMNETER - Uncomment if S-meter sensor circuit is installed 20 | // DUAL_BAND - Uncomment to enable band switching 20/40 if installed 21 | // CW - Uncomment if CW mod installed (future) 22 | 23 | //=============================== DISPLAY TYPE ============================== 24 | #define DISPLAY_LCD //uncomment for 20x4 LCD 25 | //#define DISPLAY_TFT // uncomment for 320x240 TFT 26 | //#define DISPLAY_NEXTION //uncomment for 2.8" Nextion 27 | 28 | //=============================== IF FILTER FREQ ============================ 29 | #define BFO9MHZ // uncomment for 9.0 MHz IF 30 | //#define BFO12MHZ // uncomment for 12.0 MHz IF 31 | 32 | //=============================== S-METER INSTALLED ========================= 33 | //#define SMETER // uncomment if SMETER mod installed 34 | 35 | //=============================== DUAL BAND MOD INSTALLED =================== 36 | //#define DUAL_BAND // uncomment if dual band mod installed 20/40 37 | 38 | //=============================== DISPLAY TYPE ============================== 39 | //#define CW // uncomment if CW enabled (not complete) 40 | 41 | 42 | 43 | 44 | //============================== BOARD TYPE (Nano, Every) ===================== 45 | #define Nano 0 46 | #define Every 1 47 | #define Uno 2 48 | 49 | #if defined(ARDUINO_AVR_NANO) 50 | #define _BOARDTYPE Nano 51 | #endif 52 | #if defined(ARDUINO_AVR_NANO_EVERY) 53 | #define _BOARDTYPE Every 54 | #endif 55 | #if defined (ARDUINO_AVR_UNO) 56 | #define _BOARDTYPE Uno 57 | #endif 58 | 59 | //============================= INCLUDES ===================================== 60 | #include 61 | #include "Utility.h" // General purpose common functions 62 | #include "SSB_Display.h" // Display handling 63 | #include "RotaryEnc.h" // Rotary encoder handling 64 | #include "si5351.h" // SI 5351 clock 65 | #include "CAT.h" // CAT Control handling 66 | #include "Settings.h" // Get/Store Settings in EEPROM 67 | #include "Smeter.h" // Smeter definitions 68 | 69 | 70 | //============================== Symbolic constants ========================== 71 | 72 | 73 | ////////////////////////////////////////////////////////////////////// 74 | // // 75 | // Arduino Pin Definitions // 76 | // // 77 | ////////////////////////////////////////////////////////////////////// 78 | 79 | #define ENCODER_A 2 // Rotary Lib Default - Encoder pin A D2 (interrupt pin) 80 | #define ENCODER_B 3 // Rotary Lib Default - Encoder pin B D3 (interrupt pin) 81 | #define PTT_SENSE 4 // Detect Mic PT 82 | #define PTT 5 // LOW=Rx, HIGH=Tx 83 | #define TONE_PIN 8 // Audio out for tune tone 84 | 85 | // Dual Band Pins (requires dual band mod) 86 | #define BAND_BTN 6 // Band Switch momentary button 87 | #define BAND_PIN 7 // Band Switch Relay - LOW = Band A (40M), HIGH = Band B (20M) 88 | 89 | #define VFO_BTN A0 // VFO A/B button 90 | #define SIDEBAND_BTN A1 // USB/LSB button 91 | #define TUNE_BTN A2 // Tune Button 92 | #define ENCODER_BTN A3 // Rotary Lib Default - Encoder push button 93 | #define I2C_SDA A4 // I2C SDA Pin 94 | #define I2C_SCL A5 // I2C SCL Pin 95 | 96 | // Smeter 97 | #define SMETER_PIN A7 // Requires Signal Strength Sensor 98 | 99 | 100 | // 101 | // For Nextion / Nano Every Only 102 | // Pin 0,1 Serial RX/TX 103 | 104 | 105 | // For color TFT Only 106 | // Arduino 107 | // Pin TFT Pin 108 | // -----------|--------- 109 | // 8 | RST - any free Arduino Pin (not used in this sketch) 110 | // 9 | DC - any free Arduino Pin 111 | // 10 | CS - any free Arduino Pin 112 | // 11 | MOSI - fixed 113 | // 12 | MISO - fixed 114 | // 13 | CLK - fixed 115 | // 116 | 117 | 118 | // Tune Tone 119 | #define NOTE_B5 988 // Tune tone 120 | 121 | // 122 | // Dual Band Mode Constants (requires DUAL_BAND) 123 | // 124 | #define BAND20 1 125 | #define BAND40 2 126 | #define BAND20_EDGE 14000000 127 | #define BAND40_EDGE 7000000 128 | 129 | // VFO, Sideband and TX state are used both here and in the display 130 | // Define here if not already defined 131 | 132 | // VFO selection 133 | #define VFOA 0 134 | #define VFOB 1 135 | 136 | // Sideband selection 137 | #define USB 0 138 | #define LSB 1 139 | 140 | // Transmit state 141 | #define TX LOW // TX is on when PTT switched to ground 142 | #define RX HIGH 143 | 144 | 145 | // PTT Source 146 | #define PTT_MIC 0 147 | #define PTT_CAT 1 148 | #define PTT_TUNE 2 149 | 150 | //=============== Globals ============================================ 151 | 152 | // Rotary Encoder 153 | extern Rotary encoder; 154 | 155 | // BFO 156 | extern const uint32_t USB_BFO; 157 | extern const uint32_t LSB_BFO; 158 | extern uint32_t bfo; // Startup BFO frequency 159 | extern const uint32_t BFO_DELTA; // Difference between USB and LSB for BFO change 160 | 161 | // VFO A/B frequencies 162 | extern uint32_t vfoAfreq; 163 | extern uint32_t vfoBfreq; 164 | extern byte vfoASideband; 165 | extern byte vfoBSideband; 166 | 167 | // Tuning increment 168 | extern uint32_t increment; 169 | 170 | // Active VFO indicator 171 | extern byte active_vfo; 172 | 173 | 174 | // Active sideband (USB or LSB) 175 | extern byte sideband; 176 | 177 | 178 | // Band specific memory - requires DUAL_BAND 179 | extern uint32_t band20Freq; 180 | extern uint32_t band40Freq; 181 | extern byte band20Sideband; 182 | extern byte band40Sideband; 183 | extern byte band; 184 | 185 | 186 | // Transmit state 187 | extern byte TxRxState; 188 | extern byte lastTxRxState; 189 | 190 | // Transmoit source (mic, CAT) 191 | extern byte txSource; 192 | 193 | // S Meter 194 | extern byte smeter; 195 | 196 | // Split mode 197 | extern bool split; 198 | 199 | 200 | //=============== Function Prototypes ============================================ 201 | 202 | extern void setupEncoder(); 203 | extern void setVFO(uint32_t freq); 204 | extern void setBFO(uint32_t freq); 205 | extern void CheckIncrement(); 206 | extern void AdvanceIncrement(); 207 | extern void CheckEncoder(); 208 | extern void AdjustVFO(long delta); 209 | 210 | extern void CheckSB(); 211 | extern void SwapSB(); 212 | extern void CheckTune(); 213 | extern void DoTune(); 214 | extern void CheckVFO(); 215 | extern void SwapVFO(); 216 | extern void CheckPTT(); 217 | extern void setupPins(); 218 | extern void startTx(byte PTT_source); 219 | extern void stopTx(); 220 | extern void startSplit(); 221 | extern void stopSplit(); 222 | extern void CheckBand(); // Only called if DUAL_BAND enabled 223 | extern void CheckSmeter(); // Only called if SMETER enabled 224 | 225 | #ifdef CW 226 | extern void setCW(); 227 | #endif 228 | 229 | #endif 230 | -------------------------------------------------------------------------------- /RotaryEnc.cpp: -------------------------------------------------------------------------------- 1 | /* Rotary encoder handler for arduino. 2 | * 3 | * Revision: 2020 Dean Souleles, KK4DAS, Licensed under the GNU GPL Version 3. 4 | * Contact: kk4das@gmail.com 5 | * Added default pins and button handling 6 | * 7 | * Copyright 2011 Ben Buxton. Licenced under the GNU GPL Version 3. 8 | * Contact: bb@cactii.net 9 | * 10 | */ 11 | 12 | #include "Arduino.h" 13 | #include "RotaryEnc.h" 14 | 15 | /* 16 | * The below state table has, for each state (row), the new state 17 | * to set based on the next encoder output. From left to right in, 18 | * the table, the encoder outputs are 00, 01, 10, 11, and the value 19 | * in that position is the new state to set. 20 | */ 21 | 22 | #define R_START 0x0 23 | 24 | #ifdef HALF_STEP 25 | // Use the half-step state table (emits a code at 00 and 11) // nulled out to only use full step 26 | #define R_CCW_BEGIN 0x1 27 | #define R_CW_BEGIN 0x2 28 | #define R_START_M 0x3 29 | #define R_CW_BEGIN_M 0x4 30 | #define R_CCW_BEGIN_M 0x5 31 | const unsigned char ttable[6][4] = { 32 | // R_START (00) 33 | {R_START_M, R_CW_BEGIN, R_CCW_BEGIN, R_START}, 34 | // R_CCW_BEGIN 35 | {R_START_M | DIR_CCW, R_START, R_CCW_BEGIN, R_START}, 36 | // R_CW_BEGIN 37 | {R_START_M | DIR_CW, R_CW_BEGIN, R_START, R_START}, 38 | // R_START_M (11) 39 | {R_START_M, R_CCW_BEGIN_M, R_CW_BEGIN_M, R_START}, 40 | // R_CW_BEGIN_M 41 | {R_START_M, R_START_M, R_CW_BEGIN_M, R_START | DIR_CW}, 42 | // R_CCW_BEGIN_M 43 | {R_START_M, R_CCW_BEGIN_M, R_START_M, R_START | DIR_CCW}, 44 | }; 45 | #else 46 | // Use the full-step state table (emits a code at 00 only) 47 | #define R_CW_FINAL 0x1 48 | #define R_CW_BEGIN 0x2 49 | #define R_CW_NEXT 0x3 50 | #define R_CCW_BEGIN 0x4 51 | #define R_CCW_FINAL 0x5 52 | #define R_CCW_NEXT 0x6 53 | 54 | const unsigned char ttable[7][4] = { 55 | // R_START 56 | {R_START, R_CW_BEGIN, R_CCW_BEGIN, R_START}, 57 | // R_CW_FINAL 58 | {R_CW_NEXT, R_START, R_CW_FINAL, R_START | DIR_CW}, 59 | // R_CW_BEGIN 60 | {R_CW_NEXT, R_CW_BEGIN, R_START, R_START}, 61 | // R_CW_NEXT 62 | {R_CW_NEXT, R_CW_BEGIN, R_CW_FINAL, R_START}, 63 | // R_CCW_BEGIN 64 | {R_CCW_NEXT, R_START, R_CCW_BEGIN, R_START}, 65 | // R_CCW_FINAL 66 | {R_CCW_NEXT, R_CCW_FINAL, R_START, R_START | DIR_CCW}, 67 | // R_CCW_NEXT 68 | {R_CCW_NEXT, R_CCW_FINAL, R_CCW_BEGIN, R_START}, 69 | }; 70 | #endif 71 | 72 | /* 73 | * Constructor Common 74 | */ 75 | void Rotary::Constructor_Common() { 76 | // Set pins to input. 77 | pinMode(pin1, INPUT); 78 | pinMode(pin2, INPUT); 79 | pinMode(btn, INPUT); 80 | #ifdef ENABLE_PULLUPS 81 | digitalWrite(pin1, HIGH); 82 | digitalWrite(pin2, HIGH); 83 | digitalWrite(btn, HIGH); 84 | #endif 85 | // Initialise state. 86 | state = R_START; 87 | } 88 | 89 | /* 90 | * Constructor. Each arg is the pin number for each encoder contact. 91 | */ 92 | Rotary::Rotary(char _pin1, char _pin2,char _btn) { 93 | // Assign variables. 94 | pin1 = _pin1; 95 | pin2 = _pin2; 96 | btn = _btn; 97 | Constructor_Common(); 98 | } 99 | 100 | 101 | Rotary::Rotary() { 102 | // Assign variables to defaults 103 | pin1 = ENCODER_A; 104 | pin2 = ENCODER_B; 105 | btn = ENCODER_BTN; 106 | Constructor_Common(); 107 | } 108 | 109 | 110 | unsigned char Rotary::process() { 111 | // Grab state of input pins. 112 | unsigned char pinstate = (digitalRead(pin2) << 1) | digitalRead(pin1); 113 | // Determine new state from the pins and state table. 114 | state = ttable[state & 0xf][pinstate]; 115 | // Return emit bits, ie the generated event. 116 | return state & 0x30; 117 | } 118 | 119 | byte Rotary::buttonState() { // returns the state of the Encoder button LOW=pressed 120 | // return byte(digitalRead(btn)); 121 | return digitalRead(btn); 122 | } 123 | -------------------------------------------------------------------------------- /RotaryEnc.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Rotary encoder library for Arduino. 3 | * 4 | * Revision: 2020 Dean Souleles, KK4DAS, Licensed under the GNU GPL Version 3. 5 | * Contact: kk4das@gmail.com 6 | * Added default pins and button handling and setup for clean interrupt handling 7 | * 8 | * Copyright 2011 Ben Buxton. Licenced under the GNU GPL Version 3. 9 | * Contact: bb@cactii.net 10 | * 11 | */ 12 | 13 | #ifndef RotaryEnc_h 14 | #define RotaryEnc_h 15 | 16 | 17 | #include "Arduino.h" 18 | 19 | // Enable this to emit codes twice per step. 20 | //#define HALF_STEP 21 | 22 | // Enable weak pullups 23 | #define ENABLE_PULLUPS 24 | 25 | // Values returned by 'process' 26 | // No complete step yet. 27 | #define DIR_NONE 0x0 28 | // Clockwise step. 29 | #define DIR_CW 0x10 30 | // Counter-clockwise step. 31 | #define DIR_CCW 0x20 32 | 33 | 34 | // 35 | // Default Encoder wiring Pins D2 and D3 for the encoder, A3 for the pushbutton 36 | // 37 | #ifndef ENCODER_A 38 | #define ENCODER_A 2 // Encoder pin A D2 (interrupt pin) 39 | #endif 40 | 41 | #ifndef ENCODER_B 42 | #define ENCODER_B 3 // Encoder pin B D3 (interrupt pin) 43 | #endif 44 | 45 | 46 | #ifndef ENCODER_BTN 47 | #define ENCODER_BTN A3 // Encoder push buttonh 48 | #endif 49 | 50 | 51 | class Rotary 52 | { 53 | public: 54 | Rotary(char, char, char); 55 | Rotary(); 56 | unsigned char process(); 57 | byte buttonState(); 58 | private: 59 | unsigned char state; 60 | unsigned char pin1; 61 | unsigned char pin2; 62 | unsigned char btn; 63 | void Constructor_Common(); 64 | }; 65 | 66 | #endif 67 | 68 | -------------------------------------------------------------------------------- /SSB_Display.h: -------------------------------------------------------------------------------- 1 | #ifndef SSB_Display_h 2 | #define SSB_Display_h 3 | 4 | /* 5 | * SSB_Display.h 6 | * KK4DAS, Dean Souleles, KK4DAS@gmail.com 7 | * May 30, 2020 8 | * 9 | * Constants and function prototypes used in the Display Routines 10 | * This file needs to be in included in the main sketch 11 | * 12 | */ 13 | #include "RadioControl.h" 14 | 15 | 16 | 17 | // ===========================Function Prototypes================================== 18 | 19 | extern void displaySMeter(byte level); 20 | extern void displayBanner(String s); 21 | extern void displayActVFO(uint32_t freq); 22 | extern void displayAltVFO(uint32_t freq); 23 | extern void displayVFOAB(int vfo); 24 | extern void displayTxRx(int tx_rx); 25 | extern void displayMode(int mode); 26 | extern void displayIncr(uint32_t increment); 27 | extern void displayTune(boolean on_off); 28 | extern void displaySplit(boolean splt); 29 | extern void displaySetup(String banner, 30 | uint32_t vfoActfreq, uint32_t vfoAltfreq, 31 | uint32_t activeVFO, 32 | int tx_rx, 33 | int sideband, 34 | boolean split, 35 | uint32_t increment, 36 | byte s_meter); 37 | 38 | #ifdef DISPLAY_NEXTION 39 | extern void CheckTouch(); 40 | #endif 41 | 42 | #endif 43 | -------------------------------------------------------------------------------- /SSB_LCD_Display.cpp: -------------------------------------------------------------------------------- 1 | #include "RadioControl.h" 2 | #ifdef DISPLAY_LCD 3 | 4 | #include 5 | 6 | LiquidCrystal_I2C lcd(0x27, 20, 4); //0x27 or 0x3F 0r 0x20 //1604 LCD/w I2C 7 | 8 | 9 | ////////////////////////////////////////////////////////////////////// 10 | // // 11 | // Display Routines // 12 | // Display routines for 20x4 LCD // 13 | // // 14 | ////////////////////////////////////////////////////////////////////// 15 | 16 | ////////////////////////////////////////////////////////////////////// 17 | // Display Layout 20x4 // 18 | // 1111111111 // 19 | // 01234567890123456789 // 20 | // 0: A>xX.XXX.XXX xSB // 21 | // 1: B>xX.XXX.XXX SPLIT // 22 | // 2: RX S:123456789+++ // 23 | // 3: CALL xxxx Hz vX.X // 24 | ////////////////////////////////////////////////////////////////////// 25 | 26 | 27 | 28 | //************************displayBanner***************************** 29 | // Sets up display line 3 - call sign, increment display and version 30 | // 31 | // Note - Does not use the input String (included for code compatibility 32 | // with other display modules) 33 | // 34 | void displayBanner(String s) { 35 | 36 | // Setup line 3 call and tuning increment 37 | lcd.setCursor(0, 3); 38 | lcd.print(CALLSIGN); 39 | 40 | lcd.setCursor(12, 3); 41 | lcd.print("Hz"); 42 | 43 | lcd.setCursor(16, 3); 44 | lcd.print(F("v")); 45 | lcd.print(VERSION); 46 | } 47 | 48 | 49 | //************************displaySmeter**************************** 50 | void displaySMeter(byte level) { 51 | int i; 52 | lcd.setCursor(6, 2); 53 | lcd.print("S:"); 54 | for (i = 0; i < MAXSLEVELS; i++) { 55 | if (level >= i) { 56 | if (i < 9) { 57 | lcd.print(i + 1); 58 | } else { 59 | lcd.print("+"); 60 | } 61 | } else { 62 | lcd.print(" "); 63 | } 64 | } 65 | } 66 | 67 | 68 | //************************displayVFOAB******************************* 69 | // displayVFOAB(int VFO) 70 | // Updates the display with flag for the currently active VFO - A or B 71 | // 72 | void displayVFOAB(int vfo) { 73 | // Reset the active VFO flag 74 | 75 | // Clear previous flag 76 | lcd.setCursor(1,0); 77 | lcd.print(F(" ")); 78 | lcd.setCursor(1,1); 79 | lcd.print(F(" ")); 80 | 81 | // Set flag by currently active VFO 82 | if (vfo == VFOA) { 83 | lcd.setCursor(1, 0); 84 | } else { 85 | lcd.setCursor(1, 1); 86 | } 87 | lcd.print(F(">")); 88 | } 89 | 90 | 91 | //************************displayVFO******************************* 92 | 93 | // displayVFO(vfo,freq) 94 | // Updates the dispaly for the input vfo 95 | // Displays the frequency subracting the bfo - sideband inversion 96 | // 97 | void displayVFO(int vfo, long freq) { 98 | int row = 0; 99 | 100 | if (vfo == VFOA) { 101 | row = 0; 102 | } else if (vfo == VFOB) { 103 | row = 1; 104 | } 105 | 106 | // Update the frequency 107 | lcd.setCursor(2, row); 108 | if (freq < 10000000) { 109 | lcd.print(" "); 110 | } 111 | lcd.print(int((freq / 1000000))); //millions 112 | lcd.print("."); 113 | lcd.print(((freq / 100000) % 10)); //hundredthousands 114 | lcd.print(((freq / 10000) % 10)); //tenthousands 115 | lcd.print(((freq / 1000) % 10)); //thousands 116 | lcd.print("."); 117 | lcd.print(((freq / 100) % 10)); //hundreds 118 | lcd.print(((freq / 10) % 10)); //tens 119 | lcd.print(((freq / 1) % 10)); //ones 120 | 121 | } 122 | 123 | //************************displayActVFO******************************* 124 | // Updates the frequencey display for the current active VFO A or B 125 | void displayActVFO(uint32_t freq) { 126 | displayVFO(active_vfo, freq); 127 | } 128 | 129 | //************************displayActVFO******************************* 130 | // Updates the frequencey display for the current alternate VFO A or B 131 | extern void displayAltVFO(uint32_t freq) { 132 | if (active_vfo == VFOA) { 133 | displayVFO(VFOB, freq); 134 | } else{ 135 | displayVFO(VFOA, freq); 136 | } 137 | } 138 | 139 | 140 | //************************displayMode******************************* 141 | // 142 | // displaySideband(mode) 143 | // Updates the current mode (sideband) indicator U or L 144 | // 145 | void displayMode(int mode) { 146 | char sb = ' '; 147 | 148 | if (mode == USB) { 149 | sb = 'U'; 150 | } else if (mode == LSB) { 151 | sb = 'L'; 152 | } 153 | lcd.setCursor(13, 0); 154 | lcd.print(sb); 155 | } 156 | 157 | void displayTxRx(int tx_rx) { 158 | lcd.setCursor(0, 2); 159 | if (tx_rx == RX) { 160 | lcd.print(F("RX")); 161 | } else { 162 | lcd.print(F("TX")); 163 | } 164 | } 165 | 166 | //************************displayIncr******************************* 167 | // 168 | // displayIncr(increment) 169 | // Display the tuning increment 170 | // 171 | void displayIncr(unsigned long increment) { 172 | 173 | String hertz = " "; // tune step display 174 | 175 | #ifdef DEBUG 176 | sprintf(debugmsg, "Increment: %ld", increment); 177 | Serial.println(debugmsg); 178 | #endif 179 | 180 | if (increment == 10) { 181 | hertz = F(" 10"); 182 | } else if (increment == 100) { 183 | hertz = F(" 100"); 184 | } else if (increment == 1000) { 185 | hertz = F(" 1K"); 186 | } else if (increment == 10000) { 187 | hertz = F(" 10K"); 188 | } else if (increment == 100000) { 189 | hertz = F("100K"); 190 | } else if (increment == 1000000) { 191 | hertz = F(" 1M"); 192 | } 193 | 194 | lcd.setCursor(7, 3); 195 | lcd.print(hertz); 196 | } 197 | 198 | //************************displayTune******************************* 199 | // 200 | // displayTune(On) 201 | // If On is TRUE displays TUNE message else clears it 202 | // 203 | void displayTune(bool On) { 204 | lcd.setCursor(7, 2); 205 | if (On == true) { 206 | lcd.print(F("TUNE")); 207 | } else { 208 | lcd.print(F(" ")); 209 | } 210 | } 211 | 212 | void displayDebug(String msg) { 213 | lcd.setCursor(7, 2); 214 | lcd.print(msg); 215 | } 216 | 217 | //************************displaySplit******************************* 218 | // 219 | // displayTune(split) 220 | // If split is TRUE displays SPLIT message else clears it 221 | // 222 | void displaySplit(boolean splt) { 223 | lcd.setCursor(14, 1); 224 | if (split == true) { 225 | lcd.print(F("SPLIT")); 226 | } else { 227 | lcd.print(F(" ")); 228 | } 229 | } 230 | 231 | 232 | 233 | /////////////////////////////////////////////////////////////////////////// 234 | // displaySetup() 235 | // Initialze and populate the display 236 | /////////////////////////////////////////////////////////////////////////// 237 | void displaySetup(String banner, 238 | uint32_t vfoActfreq, uint32_t vfoAltfreq, 239 | uint32_t activeVFO, 240 | int tx_rx, 241 | int sideband, 242 | boolean split, 243 | uint32_t increment, 244 | byte s_meter) { 245 | lcd.init(); 246 | lcd.clear(); 247 | delay(100); 248 | lcd.backlight(); 249 | 250 | // Setup fixed screeen elements 251 | 252 | // Line 0 - VFO A 253 | lcd.setCursor(0,0); 254 | lcd.print(F("A")); 255 | 256 | // Line 0 - sideband indicator 257 | lcd.setCursor(14,0); 258 | lcd.print(F("SB")); 259 | 260 | // Line 1 VFO B 261 | lcd.setCursor(0,1); 262 | lcd.print(F("B")); 263 | 264 | // 265 | // Display the intitial values 266 | // 267 | displayBanner(banner); 268 | displayActVFO(vfoActfreq); 269 | displayAltVFO(vfoAltfreq); 270 | displayVFOAB(activeVFO); 271 | displayTxRx(tx_rx); 272 | displayMode(sideband); 273 | displaySplit(split); 274 | displayIncr(increment); 275 | #ifdef SMETER 276 | displaySMeter(s_meter); 277 | #endif 278 | } 279 | 280 | #endif 281 | -------------------------------------------------------------------------------- /SSB_Nextion_Display.cpp: -------------------------------------------------------------------------------- 1 | #include "RadioControl.h" 2 | #ifdef DISPLAY_NEXTION 3 | 4 | //#define NEX_DEBUG // sends debug messages to the banner (bottom of screen) 5 | //#define NEX_DEBUG1 6 | 7 | // Dean Souleles, KK4DAS 8 | // 7/25/2020 9 | // 10 | // Nextion Display Module for SSB_Radio_Contrl 11 | // Nextion 2.8 Inch display 12 | // Tested on Arduino Nano 13 | // 14 | // Arduino Nano Every 15 | // PINS / WIRING 16 | // ---------------------- 17 | // Arduino | Nextion 18 | // --------|------------- 19 | // 1 (RX) | TX (Blu) -- pins 0 and 1 are for Serial1 20 | // 0 (TX) | RX (Yel) 21 | // +5 | +5 (Red) 22 | // Gnd | Gnd (Blk) 23 | //------------------------ 24 | // 25 | // Arduino Nano // deprecated - runs out of memory / stack crash 26 | // PINS / WIRING 27 | // ---------------------- 28 | // Arduino | Nextion 29 | // --------|------------- 30 | // 8 (RX) | TX (Blu) -- pins 8 and 9 are requuired by AltSoftSerial 31 | // 9 (TX) | RX (Yel) -- to use other pins you can use SofwareSerial 32 | // +5 | +5 (Red) -- but that will conflict with the interrupt 33 | // Gnd | Gnd (Blk) -- used by the Rotary Encoder 34 | //------------------------ 35 | 36 | 37 | //////////////////////////////////////////////////////////////////////////////////////////// 38 | // 39 | // We use the AltSoftSerial library to pins (8 and 9) rather than default 0,1 40 | // AltSoftSerial is used rather than SoftwareSerial to free up the interrupt vector 41 | // required for the digital encoder 42 | // NOTE: 43 | // AltSoftSerial uses only fixed pins 8,9 on an Uno or Nano 44 | // Pin 10 cannot be used for PWM 45 | // For other boards see: https://www.pjrc.com/teensy/td_libs_AltSoftSerial.html 46 | // 47 | // First, apply the serial fixes from Ray Livingston to the Nextion Libarary: 48 | // https://forum.arduino.cc/index.php?topic=620821.0 49 | // Replace files in the ../libraries/ITEADLIB_Arduino_Nextion folder: 50 | // NexConfig.h 51 | // NexHardware.h 52 | // NexHardware.cpp 53 | // Plus one more patch to fix a bug that was including SoftwareSerial when it should not have 54 | // NexUpload.cpp (change by me, Dean Souleles) 55 | // 56 | // Next we edit NexConfig.h in the ../libraries/ITEADLIB_Arduino_Nextion folder as follows: 57 | // Comment out this line: 58 | // #define nexSerial Serial 59 | // 60 | // Add these three lines: 61 | // #include 62 | // extern AltSoftSerial HMISerial; 63 | // #define nexSerial HMISerial 64 | // 65 | // Add the following to your sketch 66 | // #include 67 | // AltSoftSerial HMISerial; //RX TX - connect to Nextion TX RX - Must use Pins 9,10 on Uno/Nano 68 | // 69 | //////////////////////////////////////////////////////////////////////////////////////////////// 70 | 71 | #include // https://github.com/itead/ITEADLIB_Arduino_Nextion 72 | 73 | //#include 74 | //SoftwareSerial HMISerial(8,9); //RX TX - connect to Nextion TX RX - Pins 8,9 on Uno/Nano 75 | 76 | #if _BOARDTYPE == Nano 77 | #include 78 | AltSoftSerial HMISerial; //RX TX - connect to Nextion TX RX - Must use Pins 8,9 on Uno/Nano 79 | #endif 80 | 81 | // 82 | // Declare Nextion objects 83 | // Use the page ID, component id and component name from the Nextion IDE 84 | // 85 | 86 | // Nextion PAGE 87 | // Current design only uses one page of the display 88 | #define PAGE 0 89 | 90 | 91 | 92 | // Nextion Component IDs for buttons and text displays 93 | // These ID's must match the ID's Nextion IDE 94 | #define VFO_ID 2 95 | #define SIDEBAND_ID 5 96 | #define TUNE_ID 7 97 | #define INCREMENT_ID 6 98 | #define ACT_VFO_ID 3 99 | #define ALT_VFO_ID 4 100 | #define TX_RX_ID 8 101 | #define SMETER_ID 10 102 | #define BANNER_ID 9 103 | #define TUNE_PLUS_ID 11 104 | #define TUNE_MINUS_ID 12 105 | #define TUNE_STATE_ID 15 106 | #define ATOB_ID 18 // VFO A copy to B button 107 | #define SPLIT_ID 19 108 | #define VAB_ID 20 // VFO Inidicator 109 | 110 | // Nextion Component names 111 | // These names must match the names in the Nextion IDE 112 | #define VFO_NAME "bVFO" 113 | #define SIDEBAND_NAME "bSideband" 114 | #define TUNE_NAME "bTune" 115 | #define INCREMENT_NAME "bIncr" 116 | #define ACT_VFO_NAME "tActVFO" 117 | #define ALT_VFO_NAME "tAltVFO" 118 | #define BANNER_NAME "tBanner" 119 | #define TX_RX_NAME "tTxRx" 120 | #define SMETER_NAME "pSmeter" 121 | #define TUNE_PLUS_NAME "bPlus" 122 | #define TUNE_MINUS_NAME "bMinus" 123 | #define TUNE_STATE_NAME "tTuneState" 124 | #define ATOB_NAME "bAtoB" 125 | #define SPLIT_NAME "btSplit" 126 | #define VAB_NAME "tVAB" 127 | 128 | // Nextion Buttons 129 | // Each user interface object that the user touches needs to be defined here 130 | // 131 | NexButton bVFO = NexButton(PAGE, VFO_ID, VFO_NAME); 132 | NexButton bSideband = NexButton(PAGE, SIDEBAND_ID, SIDEBAND_NAME); 133 | NexButton bTune = NexButton(PAGE, TUNE_ID, TUNE_NAME); 134 | NexButton bIncrement = NexButton(PAGE, INCREMENT_ID, INCREMENT_NAME); 135 | NexButton bTunePlus = NexButton(PAGE, TUNE_PLUS_ID, TUNE_PLUS_NAME); 136 | NexButton bTuneMinus = NexButton(PAGE, TUNE_MINUS_ID, TUNE_MINUS_NAME); 137 | NexButton bAtoB = NexButton(PAGE, ATOB_ID, ATOB_NAME); 138 | NexDSButton btSplit = NexDSButton(PAGE, SPLIT_ID, SPLIT_NAME); 139 | 140 | 141 | // 142 | // Setup a list of objects to respond to a touch event 143 | // 144 | NexTouch *nex_listen_list[] = { 145 | &bVFO, 146 | &bSideband, 147 | &bTune, 148 | &bIncrement, 149 | &bTunePlus, 150 | &bTuneMinus, 151 | &bAtoB, 152 | &btSplit, 153 | NULL 154 | }; 155 | 156 | /////////////////////////////////////////////////////////////////////////// 157 | // HMI_send_command(cmd) 158 | // Sends one command to the Nextion Display 159 | // 160 | // Example usage: 161 | // String cmd; 162 | // cmd = F("vis "); 163 | // cmd = cmd + F(TUNE_STATE_NAME); 164 | // cmd = cmd + F(","); 165 | // if (on_off) { 166 | // cmd = cmd + F("1"); 167 | // } else { 168 | // cmd = cmd + F("0"); 169 | // } 170 | // HMI_send_command(cmd.c_str()); 171 | // 172 | /////////////////////////////////////////////////////////////////////////// 173 | void HMI_send_command(char* cmd) { 174 | 175 | /* 176 | // Send the command 177 | HMISerial.print(cmd); 178 | 179 | // Send end-of-message per Nextion protocol 180 | HMISerial.write(0xff); 181 | HMISerial.write(0xff); 182 | HMISerial.write(0xff); 183 | 184 | */ 185 | // Send the command to the Nextion 186 | nexSerial.print(cmd); 187 | 188 | // Send end-of-message per Nextion protocol 189 | nexSerial.write(0xff); 190 | nexSerial.write(0xff); 191 | nexSerial.write(0xff); 192 | 193 | } 194 | 195 | /////////////////////////////////////////////////////////////////////////// 196 | // displayActVFO(uint32_t freq) 197 | // Formats and displays Active and Alternat VFO frequencies 198 | // Legal values: Frequency in Hz 199 | // 200 | // To do - comibine into one function 201 | /////////////////////////////////////////////////////////////////////////// 202 | 203 | void displayActVFO(uint32_t freq) { 204 | String cmd; 205 | String fmt; 206 | char f[11]; 207 | uint32_t mil, hund_thou, ten_thou, thou, hund, tens, ones; 208 | 209 | // Format number as nn.nnn.nnn 210 | mil = freq / 1000000; 211 | hund_thou = (freq/100000)%10; 212 | ten_thou = (freq/10000)%10; 213 | thou = (freq/1000)%10; 214 | hund = (freq/100)%10; 215 | tens = (freq/10)%10; 216 | ones = freq%10; 217 | fmt=F("%2ld.%ld%ld%ld.%ld%ld%ld"); 218 | snprintf(f, sizeof(f),fmt.c_str(),mil, hund_thou, ten_thou,thou, hund, tens, ones); 219 | 220 | cmd = F(ACT_VFO_NAME); 221 | cmd += F(".txt=\""); 222 | cmd += f; 223 | cmd += F("\""); 224 | HMI_send_command(cmd.c_str()); 225 | 226 | } 227 | 228 | void displayAltVFO(uint32_t freq) { 229 | String cmd; 230 | String fmt; 231 | char f[11]; 232 | uint32_t mil, hund_thou, ten_thou, thou, hund, tens, ones; 233 | 234 | // Format number as nn.nnn.nnn 235 | mil = freq / 1000000; 236 | hund_thou = (freq/100000)%10; 237 | ten_thou = (freq/10000)%10; 238 | thou = (freq/1000)%10; 239 | hund = (freq/100)%10; 240 | tens = (freq/10)%10; 241 | ones = freq%10; 242 | fmt=F("%2ld.%ld%ld%ld.%ld%ld%ld"); 243 | snprintf(f, sizeof(f),fmt.c_str(),mil, hund_thou, ten_thou,thou, hund, tens, ones); 244 | 245 | cmd = F(ALT_VFO_NAME); 246 | cmd += F(".txt=\""); 247 | cmd += f; 248 | cmd += F("\""); 249 | HMI_send_command(cmd.c_str()); 250 | 251 | } 252 | 253 | void displaySMeter(byte level) { 254 | // Nextion dipslay bar graph is set by integer percent 0-100 255 | // Convert the S level into a % and send to display 256 | 257 | String cmd; 258 | float pct; 259 | int scaled_level; 260 | pct = (float(level)/13.0)*100.0; 261 | scaled_level=pct; 262 | 263 | cmd = F(SMETER_NAME); 264 | cmd += F(".val="); 265 | cmd += scaled_level; 266 | 267 | HMI_send_command(cmd.c_str()); 268 | 269 | #ifdef NEX_DEBUG1 270 | String msg = F("displaySMeter: "); 271 | msg = msg + level; 272 | msg = msg + F(" "); 273 | msg = msg + scaled_level; 274 | displayBanner(msg); 275 | #endif 276 | 277 | } 278 | 279 | void displayBanner(String s) { 280 | String cmd; 281 | cmd = F(BANNER_NAME); 282 | cmd += F(".txt=\""); 283 | cmd += s; 284 | cmd += F("\""); 285 | HMI_send_command(cmd.c_str()); 286 | } 287 | 288 | void displayVFOAB(int vfo) { 289 | String cmd; 290 | cmd = F(VAB_NAME); 291 | cmd += F(".txt=\""); 292 | if (vfo == VFOA) { 293 | cmd += F("A"); 294 | } else { 295 | cmd += F("B"); 296 | } 297 | cmd += F("\""); 298 | HMI_send_command(cmd.c_str()); 299 | 300 | #ifdef NEX_DEBUG 301 | String msg = F("displayVFOAB: "); 302 | msg = msg + vfo; 303 | displayBanner(msg); 304 | #endif 305 | 306 | } 307 | 308 | void displayTxRx(int tx_rx) { 309 | String cmd; 310 | cmd = F(TX_RX_NAME); 311 | cmd += F(".txt=\""); 312 | if (tx_rx == TX) { 313 | cmd += F("TX"); 314 | } else { 315 | cmd += F("RX"); 316 | } 317 | cmd += F("\""); 318 | HMI_send_command(cmd.c_str()); 319 | 320 | #ifdef NEX_DEBUG 321 | String msg = F("displayTxRx: "); 322 | msg = msg + tx_rx; 323 | displayBanner(msg); 324 | #endif 325 | 326 | } 327 | 328 | void displayMode(int mode) { 329 | String modeString; 330 | if (mode == USB) { 331 | modeString = F("USB"); 332 | } else { 333 | modeString = F("LSB"); 334 | } 335 | bSideband.setText(modeString.c_str()); 336 | 337 | #ifdef NEX_DEBUG 338 | String msg = F("displayMode: "); 339 | msg = msg + modeString; 340 | displayBanner(msg); 341 | #endif 342 | 343 | } 344 | 345 | void displayIncr(uint32_t increment) { 346 | String hertz; 347 | switch (increment) { 348 | case 1: hertz = F(" 1"); break; 349 | case 10: hertz = F(" 10"); break; 350 | case 100: hertz = F(" 100"); break; 351 | case 1000: hertz = F(" 1K"); break; 352 | case 10000: hertz = F(" 10K"); break; 353 | case 100000: hertz= F("100K"); break; 354 | case 1000000:hertz = F(" 1M"); 355 | } 356 | bIncrement.setText(hertz.c_str()); 357 | 358 | #ifdef NEX_DEBUG 359 | String msg = F("displayIncr: "); 360 | msg = msg + hertz; 361 | displayBanner(msg); 362 | #endif 363 | 364 | } 365 | 366 | void displayTune(boolean on_off) { 367 | // Send visibility command to Nextion 368 | // Format: vis 369 | String cmd; 370 | cmd = F("vis "); 371 | cmd += F(TUNE_STATE_NAME); 372 | cmd += F(","); 373 | cmd += on_off; 374 | HMI_send_command(cmd.c_str()); 375 | 376 | #ifdef NEX_DEBUG 377 | displayBanner(cmd); 378 | #endif 379 | } 380 | 381 | 382 | void displaySplit(boolean split) { 383 | // The split button is a dual state button 384 | // State 0 = blue = ff 385 | // State 1 = green = on 386 | // To change the state and color set the val property to 0/1 387 | // This mimics a button press on the display 388 | 389 | String cmd =F(SPLIT_NAME); 390 | cmd += F(".val="); 391 | if (split) { 392 | cmd += 1; 393 | } else { 394 | cmd += 0; 395 | } 396 | HMI_send_command(cmd.c_str()); 397 | 398 | #ifdef NEX_DEBUG 399 | String msg = F("displaySplit: "); 400 | msg = msg + split; 401 | displayBanner(msg); 402 | #endif 403 | } 404 | 405 | // 406 | // Button Callback Functions 407 | // Called whenever a button is pressed and released 408 | // 409 | 410 | // VFO A/B Button 411 | void bVFOPopCallback(void *ptr) { 412 | SwapVFO(); // call the VFO switch button handler 413 | } 414 | 415 | // LSB/USB Button 416 | void bSidebandPopCallback(void *ptr) { 417 | SwapSB(); 418 | } 419 | 420 | // Tune Plus (increase VFO) 421 | void bTunePlusPushCallback(void *ptr) { 422 | AdjustVFO(increment); 423 | } 424 | 425 | // Tune Minus (decrease VFO) 426 | void bTuneMinusPushCallback(void *ptr) { 427 | AdjustVFO(-1 * increment); 428 | } 429 | 430 | // Tune Tone 431 | void bTunePopCallback(void *ptr) { 432 | DoTune(); 433 | } 434 | 435 | // Tuning Increment Change 436 | void bIncrementPopCallback(void *ptr) { 437 | AdvanceIncrement(); 438 | } 439 | 440 | // Split on/off 441 | void btSplitPopCallback(void *ptr) { 442 | uint32_t split_val; 443 | 444 | btSplit.getValue(&split_val); 445 | if (split_val==1) { 446 | startSplit(); 447 | } else { 448 | stopSplit(); 449 | } 450 | 451 | #ifdef NEX_DEBUG 452 | String msg = F("btSplitPopCallback: "); 453 | msg = msg + split_val; 454 | displayBanner(msg); 455 | #endif 456 | } 457 | 458 | // Make Act and Alt VFO the same 459 | void bAtoBPopCallback(void *ptr) { 460 | 461 | if (active_vfo == VFOA) { 462 | vfoBfreq = vfoAfreq; 463 | } else { 464 | vfoAfreq = vfoBfreq; 465 | } 466 | displayAltVFO(vfoAfreq); // update the Alt VFO display 467 | 468 | } 469 | 470 | // 471 | // Setup 472 | // Called once at startup 473 | // 474 | void displaySetup(String banner, 475 | uint32_t vfoActfreq, uint32_t vfoAltfreq, 476 | uint32_t activeVFO, 477 | int tx_rx, 478 | int sideband, 479 | boolean split, 480 | uint32_t increment, 481 | byte s_meter) { 482 | 483 | nexInit(9600); // Initialize the Nextion library 484 | 485 | // 486 | // Attach callback routines for each button 487 | // attachPop will set the library to invoke the specified funtion each time a button is released 488 | // 489 | bVFO.attachPop(bVFOPopCallback, &bVFO); // VFO A/B button 490 | bSideband.attachPop(bSidebandPopCallback, &bSideband); // LSB/USB button 491 | bTune.attachPop(bTunePopCallback, &bTune); // Tune 492 | bIncrement.attachPop(bIncrementPopCallback, &bIncrement); // Change Incrmement 493 | btSplit.attachPop(btSplitPopCallback, &btSplit); // Split On/Off 494 | bAtoB.attachPop(bAtoBPopCallback, &bAtoB); // Make Alt VFO = Act VFO 495 | 496 | bTunePlus.attachPush(bTunePlusPushCallback, &bTunePlus); // Tune up 497 | bTuneMinus.attachPush(bTuneMinusPushCallback, &bTuneMinus); // Tune down 498 | 499 | // 500 | // Display the intitial values 501 | // 502 | displayBanner(banner); 503 | displayActVFO(vfoActfreq); 504 | displayAltVFO(vfoAltfreq); 505 | displayVFOAB(activeVFO); 506 | displayTxRx(tx_rx); 507 | displayMode(sideband); 508 | displaySplit(split); 509 | displayIncr(increment); 510 | displaySMeter(s_meter); 511 | } 512 | 513 | 514 | 515 | void CheckTouch() { 516 | // Call the Nextion check function to look for activites on your listen list 517 | nexLoop(nex_listen_list); 518 | } 519 | 520 | 521 | #endif 522 | -------------------------------------------------------------------------------- /SSB_Radio_Control.HMI: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kk4das/SSB_Radio_Control/3e7015b9badcc35f2606552c5b9f81a51970f983/SSB_Radio_Control.HMI -------------------------------------------------------------------------------- /SSB_Radio_Control.ino: -------------------------------------------------------------------------------- 1 | /* 2 | SSB_Radio_Control 3 | Dean Souleles, KK4DAS, contact kk4das@gmail.com 4 | 5 | This sketch implement s basic SSB radio control panel with the following features: 6 | Dual VFO A/B 7 | Rotary encoder for tuning, push button to change tuning increment 8 | SI5351 Clock generator for the LO and BFO 9 | CAT Control (emulates an ICOM IC-746) 10 | Split mode support (Split from CAT, manual split requres Nextion touch screen) 11 | Settings memory (last frequency and mode are saved) 12 | Optional S-Meter 13 | 14 | Controls 15 | * Rotary encoder to change frequence 16 | * Rotary encode button changes tuning increment 17 | * VFO A/B Select toggle 18 | * Mode select USB/LSB toggle 19 | * Tune button (emits 10 second pulsed tone at 800Hz for tuning) 20 | * MOX toggle - puts rig in to Tx/Rx 21 | * Optional dual-band support 20/40 22 | 23 | Modules for different display types 24 | * 20x4 LCD 25 | * 320x240 TFT color display 26 | * 2.8" Nextion Touch Screen 27 | 28 | Display features 29 | * Dual VFOS 30 | * Mode indicator SSB/LSB 31 | * Tx/Rx ndicator 32 | * TuningStep Inidicator 33 | * Optional S Meter 34 | * Banner including callsign 35 | 36 | Additional controls with the Nextion display 37 | * Continuous scanning 38 | * Split mode 39 | 40 | Version 1.4 41 | March 9 2021 42 | Rstored LCD Display Option 43 | Compile time selection of S-Meter and Dual Band mods 44 | Refactoring of encoder handling 45 | 46 | Version 1.3 47 | Jan 25, 2021 48 | Changed CAT module to IC-746 49 | 50 | Version 1.2 51 | Dec 14, 2020 52 | Dual Band 20/40 Support 53 | S-Meter 54 | 55 | Version 1.1 56 | Dec 13. 2020 57 | Ported to Nano Every for more sweet SRAM 58 | * * Updated interrupt handling in Encoder.cpp to work with Nano Every (as well as UNO/Nano) 59 | * * Replaced SoftwareSerial connections to Nextion with Hardware Serial - Serial1 60 | 61 | Version 1.0 62 | Aug 13, 2020 63 | Adapted from SimpleSSB Sketch by N6QW, Pete Juliano and others 64 | 65 | 66 | NOTE TO BUILDERS 67 | This is a reference implementation of an SSB radio control program for a homebrew SSB transceiver. 68 | It is a fully functioning SSB radio control program. While it has been built for my particular hardware 69 | configuration every attempt has been made to make it modular in design so that the builder can swap out 70 | modules at will. It should be fairly straight forward to adapt to the builder's hardware selection 71 | of switches, buttons and knobs or even alternate displays. 72 | 73 | */ 74 | 75 | #include "RadioControl.h" 76 | 77 | #ifdef DEBUG 78 | char debugmsg[25]; 79 | #endif 80 | 81 | //=============== Globals ============================================ 82 | 83 | ////////////////////////////////////////////////////////////////////// 84 | // // 85 | // si5351 Clock Module // 86 | // // 87 | ////////////////////////////////////////////////////////////////////// 88 | Si5351 si5351; 89 | 90 | // Calibration offest - adjust for variability in the SI5351 module 91 | // crystal - must be set for the particular SI5351 installed 92 | //#define CALIBRATION_OFFSET 1190 // Calibration for the SI-5351 93 | #define CALIBRATION_OFFSET 880 // Calibration for the SI-5351 94 | 95 | ////////////////////////////////////////////////////////////////////// 96 | // // 97 | // BFO and VFO Constants and Variables // 98 | // // 99 | ////////////////////////////////////////////////////////////////////// 100 | 101 | #ifdef BFO12MHZ 102 | const uint32_t USB_BFO = 12001600L; 103 | const uint32_t LSB_BFO = 11998600L; 104 | #endif 105 | 106 | #ifdef BFO9MHZ 107 | const uint32_t USB_BFO = 9001500L; 108 | const uint32_t LSB_BFO = 8998500L; 109 | #endif 110 | 111 | uint32_t bfo = LSB_BFO; // Startup BFO frequency 112 | const uint32_t BFO_DELTA = USB_BFO - LSB_BFO; // Difference between USB and LSB for BFO change 113 | 114 | // 115 | // Startup VFO A/B frequencies 116 | // 117 | uint32_t vfoAfreq = 7200000L; // 7.200.000 118 | uint32_t vfoBfreq = 7074000L; // FT-8 7.074.000 119 | byte vfoASideband = LSB; 120 | byte vfoBSideband = USB; 121 | 122 | uint32_t increment = 1000; // startup VFO tuning increment in HZ. 123 | 124 | 125 | ////////////////////////////////////////////////////////////////////// 126 | // // 127 | // Active VFO // 128 | // // 129 | ////////////////////////////////////////////////////////////////////// 130 | byte active_vfo = VFOA; // startup on VFOA 131 | 132 | 133 | ////////////////////////////////////////////////////////////////////// 134 | // // 135 | // Sideband Selection // 136 | // // 137 | ////////////////////////////////////////////////////////////////////// 138 | byte sideband = LSB; // startup in LSB 139 | 140 | ////////////////////////////////////////////////////////////////////// 141 | // // 142 | // Sideband Selection // 143 | // // 144 | ///////////////////////////////////////////////////////////////////// 145 | uint32_t band20Freq = 14200000L; // 14.200.000 146 | uint32_t band40Freq = 7200000L; // 7.200.000 147 | byte band20Sideband = USB; 148 | byte band40Sideband = LSB; 149 | byte band = BAND40; 150 | 151 | ////////////////////////////////////////////////////////////////////// 152 | // // 153 | // Transmit / Receive Indicators // 154 | // // 155 | ////////////////////////////////////////////////////////////////////// 156 | byte TxRxState = RX; // startup in RX 157 | byte lastTxRxState = RX; // previous TxRxState 158 | byte txSource = PTT_MIC; // transmit source - Mic, Tune, CAT 159 | 160 | ////////////////////////////////////////////////////////////////////// 161 | // // 162 | // S Meter 0-9 +10, +20 +30 // 163 | // // 164 | ////////////////////////////////////////////////////////////////////// 165 | byte smeter = 0; // startup s_meter reading (requires SMETER) 166 | 167 | 168 | ////////////////////////////////////////////////////////////////////// 169 | // // 170 | // Split Mode On/Off // 171 | // // 172 | ////////////////////////////////////////////////////////////////////// 173 | bool split = false; 174 | 175 | 176 | 177 | 178 | /////////////////////////////////////////////////////////// 179 | // setBandFilters(band) // 180 | // For 20 meters turn relay ON (NO) // 181 | // For 40 meters turn relay OFF (NC) // 182 | /////////////////////////////////////////////////////////// 183 | void setBandFilters(int band) { 184 | #ifdef DUAL_BAND 185 | switch (band) { 186 | case BAND20: 187 | digitalWrite(BAND_PIN, HIGH); 188 | break; 189 | case BAND40: 190 | digitalWrite(BAND_PIN, LOW); 191 | break; 192 | } 193 | #endif 194 | } 195 | 196 | #ifdef CW 197 | // 198 | // setCW() 199 | // 200 | // Experimental code to generate CW tone on key down at 700Hz above the dial frequency 201 | // Needs a bunch of scaffolding to implement CW mode 202 | // 203 | // Turns off the BFO, sets the LO to the VFO frequency + 700 204 | // 205 | // After testing - 206 | // tone produced OK but needs an amplifier to get significant power out 207 | // 208 | void setCW() { 209 | si5351.set_freq(0, 0, SI5351_CLK2); // turn off BFO 210 | si5351.set_freq(vfoAfreq+700L , SI5351_PLL_FIXED, SI5351_CLK0); // set LO to operating freq 211 | } 212 | #endif 213 | 214 | //********************setVFO****************************************** 215 | void setVFO(uint32_t freq) { 216 | // 217 | // Set CLK0 to the to input frequency adjusted for the current BFO frequency 218 | 219 | #ifdef DUAL_BAND 220 | // 221 | // Set filters for the band based on frequency 222 | // Save frequency and sideband for band switching 223 | // 224 | if (freq >= BAND20_EDGE) { 225 | setBandFilters(BAND20); 226 | band20Freq = freq; 227 | band20Sideband = sideband; 228 | band = BAND20; 229 | } else if (freq >= BAND40_EDGE) { 230 | setBandFilters(BAND40); 231 | band40Freq = freq; 232 | band40Sideband = sideband; 233 | band=BAND40; 234 | } 235 | #endif 236 | 237 | si5351.set_freq(freq + bfo, SI5351_PLL_FIXED, SI5351_CLK0); 238 | startSettingsTimer(); // start timer to save current settings 239 | } 240 | 241 | void setBFO(uint32_t freq) { 242 | // 243 | // Set CLK2 to the to input frequency 244 | // 245 | 246 | si5351.set_freq(freq, 0, SI5351_CLK2); 247 | } 248 | 249 | 250 | //*********************Setup Arduino Pins***************************** 251 | 252 | void setupPins() { 253 | // 254 | // Set the control buttons to INPUT_PULLUP 255 | // Button state will be HIGH when open and LOW when pressed 256 | // 257 | pinMode(TUNE_BTN, INPUT_PULLUP); // Tune - momentary button 258 | pinMode(VFO_BTN, INPUT_PULLUP); // VFO A/B Select - momentary button 259 | pinMode(SIDEBAND_BTN, INPUT_PULLUP); // Upper/lower SB Select - momentary button 260 | pinMode(BAND_BTN, INPUT_PULLUP); // Band Switch 20/40 - momentary button 261 | pinMode(PTT_SENSE, INPUT_PULLUP); // Mic PTT swtich 262 | pinMode(PTT, OUTPUT); digitalWrite(PTT, LOW); // HIGH to enable TX 263 | pinMode(BAND_PIN, OUTPUT); digitalWrite(BAND_PIN, LOW); // Band Switch Relay (LOW = NC = 40m, HIGH = NO = 20m) 264 | 265 | pinMode(LED_BUILTIN, OUTPUT); 266 | 267 | } 268 | 269 | 270 | //**********************Initialize SI5351****************************** 271 | 272 | void setupSI5351() { 273 | si5351.init(SI5351_CRYSTAL_LOAD_8PF); 274 | si5351.set_correction(CALIBRATION_OFFSET); // calibration offset 275 | si5351.set_pll(SI5351_PLL_FIXED, SI5351_PLLA); 276 | si5351.drive_strength(SI5351_CLK0, SI5351_DRIVE_8MA); // Higher Drive since it is a ADE-1 DBM 277 | si5351.drive_strength(SI5351_CLK2, SI5351_DRIVE_8MA); 278 | si5351.set_freq(bfo, 0, SI5351_CLK2); // Initialize the bfo 279 | 280 | // 281 | // Initialize the VFO 282 | // 283 | switch (active_vfo) { 284 | case VFOA: 285 | setVFO(vfoAfreq); 286 | break; 287 | case VFOB: 288 | setVFO(vfoBfreq); 289 | break; 290 | } 291 | } 292 | 293 | 294 | ////////////////////////////////////////////////////////////////////// 295 | // Setup // 296 | // // 297 | // Called once by the Arduino operating system at startup // 298 | // All initialization code goes here // 299 | // // 300 | ////////////////////////////////////////////////////////////////////// 301 | void setup() { 302 | 303 | uint32_t vfoActfreq; 304 | uint32_t vfoAltfreq; 305 | 306 | #ifdef DEBUG 307 | Serial.begin(57600); 308 | #endif 309 | 310 | setupPins(); // Initialize arduino pins 311 | setupEncoder(); // Initialize interrupt service for rotary encoder 312 | setupSettings(); // Retrive settings from EEPROM 313 | setupSI5351(); 314 | 315 | 316 | if (active_vfo == VFOA) { 317 | vfoActfreq = vfoAfreq; 318 | vfoAltfreq = vfoBfreq; 319 | } else { 320 | vfoActfreq = vfoBfreq; 321 | vfoAltfreq = vfoAfreq; 322 | } 323 | 324 | 325 | // Initialize the display with startup values 326 | Delay(500); // short delay to let the display initialize - needed for Nextion 327 | 328 | // Construct banner for TFT or Nextion display 329 | // "Vx.x RIGNAME CALLSIGN" 330 | String banner; 331 | banner = F("V"); 332 | banner += F(VERSION); 333 | banner += F(" "); 334 | banner += F(RIGNAME); 335 | banner += F(" "); 336 | banner += F(CALLSIGN); 337 | 338 | displaySetup(banner, // version number. call sign 339 | vfoActfreq, vfoAltfreq, // Initial active and alternate VFO 340 | active_vfo, // VFO A/B indicator 341 | TxRxState, // TX/RX indicator 342 | sideband, // LSB/USB, 343 | split, // Split mode on/off 344 | increment, // Tuning increment 345 | smeter); // S Meter 346 | 347 | setupCat(); 348 | } 349 | 350 | void loop() { 351 | 352 | CheckEncoder(); //VFO frequency changes 353 | CheckIncrement(); // Encoder Button 354 | 355 | #ifdef DUAL_BAND 356 | CheckBand(); // Band Switch 20/40 357 | #endif 358 | 359 | CheckVFO(); // VFO A/B change 360 | CheckSB(); // USB/LSB change 361 | CheckPTT(); // Check for Mic PTT 362 | CheckTune(); // Check for Tune button press 363 | 364 | #ifdef SMETER 365 | CheckSmeter(); // Signal strength 366 | #endif 367 | 368 | CheckCat(); // CAT Control 369 | CheckSettings(); // Update EEPROM on Settings Change 370 | 371 | #ifdef DISPLAY_NEXTION 372 | CheckTouch(); // Check for touch screen action 373 | #endif 374 | 375 | } 376 | -------------------------------------------------------------------------------- /SSB_Radio_Control.tft: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kk4das/SSB_Radio_Control/3e7015b9badcc35f2606552c5b9f81a51970f983/SSB_Radio_Control.tft -------------------------------------------------------------------------------- /SSB_TFT_Display.cpp: -------------------------------------------------------------------------------- 1 | #include "RadioControl.h" 2 | #ifdef DISPLAY_TFT 3 | 4 | // 5 | // 7/31/202 - Dean Souleles 6 | // Added stubbed out Split indicator function to stay consistent with main sketch 7 | // TO DO: 8 | // ADD constants and code for Split indicator 9 | // 10 | 11 | /* SSB_TFT_Display 12 | * KK4DAS, Dean Souleles, KK4DAS@gmail.com 13 | * May 30, 2020 14 | * 15 | * Basic radio display panel for a SSB transsceiver 16 | * Designed for a 320x240 Color TFT (non touch) 17 | * Tested with an ILI9341 display 18 | * Requires the following libraries be installed: 19 | * Adafruit_GFX 20 | * Adafruit_ILI9341 21 | * 22 | * Implements a basic SSB display console with the following features 23 | * Dual VFO A/B 24 | * Mode indicator SSB/LSB 25 | * Tx/Rx ndicator 26 | * TuningStep Inidicator 27 | * S Meter 28 | * Banner including Call sign 29 | * 30 | * Fully customizable. Fast display makins use of minimal resources./ 31 | * Room is left on the screen for additional features 32 | * There is room on the screen for another row of features 33 | * 34 | * Easily change colors, font sizes and layout 35 | * 36 | * Default Screeen Layout 37 | * A 7.200.000 LSB -- VFO A/B indicator, Active VFO Freq, LSB/USB inidicator 38 | * Rx 7.048.000 100K -- Rx/Tx indicator, Alternate VFO Freq, Tuning Increment 39 | * 40 | * S |_|_|_|_|_|_|_|_|_|_|_|_| -- S Meter 41 | * 1 3 5 7 9 42 | * 43 | * AGC SPL RIT -- (Planned) AGC on/of, Split On/Off, RIT On/OFF 44 | * 45 | * Ver Rig Name Call 46 | * 47 | * This module provides the following radio console dsiplayfunctions: 48 | * displaySetup - initialize the display and displays the startup values - call once from your setup function 49 | * displayBanner - Displays a text banner across the bottom of the screen 50 | * displayActVFO - Displays the frequency of the Active VFO 51 | * displayAltVFO - Displays the frequency of the Alternate VFO 52 | * displayVFOAB - Displays the indicator which VFO is active (A or B) 53 | * displayTxRx - Displays whether the rig is in (Tx or Rx) 54 | * displayMode - Displays the whiuch sideband is selected (LSB or USB) 55 | * displayIncr - Displays the tuning increment (10, 100, 1K 10K, 100K, 1M) 56 | * displaySMeter - Displays the S Meter (1-9 are gray, +10 +20 and +30 are red 57 | * 58 | * This module also provides the following general purpose displauy functions: 59 | * displayClearScreen - fills the screen with the selected backgrond color 60 | * displayPrintat - prints text or nubmers on the screen at a specific location 61 | * displayDrawBoundingBox - draw a box on the screen and fills it with a background color 62 | * displayDrawTextBox - displays text inside a boundig box 63 | * 64 | * Design notes and how to use the code 65 | * 66 | * NOTE TO BUILDERS 67 | * This is not a complete radio control sketch. It is the Display software only. In the spirit of modular design 68 | * it is stand-alone and not dependent on using an SI-5351 or any other specific hardware, or on my particular 69 | * hardware selection of switches, buttons and knobs. The demonstration sketch shows how to update the display, 70 | * but you need to provide the code to determine what the actual values should be. You will likely need other 71 | * libraries like the Si5351 and a Rotary encoder library aside from the GFX and the ILI9341. 72 | * 73 | * DESIGN PRINCIPLES 74 | * Good software design principles are to use as few hard-coded numbers as possible. 75 | * Wherever possible I have used #defines for any number that will be used more than one place in the code. 76 | * For example #define DSP_VFO_ACT_X 60 defines the X coordinate (how far from the left of the screen) 77 | * of the Active VFO frequency display. You will see multiple references to DSP_VFO_ACT_X throughout the code, 78 | * but I never use the hardcoded number 60 again. Change it once – and it is changed throughout. 79 | * 80 | * Taking the S-Meter as an example: 81 | * 82 | * To update the S-Meter display you make a call to displaySMeter(n); where n is an integer from 1 to 12 83 | * (representing S1-9, +10, +20 +30). Your sketch will need a way of monitoring signal strength (an analog input 84 | * pin on the Arduino attached to an appropriate place on your rig) and converting it to the logarithmic S scale. 85 | * 86 | * SCREEN COORDINATES 87 | * Coordinates work differently on displays than a typical graph where the origin 0,0 is in the middle abd positive 88 | * and negative values move you away from the origin. For displays 0,0, the origin, is always upper left hand corner 89 | * of the display and you only use positive numbers for the coordinates +X is pixels from the left edge, +Y is pixels 90 | * down from the top. This particular example based on a 320x240 display but should be easily portable to other 91 | * display sizes – but you have to keep in mind how the coordinate system works. 92 | * 93 | * SCREEN LAYOUT 94 | * Here are a few notes about how the demonstration display is laid out. This should help you understand the design 95 | * concept and allow you to begin to modify it. 96 | * 97 | * The VFO display is setup for a dual VFO rig. The currently Active VFO is always on the top and the alternate VFO 98 | * is just below it. Your code will need to keep track of whether VFO A or VFO B is currently selected and call the 99 | * display routines to update the display. I’ll describe how the VFO displays are are defined and that will give you 100 | * an idea how you might modify or enhance the display. 101 | * 102 | * Active VFO - top center of the screen 103 | * #define DSP_VFO_ACT_X 60 // Active VFO begins 60 pixels from the left hand edge (I picked 60 by experimenting) 104 | * #define DSP_VFO_ACT_Y 30 // Active VFO box starts 30 pixels down from the top of the screen (Try changing it to 50 105 | * // and see what happens) 106 | * #define DSP_VFO_ACT_COLOR ILI9341_GREEN // This sets the text color for the Frequency display 107 | * // use whatever colors you like 108 | * #define DSP_VFO_ACT_BK ILI9341_BLACK // This sets the background color for the Active VFO 109 | * #define DSP_VFO_ACT_SZ 3 // This is text size from Arduino TFT, values 1-5 1 is small 5 is large 110 | * // (2 was too small, 4 was too large, 3 was just right) 111 | * 112 | * Alternate VFO – the second VFO is placed directly below the Active VFO on the screen. There are a couple of things of 113 | * interest here. For the X coordinate, instead of putting in a hard coded number I refer back to the #define that I used 114 | * 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 115 | * to change one number DSP_VFO_ACT_X, and the alternate VFO will move as well. Figuring out the Y coordinate for the 116 | * alternate VFO is a little more challenging. Some math is involved. Starting with the Y coordinate of the Active VFO 117 | * 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 118 | * how many pixels tall the text characters in the Active VFO are and use that as an offset. It turns out we have everything 119 | * we need already defined. CH_W and CH_H are #defines that specify the height and width of a text character in pixels for 120 | * 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 121 | * 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 122 | * on the screen – we multiply the font size by the character height and add 16 pixels offset. The 16 was determined by 123 | * experimentation for something that looked good. The code looks like this: 124 | * 125 | * #define DSP_VFO_ALT_X DSP_VFO_ACT_X 126 | * #define DSP_VFO_ALT_Y DSP_VFO_ACT_Y + (DSP_VFO_ACT_SZ * CH_H) + 16 127 | * 128 | * Take a look the other sections of the display code and you will see similar references and calculations. The VFO A//B 129 | * indicator and LSB/USB mode indicator, for example are similarly “pinned” to the Active VFO display, so if you move the 130 | * Active VFO display to another screen location they will move also. 131 | * 132 | * In summary - each object is on the display is defined by a set of constants that indicate the X,Y coordinates 133 | * of the object on the screen and various other attributes like text size and color. The basic user interface display 134 | * object is a bounded/filed text box. You can control the text size and color, and the box fill color. With this basic 135 | * set of features you can implement a wide variety of user interface elements. The S-meter, for example, is a row of 136 | * filled boxes. 137 | * 138 | * HARDWARE NOTES 139 | * My test sketch uses an Arduino Nano. The display is an HiLetgo 2.2 Inch ILI9341 SPI TFT LCD Display 240x320 ILI9341, 140 | * but any ILI9341 display should work. There are many sources. Please note that the Arduino has 5V logic levels, 141 | * but the display requires 3.3V - so you need some sort of level shifter. I used the"HiLetgo 10pcs 4 Channels IIC I2C 142 | * Logic Level Converter Bi-Directional 3.3V-5V Shifter Module for Arduino" I used hardware SPI and the pinouts are 143 | * standard as follows: 144 | * 145 | * Arduino 146 | * Pin TFT Pin 147 | * -----------|--------- 148 | * 8 | RST - any free Arduino Pin (not used in this sketch) 149 | * 9 | DC - any free Arduino Pin 150 | * 10 | CS - any free Arduino Pin 151 | * 11 | MOSI - fixed 152 | * 12 | MISO - fixed 153 | * 13 | CLK - fixed 154 | * 155 | * That is all the wiring you need for the demonstration sketch. 156 | * 157 | * 158 | * BUILDING THE DEMONSTRATION SKETCH 159 | * 160 | * Create a folder called SSB_TFT_Display_Demo 161 | * 162 | * Copy all three files to that folder 163 | * SSB_TFT_Display_Demo.ino 164 | * SSB_TFT_Display.h 165 | * SSB_TFT_Display.cpp 166 | * 167 | * Use the Arduino IDE library manager to install teh following libraries 168 | * Adafruit_GFX 169 | * Adafruit_ILI9341 170 | * 171 | * Compile and upload the sketch 172 | ****************************************************************************************************************** 173 | */ 174 | 175 | #include 176 | #include 177 | 178 | 179 | // For the Adafruit shield, these are the default. 180 | #define TFT_DC 9 181 | #define TFT_CS 10 182 | #define TFT_MOSI 11 183 | #define TFT_CLK 13 184 | #define TFT_RST 8 185 | #define TFT_MISO 12 186 | 187 | // Use hardware SPI (on Uno, #13, #12, #11) and the above for CS/DC 188 | Adafruit_ILI9341 tft = Adafruit_ILI9341(TFT_CS, TFT_DC); 189 | 190 | #define CH_W 6 // default TFT Character width in pixels 191 | #define CH_H 8 // default TFT Charachter height in pixels 192 | 193 | 194 | 195 | #define DSP_BG_COLOR ILI9341_NAVY 196 | 197 | // Banner - Version, Rig Name, Call sign 198 | #define DSP_BANNER_X 10 199 | #define DSP_BANNER_Y 220 200 | #define DSP_BANNER_COLOR ILI9341_BLACK 201 | #define DSP_BANNER_BK ILI9341_WHITE 202 | #define DSP_BANNER_SZ 2 203 | 204 | 205 | // Active VFO - top center of the screen 206 | #define DSP_VFO_ACT_X 60 207 | #define DSP_VFO_ACT_Y 30 208 | #define DSP_VFO_ACT_COLOR ILI9341_GREEN 209 | #define DSP_VFO_ACT_BK ILI9341_BLACK 210 | #define DSP_VFO_ACT_SZ 3 211 | 212 | // Alternate VFO - directly below the Active VFO 213 | #define DSP_VFO_ALT_X DSP_VFO_ACT_X 214 | #define DSP_VFO_ALT_Y DSP_VFO_ACT_Y + (DSP_VFO_ACT_SZ * CH_H) + 16 215 | #define DSP_VFO_ALT_COLOR ILI9341_WHITE 216 | #define DSP_VFO_ALT_BK DSP_VFO_ACT_BK 217 | #define DSP_VFO_ALT_SZ 3 218 | 219 | // VFP A/B indidcator - to the left of the Active VFO 220 | #define DSP_VFO_AB_X DSP_VFO_ACT_X-40 221 | #define DSP_VFO_AB_Y DSP_VFO_ACT_Y + 6 222 | #define DSP_VFO_AB_COLOR DSP_VFO_ACT_COLOR 223 | #define DSP_VFO_AB_BK DSP_VFO_ACT_BK 224 | #define DSP_VFO_AB_SZ 2 225 | 226 | // Tx/Rx Indicator - to the left of the Alternate VFO 227 | #define DSP_RX_TX_X DSP_VFO_ACT_X-45 228 | #define DSP_RX_TX_Y DSP_VFO_ALT_Y + 4 229 | #define DSP_RX_COLOR DSP_VFO_ACT_COLOR 230 | #define DSP_TX_COLOR ILI9341_RED 231 | #define DSP_RX_TX_BK DSP_VFO_ACT_BK 232 | #define DSP_RX_TX_SZ 2 233 | 234 | // Mode (LSB/USB) inidcator - to the right of the Active VFO 235 | #define DSP_MODE_X DSP_VFO_ACT_X+205 236 | #define DSP_MODE_Y DSP_VFO_ACT_Y + 6 237 | #define DSP_MODE_COLOR DSP_VFO_ACT_COLOR 238 | #define DSP_MODE_BK DSP_VFO_ACT_BK 239 | #define DSP_MODE_SZ 2 240 | 241 | // Tuning Increment - to the right of teh Alternate VFO 242 | #define DSP_INCR_X DSP_VFO_ACT_X+200 243 | #define DSP_INCR_Y DSP_VFO_ALT_Y + 4 244 | #define DSP_INCR_COLOR DSP_VFO_ACT_COLOR 245 | #define DSP_INCR_BK DSP_VFO_ACT_BK 246 | #define DSP_INCR_SZ 2 247 | 248 | // S Meter - below the the Alt VFO (1/2 way down the screen) 249 | #define DSP_S_METER_X 56 250 | #define DSP_S_METER_Y 120 251 | #define DSP_S_METER_SEGMENTS 12 252 | #define DSP_S_METER_UNIT_SIZE 20 253 | #define DSP_S_METER_LOW_COLOR ILI9341_DARKGREY //below S9 254 | #define DSP_S_METER_HI_COLOR ILI9341_RED //above S9 255 | 256 | // S Meter Label "S" to the left of the S Meter 257 | #define DSP_S_LABEL_X DSP_S_METER_X - 18 258 | #define DSP_S_LABEL_Y DSP_S_METER_Y + 4 259 | #define DSP_S_LABEL_COLOR ILI9341_BLACK 260 | #define DSP_S_LABEL_SZ 2 261 | 262 | // S Meter Scale immediately belwo the S Meter 263 | #define DSP_S_METER_SCALE_X DSP_S_METER_X + 5 264 | #define DSP_S_METER_SCALE_Y DSP_S_METER_Y + DSP_S_METER_UNIT_SIZE + 5 265 | #define DSP_S_METER_SCALE_COLOR DSP_S_LABEL_COLOR 266 | #define DSP_S_METER_SCALE_SZ 2 267 | 268 | 269 | /////////////////////////////////////////////////////////////////////////// 270 | // displayPrintln(s) 271 | // Prints one line of text at a time on the display - for debug messages 272 | /////////////////////////////////////////////////////////////////////////// 273 | #ifdef DEBUG 274 | int ln=0; 275 | void displayPrintln(String s ) { 276 | if (ln==14) { 277 | tft.fillScreen(ILI9341_BLACK); 278 | tft.setCursor(0, 0); 279 | ln=0; 280 | } 281 | tft.println(s); 282 | ln++; 283 | } 284 | #endif 285 | 286 | /////////////////////////////////////////////////////////////////////////// 287 | // displayClearScreen() 288 | // Fills the screen with selected bacground color 289 | /////////////////////////////////////////////////////////////////////////// 290 | void displayClearScreen() { 291 | tft.fillScreen(DSP_BG_COLOR); 292 | } 293 | 294 | /////////////////////////////////////////////////////////////////////////// 295 | // displayPrintat String 296 | // Prints a string on the display 297 | // x,y: upper left pixel coordinates 298 | // fontsize: Arduino Font size (1-5) 299 | // color: text color 300 | /////////////////////////////////////////////////////////////////////////// 301 | void displayPrintat(String s, int x, int y, int fontsize, int color) { 302 | tft.setCursor(x,y); 303 | tft.setTextSize(fontsize); 304 | tft.setTextColor(color); 305 | tft.print(s); 306 | } 307 | 308 | /////////////////////////////////////////////////////////////////////////// 309 | // displayPrintat Integer 310 | // Prints a whole number on the display 311 | // x,y: upper left pixel coordinates 312 | // fontsize: Arduino Font size (1-5) 313 | // color: text color 314 | /////////////////////////////////////////////////////////////////////////// 315 | void displayPrintat(int i, int x, int y, int fontsize, int color) { 316 | tft.setCursor(x,y); 317 | tft.setTextSize(fontsize); 318 | tft.setTextColor(color); 319 | tft.print(i); 320 | } 321 | 322 | /////////////////////////////////////////////////////////////////////////// 323 | // displayDrawBoundingBox 324 | // Draw and fill a bounding box for text 325 | // Draw a recrtanglie slightly larger than the text 326 | // Fill it with the color - leave the border white 327 | // 328 | // len - length of the box in characxters 329 | // x - x coord 330 | // y - y coord 331 | // fontsiez - font size - fontsize*CH_W is the width of a character, fontsize*CH_H is the height 332 | // fillcolor - color 333 | /////////////////////////////////////////////////////////////////////////// 334 | void displayDrawBoundingBox(int len, int x, int y, int fontsize, int fillcolor) { 335 | 336 | tft.drawRect(x-8, y-6, ((len*fontsize*CH_W))+16, ((fontsize*CH_H)+8), ILI9341_WHITE); 337 | tft.fillRect(x-6, y-4, ((len*fontsize*CH_W))+12, ((fontsize*CH_H)+4), fillcolor); 338 | } 339 | 340 | /////////////////////////////////////////////////////////////////////////// 341 | // displayDrawTextBox 342 | // Display text in an outlined and filled box 343 | // 344 | // s: text to display 345 | // x,y: upper left pixel coordinates 346 | // fontsize: ARduino font size (1-5) 347 | // fillcolor: color to fill the box with 348 | /////////////////////////////////////////////////////////////////////////// 349 | void displayDrawTextBox(String s, int x, int y, int fontsize, int color, int fillcolor) { 350 | displayDrawBoundingBox(s.length(), x, y, fontsize, fillcolor); // draw the box 351 | displayPrintat(s, x, y, fontsize, color); // display the text 352 | } 353 | 354 | /////////////////////////////////////////////////////////////////////////// 355 | // displaySMeter 356 | // Display the S meter as a line of filled squares - up to the input level (1-12) S1-S0, +10dB, +20dB, +30dB 357 | // S 1-9 are filled with the Low color S9+ is filled with the High color 358 | /////////////////////////////////////////////////////////////////////////// 359 | void displaySMeter(byte level) { 360 | int i; 361 | int color; 362 | 363 | displayPrintat(F("S"), DSP_S_LABEL_X, DSP_S_LABEL_Y, DSP_S_LABEL_SZ, DSP_S_LABEL_COLOR); 364 | 365 | for (i=0; i8 && level>9) { 370 | color = DSP_S_METER_HI_COLOR; 371 | } 372 | } 373 | 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); 374 | 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); 375 | 376 | // Print scale odd numbers up to 9 377 | if ((i % 2) == 0 && i<9) { 378 | 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); 379 | } 380 | } 381 | } 382 | 383 | /////////////////////////////////////////////////////////////////////////// 384 | // displayBanner 385 | // Displays a banner acroos the bottom of the display 386 | /////////////////////////////////////////////////////////////////////////// 387 | void displayBanner(String s) { 388 | displayDrawTextBox(s,DSP_BANNER_X, DSP_BANNER_Y, DSP_BANNER_SZ, DSP_BANNER_COLOR, DSP_BANNER_BK); 389 | } 390 | 391 | 392 | /////////////////////////////////////////////////////////////////////////// 393 | // displayActVFO(uint32_t freq) 394 | // displayAltVFO(uint32_t freq 395 | // Formats and displays Active and Alternat VFO frequencies 396 | // Legal values: Frequency in Hz 397 | // 398 | // To do - comibine into one function 399 | /////////////////////////////////////////////////////////////////////////// 400 | void displayActVFO(uint32_t freq) { 401 | char f[11]; 402 | uint32_t mil, hund_thou, ten_thou, thou, hund, tens, ones; 403 | 404 | // Format number as nn.nnn.nnn 405 | mil = freq / 1000000; 406 | hund_thou = (freq/100000)%10; 407 | ten_thou = (freq/10000)%10; 408 | thou = (freq/1000)%10; 409 | hund = (freq/100)%10; 410 | tens = (freq/10)%10; 411 | ones = freq%10; 412 | snprintf(f, sizeof(f),"%2ld.%ld%ld%ld.%ld%ld%ld",mil, hund_thou, ten_thou,thou, hund, tens, ones); 413 | displayDrawTextBox(f,DSP_VFO_ACT_X, DSP_VFO_ACT_Y, DSP_VFO_ACT_SZ, DSP_VFO_ACT_COLOR, DSP_VFO_ACT_BK); 414 | } 415 | 416 | void displayAltVFO(uint32_t freq) { 417 | char f[11]; 418 | uint32_t mil, hund_thou, ten_thou, thou, hund, tens, ones; 419 | 420 | // Format number as nn.nnn.nnn 421 | mil = freq / 1000000; 422 | hund_thou = (freq/100000)%10; 423 | ten_thou = (freq/10000)%10; 424 | thou = (freq/1000)%10; 425 | hund = (freq/100)%10; 426 | tens = (freq/10)%10; 427 | ones = freq%10; 428 | snprintf(f, sizeof(f),"%2ld.%ld%ld%ld.%ld%ld%ld",mil, hund_thou, ten_thou,thou, hund, tens, ones); 429 | displayDrawTextBox(f,DSP_VFO_ALT_X, DSP_VFO_ALT_Y, DSP_VFO_ALT_SZ, DSP_VFO_ALT_COLOR, DSP_VFO_ALT_BK); 430 | } 431 | 432 | /////////////////////////////////////////////////////////////////////////// 433 | // displayVFOAB(int vfo) 434 | // Displays which VFO is currently active A or B 435 | // Legal values are VFOA and VFOB 436 | /////////////////////////////////////////////////////////////////////////// 437 | void displayVFOAB(int vfo) { 438 | String vfo_str; 439 | if (vfo == VFOA) { 440 | vfo_str = F("A"); 441 | } else { 442 | vfo_str = F("B"); 443 | } 444 | displayDrawTextBox(vfo_str,DSP_VFO_AB_X, DSP_VFO_AB_Y, DSP_VFO_AB_SZ, DSP_VFO_AB_COLOR, DSP_VFO_AB_BK); 445 | } 446 | 447 | /////////////////////////////////////////////////////////////////////////// 448 | // displayTxRx(int tx_rx 449 | // Displays whether the radio is currenlty transmitting or receiveing 450 | // Legal values are TX and RX 451 | /////////////////////////////////////////////////////////////////////////// 452 | void displayTxRx(int tx_rx) { 453 | String tx_rx_str; 454 | int color; 455 | if (tx_rx == RX) { 456 | tx_rx_str = F("Rx"); 457 | color = DSP_RX_COLOR; 458 | } else { 459 | tx_rx_str = F("Tx"); 460 | color = DSP_TX_COLOR; 461 | } 462 | displayDrawTextBox(tx_rx_str,DSP_RX_TX_X, DSP_RX_TX_Y, DSP_RX_TX_SZ, color, DSP_RX_TX_BK); 463 | } 464 | 465 | ////////////////////////////////////////////////////////////////////////// 466 | // displayMode(int mode) 467 | // Displays whether the radio is LSB or USB 468 | // Legal values are LSB and USB 469 | /////////////////////////////////////////////////////////////////////////// 470 | void displayMode(int mode) { 471 | String mode_str; 472 | if (mode == LSB) { 473 | mode_str = F("LSB"); 474 | } else { 475 | mode_str = F("USB"); 476 | } 477 | displayDrawTextBox(mode_str,DSP_MODE_X, DSP_MODE_Y, DSP_MODE_SZ, DSP_MODE_COLOR, DSP_MODE_BK); 478 | } 479 | 480 | /////////////////////////////////////////////////////////////////////////// 481 | // displayIncr(uint32_t increment) 482 | // Changes display of the tuning incrementindicator 483 | // Legal values in Hz are 1, 10, 100, 1000, 10000, 100000, 1000000 484 | /////////////////////////////////////////////////////////////////////////// 485 | void displayIncr(uint32_t increment) { 486 | String hertz; 487 | switch (increment) { 488 | case 1: hertz = F(" 1"); break; 489 | case 10: hertz = F(" 10"); break; 490 | case 100: hertz = F(" 100"); break; 491 | case 1000: hertz = F(" 1K"); break; 492 | case 10000: hertz = F(" 10K"); break; 493 | case 100000: hertz= F("100K"); break; 494 | case 1000000:hertz = F(" 1M"); 495 | } 496 | displayDrawTextBox(hertz,DSP_INCR_X, DSP_INCR_Y, DSP_INCR_SZ, DSP_INCR_COLOR, DSP_INCR_BK); 497 | } 498 | 499 | /////////////////////////////////////////////////////////////////////////// 500 | // displaySplit(boolean split) 501 | // Turns split mode indicator on/off 502 | /////////////////////////////////////////////////////////////////////////// 503 | void displaySplit(boolean splt) { 504 | if (split) { 505 | // todo 506 | } else { 507 | // todo 508 | } 509 | } 510 | 511 | /////////////////////////////////////////////////////////////////////////// 512 | // displaySplit(boolean split) 513 | // Turns split mode indicator on/off 514 | /////////////////////////////////////////////////////////////////////////// 515 | void displayTune(boolean on_off) { 516 | // todo 517 | } 518 | 519 | 520 | /////////////////////////////////////////////////////////////////////////// 521 | // displaySetup() 522 | // Initialze and populate the display 523 | /////////////////////////////////////////////////////////////////////////// 524 | void displaySetup(String banner, 525 | uint32_t vfoActfreq, uint32_t vfoAltfreq, 526 | uint32_t activeVFO, 527 | int tx_rx, 528 | int sideband, 529 | boolean split, 530 | uint32_t increment, 531 | byte s_meter) { 532 | 533 | tft.begin(); // Initialize the TFT 534 | tft.setRotation(1); // Set landscape orientation 535 | displayClearScreen(); // Fill teh screen with the background color 536 | 537 | // 538 | // Display the intitial values 539 | // 540 | displayBanner(banner); 541 | displayActVFO(vfoActfreq); 542 | displayAltVFO(vfoAltfreq); 543 | displayVFOAB(activeVFO); 544 | displayTxRx(tx_rx); 545 | displayMode(sideband); 546 | displaySplit(split); 547 | displayIncr(increment); 548 | displaySMeter(s_meter); 549 | } 550 | 551 | #endif 552 | -------------------------------------------------------------------------------- /Settings.cpp: -------------------------------------------------------------------------------- 1 | #include "RadioControl.h" 2 | #include 3 | 4 | #define EE_MAGIC_ADDR 0 5 | #define EE_SETTINGS_ADDR 4 6 | #define SETTINGS_TIMER 3000 // time to wait after a settings change before writing to EEPROM 7 | 8 | const byte magic[4] = {'4', 'D', 'A', 'S'}; 9 | 10 | 11 | struct Settings_struct { 12 | uint32_t vfoAfreq; 13 | uint32_t vfoBfreq; 14 | uint32_t increment; 15 | byte active_vfo; 16 | byte sideband; 17 | // Band specific memory 18 | uint32_t band20Freq; 19 | uint32_t band40Freq; 20 | byte band20Sideband; 21 | byte band40Sideband; 22 | }; 23 | 24 | // Settings Change 25 | unsigned long settings_time; 26 | bool settings_changed; 27 | 28 | 29 | void readSettings() { 30 | Settings_struct settings; 31 | EEPROM.get(EE_SETTINGS_ADDR, settings); 32 | vfoAfreq = settings.vfoAfreq; 33 | vfoBfreq = settings.vfoBfreq; 34 | increment = settings.increment; 35 | active_vfo = settings.active_vfo; 36 | sideband = settings.sideband; 37 | band20Freq = settings.band20Freq; 38 | band40Freq = settings.band40Freq; 39 | band20Sideband = settings.band20Sideband; 40 | band40Sideband = settings.band40Sideband; 41 | 42 | if (sideband == LSB) { 43 | bfo = LSB_BFO; 44 | } else { 45 | bfo = USB_BFO; 46 | } 47 | 48 | } 49 | 50 | void writeSettings() { 51 | Settings_struct settings; 52 | settings.vfoAfreq = vfoAfreq; 53 | settings.vfoBfreq = vfoBfreq; 54 | settings.increment = increment; 55 | settings.active_vfo = active_vfo; 56 | settings.sideband = sideband; 57 | settings.band20Freq = band20Freq; 58 | settings.band40Freq = band40Freq; 59 | settings.band20Sideband = band20Sideband; 60 | settings.band40Sideband = band40Sideband; 61 | EEPROM.put(EE_SETTINGS_ADDR, settings); 62 | } 63 | 64 | void initSettings() { 65 | EEPROM.put(EE_MAGIC_ADDR, magic); 66 | writeSettings(); 67 | } 68 | 69 | void setupSettings() { 70 | byte buff[4]; 71 | bool magic_ok = true; 72 | 73 | EEPROM.get(EE_MAGIC_ADDR, buff); 74 | 75 | for (int i = 0; i < 4; i++) { 76 | if (buff[i] != magic[i]) { 77 | magic_ok = false; 78 | break; 79 | } 80 | } 81 | 82 | if (magic_ok) { 83 | readSettings(); 84 | } else { 85 | initSettings(); 86 | } 87 | 88 | settings_time = millis(); 89 | settings_changed = false; 90 | } 91 | 92 | void CheckSettings() { 93 | 94 | if (settings_changed && (millis() - SETTINGS_TIMER) > settings_time) { 95 | writeSettings(); 96 | settings_time = millis(); 97 | settings_changed = false; 98 | } 99 | } 100 | 101 | void startSettingsTimer() { 102 | settings_time = millis(); 103 | settings_changed = true; 104 | } 105 | -------------------------------------------------------------------------------- /Settings.h: -------------------------------------------------------------------------------- 1 | #ifndef Settings_h 2 | #define Settings_h 3 | 4 | // Function prototypes 5 | 6 | extern void setupSettings(); 7 | extern void initSettings(); 8 | extern void readSettings(); 9 | extern void writeSettings(); 10 | extern void startSettingsTimer(); 11 | extern void CheckSettings(); 12 | 13 | #endif 14 | -------------------------------------------------------------------------------- /Smeter.cpp: -------------------------------------------------------------------------------- 1 | // 2 | // S Meter 3 | // 4 | #include "RadioControl.h" 5 | 6 | 7 | //#define S_DEBUG 8 | 9 | const int slevels[MAXSLEVELS] = {25, 100, 200, 375, 500, 575, 650, 775, 900, 950, 975, 1000}; 10 | 11 | //byte smeter = 0; 12 | unsigned long smeter_raw = 0; 13 | unsigned long smeter_sample_count = 0; 14 | unsigned long smeter_time = 0; 15 | 16 | //***************************************** 17 | void CheckSmeter() { 18 | 19 | int i; 20 | int smeter_avg; 21 | 22 | if (TxRxState == TX) { 23 | return; 24 | } 25 | smeter_raw = smeter_raw + analogRead(SMETER_PIN); 26 | smeter_sample_count++; 27 | 28 | 29 | if ((millis() - SMETER_SAMPLE_TIMER) > smeter_time) { 30 | /* 31 | lcd.setCursor(12, 1); 32 | lcd.print(" "); 33 | lcd.setCursor(12, 1); 34 | lcd.print(smeter_raw); 35 | */ 36 | 37 | smeter_avg = int(smeter_raw / smeter_sample_count); 38 | 39 | #ifdef S_DEBUG 40 | String msg = F("S raw:"); 41 | msg+=smeter_avg; 42 | displayBanner(msg); 43 | #endif 44 | 45 | smeter = 0; 46 | for (i = 0; i < MAXSLEVELS; i++) { 47 | if (smeter_avg >= slevels[i]) { 48 | smeter = byte(i); 49 | } 50 | 51 | } 52 | displaySMeter(smeter); 53 | smeter_raw = 0; 54 | smeter_sample_count = 0; 55 | smeter_time = millis(); 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /Smeter.h: -------------------------------------------------------------------------------- 1 | #ifndef Smeter_h 2 | #define Smeter_h 3 | /* 4 | * SMeter 5 | * Constants defining and controlling Smeter collection and display 6 | */ 7 | 8 | 9 | #define SMETER_SAMPLE_TIMER 250 // Sample time 10 | #define MAXSLEVELS 12 // S Levels 1-0, S9+10m S9+20, S9+30 11 | 12 | #endif 13 | -------------------------------------------------------------------------------- /Utility.cpp: -------------------------------------------------------------------------------- 1 | /* Utility Functions 2 | * 3 | * Delay(interval) - non-blocking delahy 4 | */ 5 | 6 | #include "Arduino.h" 7 | #include "Utility.h" 8 | 9 | /* 10 | * "Delay" is a non-blocking delay function. The standard Arduino "delay" function 11 | * turns off interrupts, which prevents the Serial I/O functions from working. That 12 | * sometimes causes characters to be missed and junk commands to show up. 13 | */ 14 | 15 | void Delay ( unsigned long interval ) 16 | { 17 | unsigned long currentTime = 0UL; 18 | 19 | unsigned long startTime = millis(); 20 | 21 | while ( startTime + interval > currentTime ) 22 | currentTime = millis(); 23 | } 24 | 25 | 26 | /* 27 | * ToggleLED - toggles built-in LED On/Off - useful for debug 28 | */ 29 | byte LEDState=LOW; 30 | void ToggleLED(){ 31 | if (LEDState == LOW){ 32 | LEDState = HIGH; 33 | } else { 34 | LEDState = LOW; 35 | } 36 | digitalWrite(LED_BUILTIN, LEDState); 37 | } 38 | -------------------------------------------------------------------------------- /Utility.h: -------------------------------------------------------------------------------- 1 | #ifndef UTILITY_h 2 | #define UTILITY_h 3 | /************************************************************** 4 | Uttility functions - general purpose 5 | ***************************************************************/ 6 | 7 | //=============== Function Prototypes ============================================ 8 | extern void Delay (unsigned long interval); // Non-blocking delay 9 | extern void ToggleLED(); // Toggle built-in LED 10 | #endif 11 | -------------------------------------------------------------------------------- /si5351.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * si5351.cpp - Si5351 library for Arduino 3 | * 4 | * Copyright (C) 2014 Jason Milldrum 5 | * 6 | * Some tuning algorithms derived from clk-si5351.c in the Linux kernel. 7 | * Sebastian Hesselbarth 8 | * Rabeeh Khoury 9 | * 10 | * rational_best_approximation() derived from lib/rational.c in 11 | * the Linux kernel. 12 | * Copyright (C) 2009 emlix GmbH, Oskar Schirmer 13 | * 14 | * This program is free software: you can redistribute it and/or modify 15 | * it under the terms of the GNU General Public License as published by 16 | * the Free Software Foundation, either version 3 of the License, or 17 | * (at your option) any later version. 18 | * 19 | * This program is distributed in the hope that it will be useful, 20 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 21 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 22 | * GNU General Public License for more details. 23 | * 24 | * You should have received a copy of the GNU General Public License 25 | * along with this program. If not, see . 26 | */ 27 | 28 | #include 29 | #include 30 | 31 | #include "Arduino.h" 32 | #include "Wire.h" 33 | #include "si5351.h" 34 | 35 | uint32_t EEMEM ee_ref_correction = 0; 36 | 37 | /********************/ 38 | /* Public functions */ 39 | /********************/ 40 | 41 | Si5351::Si5351(void) 42 | { 43 | } 44 | 45 | /* 46 | * init(uint8_t xtal_load_c) 47 | * 48 | * Setup communications to the Si5351 and set the crystal 49 | * load capacitance. 50 | * 51 | * xtal_load_c - Crystal load capacitance. Use the SI5351_CRYSTAL_LOAD_*PF 52 | * defines in the header file 53 | * 54 | */ 55 | void Si5351::init(uint8_t xtal_load_c) 56 | { 57 | // Start I2C comms 58 | Wire.begin(); 59 | 60 | // Set crystal load capacitance 61 | si5351_write(SI5351_CRYSTAL_LOAD, xtal_load_c); 62 | 63 | // Get the correction factor from EEPROM 64 | ref_correction = eeprom_read_dword(&ee_ref_correction); 65 | } 66 | 67 | /* 68 | * set_freq(uint32_t freq, uint32_t pll_freq, enum si5351_clock output) 69 | * 70 | * Sets the clock frequency of the specified CLK output 71 | * 72 | * freq - Output frequency in Hz 73 | * pll_freq - Frequency of the PLL driving the Multisynth 74 | * Use a 0 to have the function choose a PLL frequency 75 | * clk - Clock output 76 | * (use the si5351_clock enum) 77 | */ 78 | void Si5351::set_freq(uint32_t freq, uint32_t pll_freq, enum si5351_clock clk) 79 | { 80 | struct Si5351RegSet ms_reg, pll_reg; 81 | enum si5351_pll target_pll; 82 | uint8_t set_pll = 0; 83 | 84 | /* Calculate the synth parameters */ 85 | /* If pll_freq is 0, let the algorithm pick a PLL frequency */ 86 | if(pll_freq == 0) 87 | { 88 | pll_freq = multisynth_calc(freq, &ms_reg); 89 | set_pll = 1; 90 | } 91 | /* TODO: bounds checking */ 92 | else 93 | { 94 | multisynth_recalc(freq, pll_freq, &ms_reg); 95 | set_pll = 0; 96 | } 97 | 98 | /* Determine which PLL to use */ 99 | /* CLK0 gets PLLA, CLK1 gets PLLB */ 100 | /* CLK2 gets PLLB if necessary */ 101 | /* Only good for Si5351A3 variant at the moment */ 102 | if(clk == SI5351_CLK0) 103 | { 104 | target_pll = SI5351_PLLA; 105 | si5351_set_ms_source(SI5351_CLK0, SI5351_PLLA); 106 | plla_freq = pll_freq; 107 | } 108 | else if(clk == SI5351_CLK1) 109 | { 110 | target_pll = SI5351_PLLB; 111 | si5351_set_ms_source(SI5351_CLK1, SI5351_PLLB); 112 | pllb_freq = pll_freq; 113 | } 114 | else 115 | { 116 | /* need to account for CLK2 set before CLK1 */ 117 | if(pllb_freq == 0) 118 | { 119 | target_pll = SI5351_PLLB; 120 | si5351_set_ms_source(SI5351_CLK2, SI5351_PLLB); 121 | pllb_freq = pll_freq; 122 | } 123 | else 124 | { 125 | target_pll = SI5351_PLLB; 126 | si5351_set_ms_source(SI5351_CLK2, SI5351_PLLB); 127 | pll_freq = pllb_freq; 128 | multisynth_recalc(freq, pll_freq, &ms_reg); 129 | } 130 | } 131 | 132 | pll_calc(pll_freq, &pll_reg, ref_correction); 133 | 134 | /* Derive the register values to write */ 135 | 136 | /* Prepare an array for parameters to be written to */ 137 | uint8_t *params = new uint8_t[20]; 138 | uint8_t i = 0; 139 | uint8_t temp; 140 | 141 | /* PLL parameters first */ 142 | 143 | if(set_pll == 1) 144 | { 145 | /* Registers 26-27 */ 146 | temp = ((pll_reg.p3 >> 8) & 0xFF); 147 | params[i++] = temp; 148 | 149 | temp = (uint8_t)(pll_reg.p3 & 0xFF); 150 | params[i++] = temp; 151 | 152 | /* Register 28 */ 153 | temp = (uint8_t)((pll_reg.p1 >> 16) & 0x03); 154 | params[i++] = temp; 155 | 156 | /* Registers 29-30 */ 157 | temp = (uint8_t)((pll_reg.p1 >> 8) & 0xFF); 158 | params[i++] = temp; 159 | 160 | temp = (uint8_t)(pll_reg.p1 & 0xFF); 161 | params[i++] = temp; 162 | 163 | /* Register 31 */ 164 | temp = (uint8_t)((pll_reg.p3 >> 12) & 0xF0); 165 | temp += (uint8_t)((pll_reg.p2 >> 16) & 0x0F); 166 | params[i++] = temp; 167 | 168 | /* Registers 32-33 */ 169 | temp = (uint8_t)((pll_reg.p2 >> 8) & 0xFF); 170 | params[i++] = temp; 171 | 172 | temp = (uint8_t)(pll_reg.p2 & 0xFF); 173 | params[i++] = temp; 174 | 175 | /* Write the parameters */ 176 | if(target_pll == SI5351_PLLA) 177 | { 178 | si5351_write_bulk(SI5351_PLLA_PARAMETERS, i + 1, params); 179 | } 180 | else if(target_pll == SI5351_PLLB) 181 | { 182 | si5351_write_bulk(SI5351_PLLB_PARAMETERS, i + 1, params); 183 | } 184 | } 185 | 186 | delete params; 187 | 188 | /* Now the multisynth parameters */ 189 | params = new uint8_t[20]; 190 | i = 0; 191 | 192 | /* Registers 42-43 */ 193 | temp = (uint8_t)((ms_reg.p3 >> 8) & 0xFF); 194 | params[i++] = temp; 195 | 196 | temp = (uint8_t)(ms_reg.p3 & 0xFF); 197 | params[i++] = temp; 198 | 199 | /* Register 44 */ 200 | /* TODO: add code for output divider */ 201 | temp = (uint8_t)((ms_reg.p1 >> 16) & 0x03); 202 | params[i++] = temp; 203 | 204 | /* Registers 45-46 */ 205 | temp = (uint8_t)((ms_reg.p1 >> 8) & 0xFF); 206 | params[i++] = temp; 207 | 208 | temp = (uint8_t)(ms_reg.p1 & 0xFF); 209 | params[i++] = temp; 210 | 211 | /* Register 47 */ 212 | temp = (uint8_t)((ms_reg.p3 >> 12) & 0xF0); 213 | temp += (uint8_t)((ms_reg.p2 >> 16) & 0x0F); 214 | params[i++] = temp; 215 | 216 | /* Registers 48-49 */ 217 | temp = (uint8_t)((ms_reg.p2 >> 8) & 0xFF); 218 | params[i++] = temp; 219 | 220 | temp = (uint8_t)(ms_reg.p2 & 0xFF); 221 | params[i++] = temp; 222 | 223 | /* Write the parameters */ 224 | switch(clk) 225 | { 226 | case SI5351_CLK0: 227 | si5351_write_bulk(SI5351_CLK0_PARAMETERS, i + 1, params); 228 | si5351_set_ms_source(clk, target_pll); 229 | break; 230 | case SI5351_CLK1: 231 | si5351_write_bulk(SI5351_CLK1_PARAMETERS, i + 1, params); 232 | si5351_set_ms_source(clk, target_pll); 233 | break; 234 | case SI5351_CLK2: 235 | si5351_write_bulk(SI5351_CLK2_PARAMETERS, i + 1, params); 236 | si5351_set_ms_source(clk, target_pll); 237 | break; 238 | case SI5351_CLK3: 239 | si5351_write_bulk(SI5351_CLK3_PARAMETERS, i + 1, params); 240 | si5351_set_ms_source(clk, target_pll); 241 | break; 242 | case SI5351_CLK4: 243 | si5351_write_bulk(SI5351_CLK4_PARAMETERS, i + 1, params); 244 | si5351_set_ms_source(clk, target_pll); 245 | break; 246 | case SI5351_CLK5: 247 | si5351_write_bulk(SI5351_CLK5_PARAMETERS, i + 1, params); 248 | si5351_set_ms_source(clk, target_pll); 249 | break; 250 | case SI5351_CLK6: 251 | si5351_write_bulk(SI5351_CLK6_PARAMETERS, i + 1, params); 252 | si5351_set_ms_source(clk, target_pll); 253 | break; 254 | case SI5351_CLK7: 255 | si5351_write_bulk(SI5351_CLK7_PARAMETERS, i + 1, params); 256 | si5351_set_ms_source(clk, target_pll); 257 | break; 258 | } 259 | 260 | delete params; 261 | } 262 | 263 | /* 264 | * set_pll(uint32_t pll_freq, enum si5351_pll target_pll) 265 | * 266 | * Set the specified PLL to a specific oscillation frequency 267 | * 268 | * pll_freq - Desired PLL frequency 269 | * target_pll - Which PLL to set 270 | * (use the si5351_pll enum) 271 | */ 272 | void Si5351::set_pll(uint32_t pll_freq, enum si5351_pll target_pll) 273 | { 274 | struct Si5351RegSet pll_reg; 275 | 276 | pll_calc(pll_freq, &pll_reg, ref_correction); 277 | 278 | /* Derive the register values to write */ 279 | 280 | /* Prepare an array for parameters to be written to */ 281 | uint8_t *params = new uint8_t[20]; 282 | ; 283 | uint8_t i = 0; 284 | uint8_t temp; 285 | 286 | /* Registers 26-27 */ 287 | temp = ((pll_reg.p3 >> 8) & 0xFF); 288 | params[i++] = temp; 289 | 290 | temp = (uint8_t)(pll_reg.p3 & 0xFF); 291 | params[i++] = temp; 292 | 293 | /* Register 28 */ 294 | temp = (uint8_t)((pll_reg.p1 >> 16) & 0x03); 295 | params[i++] = temp; 296 | 297 | /* Registers 29-30 */ 298 | temp = (uint8_t)((pll_reg.p1 >> 8) & 0xFF); 299 | params[i++] = temp; 300 | 301 | temp = (uint8_t)(pll_reg.p1 & 0xFF); 302 | params[i++] = temp; 303 | 304 | /* Register 31 */ 305 | temp = (uint8_t)((pll_reg.p3 >> 12) & 0xF0); 306 | temp += (uint8_t)((pll_reg.p2 >> 16) & 0x0F); 307 | params[i++] = temp; 308 | 309 | /* Registers 32-33 */ 310 | temp = (uint8_t)((pll_reg.p2 >> 8) & 0xFF); 311 | params[i++] = temp; 312 | 313 | temp = (uint8_t)(pll_reg.p2 & 0xFF); 314 | params[i++] = temp; 315 | 316 | /* Write the parameters */ 317 | if(target_pll == SI5351_PLLA) 318 | { 319 | si5351_write_bulk(SI5351_PLLA_PARAMETERS, i + 1, params); 320 | } 321 | else if(target_pll == SI5351_PLLB) 322 | { 323 | si5351_write_bulk(SI5351_PLLB_PARAMETERS, i + 1, params); 324 | } 325 | 326 | delete params; 327 | } 328 | 329 | /* 330 | * clock_enable(enum si5351_clock clk, uint8_t enable) 331 | * 332 | * Enable or disable a chosen clock 333 | * clk - Clock output 334 | * (use the si5351_clock enum) 335 | * enable - Set to 1 to enable, 0 to disable 336 | */ 337 | void Si5351::clock_enable(enum si5351_clock clk, uint8_t enable) 338 | { 339 | uint8_t reg_val; 340 | 341 | reg_val = si5351_read(SI5351_OUTPUT_ENABLE_CTRL); 342 | 343 | if(enable == 1) 344 | { 345 | reg_val &= ~(1<<(uint8_t)clk); 346 | } 347 | else 348 | { 349 | reg_val |= (1<<(uint8_t)clk); 350 | } 351 | 352 | si5351_write(SI5351_OUTPUT_ENABLE_CTRL, reg_val); 353 | } 354 | 355 | /* 356 | * drive_strength(enum si5351_clock clk, enum si5351_drive drive) 357 | * 358 | * Sets the drive strength of the specified clock output 359 | * 360 | * clk - Clock output 361 | * (use the si5351_clock enum) 362 | * drive - Desired drive level 363 | * (use the si5351_drive enum) 364 | */ 365 | void Si5351::drive_strength(enum si5351_clock clk, enum si5351_drive drive) 366 | { 367 | uint8_t reg_val; 368 | const uint8_t mask = 0x03; 369 | 370 | reg_val = si5351_read(SI5351_CLK0_CTRL + (uint8_t)clk); 371 | 372 | switch(drive) 373 | { 374 | case SI5351_DRIVE_2MA: 375 | reg_val &= ~(mask); 376 | reg_val |= 0x00; 377 | break; 378 | case SI5351_DRIVE_4MA: 379 | reg_val &= ~(mask); 380 | reg_val |= 0x01; 381 | break; 382 | case SI5351_DRIVE_6MA: 383 | reg_val &= ~(mask); 384 | reg_val |= 0x02; 385 | break; 386 | case SI5351_DRIVE_8MA: 387 | reg_val &= ~(mask); 388 | reg_val |= 0x03; 389 | break; 390 | default: 391 | break; 392 | } 393 | 394 | si5351_write(SI5351_CLK0_CTRL + (uint8_t)clk, reg_val); 395 | } 396 | 397 | /* 398 | * update_status(void) 399 | * 400 | * Call this to update the status structs, then access them 401 | * via the dev_status and dev_int_status global variables. 402 | * 403 | * See the header file for the struct definitions. These 404 | * correspond to the flag names for registers 0 and 1 in 405 | * the Si5351 datasheet. 406 | */ 407 | void Si5351::update_status(void) 408 | { 409 | si5351_update_sys_status(&dev_status); 410 | si5351_update_int_status(&dev_int_status); 411 | } 412 | 413 | /* 414 | * set_correction(int32_t corr) 415 | * 416 | * Use this to set the oscillator correction factor to 417 | * EEPROM. This value is a signed 32-bit integer of the 418 | * parts-per-10 million value that the actual oscillation 419 | * frequency deviates from the specified frequency. 420 | * 421 | * The frequency calibration is done as a one-time procedure. 422 | * Any desired test frequency within the normal range of the 423 | * Si5351 should be set, then the actual output frequency 424 | * should be measured as accurately as possible. The 425 | * difference between the measured and specified frequencies 426 | * should be calculated in Hertz, then multiplied by 10 in 427 | * order to get the parts-per-10 million value. 428 | * 429 | * Since the Si5351 itself has an intrinsic 0 PPM error, this 430 | * correction factor is good across the entire tuning range of 431 | * the Si5351. Once this calibration is done accurately, it 432 | * should not have to be done again for the same Si5351 and 433 | * crystal. The library will read the correction factor from 434 | * EEPROM during initialization for use by the tuning 435 | * algorithms. 436 | */ 437 | void Si5351::set_correction(int32_t corr) 438 | { 439 | eeprom_write_dword(&ee_ref_correction, corr); 440 | ref_correction = corr; 441 | } 442 | 443 | /* 444 | * get_correction(void) 445 | * 446 | * Returns the oscillator correction factor stored 447 | * in EEPROM. 448 | */ 449 | int32_t Si5351::get_correction(void) 450 | { 451 | return eeprom_read_dword(&ee_ref_correction); 452 | } 453 | 454 | 455 | uint8_t Si5351::si5351_write_bulk(uint8_t addr, uint8_t bytes, uint8_t *data) 456 | { 457 | Wire.beginTransmission(SI5351_BUS_BASE_ADDR); 458 | Wire.write(addr); 459 | for(int i = 0; i < bytes; i++) 460 | { 461 | Wire.write(data[i]); 462 | } 463 | Wire.endTransmission(); 464 | } 465 | 466 | uint8_t Si5351::si5351_write(uint8_t addr, uint8_t data) 467 | { 468 | Wire.beginTransmission(SI5351_BUS_BASE_ADDR); 469 | Wire.write(addr); 470 | Wire.write(data); 471 | Wire.endTransmission(); 472 | } 473 | 474 | uint8_t Si5351::si5351_read(uint8_t addr) 475 | { 476 | Wire.beginTransmission(SI5351_BUS_BASE_ADDR); 477 | Wire.write(addr); 478 | Wire.endTransmission(); 479 | 480 | Wire.requestFrom(SI5351_BUS_BASE_ADDR, 1); 481 | 482 | return Wire.read(); 483 | } 484 | 485 | /*********************/ 486 | /* Private functions */ 487 | /*********************/ 488 | 489 | /* 490 | * Calculate best rational approximation for a given fraction 491 | * taking into account restricted register size, e.g. to find 492 | * appropriate values for a pll with 5 bit denominator and 493 | * 8 bit numerator register fields, trying to set up with a 494 | * frequency ratio of 3.1415, one would say: 495 | * 496 | * rational_best_approximation(31415, 10000, 497 | * (1 << 8) - 1, (1 << 5) - 1, &n, &d); 498 | * 499 | * you may look at given_numerator as a fixed point number, 500 | * with the fractional part size described in given_denominator. 501 | * 502 | * for theoretical background, see: 503 | * http://en.wikipedia.org/wiki/Continued_fraction 504 | */ 505 | 506 | void Si5351::rational_best_approximation( 507 | unsigned long given_numerator, unsigned long given_denominator, 508 | unsigned long max_numerator, unsigned long max_denominator, 509 | unsigned long *best_numerator, unsigned long *best_denominator) 510 | { 511 | unsigned long n, d, n0, d0, n1, d1; 512 | n = given_numerator; 513 | d = given_denominator; 514 | n0 = d1 = 0; 515 | n1 = d0 = 1; 516 | for (;;) { 517 | unsigned long t, a; 518 | if ((n1 > max_numerator) || (d1 > max_denominator)) { 519 | n1 = n0; 520 | d1 = d0; 521 | break; 522 | } 523 | if (d == 0) 524 | break; 525 | t = d; 526 | a = n / d; 527 | d = n % d; 528 | n = t; 529 | t = n0 + a * n1; 530 | n0 = n1; 531 | n1 = t; 532 | t = d0 + a * d1; 533 | d0 = d1; 534 | d1 = t; 535 | } 536 | *best_numerator = n1; 537 | *best_denominator = d1; 538 | } 539 | 540 | uint32_t Si5351::pll_calc(uint32_t freq, struct Si5351RegSet *reg, int32_t correction) 541 | { 542 | uint32_t ref_freq = SI5351_XTAL_FREQ; 543 | uint32_t rfrac, denom, a, b, c, p1, p2, p3; 544 | uint64_t lltmp; 545 | 546 | /* Factor calibration value into nominal crystal frequency */ 547 | /* Measured in parts-per-ten million */ 548 | ref_freq += (uint32_t)((double)(correction / 10000000.0) * (double)ref_freq); 549 | 550 | /* PLL bounds checking */ 551 | if (freq < SI5351_PLL_VCO_MIN) 552 | freq = SI5351_PLL_VCO_MIN; 553 | if (freq > SI5351_PLL_VCO_MAX) 554 | freq = SI5351_PLL_VCO_MAX; 555 | 556 | /* Determine integer part of feedback equation */ 557 | a = freq / ref_freq; 558 | 559 | if (a < SI5351_PLL_A_MIN) 560 | freq = ref_freq * SI5351_PLL_A_MIN; 561 | if (a > SI5351_PLL_A_MAX) 562 | freq = ref_freq * SI5351_PLL_A_MAX; 563 | 564 | /* find best approximation for b/c = fVCO mod fIN */ 565 | denom = 1000L * 1000L; 566 | lltmp = freq % ref_freq; 567 | lltmp *= denom; 568 | do_div(lltmp, ref_freq); 569 | rfrac = (uint32_t)lltmp; 570 | 571 | b = 0; 572 | c = 1; 573 | if (rfrac) 574 | rational_best_approximation(rfrac, denom, 575 | SI5351_PLL_B_MAX, SI5351_PLL_C_MAX, &b, &c); 576 | 577 | /* calculate parameters */ 578 | p3 = c; 579 | p2 = (128 * b) % c; 580 | p1 = 128 * a; 581 | p1 += (128 * b / c); 582 | p1 -= 512; 583 | 584 | /* recalculate rate by fIN * (a + b/c) */ 585 | lltmp = ref_freq; 586 | lltmp *= b; 587 | do_div(lltmp, c); 588 | 589 | freq = (uint32_t)lltmp; 590 | freq += ref_freq * a; 591 | 592 | reg->p1 = p1; 593 | reg->p2 = p2; 594 | reg->p3 = p3; 595 | 596 | return freq; 597 | } 598 | 599 | uint32_t Si5351::multisynth_calc(uint32_t freq, struct Si5351RegSet *reg) 600 | { 601 | uint32_t pll_freq; 602 | uint64_t lltmp; 603 | uint32_t a, b, c, p1, p2, p3; 604 | uint8_t divby4; 605 | 606 | /* Multisynth bounds checking */ 607 | if (freq > SI5351_MULTISYNTH_MAX_FREQ) 608 | freq = SI5351_MULTISYNTH_MAX_FREQ; 609 | if (freq < SI5351_MULTISYNTH_MIN_FREQ) 610 | freq = SI5351_MULTISYNTH_MIN_FREQ; 611 | 612 | divby4 = 0; 613 | if (freq > SI5351_MULTISYNTH_DIVBY4_FREQ) 614 | divby4 = 1; 615 | 616 | /* Find largest integer divider for max */ 617 | /* VCO frequency and given target frequency */ 618 | if (divby4 == 0) 619 | { 620 | lltmp = SI5351_PLL_VCO_MAX; 621 | do_div(lltmp, freq); 622 | a = (uint32_t)lltmp; 623 | } 624 | else 625 | a = 4; 626 | 627 | b = 0; 628 | c = 1; 629 | pll_freq = a * freq; 630 | 631 | /* Recalculate output frequency by fOUT = fIN / (a + b/c) */ 632 | lltmp = pll_freq; 633 | lltmp *= c; 634 | do_div(lltmp, a * c + b); 635 | freq = (unsigned long)lltmp; 636 | 637 | /* Calculate parameters */ 638 | if (divby4) 639 | { 640 | p3 = 1; 641 | p2 = 0; 642 | p1 = 0; 643 | } 644 | else 645 | { 646 | p3 = c; 647 | p2 = (128 * b) % c; 648 | p1 = 128 * a; 649 | p1 += (128 * b / c); 650 | p1 -= 512; 651 | } 652 | 653 | reg->p1 = p1; 654 | reg->p2 = p2; 655 | reg->p3 = p3; 656 | 657 | return pll_freq; 658 | } 659 | 660 | uint32_t Si5351::multisynth_recalc(uint32_t freq, uint32_t pll_freq, struct Si5351RegSet *reg) 661 | { 662 | uint64_t lltmp; 663 | uint32_t rfrac, denom, a, b, c, p1, p2, p3; 664 | uint8_t divby4; 665 | 666 | /* Multisynth bounds checking */ 667 | if (freq > SI5351_MULTISYNTH_MAX_FREQ) 668 | freq = SI5351_MULTISYNTH_MAX_FREQ; 669 | if (freq < SI5351_MULTISYNTH_MIN_FREQ) 670 | freq = SI5351_MULTISYNTH_MIN_FREQ; 671 | 672 | divby4 = 0; 673 | if (freq > SI5351_MULTISYNTH_DIVBY4_FREQ) 674 | divby4 = 1; 675 | 676 | /* Determine integer part of feedback equation */ 677 | a = pll_freq / freq; 678 | 679 | /* TODO: not sure this is correct */ 680 | if (a < SI5351_MULTISYNTH_A_MIN) 681 | freq = pll_freq / SI5351_MULTISYNTH_A_MIN; 682 | if (a > SI5351_MULTISYNTH_A_MAX) 683 | freq = pll_freq / SI5351_MULTISYNTH_A_MAX; 684 | 685 | /* find best approximation for b/c */ 686 | denom = 1000L * 1000L; 687 | lltmp = pll_freq % freq; 688 | lltmp *= denom; 689 | do_div(lltmp, freq); 690 | rfrac = (uint32_t)lltmp; 691 | 692 | b = 0; 693 | c = 1; 694 | if (rfrac) 695 | rational_best_approximation(rfrac, denom, 696 | SI5351_MULTISYNTH_B_MAX, SI5351_MULTISYNTH_C_MAX, &b, &c); 697 | 698 | /* Recalculate output frequency by fOUT = fIN / (a + b/c) */ 699 | lltmp = pll_freq; 700 | lltmp *= c; 701 | do_div(lltmp, a * c + b); 702 | freq = (unsigned long)lltmp; 703 | 704 | /* Calculate parameters */ 705 | if (divby4) 706 | { 707 | p3 = 1; 708 | p2 = 0; 709 | p1 = 0; 710 | } 711 | else 712 | { 713 | p3 = c; 714 | p2 = (128 * b) % c; 715 | p1 = 128 * a; 716 | p1 += (128 * b / c); 717 | p1 -= 512; 718 | } 719 | 720 | reg->p1 = p1; 721 | reg->p2 = p2; 722 | reg->p3 = p3; 723 | 724 | return freq; 725 | } 726 | 727 | void Si5351::si5351_update_sys_status(struct Si5351Status *status) 728 | { 729 | uint8_t reg_val = 0; 730 | 731 | reg_val = si5351_read(SI5351_DEVICE_STATUS); 732 | 733 | /* Parse the register */ 734 | status->SYS_INIT = (reg_val >> 7) & 0x01; 735 | status->LOL_B = (reg_val >> 6) & 0x01; 736 | status->LOL_A = (reg_val >> 5) & 0x01; 737 | status->LOS = (reg_val >> 4) & 0x01; 738 | status->REVID = reg_val & 0x03; 739 | } 740 | 741 | void Si5351::si5351_update_int_status(struct Si5351IntStatus *int_status) 742 | { 743 | uint8_t reg_val = 0; 744 | 745 | reg_val = si5351_read(SI5351_DEVICE_STATUS); 746 | 747 | /* Parse the register */ 748 | int_status->SYS_INIT_STKY = (reg_val >> 7) & 0x01; 749 | int_status->LOL_B_STKY = (reg_val >> 6) & 0x01; 750 | int_status->LOL_A_STKY = (reg_val >> 5) & 0x01; 751 | int_status->LOS_STKY = (reg_val >> 4) & 0x01; 752 | } 753 | 754 | void Si5351::si5351_set_ms_source(enum si5351_clock clk, enum si5351_pll pll) 755 | { 756 | uint8_t reg_val = 0x0c; 757 | uint8_t reg_val2; 758 | 759 | reg_val2 = si5351_read(SI5351_CLK0_CTRL + (uint8_t)clk); 760 | 761 | if(pll == SI5351_PLLA) 762 | { 763 | reg_val &= ~(SI5351_CLK_PLL_SELECT); 764 | } 765 | else if(pll == SI5351_PLLB) 766 | { 767 | reg_val |= SI5351_CLK_PLL_SELECT; 768 | } 769 | si5351_write(SI5351_CLK0_CTRL + (uint8_t)clk, reg_val); 770 | } 771 | -------------------------------------------------------------------------------- /si5351.h: -------------------------------------------------------------------------------- 1 | /* 2 | * si5351.h - Si5351 library for Arduino 3 | * 4 | * Copyright (C) 2014 Jason Milldrum 5 | * 6 | * Many defines derived from clk-si5351.h in the Linux kernel. 7 | * Sebastian Hesselbarth 8 | * Rabeeh Khoury 9 | * 10 | * do_div() macro derived from /include/asm-generic/div64.h in 11 | * the Linux kernel. 12 | * Copyright (C) 2003 Bernardo Innocenti 13 | * 14 | * This program is free software: you can redistribute it and/or modify 15 | * it under the terms of the GNU General Public License as published by 16 | * the Free Software Foundation, either version 3 of the License, or 17 | * (at your option) any later version. 18 | * 19 | * This program is distributed in the hope that it will be useful, 20 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 21 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 22 | * GNU General Public License for more details. 23 | * 24 | * You should have received a copy of the GNU General Public License 25 | * along with this program. If not, see . 26 | */ 27 | 28 | #ifndef SI5351_H_ 29 | #define SI5351_H_ 30 | 31 | #include "Arduino.h" 32 | #include "Wire.h" 33 | #include 34 | #include 35 | 36 | /* Define definitions */ 37 | 38 | #define SI5351_BUS_BASE_ADDR 0x60 39 | #define SI5351_XTAL_FREQ 25000000 40 | #define SI5351_PLL_FIXED 900000000 41 | 42 | #define SI5351_PLL_VCO_MIN 600000000 43 | #define SI5351_PLL_VCO_MAX 900000000 44 | #define SI5351_MULTISYNTH_MIN_FREQ 1000000 45 | #define SI5351_MULTISYNTH_DIVBY4_FREQ 150000000 46 | #define SI5351_MULTISYNTH_MAX_FREQ 160000000 47 | #define SI5351_MULTISYNTH67_MAX_FREQ SI5351_MULTISYNTH_DIVBY4_FREQ 48 | #define SI5351_CLKOUT_MIN_FREQ 8000 49 | #define SI5351_CLKOUT_MAX_FREQ SI5351_MULTISYNTH_MAX_FREQ 50 | #define SI5351_CLKOUT67_MAX_FREQ SI5351_MULTISYNTH67_MAX_FREQ 51 | 52 | #define SI5351_PLL_A_MIN 15 53 | #define SI5351_PLL_A_MAX 90 54 | #define SI5351_PLL_B_MAX (SI5351_PLL_C_MAX-1) 55 | #define SI5351_PLL_C_MAX 1048575 56 | #define SI5351_MULTISYNTH_A_MIN 6 57 | #define SI5351_MULTISYNTH_A_MAX 1800 58 | #define SI5351_MULTISYNTH67_A_MAX 254 59 | #define SI5351_MULTISYNTH_B_MAX (SI5351_MULTISYNTH_C_MAX-1) 60 | #define SI5351_MULTISYNTH_C_MAX 1048575 61 | #define SI5351_MULTISYNTH_P1_MAX ((1<<18)-1) 62 | #define SI5351_MULTISYNTH_P2_MAX ((1<<20)-1) 63 | #define SI5351_MULTISYNTH_P3_MAX ((1<<20)-1) 64 | 65 | #define SI5351_DEVICE_STATUS 0 66 | #define SI5351_INTERRUPT_STATUS 1 67 | #define SI5351_INTERRUPT_MASK 2 68 | #define SI5351_STATUS_SYS_INIT (1<<7) 69 | #define SI5351_STATUS_LOL_B (1<<6) 70 | #define SI5351_STATUS_LOL_A (1<<5) 71 | #define SI5351_STATUS_LOS (1<<4) 72 | #define SI5351_OUTPUT_ENABLE_CTRL 3 73 | #define SI5351_OEB_PIN_ENABLE_CTRL 9 74 | #define SI5351_PLL_INPUT_SOURCE 15 75 | #define SI5351_CLKIN_DIV_MASK (3<<6) 76 | #define SI5351_CLKIN_DIV_1 (0<<6) 77 | #define SI5351_CLKIN_DIV_2 (1<<6) 78 | #define SI5351_CLKIN_DIV_4 (2<<6) 79 | #define SI5351_CLKIN_DIV_8 (3<<6) 80 | #define SI5351_PLLB_SOURCE (1<<3) 81 | #define SI5351_PLLA_SOURCE (1<<2) 82 | 83 | #define SI5351_CLK0_CTRL 16 84 | #define SI5351_CLK1_CTRL 17 85 | #define SI5351_CLK2_CTRL 18 86 | #define SI5351_CLK3_CTRL 19 87 | #define SI5351_CLK4_CTRL 20 88 | #define SI5351_CLK5_CTRL 21 89 | #define SI5351_CLK6_CTRL 22 90 | #define SI5351_CLK7_CTRL 23 91 | #define SI5351_CLK_POWERDOWN (1<<7) 92 | #define SI5351_CLK_INTEGER_MODE (1<<6) 93 | #define SI5351_CLK_PLL_SELECT (1<<5) 94 | #define SI5351_CLK_INVERT (1<<4) 95 | #define SI5351_CLK_INPUT_MASK (3<<2) 96 | #define SI5351_CLK_INPUT_XTAL (0<<2) 97 | #define SI5351_CLK_INPUT_CLKIN (1<<2) 98 | #define SI5351_CLK_INPUT_MULTISYNTH_0_4 (2<<2) 99 | #define SI5351_CLK_INPUT_MULTISYNTH_N (3<<2) 100 | #define SI5351_CLK_DRIVE_STRENGTH_MASK (3<<0) 101 | #define SI5351_CLK_DRIVE_STRENGTH_2MA (0<<0) 102 | #define SI5351_CLK_DRIVE_STRENGTH_4MA (1<<0) 103 | #define SI5351_CLK_DRIVE_STRENGTH_6MA (2<<0) 104 | #define SI5351_CLK_DRIVE_STRENGTH_8MA (3<<0) 105 | 106 | #define SI5351_CLK3_0_DISABLE_STATE 24 107 | #define SI5351_CLK7_4_DISABLE_STATE 25 108 | #define SI5351_CLK_DISABLE_STATE_MASK 3 109 | #define SI5351_CLK_DISABLE_STATE_LOW 0 110 | #define SI5351_CLK_DISABLE_STATE_HIGH 1 111 | #define SI5351_CLK_DISABLE_STATE_FLOAT 2 112 | #define SI5351_CLK_DISABLE_STATE_NEVER 3 113 | 114 | #define SI5351_PARAMETERS_LENGTH 8 115 | #define SI5351_PLLA_PARAMETERS 26 116 | #define SI5351_PLLB_PARAMETERS 34 117 | #define SI5351_CLK0_PARAMETERS 42 118 | #define SI5351_CLK1_PARAMETERS 50 119 | #define SI5351_CLK2_PARAMETERS 58 120 | #define SI5351_CLK3_PARAMETERS 66 121 | #define SI5351_CLK4_PARAMETERS 74 122 | #define SI5351_CLK5_PARAMETERS 82 123 | #define SI5351_CLK6_PARAMETERS 90 124 | #define SI5351_CLK7_PARAMETERS 91 125 | #define SI5351_CLK6_7_OUTPUT_DIVIDER 92 126 | #define SI5351_OUTPUT_CLK_DIV_MASK (7 << 4) 127 | #define SI5351_OUTPUT_CLK6_DIV_MASK (7 << 0) 128 | #define SI5351_OUTPUT_CLK_DIV_SHIFT 4 129 | #define SI5351_OUTPUT_CLK_DIV6_SHIFT 0 130 | #define SI5351_OUTPUT_CLK_DIV_1 0 131 | #define SI5351_OUTPUT_CLK_DIV_2 1 132 | #define SI5351_OUTPUT_CLK_DIV_4 2 133 | #define SI5351_OUTPUT_CLK_DIV_8 3 134 | #define SI5351_OUTPUT_CLK_DIV_16 4 135 | #define SI5351_OUTPUT_CLK_DIV_32 5 136 | #define SI5351_OUTPUT_CLK_DIV_64 6 137 | #define SI5351_OUTPUT_CLK_DIV_128 7 138 | #define SI5351_OUTPUT_CLK_DIVBY4 (3<<2) 139 | 140 | #define SI5351_SSC_PARAM0 149 141 | #define SI5351_SSC_PARAM1 150 142 | #define SI5351_SSC_PARAM2 151 143 | #define SI5351_SSC_PARAM3 152 144 | #define SI5351_SSC_PARAM4 153 145 | #define SI5351_SSC_PARAM5 154 146 | #define SI5351_SSC_PARAM6 155 147 | #define SI5351_SSC_PARAM7 156 148 | #define SI5351_SSC_PARAM8 157 149 | #define SI5351_SSC_PARAM9 158 150 | #define SI5351_SSC_PARAM10 159 151 | #define SI5351_SSC_PARAM11 160 152 | #define SI5351_SSC_PARAM12 161 153 | 154 | #define SI5351_VXCO_PARAMETERS_LOW 162 155 | #define SI5351_VXCO_PARAMETERS_MID 163 156 | #define SI5351_VXCO_PARAMETERS_HIGH 164 157 | 158 | #define SI5351_CLK0_PHASE_OFFSET 165 159 | #define SI5351_CLK1_PHASE_OFFSET 166 160 | #define SI5351_CLK2_PHASE_OFFSET 167 161 | #define SI5351_CLK3_PHASE_OFFSET 168 162 | #define SI5351_CLK4_PHASE_OFFSET 169 163 | #define SI5351_CLK5_PHASE_OFFSET 170 164 | 165 | #define SI5351_PLL_RESET 177 166 | #define SI5351_PLL_RESET_B (1<<7) 167 | #define SI5351_PLL_RESET_A (1<<5) 168 | 169 | #define SI5351_CRYSTAL_LOAD 183 170 | #define SI5351_CRYSTAL_LOAD_MASK (3<<6) 171 | #define SI5351_CRYSTAL_LOAD_6PF (1<<6) 172 | #define SI5351_CRYSTAL_LOAD_8PF (2<<6) 173 | #define SI5351_CRYSTAL_LOAD_10PF (3<<6) 174 | 175 | #define SI5351_FANOUT_ENABLE 187 176 | #define SI5351_CLKIN_ENABLE (1<<7) 177 | #define SI5351_XTAL_ENABLE (1<<6) 178 | #define SI5351_MULTISYNTH_ENABLE (1<<4) 179 | 180 | /* Macro definitions */ 181 | 182 | /* 183 | * Based on former asm-ppc/div64.h and asm-m68knommu/div64.h 184 | * 185 | * The semantics of do_div() are: 186 | * 187 | * uint32_t do_div(uint64_t *n, uint32_t base) 188 | * { 189 | * uint32_t remainder = *n % base; 190 | * *n = *n / base; 191 | * return remainder; 192 | * } 193 | * 194 | * NOTE: macro parameter n is evaluated multiple times, 195 | * beware of side effects! 196 | */ 197 | 198 | # define do_div(n,base) ({ \ 199 | uint32_t __base = (base); \ 200 | uint32_t __rem; \ 201 | __rem = ((uint64_t)(n)) % __base; \ 202 | (n) = ((uint64_t)(n)) / __base; \ 203 | __rem; \ 204 | }) 205 | 206 | /* Enum definitions */ 207 | 208 | /* 209 | * enum si5351_variant - SiLabs Si5351 chip variant 210 | * @SI5351_VARIANT_A: Si5351A (8 output clocks, XTAL input) 211 | * @SI5351_VARIANT_A3: Si5351A MSOP10 (3 output clocks, XTAL input) 212 | * @SI5351_VARIANT_B: Si5351B (8 output clocks, XTAL/VXCO input) 213 | * @SI5351_VARIANT_C: Si5351C (8 output clocks, XTAL/CLKIN input) 214 | */ 215 | enum si5351_variant { 216 | SI5351_VARIANT_A = 1, 217 | SI5351_VARIANT_A3 = 2, 218 | SI5351_VARIANT_B = 3, 219 | SI5351_VARIANT_C = 4, 220 | }; 221 | 222 | enum si5351_clock {SI5351_CLK0, SI5351_CLK1, SI5351_CLK2, SI5351_CLK3, 223 | SI5351_CLK4, SI5351_CLK5, SI5351_CLK6, SI5351_CLK7}; 224 | 225 | enum si5351_pll {SI5351_PLLA, SI5351_PLLB}; 226 | 227 | enum si5351_drive {SI5351_DRIVE_2MA, SI5351_DRIVE_4MA, SI5351_DRIVE_6MA, SI5351_DRIVE_8MA}; 228 | 229 | /* Struct definitions */ 230 | 231 | struct Si5351RegSet 232 | { 233 | uint32_t p1; 234 | uint32_t p2; 235 | uint32_t p3; 236 | }; 237 | 238 | struct Si5351Status 239 | { 240 | uint8_t SYS_INIT; 241 | uint8_t LOL_B; 242 | uint8_t LOL_A; 243 | uint8_t LOS; 244 | uint8_t REVID; 245 | }; 246 | 247 | struct Si5351IntStatus 248 | { 249 | uint8_t SYS_INIT_STKY; 250 | uint8_t LOL_B_STKY; 251 | uint8_t LOL_A_STKY; 252 | uint8_t LOS_STKY; 253 | }; 254 | 255 | class Si5351 256 | { 257 | public: 258 | Si5351(void); 259 | void init(uint8_t); 260 | void set_freq(uint32_t, uint32_t, enum si5351_clock); 261 | void set_pll(uint32_t, enum si5351_pll); 262 | void clock_enable(enum si5351_clock, uint8_t); 263 | void drive_strength(enum si5351_clock, enum si5351_drive); 264 | void update_status(void); 265 | void set_correction(int32_t); 266 | int32_t get_correction(void); 267 | uint8_t si5351_write_bulk(uint8_t, uint8_t, uint8_t *); 268 | uint8_t si5351_write(uint8_t, uint8_t); 269 | uint8_t si5351_read(uint8_t); 270 | struct Si5351Status dev_status; 271 | struct Si5351IntStatus dev_int_status; 272 | private: 273 | void rational_best_approximation( 274 | unsigned long, unsigned long, 275 | unsigned long, unsigned long, 276 | unsigned long *, unsigned long *); 277 | uint32_t pll_calc(uint32_t, struct Si5351RegSet *, int32_t); 278 | uint32_t multisynth_calc(uint32_t, struct Si5351RegSet *); 279 | uint32_t multisynth_recalc(uint32_t, uint32_t,struct Si5351RegSet *); 280 | void si5351_update_sys_status(struct Si5351Status *); 281 | void si5351_update_int_status(struct Si5351IntStatus *); 282 | void si5351_set_ms_source(enum si5351_clock, enum si5351_pll); 283 | uint32_t ee_ref_correction; 284 | int32_t ref_correction; 285 | uint32_t plla_freq; 286 | uint32_t pllb_freq; 287 | }; 288 | 289 | #endif /* SI5351_H_ */ 290 | --------------------------------------------------------------------------------