V2.0
kk4das 2021-03-11 12:27:23 -05:00 zatwierdzone przez GitHub
commit b8605a9ad6
Nie znaleziono w bazie danych klucza dla tego podpisu
ID klucza GPG: 4AEE18F83AFDEB23
22 zmienionych plików z 4387 dodań i 0 usunięć

362
ButtonHandler.cpp 100644
Wyświetl plik

@ -0,0 +1,362 @@
// Button handler
#include "RadioControl.h"
//#define BTN_DEBUG
//////////////////////////////////////////////////////////////////////
// //
// Button Control Variables //
// //
//////////////////////////////////////////////////////////////////////
byte TuneButtonState = 0;
byte lastTuneButtonState = 0;
// Encoder button control
byte EncButtonState = 0;
byte lastEncButtonState = 0;
// Sideband select button control
byte SideBandButtonState = 0;
byte lastSideBandButtonState = 0;
// Band Switch button control
byte BandButtonState = 0;
byte lastBandButtonState = 0;
// VFO select button control
byte VFOButtonState = 0;
byte lastVFOButtonState = 0;
// PTT Control
byte PTTState = 0;
byte lastPTTState = 0;
//*********************Check Band ************************************
void SwapBand() {
uint32_t freq;
if (band == BAND20) { // Switch to 40 Meter Band
band=BAND40;
sideband=band40Sideband;
freq=band40Freq;
} else { // Switch to 20 Meter Band
band = BAND20;
sideband=band20Sideband;
freq=band20Freq;
}
//
// Make sure BFO clock tracks with band change
//
if (sideband == USB) {
bfo = USB_BFO;
} else {
bfo = LSB_BFO;
}
//
// Change the clock frequency to the new bfo
//
setBFO(bfo);
//
// Set the active VFO to new frequency
//
switch (active_vfo) {
case VFOA:
vfoAfreq=freq;
setVFO(vfoAfreq);
displayActVFO(vfoAfreq);
break;
case VFOB:
vfoBfreq=freq;
setVFO(vfoBfreq);
displayActVFO(vfoBfreq);
break;
}
//
// Update the display to show the active mode/sideband
//
displayMode(sideband);
startSettingsTimer();
}
void CheckBand() {
BandButtonState = digitalRead(BAND_BTN);
if (BandButtonState != lastBandButtonState) {
//
// On button press, change band 20/40
//
if (BandButtonState == LOW) { // if button pressed
#ifdef BTN_DEBUG
ToggleLED();
String msg = F("CheckBand");
displayBanner(msg);
#endif
SwapBand();
}
lastBandButtonState = BandButtonState;
Delay(50);
BandButtonState = digitalRead(BAND_BTN); //debounce
}
}
//*********************Check Sideband ************************************
void SwapSB() {
if (sideband == USB) { // Switch to LSB
sideband = LSB;
bfo = LSB_BFO;
} else { // Switch to USB
sideband = USB;
bfo = USB_BFO;
}
//
// Keep track of which SSB is currently selected for current band
//
if (band == BAND20) {
band20Sideband = sideband;
} else {
band40Sideband = sideband;
}
//
// Change the clock frequency to the new bfo
//
setBFO(bfo);
//
// Set the active VFO to adjusted frequency
//
switch (active_vfo) {
case VFOA:
setVFO(vfoAfreq);
vfoASideband=sideband;
break;
case VFOB:
setVFO(vfoBfreq);
vfoBSideband=sideband;
break;
}
//
// Update the display to show the active mode/sideband
//
displayMode(sideband);
startSettingsTimer();
}
void CheckSB() {
SideBandButtonState = digitalRead(SIDEBAND_BTN);
if (SideBandButtonState != lastSideBandButtonState) {
//
// On button press, change active sideband
// Set the BFO as appropriate
//
if (SideBandButtonState == LOW) { // if button pressed
SwapSB();
}
lastSideBandButtonState = SideBandButtonState;
Delay(50);
SideBandButtonState = digitalRead(SIDEBAND_BTN); //debounce
}
}
/*
//********************* Tune Button Handling ************************************
void DoTune() {
//
//
// CW experiment - call doCW to turn off BFO, set LO to operating frequencey+700
// Then activate Tx - should generate a carrier
//
// Does but level is too low to drive the Tx amps
//
displayTune(true);
setCW();
startTx(PTT_TUNE);
for (int i = 0; i < 100; i++) {
// tone(TONE_PIN, NOTE_B5);
Delay(50);
// noTone(TONE_PIN);
Delay(50);
}
stopTx();
displayTune(false);
}
*/
//********************* Tune Button Handling ************************************
void DoTune() {
//
// Temp code - 30 second full duty tone for debugging circuits - cheap tone generator.
// Delay(12);
// tone(TONE_PIN, NOTE_B5);
// Delay(30000);
// noTone(TONE_PIN);
displayTune(true);
startTx(PTT_TUNE);
for (int i = 0; i < 100; i++) {
tone(TONE_PIN, NOTE_B5);
Delay(50);
noTone(TONE_PIN);
Delay(50);
}
stopTx();
displayTune(false);
}
void CheckTune() {
TuneButtonState = digitalRead(TUNE_BTN); // creates a 10 second tuning pulse trani 50% duty cycle and makes TUNE appear on the screen
if (TuneButtonState != lastTuneButtonState) {
if (TuneButtonState == LOW) {
DoTune();
}
lastTuneButtonState = TuneButtonState;
Delay(50);
}
}
//*********************VFO switch******* VFO A or B ****************
void SwapVFO() {
if (active_vfo == VFOA) {
active_vfo = VFOB; // Make VFOB Active
sideband=vfoBSideband;
} else {
active_vfo = VFOA; // Make VFOA Active
sideband=vfoASideband;
}
//
// Adjust BFO in case sideband has changed
//
if (sideband == USB) { // Switch to LSB
bfo = USB_BFO;
} else { // Switch to USB
bfo = LSB_BFO;
}
setBFO(bfo);
displayMode(sideband); // Change sideband indicator
#ifdef BTN_DEBUG
ToggleLED();
String msg = F("SwapVFO: active_vfo=");
msg += active_vfo;
displayBanner(msg);
#endif
//
// Swap Active/Alternate frequency displays
//
switch (active_vfo) {
case VFOA:
setVFO(vfoAfreq);
displayActVFO(vfoAfreq);
displayAltVFO(vfoBfreq);
break;
case VFOB:
setVFO(vfoBfreq);
displayActVFO(vfoBfreq);
displayAltVFO(vfoAfreq);
break;
}
displayVFOAB(active_vfo); // Change the A/B indicator
displayMode(sideband); // Change sideband indicator
startSettingsTimer();
}
void CheckVFO() {
VFOButtonState = digitalRead(VFO_BTN);
if (VFOButtonState != lastVFOButtonState) {
if (VFOButtonState == LOW) { // button pressed
SwapVFO();
}
lastVFOButtonState = VFOButtonState;
Delay(50);
VFOButtonState = digitalRead(VFO_BTN); //debounce
}
}
// startSplit
// Turn on split mode and update the display
void startSplit() {
split = true;
displaySplit(split);
}
// Turnb off split mode and update the display
void stopSplit() {
split = false;
displaySplit(split);
}
// startTx
// if split mode is on swap Act and Alt VFO
// Put the rig in to TX by triggering the PTT pin
// Update the display to Tx
void startTx(byte PTT_source) {
if (split) SwapVFO();
digitalWrite(PTT,HIGH);
displayTxRx(TX);
TxRxState = TX;
txSource = PTT_source;
}
// stopTx
// Return the rig to Rx by lowering the PTT pin
// If split mode is on swap Act and Alt VFO
// Update the display to Tx
void stopTx() {
digitalWrite(PTT,LOW);
if (split) SwapVFO();
displayTxRx(RX);
TxRxState = RX;
}
//********************* PTT ****************************
void CheckPTT(){
if ((TxRxState==TX) && (txSource != PTT_MIC)) {
return;
}
TxRxState = digitalRead(PTT_SENSE);
if(TxRxState != lastTxRxState){
if(TxRxState == TX){
startTx(PTT_MIC);
} else {
if (txSource == PTT_MIC) {
stopTx();
}
}
lastTxRxState = TxRxState;
Delay(50);
}
}

287
CAT.cpp 100644
Wyświetl plik

@ -0,0 +1,287 @@
/**************************************************************
F40 CAT Control
1/24/2021 - KK4DAS Version 1.3
Changed to IC746 CAT Library
***************************************************************/
#include "RadioControl.h"
#include <IC746.h>
IC746 radio = IC746();
//#define CAT_DEBUG
// radio modes
#define MODE_LSB 00
#define MODE_USB 01
#define MODE_CW 02
// TX Rx State
// FT857D combine TX and RX status
// 0b abcdefgh
// a = 0 = PTT on
// a = 1 = PTT off
// b = 0 = HI SWR off
// b = 1 = HI SWR on
// c = 0 = split on
// c = 1 = split off
// d = dummy data
// efgh = SWR meter data ??
// function to run when we must put radio on TX/RX
// If PTT requests transmit and the rig is not already transmitting, start TX
// If PTT request stop transmit and the current trasnmit source is PTT_CAT then stop TX
//
void catGoPtt(boolean pttf) {
#ifdef CAT_DEBUG
String msg = F("CatGoPtt ");
msg += pttf;
displayBanner(msg);
#endif
if ((TxRxState == TX) && (txSource != PTT_CAT)) {
return;
}
if (pttf) {
// displayDebug("CAT PTT ON ");
if (TxRxState == RX) {
startTx(PTT_CAT);
}
} else {
if (txSource == PTT_CAT) {
// displayDebug("CAT PTT OFF");
stopTx();
}
}
}
boolean catGetPtt() {
#if defined (DEBUG)
String msg = "GetPTT: ";
if (ptt == PTT_TX) {
msg += "Tx";
} else {
msg += "Rx";
}
displayPrintln(msg);
#endif
if (TxRxState == TX) {
return true;
} else {
return false;
}
}
void catGoSplit(boolean cat_split) {
#ifdef CAT_DEBUG
String msg = F("CatGoSplit ");
msg += cat_split;
displayBanner(msg);
#endif
if (cat_split) {
startSplit();
} else {
stopSplit();
}
}
// function to run when VFOs A/B are toggled
void catSwapVfo() {
SwapVFO();
#ifdef CAT_DEBUG
String msg = F("catGoToggleVFOs");
displayBanner(msg);
#endif
}
// function to set a freq from CAT
void catSetFreq(long f) {
if (f == 0) return; // ignore spurious command
//
// Change the frequency of the current active VFO
// Clock frequency is the operating frequecy plus the BFO
//
if (active_vfo == VFOA) {
vfoAfreq = f;
setVFO(vfoAfreq);
displayActVFO(vfoAfreq);
} else {
vfoBfreq = f;
setVFO(vfoBfreq);
displayActVFO(vfoBfreq);
}
#ifdef CAT_DEBUG
String msg = F("catsetFreq ");
msg += f;
displayBanner(msg);
#endif
startSettingsTimer();
}
// function to set the mode(LSB or USB) from the cat command
void catSetMode(byte m) {
// If the new mode is different from the current sideband, then swap USB/LSB
switch (sideband) {
case USB:
if (m == MODE_LSB) SwapSB();
break;
case LSB:
if (m == MODE_USB) SwapSB();
break;
}
#ifdef CAT_DEBUG
String msg = F("CatSetMode ");
if (sideband == USB ) {
msg += F("USB ");
} else {
msg += F("LSB ");
}
msg += m;
displayBanner(msg);
#endif
}
// function to pass the freq to the cat library
long catGetFreq() {
// this must return the freq as an unsigned long in Hz, you must prepare it before
long freq;
// displayPrintln("catGetFreq called");
if (active_vfo == VFOA) {
freq = vfoAfreq;
} else {
freq = vfoBfreq;
}
#ifdef CAT_DEBUG
String msg = F("catGetFreq ");
msg += freq;
displayBanner(msg);
#endif
return freq;
}
// function to pass the mode to the cat library
byte catGetMode() {
// this must return the mode in the wat the CAT protocol expect it
byte mode;
if (sideband == USB) {
mode = MODE_USB;
} else {
mode = MODE_LSB;
}
#ifdef CAT_DEBUG
String msg = F("CatGetMode ");
if (sideband == USB ) {
msg += F("USB ");
} else {
msg += F("LSB ");
}
msg += mode;
displayBanner(msg);
#endif
return mode;
}
// function to pass the smeter reading in RX mode
byte catGetSMeter() {
// this must return a byte in with the 4 LSB are the S meter data
// so this procedure must take care of convert your S meter and scale it
// up to just 4 bits
#ifdef CAT_DEBUG
String msg = F("CatGetSMeter ");
msg += smeter;
displayBanner(msg);
#endif
return smeter;
}
// Function to select the active VFO from the cat command
// If requested VFO is not already active then swap active and alternate
void catSetVFO(byte catVfo) {
if ( ((catVfo == CAT_VFO_A) && (active_vfo == VFOB)) ||
((catVfo == CAT_VFO_B) && (active_vfo == VFOA))) {
SwapVFO();
}
#if defined (DEBUG)
String msg = "SetVFO: ";
if (v == CAT_VFO_A) {
msg += "VFO-A";
} else {
msg += "VFO-B";
}
displayPrintln(msg);
#endif
}
// Function to make VFOS the same
void catVfoAtoB() {
if (active_vfo == VFOA) {
vfoBfreq = vfoAfreq;
vfoBSideband = vfoASideband;
displayAltVFO(vfoBfreq);
} else {
vfoAfreq = vfoBfreq;
vfoASideband = vfoBSideband;
displayAltVFO(vfoAfreq);
}
#if defined (DEBUG)
String msg = "VfoAtoB";
displayPrintln(msg);
#endif
}
void setupCat() {
// Setup the CAT control command handlers
radio.addCATPtt(catGoPtt);
radio.addCATGetPtt(catGetPtt);
radio.addCATAtoB(catVfoAtoB);
radio.addCATSwapVfo(catSwapVfo);
radio.addCATsplit(catGoSplit);
radio.addCATFSet(catSetFreq);
radio.addCATMSet(catSetMode);
radio.addCATVSet(catSetVFO);
radio.addCATGetFreq(catGetFreq);
radio.addCATGetMode(catGetMode);
radio.addCATSMeter(catGetSMeter);
// now we activate the library
radio.begin(19200, SERIAL_8N1);
#ifdef CAT_DEBUG
displayBanner(String(F("setupCAT")));
#endif
}
void CheckCat() {
radio.check();
}

16
CAT.h 100644
Wyświetl plik

@ -0,0 +1,16 @@
#ifndef CAT_h
#define CAT_H
/**************************************************************
F40 CAT Control
5/23/202 - KK4DAS Version 0.9
Incorporated CAT Control Library
FT857D CAT Library, by Pavel Milanes, CO7WT, pavelmc@gmail.
https://github.com/pavelmc/FT857d/
***************************************************************/
//=============== Function Prototypes ============================================
extern void setupCat();
extern void CheckCat();
#endif

243
Encoder.cpp 100644
Wyświetl plik

@ -0,0 +1,243 @@
//
// Encoder fuctions
//
#include "RadioControl.h"
//#DEFINE DEBUG_ENC
//////////////////////////////////////////////////////////////////////
// Rotary Enconder //
//////////////////////////////////////////////////////////////////////
Rotary encoder = Rotary(ENCODER_A, ENCODER_B, ENCODER_BTN); // Setup Encoder
//////////////////////////////////////////////////////////////////////
// //
// Rotary Encoder Variables //
// Number of clockwise and counterclockwise ticks //
// Delta between successive measurements //
// //
//////////////////////////////////////////////////////////////////////
volatile int encoder_count = 0; // count of encoder clicks +1 for CW, -1 for CCW (volatile since used in ISR)
int prev_encoder_count = 0; // used to measure changes over time
int encoder_delta = 0; // differrnce between successive checks of encoder count
bool incrementChanged = false;
// when multiplied by tuning increment tells what the frequency change on
// on the active VFO will be
// Encoder button control
extern byte EncButtonState;
extern byte lastEncButtonState;
///////////////////////////////////////////////////////////
// ************* ISR **************** //
// Interrupt service routine, called on encoder movement //
// Only interested in completed clicks //
// +1 for Clocwwise //
// -1 for Counter Clockwise //
// Ignore intermediate values //
///////////////////////////////////////////////////////////
#if _BOARDTYPE != Every
ISR(PCINT2_vect) {
unsigned char result = encoder.process();
if (result == DIR_CW) {
encoder_count++;
} else if (result == DIR_CCW) {
encoder_count--;
}
}
#else
#define PA0_INTERRUPT PORTA.INTFLAGS & PIN0_bm
#define PA0_CLEAR_INTERRUPT_FLAG PORTA.INTFLAGS &= PIN0_bm
#define PF5_INTERRUPT PORTF.INTFLAGS & PIN5_bm
#define PF5_CLEAR_INTERRUPT_FLAG PORTF.INTFLAGS &= PIN5_bm
ISR(PORTA_PORT_vect) {
unsigned char result = encoder.process();
if (PA0_INTERRUPT)
PA0_CLEAR_INTERRUPT_FLAG;
if (result == DIR_CW) {
encoder_count++;
} else if (result == DIR_CCW) {
encoder_count--;
}
}
ISR(PORTF_PORT_vect) {
unsigned char result = encoder.process();
if (PF5_INTERRUPT)
PF5_CLEAR_INTERRUPT_FLAG;
if (result == DIR_CW) {
encoder_count++;
} else if (result == DIR_CCW) {
encoder_count--;
}
}
#endif
//*******************Setup Interrupt Service Routine********************
void setupEncoderISR() {
cli();
// Set up for the rotary encoder interrupts
#if _BOARDTYPE != Every
PCICR |= (1 << PCIE2);
PCMSK2 |= (1 << PCINT18) | (1 << PCINT19);
#endif
#if _BOARDTYPE == Every
PORTA.PIN0CTRL |= PORT_PULLUPEN_bm | PORT_ISC_BOTHEDGES_gc;
PORTF.PIN5CTRL |= PORT_PULLUPEN_bm | PORT_ISC_BOTHEDGES_gc;
#endif
sei();
}
//********************CheckEncoder*******************************************
// roundedF - Calculates new frequency
// After an increment change rounds up or down to the next even increment
// Otherwise just changes increments or decrements the frequency
//
uint32_t roundedF(uint32_t freq, int32_t delta) {
uint32_t f = freq;
/* if (incrementChanged) {
if (f % increment) {
if (delta > 0 ) {
f = f + increment - (f % increment);
} else {
f = f - (f % increment);
}
}
incrementChanged = false;
} else {
f = f + delta;
}
*/
if (f % increment) {
if (delta > 0 ) {
f = f + increment - (f % increment);
} else {
f = f - (f % increment);
}
} else {
f = f + delta;
}
return f;
}
void AdjustVFO(long delta) {
switch (active_vfo) {
case VFOA:
// vfoAfreq = vfoAfreq + delta;
vfoAfreq = roundedF(vfoAfreq, delta);
setVFO(vfoAfreq);
displayActVFO(vfoAfreq);
break;
case VFOB:
// vfoBfreq = vfoBfreq + delta;
vfoBfreq = roundedF(vfoBfreq, delta);
setVFO(vfoBfreq);
displayActVFO(vfoBfreq);
break;
}
startSettingsTimer();
}
void CheckEncoder() {
int current_count = encoder_count; // grab the current encoder_count
int32_t encoder_delta = 0;
if (current_count != prev_encoder_count) { // if there is any change in the encoder coount
#ifdef DEBUG_ENC
sprintf(debugmsg, "VFOA: %ld", vfoAfreq);
Serial.println(debugmsg);
#endif
//
// Calculate the delta (how many click positive or negaitve)
//
encoder_delta = current_count - prev_encoder_count;
//
// Adjust the currently selected VFO
//
AdjustVFO(encoder_delta * increment);
#ifdef DEBUG
sprintf(debugmsg, "current_count: %d, New VFOA: %ld", current_count, vfoAfreq);
Serial.println(debugmsg);
#endif
prev_encoder_count = current_count; // save the current_count for next time around
}
}
//********************CheckIncrement*******************************************
// Cycle through tuning increment values on button press
// 10, 100, 1K, 10K, 100K, 1M
//********************CheckIncrement*******************************************
void AdvanceIncrement() {
if (increment == 10) {
increment = 100;
}
else if (increment == 100) {
increment = 1000;
}
else if (increment == 1000) {
increment = 10000;
}
else if (increment == 10000) {
increment = 100000;
}
else if (increment == 100000) {
increment = 1000000;
}
else {
increment = 10;
}
displayIncr(increment);
incrementChanged = true;
startSettingsTimer();
}
void CheckIncrement () {
EncButtonState = encoder.buttonState();
//EncButtonState = digitalRead(ENCODER_BTN);
#ifdef DEBUG
sprintf(debugmsg, "Encoder button state: %d", EncButtonState);
Serial.println(debugmsg);
Delay(1000);
#endif
if (EncButtonState != lastEncButtonState) {
#ifdef DEBUG
sprintf(debugmsg, "Encoder button state: %d", EncButtonState);
Serial.println(debugmsg);
#endif
if (EncButtonState == LOW) {
AdvanceIncrement();
}
lastEncButtonState = EncButtonState;
Delay(50);
EncButtonState = encoder.buttonState(); //debounce
//EncButtonState = digitalRead(ENCODER_BTN);
}
}
void setupEncoder() {
setupEncoderISR();
}

229
RadioControl.h 100644
Wyświetl plik

@ -0,0 +1,229 @@
#ifndef RadioControl_h
#define RadioControl_h
#define CALLSIGN "KK4DAS"
#define VERSION "1.4"
#define RIGNAME "SimpleSSB"
//#define DEBUG
#ifdef DEBUG
extern char debugmsg[];
#endif
//=============================== FEATURE SELECTION =========================
// Each flag below enables optional features
// DISPLAY_X - uncomment one line depending on the display that is attached
// BFOxXMHS - IF filter center frequency - eitherr 9MHz or 12MHZ
// SMNETER - Uncomment if S-meter sensor circuit is installed
// DUAL_BAND - Uncomment to enable band switching 20/40 if installed
// CW - Uncomment if CW mod installed (future)
//=============================== DISPLAY TYPE ==============================
#define DISPLAY_LCD //uncomment for 20x4 LCD
//#define DISPLAY_TFT // uncomment for 320x240 TFT
//#define DISPLAY_NEXTION //uncomment for 2.8" Nextion
//=============================== IF FILTER FREQ ============================
#define BFO9MHZ // uncomment for 9.0 MHz IF
//#define BFO12MHZ // uncomment for 12.0 MHz IF
//=============================== S-METER INSTALLED =========================
//#define SMETER // uncomment if SMETER mod installed
//=============================== DUAL BAND MOD INSTALLED ===================
//#define DUAL_BAND // uncomment if dual band mod installed 20/40
//=============================== DISPLAY TYPE ==============================
//#define CW // uncomment if CW enabled (not complete)
//============================== BOARD TYPE (Nano, Every) =====================
#define Nano 0
#define Every 1
#define Uno 2
#if defined(ARDUINO_AVR_NANO)
#define _BOARDTYPE Nano
#endif
#if defined(ARDUINO_AVR_NANO_EVERY)
#define _BOARDTYPE Every
#endif
#if defined (ARDUINO_AVR_UNO)
#define _BOARDTYPE Uno
#endif
//============================= INCLUDES =====================================
#include <Arduino.h>
#include "Utility.h" // General purpose common functions
#include "SSB_Display.h" // Display handling
#include "RotaryEnc.h" // Rotary encoder handling
#include "si5351.h" // SI 5351 clock
#include "CAT.h" // CAT Control handling
#include "Settings.h" // Get/Store Settings in EEPROM
#include "Smeter.h" // Smeter definitions
//============================== Symbolic constants ==========================
//////////////////////////////////////////////////////////////////////
// //
// Arduino Pin Definitions //
// //
//////////////////////////////////////////////////////////////////////
#define ENCODER_A 2 // Rotary Lib Default - Encoder pin A D2 (interrupt pin)
#define ENCODER_B 3 // Rotary Lib Default - Encoder pin B D3 (interrupt pin)
#define PTT_SENSE 4 // Detect Mic PT
#define PTT 5 // LOW=Rx, HIGH=Tx
// Dual Band Pins (requires dual band mod)
#define BAND_BTN 6 // Band Switch momentary button
#define BAND_PIN 7 // Band Switch Relay - LOW = Band A (40M), HIGH = Band B (20M)
#define VFO_BTN A0 // VFO A/B button
#define SIDEBAND_BTN A1 // USB/LSB button
#define TUNE_BTN A2 // Tune Button
#define ENCODER_BTN A3 // Rotary Lib Default - Encoder push button
#define I2C_SDA A4 // I2C SDA Pin
#define I2C_SCL A5 // I2C SCL Pin
#define TONE_PIN A6 // Audio out for tune tone
// Smeter
#define SMETER_PIN A7 // Requires Signal Strength Sensor
//
// For Nextion / Nano Every Only
// Pin 0,1 Serial RX/TX
// For color TFT Only
// Arduino
// Pin TFT Pin
// -----------|---------
// 8 | RST - any free Arduino Pin (not used in this sketch)
// 9 | DC - any free Arduino Pin
// 10 | CS - any free Arduino Pin
// 11 | MOSI - fixed
// 12 | MISO - fixed
// 13 | CLK - fixed
//
// Tune Tone
#define NOTE_B5 988 // Tune tone
//
// Dual Band Mode Constants (requires DUAL_BAND)
//
#define BAND20 1
#define BAND40 2
#define BAND20_EDGE 14000000
#define BAND40_EDGE 7000000
// VFO, Sideband and TX state are used both here and in the display
// Define here if not already defined
// VFO selection
#define VFOA 0
#define VFOB 1
// Sideband selection
#define USB 0
#define LSB 1
// Transmit state
#define TX LOW // TX is on when PTT switched to ground
#define RX HIGH
// PTT Source
#define PTT_MIC 0
#define PTT_CAT 1
#define PTT_TUNE 2
//=============== Globals ============================================
// Rotary Encoder
extern Rotary encoder;
// BFO
extern const uint32_t USB_BFO;
extern const uint32_t LSB_BFO;
extern uint32_t bfo; // Startup BFO frequency
extern const uint32_t BFO_DELTA; // Difference between USB and LSB for BFO change
// VFO A/B frequencies
extern uint32_t vfoAfreq;
extern uint32_t vfoBfreq;
extern byte vfoASideband;
extern byte vfoBSideband;
// Tuning increment
extern uint32_t increment;
// Active VFO indicator
extern byte active_vfo;
// Active sideband (USB or LSB)
extern byte sideband;
// Band specific memory - requires DUAL_BAND
extern uint32_t band20Freq;
extern uint32_t band40Freq;
extern byte band20Sideband;
extern byte band40Sideband;
extern byte band;
// Transmit state
extern byte TxRxState;
extern byte lastTxRxState;
// Transmoit source (mic, CAT)
extern byte txSource;
// S Meter
extern byte smeter;
// Split mode
extern bool split;
//=============== Function Prototypes ============================================
extern void setupEncoder();
extern void setVFO(uint32_t freq);
extern void setBFO(uint32_t freq);
extern void CheckIncrement();
extern void AdvanceIncrement();
extern void CheckEncoder();
extern void AdjustVFO(long delta);
extern void CheckSB();
extern void SwapSB();
extern void CheckTune();
extern void DoTune();
extern void CheckVFO();
extern void SwapVFO();
extern void CheckPTT();
extern void setupPins();
extern void startTx(byte PTT_source);
extern void stopTx();
extern void startSplit();
extern void stopSplit();
extern void CheckBand(); // Only called if DUAL_BAND enabled
extern void CheckSmeter(); // Only called if SMETER enabled
#ifdef CW
extern void setCW();
#endif
#endif

122
RotaryEnc.cpp 100644
Wyświetl plik

@ -0,0 +1,122 @@
/* Rotary encoder handler for arduino.
*
* Revision: 2020 Dean Souleles, KK4DAS, Licensed under the GNU GPL Version 3.
* Contact: kk4das@gmail.com
* Added default pins and button handling
*
* Copyright 2011 Ben Buxton. Licenced under the GNU GPL Version 3.
* Contact: bb@cactii.net
*
*/
#include "Arduino.h"
#include "RotaryEnc.h"
/*
* The below state table has, for each state (row), the new state
* to set based on the next encoder output. From left to right in,
* the table, the encoder outputs are 00, 01, 10, 11, and the value
* in that position is the new state to set.
*/
#define R_START 0x0
#ifdef HALF_STEP
// Use the half-step state table (emits a code at 00 and 11) // nulled out to only use full step
#define R_CCW_BEGIN 0x1
#define R_CW_BEGIN 0x2
#define R_START_M 0x3
#define R_CW_BEGIN_M 0x4
#define R_CCW_BEGIN_M 0x5
const unsigned char ttable[6][4] = {
// R_START (00)
{R_START_M, R_CW_BEGIN, R_CCW_BEGIN, R_START},
// R_CCW_BEGIN
{R_START_M | DIR_CCW, R_START, R_CCW_BEGIN, R_START},
// R_CW_BEGIN
{R_START_M | DIR_CW, R_CW_BEGIN, R_START, R_START},
// R_START_M (11)
{R_START_M, R_CCW_BEGIN_M, R_CW_BEGIN_M, R_START},
// R_CW_BEGIN_M
{R_START_M, R_START_M, R_CW_BEGIN_M, R_START | DIR_CW},
// R_CCW_BEGIN_M
{R_START_M, R_CCW_BEGIN_M, R_START_M, R_START | DIR_CCW},
};
#else
// Use the full-step state table (emits a code at 00 only)
#define R_CW_FINAL 0x1
#define R_CW_BEGIN 0x2
#define R_CW_NEXT 0x3
#define R_CCW_BEGIN 0x4
#define R_CCW_FINAL 0x5
#define R_CCW_NEXT 0x6
const unsigned char ttable[7][4] = {
// R_START
{R_START, R_CW_BEGIN, R_CCW_BEGIN, R_START},
// R_CW_FINAL
{R_CW_NEXT, R_START, R_CW_FINAL, R_START | DIR_CW},
// R_CW_BEGIN
{R_CW_NEXT, R_CW_BEGIN, R_START, R_START},
// R_CW_NEXT
{R_CW_NEXT, R_CW_BEGIN, R_CW_FINAL, R_START},
// R_CCW_BEGIN
{R_CCW_NEXT, R_START, R_CCW_BEGIN, R_START},
// R_CCW_FINAL
{R_CCW_NEXT, R_CCW_FINAL, R_START, R_START | DIR_CCW},
// R_CCW_NEXT
{R_CCW_NEXT, R_CCW_FINAL, R_CCW_BEGIN, R_START},
};
#endif
/*
* Constructor Common
*/
void Rotary::Constructor_Common() {
// Set pins to input.
pinMode(pin1, INPUT);
pinMode(pin2, INPUT);
pinMode(btn, INPUT);
#ifdef ENABLE_PULLUPS
digitalWrite(pin1, HIGH);
digitalWrite(pin2, HIGH);
digitalWrite(btn, HIGH);
#endif
// Initialise state.
state = R_START;
}
/*
* Constructor. Each arg is the pin number for each encoder contact.
*/
Rotary::Rotary(char _pin1, char _pin2,char _btn) {
// Assign variables.
pin1 = _pin1;
pin2 = _pin2;
btn = _btn;
Constructor_Common();
}
Rotary::Rotary() {
// Assign variables to defaults
pin1 = ENCODER_A;
pin2 = ENCODER_B;
btn = ENCODER_BTN;
Constructor_Common();
}
unsigned char Rotary::process() {
// Grab state of input pins.
unsigned char pinstate = (digitalRead(pin2) << 1) | digitalRead(pin1);
// Determine new state from the pins and state table.
state = ttable[state & 0xf][pinstate];
// Return emit bits, ie the generated event.
return state & 0x30;
}
byte Rotary::buttonState() { // returns the state of the Encoder button LOW=pressed
// return byte(digitalRead(btn));
return digitalRead(btn);
}

67
RotaryEnc.h 100644
Wyświetl plik

@ -0,0 +1,67 @@
/*
* Rotary encoder library for Arduino.
*
* Revision: 2020 Dean Souleles, KK4DAS, Licensed under the GNU GPL Version 3.
* Contact: kk4das@gmail.com
* Added default pins and button handling and setup for clean interrupt handling
*
* Copyright 2011 Ben Buxton. Licenced under the GNU GPL Version 3.
* Contact: bb@cactii.net
*
*/
#ifndef RotaryEnc_h
#define RotaryEnc_h
#include "Arduino.h"
// Enable this to emit codes twice per step.
//#define HALF_STEP
// Enable weak pullups
#define ENABLE_PULLUPS
// Values returned by 'process'
// No complete step yet.
#define DIR_NONE 0x0
// Clockwise step.
#define DIR_CW 0x10
// Counter-clockwise step.
#define DIR_CCW 0x20
//
// Default Encoder wiring Pins D2 and D3 for the encoder, A3 for the pushbutton
//
#ifndef ENCODER_A
#define ENCODER_A 2 // Encoder pin A D2 (interrupt pin)
#endif
#ifndef ENCODER_B
#define ENCODER_B 3 // Encoder pin B D3 (interrupt pin)
#endif
#ifndef ENCODER_BTN
#define ENCODER_BTN A3 // Encoder push buttonh
#endif
class Rotary
{
public:
Rotary(char, char, char);
Rotary();
unsigned char process();
byte buttonState();
private:
unsigned char state;
unsigned char pin1;
unsigned char pin2;
unsigned char btn;
void Constructor_Common();
};
#endif

42
SSB_Display.h 100644
Wyświetl plik

@ -0,0 +1,42 @@
#ifndef SSB_Display_h
#define SSB_Display_h
/*
* SSB_Display.h
* KK4DAS, Dean Souleles, KK4DAS@gmail.com
* May 30, 2020
*
* Constants and function prototypes used in the Display Routines
* This file needs to be in included in the main sketch
*
*/
#include "RadioControl.h"
// ===========================Function Prototypes==================================
extern void displaySMeter(byte level);
extern void displayBanner(String s);
extern void displayActVFO(uint32_t freq);
extern void displayAltVFO(uint32_t freq);
extern void displayVFOAB(int vfo);
extern void displayTxRx(int tx_rx);
extern void displayMode(int mode);
extern void displayIncr(uint32_t increment);
extern void displayTune(boolean on_off);
extern void displaySplit(boolean splt);
extern void displaySetup(String banner,
uint32_t vfoActfreq, uint32_t vfoAltfreq,
uint32_t activeVFO,
int tx_rx,
int sideband,
boolean split,
uint32_t increment,
byte s_meter);
#ifdef DISPLAY_NEXTION
extern void CheckTouch();
#endif
#endif

280
SSB_LCD_Display.cpp 100644
Wyświetl plik

@ -0,0 +1,280 @@
#include "RadioControl.h"
#ifdef DISPLAY_LCD
#include <LiquidCrystal_I2C.h>
LiquidCrystal_I2C lcd(0x27, 20, 4); //0x27 or 0x3F 0r 0x20 //1604 LCD/w I2C
//////////////////////////////////////////////////////////////////////
// //
// Display Routines //
// Display routines for 20x4 LCD //
// //
//////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////
// Display Layout 20x4 //
// 1111111111 //
// 01234567890123456789 //
// 0: A>xX.XXX.XXX xSB //
// 1: B>xX.XXX.XXX SPLIT //
// 2: RX S:123456789+++ //
// 3: CALL xxxx Hz vX.X //
//////////////////////////////////////////////////////////////////////
//************************displayBanner*****************************
// Sets up display line 3 - call sign, increment display and version
//
// Note - Does not use the input String (included for code compatibility
// with other display modules)
//
void displayBanner(String s) {
// Setup line 3 call and tuning increment
lcd.setCursor(0, 3);
lcd.print(CALLSIGN);
lcd.setCursor(12, 3);
lcd.print("Hz");
lcd.setCursor(16, 3);
lcd.print(F("v"));
lcd.print(VERSION);
}
//************************displaySmeter****************************
void displaySMeter(byte level) {
int i;
lcd.setCursor(6, 2);
lcd.print("S:");
for (i = 0; i < MAXSLEVELS; i++) {
if (level >= i) {
if (i < 9) {
lcd.print(i + 1);
} else {
lcd.print("+");
}
} else {
lcd.print(" ");
}
}
}
//************************displayVFOAB*******************************
// displayVFOAB(int VFO)
// Updates the display with flag for the currently active VFO - A or B
//
void displayVFOAB(int vfo) {
// Reset the active VFO flag
// Clear previous flag
lcd.setCursor(1,0);
lcd.print(F(" "));
lcd.setCursor(1,1);
lcd.print(F(" "));
// Set flag by currently active VFO
if (vfo == VFOA) {
lcd.setCursor(1, 0);
} else {
lcd.setCursor(1, 1);
}
lcd.print(F(">"));
}
//************************displayVFO*******************************
// displayVFO(vfo,freq)
// Updates the dispaly for the input vfo
// Displays the frequency subracting the bfo - sideband inversion
//
void displayVFO(int vfo, long freq) {
int row = 0;
if (vfo == VFOA) {
row = 0;
} else if (vfo == VFOB) {
row = 1;
}
// Update the frequency
lcd.setCursor(2, row);
if (freq < 10000000) {
lcd.print(" ");
}
lcd.print(int((freq / 1000000))); //millions
lcd.print(".");
lcd.print(((freq / 100000) % 10)); //hundredthousands
lcd.print(((freq / 10000) % 10)); //tenthousands
lcd.print(((freq / 1000) % 10)); //thousands
lcd.print(".");
lcd.print(((freq / 100) % 10)); //hundreds
lcd.print(((freq / 10) % 10)); //tens
lcd.print(((freq / 1) % 10)); //ones
}
//************************displayActVFO*******************************
// Updates the frequencey display for the current active VFO A or B
void displayActVFO(uint32_t freq) {
displayVFO(active_vfo, freq);
}
//************************displayActVFO*******************************
// Updates the frequencey display for the current alternate VFO A or B
extern void displayAltVFO(uint32_t freq) {
if (active_vfo == VFOA) {
displayVFO(VFOB, freq);
} else{
displayVFO(VFOA, freq);
}
}
//************************displayMode*******************************
//
// displaySideband(mode)
// Updates the current mode (sideband) indicator U or L
//
void displayMode(int mode) {
char sb = ' ';
if (mode == USB) {
sb = 'U';
} else if (mode == LSB) {
sb = 'L';
}
lcd.setCursor(13, 0);
lcd.print(sb);
}
void displayTxRx(int tx_rx) {
lcd.setCursor(0, 2);
if (tx_rx == RX) {
lcd.print(F("RX"));
} else {
lcd.print(F("TX"));
}
}
//************************displayIncr*******************************
//
// displayIncr(increment)
// Display the tuning increment
//
void displayIncr(unsigned long increment) {
String hertz = " "; // tune step display
#ifdef DEBUG
sprintf(debugmsg, "Increment: %ld", increment);
Serial.println(debugmsg);
#endif
if (increment == 10) {
hertz = F(" 10");
} else if (increment == 100) {
hertz = F(" 100");
} else if (increment == 1000) {
hertz = F(" 1K");
} else if (increment == 10000) {
hertz = F(" 10K");
} else if (increment == 100000) {
hertz = F("100K");
} else if (increment == 1000000) {
hertz = F(" 1M");
}
lcd.setCursor(7, 3);
lcd.print(hertz);
}
//************************displayTune*******************************
//
// displayTune(On)
// If On is TRUE displays TUNE message else clears it
//
void displayTune(bool On) {
lcd.setCursor(7, 2);
if (On == true) {
lcd.print(F("TUNE"));
} else {
lcd.print(F(" "));
}
}
void displayDebug(String msg) {
lcd.setCursor(7, 2);
lcd.print(msg);
}
//************************displaySplit*******************************
//
// displayTune(split)
// If split is TRUE displays SPLIT message else clears it
//
void displaySplit(boolean splt) {
lcd.setCursor(14, 1);
if (split == true) {
lcd.print(F("SPLIT"));
} else {
lcd.print(F(" "));
}
}
///////////////////////////////////////////////////////////////////////////
// displaySetup()
// Initialze and populate the display
///////////////////////////////////////////////////////////////////////////
void displaySetup(String banner,
uint32_t vfoActfreq, uint32_t vfoAltfreq,
uint32_t activeVFO,
int tx_rx,
int sideband,
boolean split,
uint32_t increment,
byte s_meter) {
lcd.init();
lcd.clear();
delay(100);
lcd.backlight();
// Setup fixed screeen elements
// Line 0 - VFO A
lcd.setCursor(0,0);
lcd.print(F("A"));
// Line 0 - sideband indicator
lcd.setCursor(14,0);
lcd.print(F("SB"));
// Line 1 VFO B
lcd.setCursor(0,1);
lcd.print(F("B"));
//
// Display the intitial values
//
displayBanner(banner);
displayActVFO(vfoActfreq);
displayAltVFO(vfoAltfreq);
displayVFOAB(activeVFO);
displayTxRx(tx_rx);
displayMode(sideband);
displaySplit(split);
displayIncr(increment);
#ifdef SMETER
displaySMeter(s_meter);
#endif
}
#endif

Wyświetl plik

@ -0,0 +1,521 @@
#include "RadioControl.h"
#ifdef DISPLAY_NEXTION
//#define NEX_DEBUG // sends debug messages to the banner (bottom of screen)
//#define NEX_DEBUG1
// Dean Souleles, KK4DAS
// 7/25/2020
//
// Nextion Display Module for SSB_Radio_Contrl
// Nextion 2.8 Inch display
// Tested on Arduino Nano
//
// Arduino Nano Every
// PINS / WIRING
// ----------------------
// Arduino | Nextion
// --------|-------------
// 1 (RX) | TX (Blu) -- pins 0 and 1 are for Serial1
// 0 (TX) | RX (Yel)
// +5 | +5 (Red)
// Gnd | Gnd (Blk)
//------------------------
//
// Arduino Nano // deprecated - runs out of memory / stack crash
// PINS / WIRING
// ----------------------
// Arduino | Nextion
// --------|-------------
// 8 (RX) | TX (Blu) -- pins 8 and 9 are requuired by AltSoftSerial
// 9 (TX) | RX (Yel) -- to use other pins you can use SofwareSerial
// +5 | +5 (Red) -- but that will conflict with the interrupt
// Gnd | Gnd (Blk) -- used by the Rotary Encoder
//------------------------
////////////////////////////////////////////////////////////////////////////////////////////
//
// We use the AltSoftSerial library to pins (8 and 9) rather than default 0,1
// AltSoftSerial is used rather than SoftwareSerial to free up the interrupt vector
// required for the digital encoder
// NOTE:
// AltSoftSerial uses only fixed pins 8,9 on an Uno or Nano
// Pin 10 cannot be used for PWM
// For other boards see: https://www.pjrc.com/teensy/td_libs_AltSoftSerial.html
//
// First, apply the serial fixes from Ray Livingston to the Nextion Libarary:
// https://forum.arduino.cc/index.php?topic=620821.0
// Replace files in the ../libraries/ITEADLIB_Arduino_Nextion folder:
// NexConfig.h
// NexHardware.h
// NexHardware.cpp
// Plus one more patch to fix a bug that was including SoftwareSerial when it should not have
// NexUpload.cpp (change by me, Dean Souleles)
//
// Next we edit NexConfig.h in the ../libraries/ITEADLIB_Arduino_Nextion folder as follows:
// Comment out this line:
// #define nexSerial Serial
//
// Add these three lines:
// #include <AltSoftSerial.h>
// extern AltSoftSerial HMISerial;
// #define nexSerial HMISerial
//
// Add the following to your sketch
// #include <AltSoftSerial.h>
// AltSoftSerial HMISerial; //RX TX - connect to Nextion TX RX - Must use Pins 9,10 on Uno/Nano
//
////////////////////////////////////////////////////////////////////////////////////////////////
#include <Nextion.h> // https://github.com/itead/ITEADLIB_Arduino_Nextion
//#include <SoftwareSerial.h>
//SoftwareSerial HMISerial(8,9); //RX TX - connect to Nextion TX RX - Pins 8,9 on Uno/Nano
#if _BOARDTYPE == Nano
#include <AltSoftSerial.h>
AltSoftSerial HMISerial; //RX TX - connect to Nextion TX RX - Must use Pins 8,9 on Uno/Nano
#endif
//
// Declare Nextion objects
// Use the page ID, component id and component name from the Nextion IDE
//
// Nextion PAGE
// Current design only uses one page of the display
#define PAGE 0
// Nextion Component IDs for buttons and text displays
// These ID's must match the ID's Nextion IDE
#define VFO_ID 2
#define SIDEBAND_ID 5
#define TUNE_ID 7
#define INCREMENT_ID 6
#define ACT_VFO_ID 3
#define ALT_VFO_ID 4
#define TX_RX_ID 8
#define SMETER_ID 10
#define BANNER_ID 9
#define TUNE_PLUS_ID 11
#define TUNE_MINUS_ID 12
#define TUNE_STATE_ID 15
#define ATOB_ID 18 // VFO A copy to B button
#define SPLIT_ID 19
#define VAB_ID 20 // VFO Inidicator
// Nextion Component names
// These names must match the names in the Nextion IDE
#define VFO_NAME "bVFO"
#define SIDEBAND_NAME "bSideband"
#define TUNE_NAME "bTune"
#define INCREMENT_NAME "bIncr"
#define ACT_VFO_NAME "tActVFO"
#define ALT_VFO_NAME "tAltVFO"
#define BANNER_NAME "tBanner"
#define TX_RX_NAME "tTxRx"
#define SMETER_NAME "pSmeter"
#define TUNE_PLUS_NAME "bPlus"
#define TUNE_MINUS_NAME "bMinus"
#define TUNE_STATE_NAME "tTuneState"
#define ATOB_NAME "bAtoB"
#define SPLIT_NAME "btSplit"
#define VAB_NAME "tVAB"
// Nextion Buttons
// Each user interface object that the user touches needs to be defined here
//
NexButton bVFO = NexButton(PAGE, VFO_ID, VFO_NAME);
NexButton bSideband = NexButton(PAGE, SIDEBAND_ID, SIDEBAND_NAME);
NexButton bTune = NexButton(PAGE, TUNE_ID, TUNE_NAME);
NexButton bIncrement = NexButton(PAGE, INCREMENT_ID, INCREMENT_NAME);
NexButton bTunePlus = NexButton(PAGE, TUNE_PLUS_ID, TUNE_PLUS_NAME);
NexButton bTuneMinus = NexButton(PAGE, TUNE_MINUS_ID, TUNE_MINUS_NAME);
NexButton bAtoB = NexButton(PAGE, ATOB_ID, ATOB_NAME);
NexDSButton btSplit = NexDSButton(PAGE, SPLIT_ID, SPLIT_NAME);
//
// Setup a list of objects to respond to a touch event
//
NexTouch *nex_listen_list[] = {
&bVFO,
&bSideband,
&bTune,
&bIncrement,
&bTunePlus,
&bTuneMinus,
&bAtoB,
&btSplit,
NULL
};
///////////////////////////////////////////////////////////////////////////
// HMI_send_command(cmd)
// Sends one command to the Nextion Display
//
// Example usage:
// String cmd;
// cmd = F("vis ");
// cmd = cmd + F(TUNE_STATE_NAME);
// cmd = cmd + F(",");
// if (on_off) {
// cmd = cmd + F("1");
// } else {
// cmd = cmd + F("0");
// }
// HMI_send_command(cmd.c_str());
//
///////////////////////////////////////////////////////////////////////////
void HMI_send_command(char* cmd) {
/*
// Send the command
HMISerial.print(cmd);
// Send end-of-message per Nextion protocol
HMISerial.write(0xff);
HMISerial.write(0xff);
HMISerial.write(0xff);
*/
// Send the command to the Nextion
nexSerial.print(cmd);
// Send end-of-message per Nextion protocol
nexSerial.write(0xff);
nexSerial.write(0xff);
nexSerial.write(0xff);
}
///////////////////////////////////////////////////////////////////////////
// displayActVFO(uint32_t freq)
// Formats and displays Active and Alternat VFO frequencies
// Legal values: Frequency in Hz
//
// To do - comibine into one function
///////////////////////////////////////////////////////////////////////////
void displayActVFO(uint32_t freq) {
String cmd;
String fmt;
char f[11];
uint32_t mil, hund_thou, ten_thou, thou, hund, tens, ones;
// Format number as nn.nnn.nnn
mil = freq / 1000000;
hund_thou = (freq/100000)%10;
ten_thou = (freq/10000)%10;
thou = (freq/1000)%10;
hund = (freq/100)%10;
tens = (freq/10)%10;
ones = freq%10;
fmt=F("%2ld.%ld%ld%ld.%ld%ld%ld");
snprintf(f, sizeof(f),fmt.c_str(),mil, hund_thou, ten_thou,thou, hund, tens, ones);
cmd = F(ACT_VFO_NAME);
cmd += F(".txt=\"");
cmd += f;
cmd += F("\"");
HMI_send_command(cmd.c_str());
}
void displayAltVFO(uint32_t freq) {
String cmd;
String fmt;
char f[11];
uint32_t mil, hund_thou, ten_thou, thou, hund, tens, ones;
// Format number as nn.nnn.nnn
mil = freq / 1000000;
hund_thou = (freq/100000)%10;
ten_thou = (freq/10000)%10;
thou = (freq/1000)%10;
hund = (freq/100)%10;
tens = (freq/10)%10;
ones = freq%10;
fmt=F("%2ld.%ld%ld%ld.%ld%ld%ld");
snprintf(f, sizeof(f),fmt.c_str(),mil, hund_thou, ten_thou,thou, hund, tens, ones);
cmd = F(ALT_VFO_NAME);
cmd += F(".txt=\"");
cmd += f;
cmd += F("\"");
HMI_send_command(cmd.c_str());
}
void displaySMeter(byte level) {
// Nextion dipslay bar graph is set by integer percent 0-100
// Convert the S level into a % and send to display
String cmd;
float pct;
int scaled_level;
pct = (float(level)/13.0)*100.0;
scaled_level=pct;
cmd = F(SMETER_NAME);
cmd += F(".val=");
cmd += scaled_level;
HMI_send_command(cmd.c_str());
#ifdef NEX_DEBUG1
String msg = F("displaySMeter: ");
msg = msg + level;
msg = msg + F(" ");
msg = msg + scaled_level;
displayBanner(msg);
#endif
}
void displayBanner(String s) {
String cmd;
cmd = F(BANNER_NAME);
cmd += F(".txt=\"");
cmd += s;
cmd += F("\"");
HMI_send_command(cmd.c_str());
}
void displayVFOAB(int vfo) {
String cmd;
cmd = F(VAB_NAME);
cmd += F(".txt=\"");
if (vfo == VFOA) {
cmd += F("A");
} else {
cmd += F("B");
}
cmd += F("\"");
HMI_send_command(cmd.c_str());
#ifdef NEX_DEBUG
String msg = F("displayVFOAB: ");
msg = msg + vfo;
displayBanner(msg);
#endif
}
void displayTxRx(int tx_rx) {
String cmd;
cmd = F(TX_RX_NAME);
cmd += F(".txt=\"");
if (tx_rx == TX) {
cmd += F("TX");
} else {
cmd += F("RX");
}
cmd += F("\"");
HMI_send_command(cmd.c_str());
#ifdef NEX_DEBUG
String msg = F("displayTxRx: ");
msg = msg + tx_rx;
displayBanner(msg);
#endif
}
void displayMode(int mode) {
String modeString;
if (mode == USB) {
modeString = F("USB");
} else {
modeString = F("LSB");
}
bSideband.setText(modeString.c_str());
#ifdef NEX_DEBUG
String msg = F("displayMode: ");
msg = msg + modeString;
displayBanner(msg);
#endif
}
void displayIncr(uint32_t increment) {
String hertz;
switch (increment) {
case 1: hertz = F(" 1"); break;
case 10: hertz = F(" 10"); break;
case 100: hertz = F(" 100"); break;
case 1000: hertz = F(" 1K"); break;
case 10000: hertz = F(" 10K"); break;
case 100000: hertz= F("100K"); break;
case 1000000:hertz = F(" 1M");
}
bIncrement.setText(hertz.c_str());
#ifdef NEX_DEBUG
String msg = F("displayIncr: ");
msg = msg + hertz;
displayBanner(msg);
#endif
}
void displayTune(boolean on_off) {
// Send visibility command to Nextion
// Format: vis <object_name),<0/1>
String cmd;
cmd = F("vis ");
cmd += F(TUNE_STATE_NAME);
cmd += F(",");
cmd += on_off;
HMI_send_command(cmd.c_str());
#ifdef NEX_DEBUG
displayBanner(cmd);
#endif
}
void displaySplit(boolean split) {
// The split button is a dual state button
// State 0 = blue = ff
// State 1 = green = on
// To change the state and color set the val property to 0/1
// This mimics a button press on the display
String cmd =F(SPLIT_NAME);
cmd += F(".val=");
if (split) {
cmd += 1;
} else {
cmd += 0;
}
HMI_send_command(cmd.c_str());
#ifdef NEX_DEBUG
String msg = F("displaySplit: ");
msg = msg + split;
displayBanner(msg);
#endif
}
//
// Button Callback Functions
// Called whenever a button is pressed and released
//
// VFO A/B Button
void bVFOPopCallback(void *ptr) {
SwapVFO(); // call the VFO switch button handler
}
// LSB/USB Button
void bSidebandPopCallback(void *ptr) {
SwapSB();
}
// Tune Plus (increase VFO)
void bTunePlusPushCallback(void *ptr) {
AdjustVFO(increment);
}
// Tune Minus (decrease VFO)
void bTuneMinusPushCallback(void *ptr) {
AdjustVFO(-1 * increment);
}
// Tune Tone
void bTunePopCallback(void *ptr) {
DoTune();
}
// Tuning Increment Change
void bIncrementPopCallback(void *ptr) {
AdvanceIncrement();
}
// Split on/off
void btSplitPopCallback(void *ptr) {
uint32_t split_val;
btSplit.getValue(&split_val);
if (split_val==1) {
startSplit();
} else {
stopSplit();
}
#ifdef NEX_DEBUG
String msg = F("btSplitPopCallback: ");
msg = msg + split_val;
displayBanner(msg);
#endif
}
// Make Act and Alt VFO the same
void bAtoBPopCallback(void *ptr) {
if (active_vfo == VFOA) {
vfoBfreq = vfoAfreq;
} else {
vfoAfreq = vfoBfreq;
}
displayAltVFO(vfoAfreq); // update the Alt VFO display
}
//
// Setup
// Called once at startup
//
void displaySetup(String banner,
uint32_t vfoActfreq, uint32_t vfoAltfreq,
uint32_t activeVFO,
int tx_rx,
int sideband,
boolean split,
uint32_t increment,
byte s_meter) {
nexInit(9600); // Initialize the Nextion library
//
// Attach callback routines for each button
// attachPop will set the library to invoke the specified funtion each time a button is released
//
bVFO.attachPop(bVFOPopCallback, &bVFO); // VFO A/B button
bSideband.attachPop(bSidebandPopCallback, &bSideband); // LSB/USB button
bTune.attachPop(bTunePopCallback, &bTune); // Tune
bIncrement.attachPop(bIncrementPopCallback, &bIncrement); // Change Incrmement
btSplit.attachPop(btSplitPopCallback, &btSplit); // Split On/Off
bAtoB.attachPop(bAtoBPopCallback, &bAtoB); // Make Alt VFO = Act VFO
bTunePlus.attachPush(bTunePlusPushCallback, &bTunePlus); // Tune up
bTuneMinus.attachPush(bTuneMinusPushCallback, &bTuneMinus); // Tune down
//
// Display the intitial values
//
displayBanner(banner);
displayActVFO(vfoActfreq);
displayAltVFO(vfoAltfreq);
displayVFOAB(activeVFO);
displayTxRx(tx_rx);
displayMode(sideband);
displaySplit(split);
displayIncr(increment);
displaySMeter(s_meter);
}
void CheckTouch() {
// Call the Nextion check function to look for activites on your listen list
nexLoop(nex_listen_list);
}
#endif

Plik binarny nie jest wyświetlany.

Wyświetl plik

@ -0,0 +1,375 @@
/*
SSB_Radio_Control
Dean Souleles, KK4DAS, contact kk4das@gmail.com
This sketch implement s basic SSB radio control panel with the following features:
Dual VFO A/B
Rotary encoder for tuning, push button to change tuning increment
SI5351 Clock generator for the LO and BFO
CAT Control (emulates an ICOM IC-746)
Split mode support (Split from CAT, manual split requres Nextion touch screen)
Settings memory (last frequency and mode are saved)
Optional S-Meter
Controls
* Rotary encoder to change frequence
* Rotary encode button changes tuning increment
* VFO A/B Select toggle
* Mode select USB/LSB toggle
* Tune button (emits 10 second pulsed tone at 800Hz for tuning)
* MOX toggle - puts rig in to Tx/Rx
* Optional dual-band support 20/40
Modules for different display types
* 20x4 LCD
* 320x240 TFT color display
* 2.8" Nextion Touch Screen
Display features
* Dual VFOS
* Mode indicator SSB/LSB
* Tx/Rx ndicator
* TuningStep Inidicator
* Optional S Meter
* Banner including callsign
Additional controls with the Nextion display
* Continuous scanning
* Split mode
Version 1.4
March 9 2021
Rstored LCD Display Option
Compile time selection of S-Meter and Dual Band mods
Refactoring of encoder handling
Version 1.3
Jan 25, 2021
Changed CAT module to IC-746
Version 1.2
Dec 14, 2020
Dual Band 20/40 Support
S-Meter
Version 1.1
Dec 13. 2020
Ported to Nano Every for more sweet SRAM
* * Updated interrupt handling in Encoder.cpp to work with Nano Every (as well as UNO/Nano)
* * Replaced SoftwareSerial connections to Nextion with Hardware Serial - Serial1
Version 1.0
Aug 13, 2020
Adapted from SimpleSSB Sketch by N6QW, Pete Juliano and others
NOTE TO BUILDERS
This is a reference implementation of an SSB radio control program for a homebrew SSB transceiver.
It is a fully functioning SSB radio control program. While it has been built for my particular hardware
configuration every attempt has been made to make it modular in design so that the builder can swap out
modules at will. It should be fairly straight forward to adapt to the builder's hardware selection
of switches, buttons and knobs or even alternate displays.
*/
#include "RadioControl.h"
#ifdef DEBUG
char debugmsg[25];
#endif
//=============== Globals ============================================
//////////////////////////////////////////////////////////////////////
// //
// si5351 Clock Module //
// //
//////////////////////////////////////////////////////////////////////
Si5351 si5351;
// Calibration offest - adjust for variability in the SI5351 module
// crystal - must be set for the particular SI5351 installed
//#define CALIBRATION_OFFSET 1190 // Calibration for the SI-5351
#define CALIBRATION_OFFSET 880 // Calibration for the SI-5351
//////////////////////////////////////////////////////////////////////
// //
// BFO and VFO Constants and Variables //
// //
//////////////////////////////////////////////////////////////////////
#ifdef BFO12MHZ
const uint32_t USB_BFO = 12001600L;
const uint32_t LSB_BFO = 11998600L;
#endif
#ifdef BFO9MHZ
const uint32_t USB_BFO = 9001500L;
const uint32_t LSB_BFO = 8998500L;
#endif
uint32_t bfo = LSB_BFO; // Startup BFO frequency
const uint32_t BFO_DELTA = USB_BFO - LSB_BFO; // Difference between USB and LSB for BFO change
//
// Startup VFO A/B frequencies
//
uint32_t vfoAfreq = 7200000L; // 7.200.000
uint32_t vfoBfreq = 7074000L; // FT-8 7.074.000
byte vfoASideband = LSB;
byte vfoBSideband = USB;
uint32_t increment = 1000; // startup VFO tuning increment in HZ.
//////////////////////////////////////////////////////////////////////
// //
// Active VFO //
// //
//////////////////////////////////////////////////////////////////////
byte active_vfo = VFOA; // startup on VFOA
//////////////////////////////////////////////////////////////////////
// //
// Sideband Selection //
// //
//////////////////////////////////////////////////////////////////////
byte sideband = LSB; // startup in LSB
//////////////////////////////////////////////////////////////////////
// //
// Sideband Selection //
// //
/////////////////////////////////////////////////////////////////////
uint32_t band20Freq = 14200000L; // 14.200.000
uint32_t band40Freq = 7200000L; // 7.200.000
byte band20Sideband = USB;
byte band40Sideband = LSB;
byte band = BAND40;
//////////////////////////////////////////////////////////////////////
// //
// Transmit / Receive Indicators //
// //
//////////////////////////////////////////////////////////////////////
byte TxRxState = RX; // startup in RX
byte lastTxRxState = RX; // previous TxRxState
byte txSource = PTT_MIC; // transmit source - Mic, Tune, CAT
//////////////////////////////////////////////////////////////////////
// //
// S Meter 0-9 +10, +20 +30 //
// //
//////////////////////////////////////////////////////////////////////
byte smeter = 0; // startup s_meter reading (requires SMETER)
//////////////////////////////////////////////////////////////////////
// //
// Split Mode On/Off //
// //
//////////////////////////////////////////////////////////////////////
bool split = false;
///////////////////////////////////////////////////////////
// setBandFilters(band) //
// For 20 meters turn relay ON (NO) //
// For 40 meters turn relay OFF (NC) //
///////////////////////////////////////////////////////////
void setBandFilters(int band) {
#ifdef DUAL_BAND
switch (band) {
case BAND20:
digitalWrite(BAND_PIN, HIGH);
break;
case BAND40:
digitalWrite(BAND_PIN, LOW);
break;
}
#endif
}
#ifdef CW
//
// setCW()
//
// Experimental code to generate CW tone on key down at 700Hz above the dial frequency
// Needs a bunch of scaffolding to implement CW mode
//
// Turns off the BFO, sets the LO to the VFO frequency + 700
//
// After testing -
// tone produced OK but needs an amplifier to get significant power out
//
void setCW() {
si5351.set_freq(0, 0, SI5351_CLK2); // turn off BFO
si5351.set_freq(vfoAfreq+700L , SI5351_PLL_FIXED, SI5351_CLK0); // set LO to operating freq
}
#endif
//********************setVFO******************************************
void setVFO(uint32_t freq) {
//
// Set CLK0 to the to input frequency adjusted for the current BFO frequency
#ifdef DUAL_BAND
//
// Set filters for the band based on frequency
// Save frequency and sideband for band switching
//
if (freq >= BAND20_EDGE) {
setBandFilters(BAND20);
band20Freq = freq;
band20Sideband = sideband;
band = BAND20;
} else if (freq >= BAND40_EDGE) {
setBandFilters(BAND40);
band40Freq = freq;
band40Sideband = sideband;
band=BAND40;
}
#endif
si5351.set_freq(freq + bfo, SI5351_PLL_FIXED, SI5351_CLK0);
startSettingsTimer(); // start timer to save current settings
}
void setBFO(uint32_t freq) {
//
// Set CLK2 to the to input frequency
//
si5351.set_freq(freq, 0, SI5351_CLK2);
}
//*********************Setup Arduino Pins*****************************
void setupPins() {
//
// Set the control buttons to INPUT_PULLUP
// Button state will be HIGH when open and LOW when pressed
//
pinMode(TUNE_BTN, INPUT_PULLUP); // Tune - momentary button
pinMode(VFO_BTN, INPUT_PULLUP); // VFO A/B Select - momentary button
pinMode(SIDEBAND_BTN, INPUT_PULLUP); // Upper/lower SB Select - momentary button
pinMode(BAND_BTN, INPUT_PULLUP); // Band Switch 20/40 - momentary button
pinMode(PTT_SENSE, INPUT_PULLUP); // Mic PTT swtich
pinMode(PTT, OUTPUT); digitalWrite(PTT, LOW); // HIGH to enable TX
pinMode(BAND_PIN, OUTPUT); digitalWrite(BAND_PIN, LOW); // Band Switch Relay (LOW = NC = 40m, HIGH = NO = 20m)
pinMode(LED_BUILTIN, OUTPUT);
}
//**********************Initialize SI5351******************************
void setupSI5351() {
si5351.init(SI5351_CRYSTAL_LOAD_8PF);
si5351.set_correction(CALIBRATION_OFFSET); // calibration offset
si5351.set_pll(SI5351_PLL_FIXED, SI5351_PLLA);
si5351.drive_strength(SI5351_CLK0, SI5351_DRIVE_8MA); // Higher Drive since it is a ADE-1 DBM
si5351.drive_strength(SI5351_CLK2, SI5351_DRIVE_8MA);
si5351.set_freq(bfo, 0, SI5351_CLK2); // Initialize the bfo
//
// Initialize the VFO
//
switch (active_vfo) {
case VFOA:
setVFO(vfoAfreq);
break;
case VFOB:
setVFO(vfoBfreq);
break;
}
}
//////////////////////////////////////////////////////////////////////
// Setup //
// //
// Called once by the Arduino operating system at startup //
// All initialization code goes here //
// //
//////////////////////////////////////////////////////////////////////
void setup() {
uint32_t vfoActfreq;
uint32_t vfoAltfreq;
#ifdef DEBUG
Serial.begin(57600);
#endif
setupPins(); // Initialize arduino pins
setupEncoder(); // Initialize interrupt service for rotary encoder
setupSettings(); // Retrive settings from EEPROM
setupSI5351();
if (active_vfo == VFOA) {
vfoActfreq = vfoAfreq;
vfoAltfreq = vfoBfreq;
} else {
vfoActfreq = vfoBfreq;
vfoAltfreq = vfoAfreq;
}
// Initialize the display with startup values
Delay(500); // short delay to let the display initialize - needed for Nextion
// Construct banner for TFT or Nextion display
// "Vx.x RIGNAME CALLSIGN"
String banner;
banner = F("V");
banner += F(VERSION);
banner += F(" ");
banner += F(RIGNAME);
banner += F(" ");
banner += F(CALLSIGN);
displaySetup(banner, // version number. call sign
vfoActfreq, vfoAltfreq, // Initial active and alternate VFO
active_vfo, // VFO A/B indicator
TxRxState, // TX/RX indicator
sideband, // LSB/USB,
split, // Split mode on/off
increment, // Tuning increment
smeter); // S Meter
setupCat();
}
void loop() {
CheckEncoder(); //VFO frequency changes
CheckIncrement(); // Encoder Button
#ifdef DUAL_BAND
CheckBand(); // Band Switch 20/40
#endif
CheckVFO(); // VFO A/B change
CheckSB(); // USB/LSB change
CheckPTT(); // Check for Mic PTT
CheckTune(); // Check for Tune button press
#ifdef SMETER
CheckSmeter(); // Signal strength
#endif
CheckCat(); // CAT Control
CheckSettings(); // Update EEPROM on Settings Change
#ifdef DISPLAY_NEXTION
CheckTouch(); // Check for touch screen action
#endif
}

Plik binarny nie jest wyświetlany.

551
SSB_TFT_Display.cpp 100644
Wyświetl plik

@ -0,0 +1,551 @@
#include "RadioControl.h"
#ifdef DISPLAY_TFT
//
// 7/31/202 - Dean Souleles
// Added stubbed out Split indicator function to stay consistent with main sketch
// TO DO:
// ADD constants and code for Split indicator
//
/* SSB_TFT_Display
* KK4DAS, Dean Souleles, KK4DAS@gmail.com
* May 30, 2020
*
* Basic radio display panel for a SSB transsceiver
* Designed for a 320x240 Color TFT (non touch)
* Tested with an ILI9341 display
* Requires the following libraries be installed:
* Adafruit_GFX
* Adafruit_ILI9341
*
* Implements a basic SSB display console with the following features
* Dual VFO A/B
* Mode indicator SSB/LSB
* Tx/Rx ndicator
* TuningStep Inidicator
* S Meter
* Banner including Call sign
*
* Fully customizable. Fast display makins use of minimal resources./
* Room is left on the screen for additional features
* There is room on the screen for another row of features
*
* Easily change colors, font sizes and layout
*
* Default Screeen Layout
* A 7.200.000 LSB -- VFO A/B indicator, Active VFO Freq, LSB/USB inidicator
* Rx 7.048.000 100K -- Rx/Tx indicator, Alternate VFO Freq, Tuning Increment
*
* S |_|_|_|_|_|_|_|_|_|_|_|_| -- S Meter
* 1 3 5 7 9
*
* AGC SPL RIT -- (Planned) AGC on/of, Split On/Off, RIT On/OFF
*
* Ver Rig Name Call
*
* This module provides the following radio console dsiplayfunctions:
* displaySetup - initialize the display and displays the startup values - call once from your setup function
* displayBanner - Displays a text banner across the bottom of the screen
* displayActVFO - Displays the frequency of the Active VFO
* displayAltVFO - Displays the frequency of the Alternate VFO
* displayVFOAB - Displays the indicator which VFO is active (A or B)
* displayTxRx - Displays whether the rig is in (Tx or Rx)
* displayMode - Displays the whiuch sideband is selected (LSB or USB)
* displayIncr - Displays the tuning increment (10, 100, 1K 10K, 100K, 1M)
* displaySMeter - Displays the S Meter (1-9 are gray, +10 +20 and +30 are red
*
* This module also provides the following general purpose displauy functions:
* displayClearScreen - fills the screen with the selected backgrond color
* displayPrintat - prints text or nubmers on the screen at a specific location
* displayDrawBoundingBox - draw a box on the screen and fills it with a background color
* displayDrawTextBox - displays text inside a boundig box
*
* Design notes and how to use the code
*
* NOTE TO BUILDERS
* This is not a complete radio control sketch. It is the Display software only. In the spirit of modular design
* it is stand-alone and not dependent on using an SI-5351 or any other specific hardware, or on my particular
* hardware selection of switches, buttons and knobs. The demonstration sketch shows how to update the display,
* but you need to provide the code to determine what the actual values should be. You will likely need other
* libraries like the Si5351 and a Rotary encoder library aside from the GFX and the ILI9341.
*
* DESIGN PRINCIPLES
* Good software design principles are to use as few hard-coded numbers as possible.
* Wherever possible I have used #defines for any number that will be used more than one place in the code.
* For example #define DSP_VFO_ACT_X 60 defines the X coordinate (how far from the left of the screen)
* of the Active VFO frequency display. You will see multiple references to DSP_VFO_ACT_X throughout the code,
* but I never use the hardcoded number 60 again. Change it once and it is changed throughout.
*
* Taking the S-Meter as an example:
*
* To update the S-Meter display you make a call to displaySMeter(n); where n is an integer from 1 to 12
* (representing S1-9, +10, +20 +30). Your sketch will need a way of monitoring signal strength (an analog input
* pin on the Arduino attached to an appropriate place on your rig) and converting it to the logarithmic S scale.
*
* SCREEN COORDINATES
* Coordinates work differently on displays than a typical graph where the origin 0,0 is in the middle abd positive
* and negative values move you away from the origin. For displays 0,0, the origin, is always upper left hand corner
* of the display and you only use positive numbers for the coordinates +X is pixels from the left edge, +Y is pixels
* down from the top. This particular example based on a 320x240 display but should be easily portable to other
* display sizes but you have to keep in mind how the coordinate system works.
*
* SCREEN LAYOUT
* Here are a few notes about how the demonstration display is laid out. This should help you understand the design
* concept and allow you to begin to modify it.
*
* The VFO display is setup for a dual VFO rig. The currently Active VFO is always on the top and the alternate VFO
* is just below it. Your code will need to keep track of whether VFO A or VFO B is currently selected and call the
* display routines to update the display. Ill describe how the VFO displays are are defined and that will give you
* an idea how you might modify or enhance the display.
*
* Active VFO - top center of the screen
* #define DSP_VFO_ACT_X 60 // Active VFO begins 60 pixels from the left hand edge (I picked 60 by experimenting)
* #define DSP_VFO_ACT_Y 30 // Active VFO box starts 30 pixels down from the top of the screen (Try changing it to 50
* // and see what happens)
* #define DSP_VFO_ACT_COLOR ILI9341_GREEN // This sets the text color for the Frequency display
* // use whatever colors you like
* #define DSP_VFO_ACT_BK ILI9341_BLACK // This sets the background color for the Active VFO
* #define DSP_VFO_ACT_SZ 3 // This is text size from Arduino TFT, values 1-5 1 is small 5 is large
* // (2 was too small, 4 was too large, 3 was just right)
*
* Alternate VFO the second VFO is placed directly below the Active VFO on the screen. There are a couple of things of
* interest here. For the X coordinate, instead of putting in a hard coded number I refer back to the #define that I used
* for the Active VFO (DSP_VFO_ACT). That way, if I want to move VFO section to a different part of the screen I only need
* to change one number DSP_VFO_ACT_X, and the alternate VFO will move as well. Figuring out the Y coordinate for the
* alternate VFO is a little more challenging. Some math is involved. Starting with the Y coordinate of the Active VFO
* I need to calculate where how far down the display I need to go to place the second VFO. To do that I need calculate
* how many pixels tall the text characters in the Active VFO are and use that as an offset. It turns out we have everything
* we need already defined. CH_W and CH_H are #defines that specify the height and width of a text character in pixels for
* TFT font size 1. Size 2 through 5 are even multiples of that so font height for size 2 is 2*CH_H pixels and font width
* for size 4 is 4*CH_W pixels and so on. so we have everything we need to calculate how many pixels the Active VFO takes
* on the screen we multiply the font size by the character height and add 16 pixels offset. The 16 was determined by
* experimentation for something that looked good. The code looks like this:
*
* #define DSP_VFO_ALT_X DSP_VFO_ACT_X
* #define DSP_VFO_ALT_Y DSP_VFO_ACT_Y + (DSP_VFO_ACT_SZ * CH_H) + 16
*
* Take a look the other sections of the display code and you will see similar references and calculations. The VFO A//B
* indicator and LSB/USB mode indicator, for example are similarly pinned to the Active VFO display, so if you move the
* Active VFO display to another screen location they will move also.
*
* In summary - each object is on the display is defined by a set of constants that indicate the X,Y coordinates
* of the object on the screen and various other attributes like text size and color. The basic user interface display
* object is a bounded/filed text box. You can control the text size and color, and the box fill color. With this basic
* set of features you can implement a wide variety of user interface elements. The S-meter, for example, is a row of
* filled boxes.
*
* HARDWARE NOTES
* My test sketch uses an Arduino Nano. The display is an HiLetgo 2.2 Inch ILI9341 SPI TFT LCD Display 240x320 ILI9341,
* but any ILI9341 display should work. There are many sources. Please note that the Arduino has 5V logic levels,
* but the display requires 3.3V - so you need some sort of level shifter. I used the"HiLetgo 10pcs 4 Channels IIC I2C
* Logic Level Converter Bi-Directional 3.3V-5V Shifter Module for Arduino" I used hardware SPI and the pinouts are
* standard as follows:
*
* Arduino
* Pin TFT Pin
* -----------|---------
* 8 | RST - any free Arduino Pin (not used in this sketch)
* 9 | DC - any free Arduino Pin
* 10 | CS - any free Arduino Pin
* 11 | MOSI - fixed
* 12 | MISO - fixed
* 13 | CLK - fixed
*
* That is all the wiring you need for the demonstration sketch.
*
*
* BUILDING THE DEMONSTRATION SKETCH
*
* Create a folder called SSB_TFT_Display_Demo
*
* Copy all three files to that folder
* SSB_TFT_Display_Demo.ino
* SSB_TFT_Display.h
* SSB_TFT_Display.cpp
*
* Use the Arduino IDE library manager to install teh following libraries
* Adafruit_GFX
* Adafruit_ILI9341
*
* Compile and upload the sketch
******************************************************************************************************************
*/
#include <Adafruit_GFX.h>
#include <Adafruit_ILI9341.h>
// For the Adafruit shield, these are the default.
#define TFT_DC 9
#define TFT_CS 10
#define TFT_MOSI 11
#define TFT_CLK 13
#define TFT_RST 8
#define TFT_MISO 12
// Use hardware SPI (on Uno, #13, #12, #11) and the above for CS/DC
Adafruit_ILI9341 tft = Adafruit_ILI9341(TFT_CS, TFT_DC);
#define CH_W 6 // default TFT Character width in pixels
#define CH_H 8 // default TFT Charachter height in pixels
#define DSP_BG_COLOR ILI9341_NAVY
// Banner - Version, Rig Name, Call sign
#define DSP_BANNER_X 10
#define DSP_BANNER_Y 220
#define DSP_BANNER_COLOR ILI9341_BLACK
#define DSP_BANNER_BK ILI9341_WHITE
#define DSP_BANNER_SZ 2
// Active VFO - top center of the screen
#define DSP_VFO_ACT_X 60
#define DSP_VFO_ACT_Y 30
#define DSP_VFO_ACT_COLOR ILI9341_GREEN
#define DSP_VFO_ACT_BK ILI9341_BLACK
#define DSP_VFO_ACT_SZ 3
// Alternate VFO - directly below the Active VFO
#define DSP_VFO_ALT_X DSP_VFO_ACT_X
#define DSP_VFO_ALT_Y DSP_VFO_ACT_Y + (DSP_VFO_ACT_SZ * CH_H) + 16
#define DSP_VFO_ALT_COLOR ILI9341_WHITE
#define DSP_VFO_ALT_BK DSP_VFO_ACT_BK
#define DSP_VFO_ALT_SZ 3
// VFP A/B indidcator - to the left of the Active VFO
#define DSP_VFO_AB_X DSP_VFO_ACT_X-40
#define DSP_VFO_AB_Y DSP_VFO_ACT_Y + 6
#define DSP_VFO_AB_COLOR DSP_VFO_ACT_COLOR
#define DSP_VFO_AB_BK DSP_VFO_ACT_BK
#define DSP_VFO_AB_SZ 2
// Tx/Rx Indicator - to the left of the Alternate VFO
#define DSP_RX_TX_X DSP_VFO_ACT_X-45
#define DSP_RX_TX_Y DSP_VFO_ALT_Y + 4
#define DSP_RX_COLOR DSP_VFO_ACT_COLOR
#define DSP_TX_COLOR ILI9341_RED
#define DSP_RX_TX_BK DSP_VFO_ACT_BK
#define DSP_RX_TX_SZ 2
// Mode (LSB/USB) inidcator - to the right of the Active VFO
#define DSP_MODE_X DSP_VFO_ACT_X+205
#define DSP_MODE_Y DSP_VFO_ACT_Y + 6
#define DSP_MODE_COLOR DSP_VFO_ACT_COLOR
#define DSP_MODE_BK DSP_VFO_ACT_BK
#define DSP_MODE_SZ 2
// Tuning Increment - to the right of teh Alternate VFO
#define DSP_INCR_X DSP_VFO_ACT_X+200
#define DSP_INCR_Y DSP_VFO_ALT_Y + 4
#define DSP_INCR_COLOR DSP_VFO_ACT_COLOR
#define DSP_INCR_BK DSP_VFO_ACT_BK
#define DSP_INCR_SZ 2
// S Meter - below the the Alt VFO (1/2 way down the screen)
#define DSP_S_METER_X 56
#define DSP_S_METER_Y 120
#define DSP_S_METER_SEGMENTS 12
#define DSP_S_METER_UNIT_SIZE 20
#define DSP_S_METER_LOW_COLOR ILI9341_DARKGREY //below S9
#define DSP_S_METER_HI_COLOR ILI9341_RED //above S9
// S Meter Label "S" to the left of the S Meter
#define DSP_S_LABEL_X DSP_S_METER_X - 18
#define DSP_S_LABEL_Y DSP_S_METER_Y + 4
#define DSP_S_LABEL_COLOR ILI9341_BLACK
#define DSP_S_LABEL_SZ 2
// S Meter Scale immediately belwo the S Meter
#define DSP_S_METER_SCALE_X DSP_S_METER_X + 5
#define DSP_S_METER_SCALE_Y DSP_S_METER_Y + DSP_S_METER_UNIT_SIZE + 5
#define DSP_S_METER_SCALE_COLOR DSP_S_LABEL_COLOR
#define DSP_S_METER_SCALE_SZ 2
///////////////////////////////////////////////////////////////////////////
// displayPrintln(s)
// Prints one line of text at a time on the display - for debug messages
///////////////////////////////////////////////////////////////////////////
#ifdef DEBUG
int ln=0;
void displayPrintln(String s ) {
if (ln==14) {
tft.fillScreen(ILI9341_BLACK);
tft.setCursor(0, 0);
ln=0;
}
tft.println(s);
ln++;
}
#endif
///////////////////////////////////////////////////////////////////////////
// displayClearScreen()
// Fills the screen with selected bacground color
///////////////////////////////////////////////////////////////////////////
void displayClearScreen() {
tft.fillScreen(DSP_BG_COLOR);
}
///////////////////////////////////////////////////////////////////////////
// displayPrintat String
// Prints a string on the display
// x,y: upper left pixel coordinates
// fontsize: Arduino Font size (1-5)
// color: text color
///////////////////////////////////////////////////////////////////////////
void displayPrintat(String s, int x, int y, int fontsize, int color) {
tft.setCursor(x,y);
tft.setTextSize(fontsize);
tft.setTextColor(color);
tft.print(s);
}
///////////////////////////////////////////////////////////////////////////
// displayPrintat Integer
// Prints a whole number on the display
// x,y: upper left pixel coordinates
// fontsize: Arduino Font size (1-5)
// color: text color
///////////////////////////////////////////////////////////////////////////
void displayPrintat(int i, int x, int y, int fontsize, int color) {
tft.setCursor(x,y);
tft.setTextSize(fontsize);
tft.setTextColor(color);
tft.print(i);
}
///////////////////////////////////////////////////////////////////////////
// displayDrawBoundingBox
// Draw and fill a bounding box for text
// Draw a recrtanglie slightly larger than the text
// Fill it with the color - leave the border white
//
// len - length of the box in characxters
// x - x coord
// y - y coord
// fontsiez - font size - fontsize*CH_W is the width of a character, fontsize*CH_H is the height
// fillcolor - color
///////////////////////////////////////////////////////////////////////////
void displayDrawBoundingBox(int len, int x, int y, int fontsize, int fillcolor) {
tft.drawRect(x-8, y-6, ((len*fontsize*CH_W))+16, ((fontsize*CH_H)+8), ILI9341_WHITE);
tft.fillRect(x-6, y-4, ((len*fontsize*CH_W))+12, ((fontsize*CH_H)+4), fillcolor);
}
///////////////////////////////////////////////////////////////////////////
// displayDrawTextBox
// Display text in an outlined and filled box
//
// s: text to display
// x,y: upper left pixel coordinates
// fontsize: ARduino font size (1-5)
// fillcolor: color to fill the box with
///////////////////////////////////////////////////////////////////////////
void displayDrawTextBox(String s, int x, int y, int fontsize, int color, int fillcolor) {
displayDrawBoundingBox(s.length(), x, y, fontsize, fillcolor); // draw the box
displayPrintat(s, x, y, fontsize, color); // display the text
}
///////////////////////////////////////////////////////////////////////////
// displaySMeter
// Display the S meter as a line of filled squares - up to the input level (1-12) S1-S0, +10dB, +20dB, +30dB
// S 1-9 are filled with the Low color S9+ is filled with the High color
///////////////////////////////////////////////////////////////////////////
void displaySMeter(byte level) {
int i;
int color;
displayPrintat(F("S"), DSP_S_LABEL_X, DSP_S_LABEL_Y, DSP_S_LABEL_SZ, DSP_S_LABEL_COLOR);
for (i=0; i<DSP_S_METER_SEGMENTS; i++) {
color = ILI9341_BLACK;
if (i<level) {
color = DSP_S_METER_LOW_COLOR;
if (i>8 && level>9) {
color = DSP_S_METER_HI_COLOR;
}
}
tft.drawRect(DSP_S_METER_X+(i*DSP_S_METER_UNIT_SIZE), DSP_S_METER_Y, DSP_S_METER_UNIT_SIZE, DSP_S_METER_UNIT_SIZE, ILI9341_WHITE);
tft.fillRect(DSP_S_METER_X+(i*DSP_S_METER_UNIT_SIZE)+2, DSP_S_METER_Y+2, DSP_S_METER_UNIT_SIZE-4, DSP_S_METER_UNIT_SIZE-4, color);
// Print scale odd numbers up to 9
if ((i % 2) == 0 && i<9) {
displayPrintat(i+1, DSP_S_METER_SCALE_X+(i*DSP_S_METER_UNIT_SIZE), DSP_S_METER_SCALE_Y, DSP_S_METER_SCALE_SZ, DSP_S_METER_SCALE_COLOR);
}
}
}
///////////////////////////////////////////////////////////////////////////
// displayBanner
// Displays a banner acroos the bottom of the display
///////////////////////////////////////////////////////////////////////////
void displayBanner(String s) {
displayDrawTextBox(s,DSP_BANNER_X, DSP_BANNER_Y, DSP_BANNER_SZ, DSP_BANNER_COLOR, DSP_BANNER_BK);
}
///////////////////////////////////////////////////////////////////////////
// displayActVFO(uint32_t freq)
// displayAltVFO(uint32_t freq
// Formats and displays Active and Alternat VFO frequencies
// Legal values: Frequency in Hz
//
// To do - comibine into one function
///////////////////////////////////////////////////////////////////////////
void displayActVFO(uint32_t freq) {
char f[11];
uint32_t mil, hund_thou, ten_thou, thou, hund, tens, ones;
// Format number as nn.nnn.nnn
mil = freq / 1000000;
hund_thou = (freq/100000)%10;
ten_thou = (freq/10000)%10;
thou = (freq/1000)%10;
hund = (freq/100)%10;
tens = (freq/10)%10;
ones = freq%10;
snprintf(f, sizeof(f),"%2ld.%ld%ld%ld.%ld%ld%ld",mil, hund_thou, ten_thou,thou, hund, tens, ones);
displayDrawTextBox(f,DSP_VFO_ACT_X, DSP_VFO_ACT_Y, DSP_VFO_ACT_SZ, DSP_VFO_ACT_COLOR, DSP_VFO_ACT_BK);
}
void displayAltVFO(uint32_t freq) {
char f[11];
uint32_t mil, hund_thou, ten_thou, thou, hund, tens, ones;
// Format number as nn.nnn.nnn
mil = freq / 1000000;
hund_thou = (freq/100000)%10;
ten_thou = (freq/10000)%10;
thou = (freq/1000)%10;
hund = (freq/100)%10;
tens = (freq/10)%10;
ones = freq%10;
snprintf(f, sizeof(f),"%2ld.%ld%ld%ld.%ld%ld%ld",mil, hund_thou, ten_thou,thou, hund, tens, ones);
displayDrawTextBox(f,DSP_VFO_ALT_X, DSP_VFO_ALT_Y, DSP_VFO_ALT_SZ, DSP_VFO_ALT_COLOR, DSP_VFO_ALT_BK);
}
///////////////////////////////////////////////////////////////////////////
// displayVFOAB(int vfo)
// Displays which VFO is currently active A or B
// Legal values are VFOA and VFOB
///////////////////////////////////////////////////////////////////////////
void displayVFOAB(int vfo) {
String vfo_str;
if (vfo == VFOA) {
vfo_str = F("A");
} else {
vfo_str = F("B");
}
displayDrawTextBox(vfo_str,DSP_VFO_AB_X, DSP_VFO_AB_Y, DSP_VFO_AB_SZ, DSP_VFO_AB_COLOR, DSP_VFO_AB_BK);
}
///////////////////////////////////////////////////////////////////////////
// displayTxRx(int tx_rx
// Displays whether the radio is currenlty transmitting or receiveing
// Legal values are TX and RX
///////////////////////////////////////////////////////////////////////////
void displayTxRx(int tx_rx) {
String tx_rx_str;
int color;
if (tx_rx == RX) {
tx_rx_str = F("Rx");
color = DSP_RX_COLOR;
} else {
tx_rx_str = F("Tx");
color = DSP_TX_COLOR;
}
displayDrawTextBox(tx_rx_str,DSP_RX_TX_X, DSP_RX_TX_Y, DSP_RX_TX_SZ, color, DSP_RX_TX_BK);
}
//////////////////////////////////////////////////////////////////////////
// displayMode(int mode)
// Displays whether the radio is LSB or USB
// Legal values are LSB and USB
///////////////////////////////////////////////////////////////////////////
void displayMode(int mode) {
String mode_str;
if (mode == LSB) {
mode_str = F("LSB");
} else {
mode_str = F("USB");
}
displayDrawTextBox(mode_str,DSP_MODE_X, DSP_MODE_Y, DSP_MODE_SZ, DSP_MODE_COLOR, DSP_MODE_BK);
}
///////////////////////////////////////////////////////////////////////////
// displayIncr(uint32_t increment)
// Changes display of the tuning incrementindicator
// Legal values in Hz are 1, 10, 100, 1000, 10000, 100000, 1000000
///////////////////////////////////////////////////////////////////////////
void displayIncr(uint32_t increment) {
String hertz;
switch (increment) {
case 1: hertz = F(" 1"); break;
case 10: hertz = F(" 10"); break;
case 100: hertz = F(" 100"); break;
case 1000: hertz = F(" 1K"); break;
case 10000: hertz = F(" 10K"); break;
case 100000: hertz= F("100K"); break;
case 1000000:hertz = F(" 1M");
}
displayDrawTextBox(hertz,DSP_INCR_X, DSP_INCR_Y, DSP_INCR_SZ, DSP_INCR_COLOR, DSP_INCR_BK);
}
///////////////////////////////////////////////////////////////////////////
// displaySplit(boolean split)
// Turns split mode indicator on/off
///////////////////////////////////////////////////////////////////////////
void displaySplit(boolean splt) {
if (split) {
// todo
} else {
// todo
}
}
///////////////////////////////////////////////////////////////////////////
// displaySplit(boolean split)
// Turns split mode indicator on/off
///////////////////////////////////////////////////////////////////////////
void displayTune(boolean on_off) {
// todo
}
///////////////////////////////////////////////////////////////////////////
// displaySetup()
// Initialze and populate the display
///////////////////////////////////////////////////////////////////////////
void displaySetup(String banner,
uint32_t vfoActfreq, uint32_t vfoAltfreq,
uint32_t activeVFO,
int tx_rx,
int sideband,
boolean split,
uint32_t increment,
byte s_meter) {
tft.begin(); // Initialize the TFT
tft.setRotation(1); // Set landscape orientation
displayClearScreen(); // Fill teh screen with the background color
//
// Display the intitial values
//
displayBanner(banner);
displayActVFO(vfoActfreq);
displayAltVFO(vfoAltfreq);
displayVFOAB(activeVFO);
displayTxRx(tx_rx);
displayMode(sideband);
displaySplit(split);
displayIncr(increment);
displaySMeter(s_meter);
}
#endif

104
Settings.cpp 100644
Wyświetl plik

@ -0,0 +1,104 @@
#include "RadioControl.h"
#include <EEPROM.h>
#define EE_MAGIC_ADDR 0
#define EE_SETTINGS_ADDR 4
#define SETTINGS_TIMER 3000 // time to wait after a settings change before writing to EEPROM
const byte magic[4] = {'4', 'D', 'A', 'S'};
struct Settings_struct {
uint32_t vfoAfreq;
uint32_t vfoBfreq;
uint32_t increment;
byte active_vfo;
byte sideband;
// Band specific memory
uint32_t band20Freq;
uint32_t band40Freq;
byte band20Sideband;
byte band40Sideband;
};
// Settings Change
unsigned long settings_time;
bool settings_changed;
void readSettings() {
Settings_struct settings;
EEPROM.get(EE_SETTINGS_ADDR, settings);
vfoAfreq = settings.vfoAfreq;
vfoBfreq = settings.vfoBfreq;
increment = settings.increment;
active_vfo = settings.active_vfo;
sideband = settings.sideband;
band20Freq = settings.band20Freq;
band40Freq = settings.band40Freq;
band20Sideband = settings.band20Sideband;
band40Sideband = settings.band40Sideband;
if (sideband == LSB) {
bfo = LSB_BFO;
} else {
bfo = USB_BFO;
}
}
void writeSettings() {
Settings_struct settings;
settings.vfoAfreq = vfoAfreq;
settings.vfoBfreq = vfoBfreq;
settings.increment = increment;
settings.active_vfo = active_vfo;
settings.sideband = sideband;
settings.band20Freq = band20Freq;
settings.band40Freq = band40Freq;
settings.band20Sideband = band20Sideband;
settings.band40Sideband = band40Sideband;
EEPROM.put(EE_SETTINGS_ADDR, settings);
}
void initSettings() {
EEPROM.put(EE_MAGIC_ADDR, magic);
writeSettings();
}
void setupSettings() {
byte buff[4];
bool magic_ok = true;
EEPROM.get(EE_MAGIC_ADDR, buff);
for (int i = 0; i < 4; i++) {
if (buff[i] != magic[i]) {
magic_ok = false;
break;
}
}
if (magic_ok) {
readSettings();
} else {
initSettings();
}
settings_time = millis();
settings_changed = false;
}
void CheckSettings() {
if (settings_changed && (millis() - SETTINGS_TIMER) > settings_time) {
writeSettings();
settings_time = millis();
settings_changed = false;
}
}
void startSettingsTimer() {
settings_time = millis();
settings_changed = true;
}

13
Settings.h 100644
Wyświetl plik

@ -0,0 +1,13 @@
#ifndef Settings_h
#define Settings_h
// Function prototypes
extern void setupSettings();
extern void initSettings();
extern void readSettings();
extern void writeSettings();
extern void startSettingsTimer();
extern void CheckSettings();
#endif

57
Smeter.cpp 100644
Wyświetl plik

@ -0,0 +1,57 @@
//
// S Meter
//
#include "RadioControl.h"
//#define S_DEBUG
const int slevels[MAXSLEVELS] = {25, 100, 200, 375, 500, 575, 650, 775, 900, 950, 975, 1000};
//byte smeter = 0;
unsigned long smeter_raw = 0;
unsigned long smeter_sample_count = 0;
unsigned long smeter_time = 0;
//*****************************************
void CheckSmeter() {
int i;
int smeter_avg;
if (TxRxState == TX) {
return;
}
smeter_raw = smeter_raw + analogRead(SMETER_PIN);
smeter_sample_count++;
if ((millis() - SMETER_SAMPLE_TIMER) > smeter_time) {
/*
lcd.setCursor(12, 1);
lcd.print(" ");
lcd.setCursor(12, 1);
lcd.print(smeter_raw);
*/
smeter_avg = int(smeter_raw / smeter_sample_count);
#ifdef S_DEBUG
String msg = F("S raw:");
msg+=smeter_avg;
displayBanner(msg);
#endif
smeter = 0;
for (i = 0; i < MAXSLEVELS; i++) {
if (smeter_avg >= slevels[i]) {
smeter = byte(i);
}
}
displaySMeter(smeter);
smeter_raw = 0;
smeter_sample_count = 0;
smeter_time = millis();
}
}

12
Smeter.h 100644
Wyświetl plik

@ -0,0 +1,12 @@
#ifndef Smeter_h
#define Smeter_h
/*
* SMeter
* Constants defining and controlling Smeter collection and display
*/
#define SMETER_SAMPLE_TIMER 250 // Sample time
#define MAXSLEVELS 12 // S Levels 1-0, S9+10m S9+20, S9+30
#endif

37
Utility.cpp 100644
Wyświetl plik

@ -0,0 +1,37 @@
/* Utility Functions
*
* Delay(interval) - non-blocking delahy
*/
#include "Arduino.h"
#include "Utility.h"
/*
* "Delay" is a non-blocking delay function. The standard Arduino "delay" function
* turns off interrupts, which prevents the Serial I/O functions from working. That
* sometimes causes characters to be missed and junk commands to show up.
*/
void Delay ( unsigned long interval )
{
unsigned long currentTime = 0UL;
unsigned long startTime = millis();
while ( startTime + interval > currentTime )
currentTime = millis();
}
/*
* ToggleLED - toggles built-in LED On/Off - useful for debug
*/
byte LEDState=LOW;
void ToggleLED(){
if (LEDState == LOW){
LEDState = HIGH;
} else {
LEDState = LOW;
}
digitalWrite(LED_BUILTIN, LEDState);
}

10
Utility.h 100644
Wyświetl plik

@ -0,0 +1,10 @@
#ifndef UTILITY_h
#define UTILITY_h
/**************************************************************
Uttility functions - general purpose
***************************************************************/
//=============== Function Prototypes ============================================
extern void Delay (unsigned long interval); // Non-blocking delay
extern void ToggleLED(); // Toggle built-in LED
#endif

770
si5351.cpp 100644
Wyświetl plik

@ -0,0 +1,770 @@
/*
* si5351.cpp - Si5351 library for Arduino
*
* Copyright (C) 2014 Jason Milldrum <milldrum@gmail.com>
*
* Some tuning algorithms derived from clk-si5351.c in the Linux kernel.
* Sebastian Hesselbarth <sebastian.hesselbarth@gmail.com>
* Rabeeh Khoury <rabeeh@solid-run.com>
*
* rational_best_approximation() derived from lib/rational.c in
* the Linux kernel.
* Copyright (C) 2009 emlix GmbH, Oskar Schirmer <oskar@scara.com>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include <stdint.h>
#include <avr/eeprom.h>
#include "Arduino.h"
#include "Wire.h"
#include "si5351.h"
uint32_t EEMEM ee_ref_correction = 0;
/********************/
/* Public functions */
/********************/
Si5351::Si5351(void)
{
}
/*
* init(uint8_t xtal_load_c)
*
* Setup communications to the Si5351 and set the crystal
* load capacitance.
*
* xtal_load_c - Crystal load capacitance. Use the SI5351_CRYSTAL_LOAD_*PF
* defines in the header file
*
*/
void Si5351::init(uint8_t xtal_load_c)
{
// Start I2C comms
Wire.begin();
// Set crystal load capacitance
si5351_write(SI5351_CRYSTAL_LOAD, xtal_load_c);
// Get the correction factor from EEPROM
ref_correction = eeprom_read_dword(&ee_ref_correction);
}
/*
* set_freq(uint32_t freq, uint32_t pll_freq, enum si5351_clock output)
*
* Sets the clock frequency of the specified CLK output
*
* freq - Output frequency in Hz
* pll_freq - Frequency of the PLL driving the Multisynth
* Use a 0 to have the function choose a PLL frequency
* clk - Clock output
* (use the si5351_clock enum)
*/
void Si5351::set_freq(uint32_t freq, uint32_t pll_freq, enum si5351_clock clk)
{
struct Si5351RegSet ms_reg, pll_reg;
enum si5351_pll target_pll;
uint8_t set_pll = 0;
/* Calculate the synth parameters */
/* If pll_freq is 0, let the algorithm pick a PLL frequency */
if(pll_freq == 0)
{
pll_freq = multisynth_calc(freq, &ms_reg);
set_pll = 1;
}
/* TODO: bounds checking */
else
{
multisynth_recalc(freq, pll_freq, &ms_reg);
set_pll = 0;
}
/* Determine which PLL to use */
/* CLK0 gets PLLA, CLK1 gets PLLB */
/* CLK2 gets PLLB if necessary */
/* Only good for Si5351A3 variant at the moment */
if(clk == SI5351_CLK0)
{
target_pll = SI5351_PLLA;
si5351_set_ms_source(SI5351_CLK0, SI5351_PLLA);
plla_freq = pll_freq;
}
else if(clk == SI5351_CLK1)
{
target_pll = SI5351_PLLB;
si5351_set_ms_source(SI5351_CLK1, SI5351_PLLB);
pllb_freq = pll_freq;
}
else
{
/* need to account for CLK2 set before CLK1 */
if(pllb_freq == 0)
{
target_pll = SI5351_PLLB;
si5351_set_ms_source(SI5351_CLK2, SI5351_PLLB);
pllb_freq = pll_freq;
}
else
{
target_pll = SI5351_PLLB;
si5351_set_ms_source(SI5351_CLK2, SI5351_PLLB);
pll_freq = pllb_freq;
multisynth_recalc(freq, pll_freq, &ms_reg);
}
}
pll_calc(pll_freq, &pll_reg, ref_correction);
/* Derive the register values to write */
/* Prepare an array for parameters to be written to */
uint8_t *params = new uint8_t[20];
uint8_t i = 0;
uint8_t temp;
/* PLL parameters first */
if(set_pll == 1)
{
/* Registers 26-27 */
temp = ((pll_reg.p3 >> 8) & 0xFF);
params[i++] = temp;
temp = (uint8_t)(pll_reg.p3 & 0xFF);
params[i++] = temp;
/* Register 28 */
temp = (uint8_t)((pll_reg.p1 >> 16) & 0x03);
params[i++] = temp;
/* Registers 29-30 */
temp = (uint8_t)((pll_reg.p1 >> 8) & 0xFF);
params[i++] = temp;
temp = (uint8_t)(pll_reg.p1 & 0xFF);
params[i++] = temp;
/* Register 31 */
temp = (uint8_t)((pll_reg.p3 >> 12) & 0xF0);
temp += (uint8_t)((pll_reg.p2 >> 16) & 0x0F);
params[i++] = temp;
/* Registers 32-33 */
temp = (uint8_t)((pll_reg.p2 >> 8) & 0xFF);
params[i++] = temp;
temp = (uint8_t)(pll_reg.p2 & 0xFF);
params[i++] = temp;
/* Write the parameters */
if(target_pll == SI5351_PLLA)
{
si5351_write_bulk(SI5351_PLLA_PARAMETERS, i + 1, params);
}
else if(target_pll == SI5351_PLLB)
{
si5351_write_bulk(SI5351_PLLB_PARAMETERS, i + 1, params);
}
}
delete params;
/* Now the multisynth parameters */
params = new uint8_t[20];
i = 0;
/* Registers 42-43 */
temp = (uint8_t)((ms_reg.p3 >> 8) & 0xFF);
params[i++] = temp;
temp = (uint8_t)(ms_reg.p3 & 0xFF);
params[i++] = temp;
/* Register 44 */
/* TODO: add code for output divider */
temp = (uint8_t)((ms_reg.p1 >> 16) & 0x03);
params[i++] = temp;
/* Registers 45-46 */
temp = (uint8_t)((ms_reg.p1 >> 8) & 0xFF);
params[i++] = temp;
temp = (uint8_t)(ms_reg.p1 & 0xFF);
params[i++] = temp;
/* Register 47 */
temp = (uint8_t)((ms_reg.p3 >> 12) & 0xF0);
temp += (uint8_t)((ms_reg.p2 >> 16) & 0x0F);
params[i++] = temp;
/* Registers 48-49 */
temp = (uint8_t)((ms_reg.p2 >> 8) & 0xFF);
params[i++] = temp;
temp = (uint8_t)(ms_reg.p2 & 0xFF);
params[i++] = temp;
/* Write the parameters */
switch(clk)
{
case SI5351_CLK0:
si5351_write_bulk(SI5351_CLK0_PARAMETERS, i + 1, params);
si5351_set_ms_source(clk, target_pll);
break;
case SI5351_CLK1:
si5351_write_bulk(SI5351_CLK1_PARAMETERS, i + 1, params);
si5351_set_ms_source(clk, target_pll);
break;
case SI5351_CLK2:
si5351_write_bulk(SI5351_CLK2_PARAMETERS, i + 1, params);
si5351_set_ms_source(clk, target_pll);
break;
case SI5351_CLK3:
si5351_write_bulk(SI5351_CLK3_PARAMETERS, i + 1, params);
si5351_set_ms_source(clk, target_pll);
break;
case SI5351_CLK4:
si5351_write_bulk(SI5351_CLK4_PARAMETERS, i + 1, params);
si5351_set_ms_source(clk, target_pll);
break;
case SI5351_CLK5:
si5351_write_bulk(SI5351_CLK5_PARAMETERS, i + 1, params);
si5351_set_ms_source(clk, target_pll);
break;
case SI5351_CLK6:
si5351_write_bulk(SI5351_CLK6_PARAMETERS, i + 1, params);
si5351_set_ms_source(clk, target_pll);
break;
case SI5351_CLK7:
si5351_write_bulk(SI5351_CLK7_PARAMETERS, i + 1, params);
si5351_set_ms_source(clk, target_pll);
break;
}
delete params;
}
/*
* set_pll(uint32_t pll_freq, enum si5351_pll target_pll)
*
* Set the specified PLL to a specific oscillation frequency
*
* pll_freq - Desired PLL frequency
* target_pll - Which PLL to set
* (use the si5351_pll enum)
*/
void Si5351::set_pll(uint32_t pll_freq, enum si5351_pll target_pll)
{
struct Si5351RegSet pll_reg;
pll_calc(pll_freq, &pll_reg, ref_correction);
/* Derive the register values to write */
/* Prepare an array for parameters to be written to */
uint8_t *params = new uint8_t[20];
;
uint8_t i = 0;
uint8_t temp;
/* Registers 26-27 */
temp = ((pll_reg.p3 >> 8) & 0xFF);
params[i++] = temp;
temp = (uint8_t)(pll_reg.p3 & 0xFF);
params[i++] = temp;
/* Register 28 */
temp = (uint8_t)((pll_reg.p1 >> 16) & 0x03);
params[i++] = temp;
/* Registers 29-30 */
temp = (uint8_t)((pll_reg.p1 >> 8) & 0xFF);
params[i++] = temp;
temp = (uint8_t)(pll_reg.p1 & 0xFF);
params[i++] = temp;
/* Register 31 */
temp = (uint8_t)((pll_reg.p3 >> 12) & 0xF0);
temp += (uint8_t)((pll_reg.p2 >> 16) & 0x0F);
params[i++] = temp;
/* Registers 32-33 */
temp = (uint8_t)((pll_reg.p2 >> 8) & 0xFF);
params[i++] = temp;
temp = (uint8_t)(pll_reg.p2 & 0xFF);
params[i++] = temp;
/* Write the parameters */
if(target_pll == SI5351_PLLA)
{
si5351_write_bulk(SI5351_PLLA_PARAMETERS, i + 1, params);
}
else if(target_pll == SI5351_PLLB)
{
si5351_write_bulk(SI5351_PLLB_PARAMETERS, i + 1, params);
}
delete params;
}
/*
* clock_enable(enum si5351_clock clk, uint8_t enable)
*
* Enable or disable a chosen clock
* clk - Clock output
* (use the si5351_clock enum)
* enable - Set to 1 to enable, 0 to disable
*/
void Si5351::clock_enable(enum si5351_clock clk, uint8_t enable)
{
uint8_t reg_val;
reg_val = si5351_read(SI5351_OUTPUT_ENABLE_CTRL);
if(enable == 1)
{
reg_val &= ~(1<<(uint8_t)clk);
}
else
{
reg_val |= (1<<(uint8_t)clk);
}
si5351_write(SI5351_OUTPUT_ENABLE_CTRL, reg_val);
}
/*
* drive_strength(enum si5351_clock clk, enum si5351_drive drive)
*
* Sets the drive strength of the specified clock output
*
* clk - Clock output
* (use the si5351_clock enum)
* drive - Desired drive level
* (use the si5351_drive enum)
*/
void Si5351::drive_strength(enum si5351_clock clk, enum si5351_drive drive)
{
uint8_t reg_val;
const uint8_t mask = 0x03;
reg_val = si5351_read(SI5351_CLK0_CTRL + (uint8_t)clk);
switch(drive)
{
case SI5351_DRIVE_2MA:
reg_val &= ~(mask);
reg_val |= 0x00;
break;
case SI5351_DRIVE_4MA:
reg_val &= ~(mask);
reg_val |= 0x01;
break;
case SI5351_DRIVE_6MA:
reg_val &= ~(mask);
reg_val |= 0x02;
break;
case SI5351_DRIVE_8MA:
reg_val &= ~(mask);
reg_val |= 0x03;
break;
default:
break;
}
si5351_write(SI5351_CLK0_CTRL + (uint8_t)clk, reg_val);
}
/*
* update_status(void)
*
* Call this to update the status structs, then access them
* via the dev_status and dev_int_status global variables.
*
* See the header file for the struct definitions. These
* correspond to the flag names for registers 0 and 1 in
* the Si5351 datasheet.
*/
void Si5351::update_status(void)
{
si5351_update_sys_status(&dev_status);
si5351_update_int_status(&dev_int_status);
}
/*
* set_correction(int32_t corr)
*
* Use this to set the oscillator correction factor to
* EEPROM. This value is a signed 32-bit integer of the
* parts-per-10 million value that the actual oscillation
* frequency deviates from the specified frequency.
*
* The frequency calibration is done as a one-time procedure.
* Any desired test frequency within the normal range of the
* Si5351 should be set, then the actual output frequency
* should be measured as accurately as possible. The
* difference between the measured and specified frequencies
* should be calculated in Hertz, then multiplied by 10 in
* order to get the parts-per-10 million value.
*
* Since the Si5351 itself has an intrinsic 0 PPM error, this
* correction factor is good across the entire tuning range of
* the Si5351. Once this calibration is done accurately, it
* should not have to be done again for the same Si5351 and
* crystal. The library will read the correction factor from
* EEPROM during initialization for use by the tuning
* algorithms.
*/
void Si5351::set_correction(int32_t corr)
{
eeprom_write_dword(&ee_ref_correction, corr);
ref_correction = corr;
}
/*
* get_correction(void)
*
* Returns the oscillator correction factor stored
* in EEPROM.
*/
int32_t Si5351::get_correction(void)
{
return eeprom_read_dword(&ee_ref_correction);
}
uint8_t Si5351::si5351_write_bulk(uint8_t addr, uint8_t bytes, uint8_t *data)
{
Wire.beginTransmission(SI5351_BUS_BASE_ADDR);
Wire.write(addr);
for(int i = 0; i < bytes; i++)
{
Wire.write(data[i]);
}
Wire.endTransmission();
}
uint8_t Si5351::si5351_write(uint8_t addr, uint8_t data)
{
Wire.beginTransmission(SI5351_BUS_BASE_ADDR);
Wire.write(addr);
Wire.write(data);
Wire.endTransmission();
}
uint8_t Si5351::si5351_read(uint8_t addr)
{
Wire.beginTransmission(SI5351_BUS_BASE_ADDR);
Wire.write(addr);
Wire.endTransmission();
Wire.requestFrom(SI5351_BUS_BASE_ADDR, 1);
return Wire.read();
}
/*********************/
/* Private functions */
/*********************/
/*
* Calculate best rational approximation for a given fraction
* taking into account restricted register size, e.g. to find
* appropriate values for a pll with 5 bit denominator and
* 8 bit numerator register fields, trying to set up with a
* frequency ratio of 3.1415, one would say:
*
* rational_best_approximation(31415, 10000,
* (1 << 8) - 1, (1 << 5) - 1, &n, &d);
*
* you may look at given_numerator as a fixed point number,
* with the fractional part size described in given_denominator.
*
* for theoretical background, see:
* http://en.wikipedia.org/wiki/Continued_fraction
*/
void Si5351::rational_best_approximation(
unsigned long given_numerator, unsigned long given_denominator,
unsigned long max_numerator, unsigned long max_denominator,
unsigned long *best_numerator, unsigned long *best_denominator)
{
unsigned long n, d, n0, d0, n1, d1;
n = given_numerator;
d = given_denominator;
n0 = d1 = 0;
n1 = d0 = 1;
for (;;) {
unsigned long t, a;
if ((n1 > max_numerator) || (d1 > max_denominator)) {
n1 = n0;
d1 = d0;
break;
}
if (d == 0)
break;
t = d;
a = n / d;
d = n % d;
n = t;
t = n0 + a * n1;
n0 = n1;
n1 = t;
t = d0 + a * d1;
d0 = d1;
d1 = t;
}
*best_numerator = n1;
*best_denominator = d1;
}
uint32_t Si5351::pll_calc(uint32_t freq, struct Si5351RegSet *reg, int32_t correction)
{
uint32_t ref_freq = SI5351_XTAL_FREQ;
uint32_t rfrac, denom, a, b, c, p1, p2, p3;
uint64_t lltmp;
/* Factor calibration value into nominal crystal frequency */
/* Measured in parts-per-ten million */
ref_freq += (uint32_t)((double)(correction / 10000000.0) * (double)ref_freq);
/* PLL bounds checking */
if (freq < SI5351_PLL_VCO_MIN)
freq = SI5351_PLL_VCO_MIN;
if (freq > SI5351_PLL_VCO_MAX)
freq = SI5351_PLL_VCO_MAX;
/* Determine integer part of feedback equation */
a = freq / ref_freq;
if (a < SI5351_PLL_A_MIN)
freq = ref_freq * SI5351_PLL_A_MIN;
if (a > SI5351_PLL_A_MAX)
freq = ref_freq * SI5351_PLL_A_MAX;
/* find best approximation for b/c = fVCO mod fIN */
denom = 1000L * 1000L;
lltmp = freq % ref_freq;
lltmp *= denom;
do_div(lltmp, ref_freq);
rfrac = (uint32_t)lltmp;
b = 0;
c = 1;
if (rfrac)
rational_best_approximation(rfrac, denom,
SI5351_PLL_B_MAX, SI5351_PLL_C_MAX, &b, &c);
/* calculate parameters */
p3 = c;
p2 = (128 * b) % c;
p1 = 128 * a;
p1 += (128 * b / c);
p1 -= 512;
/* recalculate rate by fIN * (a + b/c) */
lltmp = ref_freq;
lltmp *= b;
do_div(lltmp, c);
freq = (uint32_t)lltmp;
freq += ref_freq * a;
reg->p1 = p1;
reg->p2 = p2;
reg->p3 = p3;
return freq;
}
uint32_t Si5351::multisynth_calc(uint32_t freq, struct Si5351RegSet *reg)
{
uint32_t pll_freq;
uint64_t lltmp;
uint32_t a, b, c, p1, p2, p3;
uint8_t divby4;
/* Multisynth bounds checking */
if (freq > SI5351_MULTISYNTH_MAX_FREQ)
freq = SI5351_MULTISYNTH_MAX_FREQ;
if (freq < SI5351_MULTISYNTH_MIN_FREQ)
freq = SI5351_MULTISYNTH_MIN_FREQ;
divby4 = 0;
if (freq > SI5351_MULTISYNTH_DIVBY4_FREQ)
divby4 = 1;
/* Find largest integer divider for max */
/* VCO frequency and given target frequency */
if (divby4 == 0)
{
lltmp = SI5351_PLL_VCO_MAX;
do_div(lltmp, freq);
a = (uint32_t)lltmp;
}
else
a = 4;
b = 0;
c = 1;
pll_freq = a * freq;
/* Recalculate output frequency by fOUT = fIN / (a + b/c) */
lltmp = pll_freq;
lltmp *= c;
do_div(lltmp, a * c + b);
freq = (unsigned long)lltmp;
/* Calculate parameters */
if (divby4)
{
p3 = 1;
p2 = 0;
p1 = 0;
}
else
{
p3 = c;
p2 = (128 * b) % c;
p1 = 128 * a;
p1 += (128 * b / c);
p1 -= 512;
}
reg->p1 = p1;
reg->p2 = p2;
reg->p3 = p3;
return pll_freq;
}
uint32_t Si5351::multisynth_recalc(uint32_t freq, uint32_t pll_freq, struct Si5351RegSet *reg)
{
uint64_t lltmp;
uint32_t rfrac, denom, a, b, c, p1, p2, p3;
uint8_t divby4;
/* Multisynth bounds checking */
if (freq > SI5351_MULTISYNTH_MAX_FREQ)
freq = SI5351_MULTISYNTH_MAX_FREQ;
if (freq < SI5351_MULTISYNTH_MIN_FREQ)
freq = SI5351_MULTISYNTH_MIN_FREQ;
divby4 = 0;
if (freq > SI5351_MULTISYNTH_DIVBY4_FREQ)
divby4 = 1;
/* Determine integer part of feedback equation */
a = pll_freq / freq;
/* TODO: not sure this is correct */
if (a < SI5351_MULTISYNTH_A_MIN)
freq = pll_freq / SI5351_MULTISYNTH_A_MIN;
if (a > SI5351_MULTISYNTH_A_MAX)
freq = pll_freq / SI5351_MULTISYNTH_A_MAX;
/* find best approximation for b/c */
denom = 1000L * 1000L;
lltmp = pll_freq % freq;
lltmp *= denom;
do_div(lltmp, freq);
rfrac = (uint32_t)lltmp;
b = 0;
c = 1;
if (rfrac)
rational_best_approximation(rfrac, denom,
SI5351_MULTISYNTH_B_MAX, SI5351_MULTISYNTH_C_MAX, &b, &c);
/* Recalculate output frequency by fOUT = fIN / (a + b/c) */
lltmp = pll_freq;
lltmp *= c;
do_div(lltmp, a * c + b);
freq = (unsigned long)lltmp;
/* Calculate parameters */
if (divby4)
{
p3 = 1;
p2 = 0;
p1 = 0;
}
else
{
p3 = c;
p2 = (128 * b) % c;
p1 = 128 * a;
p1 += (128 * b / c);
p1 -= 512;
}
reg->p1 = p1;
reg->p2 = p2;
reg->p3 = p3;
return freq;
}
void Si5351::si5351_update_sys_status(struct Si5351Status *status)
{
uint8_t reg_val = 0;
reg_val = si5351_read(SI5351_DEVICE_STATUS);
/* Parse the register */
status->SYS_INIT = (reg_val >> 7) & 0x01;
status->LOL_B = (reg_val >> 6) & 0x01;
status->LOL_A = (reg_val >> 5) & 0x01;
status->LOS = (reg_val >> 4) & 0x01;
status->REVID = reg_val & 0x03;
}
void Si5351::si5351_update_int_status(struct Si5351IntStatus *int_status)
{
uint8_t reg_val = 0;
reg_val = si5351_read(SI5351_DEVICE_STATUS);
/* Parse the register */
int_status->SYS_INIT_STKY = (reg_val >> 7) & 0x01;
int_status->LOL_B_STKY = (reg_val >> 6) & 0x01;
int_status->LOL_A_STKY = (reg_val >> 5) & 0x01;
int_status->LOS_STKY = (reg_val >> 4) & 0x01;
}
void Si5351::si5351_set_ms_source(enum si5351_clock clk, enum si5351_pll pll)
{
uint8_t reg_val = 0x0c;
uint8_t reg_val2;
reg_val2 = si5351_read(SI5351_CLK0_CTRL + (uint8_t)clk);
if(pll == SI5351_PLLA)
{
reg_val &= ~(SI5351_CLK_PLL_SELECT);
}
else if(pll == SI5351_PLLB)
{
reg_val |= SI5351_CLK_PLL_SELECT;
}
si5351_write(SI5351_CLK0_CTRL + (uint8_t)clk, reg_val);
}

289
si5351.h 100644
Wyświetl plik

@ -0,0 +1,289 @@
/*
* si5351.h - Si5351 library for Arduino
*
* Copyright (C) 2014 Jason Milldrum <milldrum@gmail.com>
*
* Many defines derived from clk-si5351.h in the Linux kernel.
* Sebastian Hesselbarth <sebastian.hesselbarth@gmail.com>
* Rabeeh Khoury <rabeeh@solid-run.com>
*
* do_div() macro derived from /include/asm-generic/div64.h in
* the Linux kernel.
* Copyright (C) 2003 Bernardo Innocenti <bernie@develer.com>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef SI5351_H_
#define SI5351_H_
#include "Arduino.h"
#include "Wire.h"
#include <stdint.h>
#include <avr/eeprom.h>
/* Define definitions */
#define SI5351_BUS_BASE_ADDR 0x60
#define SI5351_XTAL_FREQ 25000000
#define SI5351_PLL_FIXED 900000000
#define SI5351_PLL_VCO_MIN 600000000
#define SI5351_PLL_VCO_MAX 900000000
#define SI5351_MULTISYNTH_MIN_FREQ 1000000
#define SI5351_MULTISYNTH_DIVBY4_FREQ 150000000
#define SI5351_MULTISYNTH_MAX_FREQ 160000000
#define SI5351_MULTISYNTH67_MAX_FREQ SI5351_MULTISYNTH_DIVBY4_FREQ
#define SI5351_CLKOUT_MIN_FREQ 8000
#define SI5351_CLKOUT_MAX_FREQ SI5351_MULTISYNTH_MAX_FREQ
#define SI5351_CLKOUT67_MAX_FREQ SI5351_MULTISYNTH67_MAX_FREQ
#define SI5351_PLL_A_MIN 15
#define SI5351_PLL_A_MAX 90
#define SI5351_PLL_B_MAX (SI5351_PLL_C_MAX-1)
#define SI5351_PLL_C_MAX 1048575
#define SI5351_MULTISYNTH_A_MIN 6
#define SI5351_MULTISYNTH_A_MAX 1800
#define SI5351_MULTISYNTH67_A_MAX 254
#define SI5351_MULTISYNTH_B_MAX (SI5351_MULTISYNTH_C_MAX-1)
#define SI5351_MULTISYNTH_C_MAX 1048575
#define SI5351_MULTISYNTH_P1_MAX ((1<<18)-1)
#define SI5351_MULTISYNTH_P2_MAX ((1<<20)-1)
#define SI5351_MULTISYNTH_P3_MAX ((1<<20)-1)
#define SI5351_DEVICE_STATUS 0
#define SI5351_INTERRUPT_STATUS 1
#define SI5351_INTERRUPT_MASK 2
#define SI5351_STATUS_SYS_INIT (1<<7)
#define SI5351_STATUS_LOL_B (1<<6)
#define SI5351_STATUS_LOL_A (1<<5)
#define SI5351_STATUS_LOS (1<<4)
#define SI5351_OUTPUT_ENABLE_CTRL 3
#define SI5351_OEB_PIN_ENABLE_CTRL 9
#define SI5351_PLL_INPUT_SOURCE 15
#define SI5351_CLKIN_DIV_MASK (3<<6)
#define SI5351_CLKIN_DIV_1 (0<<6)
#define SI5351_CLKIN_DIV_2 (1<<6)
#define SI5351_CLKIN_DIV_4 (2<<6)
#define SI5351_CLKIN_DIV_8 (3<<6)
#define SI5351_PLLB_SOURCE (1<<3)
#define SI5351_PLLA_SOURCE (1<<2)
#define SI5351_CLK0_CTRL 16
#define SI5351_CLK1_CTRL 17
#define SI5351_CLK2_CTRL 18
#define SI5351_CLK3_CTRL 19
#define SI5351_CLK4_CTRL 20
#define SI5351_CLK5_CTRL 21
#define SI5351_CLK6_CTRL 22
#define SI5351_CLK7_CTRL 23
#define SI5351_CLK_POWERDOWN (1<<7)
#define SI5351_CLK_INTEGER_MODE (1<<6)
#define SI5351_CLK_PLL_SELECT (1<<5)
#define SI5351_CLK_INVERT (1<<4)
#define SI5351_CLK_INPUT_MASK (3<<2)
#define SI5351_CLK_INPUT_XTAL (0<<2)
#define SI5351_CLK_INPUT_CLKIN (1<<2)
#define SI5351_CLK_INPUT_MULTISYNTH_0_4 (2<<2)
#define SI5351_CLK_INPUT_MULTISYNTH_N (3<<2)
#define SI5351_CLK_DRIVE_STRENGTH_MASK (3<<0)
#define SI5351_CLK_DRIVE_STRENGTH_2MA (0<<0)
#define SI5351_CLK_DRIVE_STRENGTH_4MA (1<<0)
#define SI5351_CLK_DRIVE_STRENGTH_6MA (2<<0)
#define SI5351_CLK_DRIVE_STRENGTH_8MA (3<<0)
#define SI5351_CLK3_0_DISABLE_STATE 24
#define SI5351_CLK7_4_DISABLE_STATE 25
#define SI5351_CLK_DISABLE_STATE_MASK 3
#define SI5351_CLK_DISABLE_STATE_LOW 0
#define SI5351_CLK_DISABLE_STATE_HIGH 1
#define SI5351_CLK_DISABLE_STATE_FLOAT 2
#define SI5351_CLK_DISABLE_STATE_NEVER 3
#define SI5351_PARAMETERS_LENGTH 8
#define SI5351_PLLA_PARAMETERS 26
#define SI5351_PLLB_PARAMETERS 34
#define SI5351_CLK0_PARAMETERS 42
#define SI5351_CLK1_PARAMETERS 50
#define SI5351_CLK2_PARAMETERS 58
#define SI5351_CLK3_PARAMETERS 66
#define SI5351_CLK4_PARAMETERS 74
#define SI5351_CLK5_PARAMETERS 82
#define SI5351_CLK6_PARAMETERS 90
#define SI5351_CLK7_PARAMETERS 91
#define SI5351_CLK6_7_OUTPUT_DIVIDER 92
#define SI5351_OUTPUT_CLK_DIV_MASK (7 << 4)
#define SI5351_OUTPUT_CLK6_DIV_MASK (7 << 0)
#define SI5351_OUTPUT_CLK_DIV_SHIFT 4
#define SI5351_OUTPUT_CLK_DIV6_SHIFT 0
#define SI5351_OUTPUT_CLK_DIV_1 0
#define SI5351_OUTPUT_CLK_DIV_2 1
#define SI5351_OUTPUT_CLK_DIV_4 2
#define SI5351_OUTPUT_CLK_DIV_8 3
#define SI5351_OUTPUT_CLK_DIV_16 4
#define SI5351_OUTPUT_CLK_DIV_32 5
#define SI5351_OUTPUT_CLK_DIV_64 6
#define SI5351_OUTPUT_CLK_DIV_128 7
#define SI5351_OUTPUT_CLK_DIVBY4 (3<<2)
#define SI5351_SSC_PARAM0 149
#define SI5351_SSC_PARAM1 150
#define SI5351_SSC_PARAM2 151
#define SI5351_SSC_PARAM3 152
#define SI5351_SSC_PARAM4 153
#define SI5351_SSC_PARAM5 154
#define SI5351_SSC_PARAM6 155
#define SI5351_SSC_PARAM7 156
#define SI5351_SSC_PARAM8 157
#define SI5351_SSC_PARAM9 158
#define SI5351_SSC_PARAM10 159
#define SI5351_SSC_PARAM11 160
#define SI5351_SSC_PARAM12 161
#define SI5351_VXCO_PARAMETERS_LOW 162
#define SI5351_VXCO_PARAMETERS_MID 163
#define SI5351_VXCO_PARAMETERS_HIGH 164
#define SI5351_CLK0_PHASE_OFFSET 165
#define SI5351_CLK1_PHASE_OFFSET 166
#define SI5351_CLK2_PHASE_OFFSET 167
#define SI5351_CLK3_PHASE_OFFSET 168
#define SI5351_CLK4_PHASE_OFFSET 169
#define SI5351_CLK5_PHASE_OFFSET 170
#define SI5351_PLL_RESET 177
#define SI5351_PLL_RESET_B (1<<7)
#define SI5351_PLL_RESET_A (1<<5)
#define SI5351_CRYSTAL_LOAD 183
#define SI5351_CRYSTAL_LOAD_MASK (3<<6)
#define SI5351_CRYSTAL_LOAD_6PF (1<<6)
#define SI5351_CRYSTAL_LOAD_8PF (2<<6)
#define SI5351_CRYSTAL_LOAD_10PF (3<<6)
#define SI5351_FANOUT_ENABLE 187
#define SI5351_CLKIN_ENABLE (1<<7)
#define SI5351_XTAL_ENABLE (1<<6)
#define SI5351_MULTISYNTH_ENABLE (1<<4)
/* Macro definitions */
/*
* Based on former asm-ppc/div64.h and asm-m68knommu/div64.h
*
* The semantics of do_div() are:
*
* uint32_t do_div(uint64_t *n, uint32_t base)
* {
* uint32_t remainder = *n % base;
* *n = *n / base;
* return remainder;
* }
*
* NOTE: macro parameter n is evaluated multiple times,
* beware of side effects!
*/
# define do_div(n,base) ({ \
uint32_t __base = (base); \
uint32_t __rem; \
__rem = ((uint64_t)(n)) % __base; \
(n) = ((uint64_t)(n)) / __base; \
__rem; \
})
/* Enum definitions */
/*
* enum si5351_variant - SiLabs Si5351 chip variant
* @SI5351_VARIANT_A: Si5351A (8 output clocks, XTAL input)
* @SI5351_VARIANT_A3: Si5351A MSOP10 (3 output clocks, XTAL input)
* @SI5351_VARIANT_B: Si5351B (8 output clocks, XTAL/VXCO input)
* @SI5351_VARIANT_C: Si5351C (8 output clocks, XTAL/CLKIN input)
*/
enum si5351_variant {
SI5351_VARIANT_A = 1,
SI5351_VARIANT_A3 = 2,
SI5351_VARIANT_B = 3,
SI5351_VARIANT_C = 4,
};
enum si5351_clock {SI5351_CLK0, SI5351_CLK1, SI5351_CLK2, SI5351_CLK3,
SI5351_CLK4, SI5351_CLK5, SI5351_CLK6, SI5351_CLK7};
enum si5351_pll {SI5351_PLLA, SI5351_PLLB};
enum si5351_drive {SI5351_DRIVE_2MA, SI5351_DRIVE_4MA, SI5351_DRIVE_6MA, SI5351_DRIVE_8MA};
/* Struct definitions */
struct Si5351RegSet
{
uint32_t p1;
uint32_t p2;
uint32_t p3;
};
struct Si5351Status
{
uint8_t SYS_INIT;
uint8_t LOL_B;
uint8_t LOL_A;
uint8_t LOS;
uint8_t REVID;
};
struct Si5351IntStatus
{
uint8_t SYS_INIT_STKY;
uint8_t LOL_B_STKY;
uint8_t LOL_A_STKY;
uint8_t LOS_STKY;
};
class Si5351
{
public:
Si5351(void);
void init(uint8_t);
void set_freq(uint32_t, uint32_t, enum si5351_clock);
void set_pll(uint32_t, enum si5351_pll);
void clock_enable(enum si5351_clock, uint8_t);
void drive_strength(enum si5351_clock, enum si5351_drive);
void update_status(void);
void set_correction(int32_t);
int32_t get_correction(void);
uint8_t si5351_write_bulk(uint8_t, uint8_t, uint8_t *);
uint8_t si5351_write(uint8_t, uint8_t);
uint8_t si5351_read(uint8_t);
struct Si5351Status dev_status;
struct Si5351IntStatus dev_int_status;
private:
void rational_best_approximation(
unsigned long, unsigned long,
unsigned long, unsigned long,
unsigned long *, unsigned long *);
uint32_t pll_calc(uint32_t, struct Si5351RegSet *, int32_t);
uint32_t multisynth_calc(uint32_t, struct Si5351RegSet *);
uint32_t multisynth_recalc(uint32_t, uint32_t,struct Si5351RegSet *);
void si5351_update_sys_status(struct Si5351Status *);
void si5351_update_int_status(struct Si5351IntStatus *);
void si5351_set_ms_source(enum si5351_clock, enum si5351_pll);
uint32_t ee_ref_correction;
int32_t ref_correction;
uint32_t plla_freq;
uint32_t pllb_freq;
};
#endif /* SI5351_H_ */