The ubitx production sktech, wireup and circuit
This is the snap of the circuit, wiring instructions for the ubitx pcb. the sketch may change slightly for factory alignment but the rest will remain the same.
This commit is contained in:
parent
37aaaf4e89
commit
e481ea2a24
558
ubitx_20/ubitx_20.ino
Normal file
558
ubitx_20/ubitx_20.ino
Normal file
@ -0,0 +1,558 @@
|
|||||||
|
/**
|
||||||
|
* This source file is under General Public License version 3.
|
||||||
|
*
|
||||||
|
* This verision uses a built-in Si5351 library
|
||||||
|
* Most source code are meant to be understood by the compilers and the computers.
|
||||||
|
* Code that has to be hackable needs to be well understood and properly documented.
|
||||||
|
* Donald Knuth coined the term Literate Programming to indicate code that is written be
|
||||||
|
* easily read and understood.
|
||||||
|
*
|
||||||
|
* The Raduino is a small board that includes the Arduin Nano, a 16x2 LCD display and
|
||||||
|
* an Si5351a frequency synthesizer. This board is manufactured by Paradigm Ecomm Pvt Ltd
|
||||||
|
*
|
||||||
|
* To learn more about Arduino you may visit www.arduino.cc.
|
||||||
|
*
|
||||||
|
* The Arduino works by starts executing the code in a function called setup() and then it
|
||||||
|
* repeatedly keeps calling loop() forever. All the initialization code is kept in setup()
|
||||||
|
* and code to continuously sense the tuning knob, the function button, transmit/receive,
|
||||||
|
* etc is all in the loop() function. If you wish to study the code top down, then scroll
|
||||||
|
* to the bottom of this file and read your way up.
|
||||||
|
*
|
||||||
|
* Below are the libraries to be included for building the Raduino
|
||||||
|
* The EEPROM library is used to store settings like the frequency memory, caliberation data,
|
||||||
|
* callsign etc .
|
||||||
|
*
|
||||||
|
* The main chip which generates upto three oscillators of various frequencies in the
|
||||||
|
* Raduino is the Si5351a. To learn more about Si5351a you can download the datasheet
|
||||||
|
* from www.silabs.com although, strictly speaking it is not a requirment to understand this code.
|
||||||
|
* Instead, you can look up the Si5351 library written by xxx, yyy. You can download and
|
||||||
|
* install it from www.url.com to complile this file.
|
||||||
|
* The Wire.h library is used to talk to the Si5351 and we also declare an instance of
|
||||||
|
* Si5351 object to control the clocks.
|
||||||
|
*/
|
||||||
|
#include <Wire.h>
|
||||||
|
#include <EEPROM.h>
|
||||||
|
|
||||||
|
/**
|
||||||
|
The main chip which generates upto three oscillators of various frequencies in the
|
||||||
|
Raduino is the Si5351a. To learn more about Si5351a you can download the datasheet
|
||||||
|
from www.silabs.com although, strictly speaking it is not a requirment to understand this code.
|
||||||
|
|
||||||
|
We no longer use the standard SI5351 library because of its huge overhead due to many unused
|
||||||
|
features consuming a lot of program space. Instead of depending on an external library we now use
|
||||||
|
Jerry Gaffke's, KE7ER, lightweight standalone mimimalist "si5351bx" routines (see further down the
|
||||||
|
code). Here are some defines and declarations used by Jerry's routines:
|
||||||
|
*/
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* We need to carefully pick assignment of pin for various purposes.
|
||||||
|
* There are two sets of completely programmable pins on the Raduino.
|
||||||
|
* First, on the top of the board, in line with the LCD connector is an 8-pin connector
|
||||||
|
* that is largely meant for analog inputs and front-panel control. It has a regulated 5v output,
|
||||||
|
* ground and six pins. Each of these six pins can be individually programmed
|
||||||
|
* either as an analog input, a digital input or a digital output.
|
||||||
|
* The pins are assigned as follows (left to right, display facing you):
|
||||||
|
* Pin 1 (Violet), A7, SPARE
|
||||||
|
* Pin 2 (Blue), A6, KEYER (DATA)
|
||||||
|
* Pin 3 (Green), +5v
|
||||||
|
* Pin 4 (Yellow), Gnd
|
||||||
|
* Pin 5 (Orange), A3, PTT
|
||||||
|
* Pin 6 (Red), A2, F BUTTON
|
||||||
|
* Pin 7 (Brown), A1, ENC B
|
||||||
|
* Pin 8 (Black), A0, ENC A
|
||||||
|
*Note: A5, A4 are wired to the Si5351 as I2C interface
|
||||||
|
* *
|
||||||
|
* Though, this can be assigned anyway, for this application of the Arduino, we will make the following
|
||||||
|
* assignment
|
||||||
|
* A2 will connect to the PTT line, which is the usually a part of the mic connector
|
||||||
|
* A3 is connected to a push button that can momentarily ground this line. This will be used for RIT/Bandswitching, etc.
|
||||||
|
* A6 is to implement a keyer, it is reserved and not yet implemented
|
||||||
|
* A7 is connected to a center pin of good quality 100K or 10K linear potentiometer with the two other ends connected to
|
||||||
|
* ground and +5v lines available on the connector. This implments the tuning mechanism
|
||||||
|
*/
|
||||||
|
|
||||||
|
#define ENC_A (A0)
|
||||||
|
#define ENC_B (A1)
|
||||||
|
#define FBUTTON (A2)
|
||||||
|
#define PTT (A3)
|
||||||
|
#define ANALOG_KEYER (A6)
|
||||||
|
#define ANALOG_SPARE (A7)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The Raduino board is the size of a standard 16x2 LCD panel. It has three connectors:
|
||||||
|
*
|
||||||
|
* First, is an 8 pin connector that provides +5v, GND and six analog input pins that can also be
|
||||||
|
* configured to be used as digital input or output pins. These are referred to as A0,A1,A2,
|
||||||
|
* A3,A6 and A7 pins. The A4 and A5 pins are missing from this connector as they are used to
|
||||||
|
* talk to the Si5351 over I2C protocol.
|
||||||
|
*
|
||||||
|
* Second is a 16 pin LCD connector. This connector is meant specifically for the standard 16x2
|
||||||
|
* LCD display in 4 bit mode. The 4 bit mode requires 4 data lines and two control lines to work:
|
||||||
|
* Lines used are : RESET, ENABLE, D4, D5, D6, D7
|
||||||
|
* We include the library and declare the configuration of the LCD panel too
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <LiquidCrystal.h>
|
||||||
|
LiquidCrystal lcd(8,9,10,11,12,13);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The Arduino, unlike C/C++ on a regular computer with gigabytes of RAM, has very little memory.
|
||||||
|
* We have to be very careful with variables that are declared inside the functions as they are
|
||||||
|
* created in a memory region called the stack. The stack has just a few bytes of space on the Arduino
|
||||||
|
* if you declare large strings inside functions, they can easily exceed the capacity of the stack
|
||||||
|
* and mess up your programs.
|
||||||
|
* We circumvent this by declaring a few global buffers as kitchen counters where we can
|
||||||
|
* slice and dice our strings. These strings are mostly used to control the display or handle
|
||||||
|
* the input and output from the USB port. We must keep a count of the bytes used while reading
|
||||||
|
* the serial port as we can easily run out of buffer space. This is done in the serial_in_count variable.
|
||||||
|
*/
|
||||||
|
char c[30], b[30];
|
||||||
|
char printBuff[2][17]; //mirrors what is showing on the two lines of the display
|
||||||
|
int count = 0; //to generally count ticks, loops, etc
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The second set of 16 pins on the Raduino's bottom connector are have the three clock outputs and the digital lines to control the rig.
|
||||||
|
* This assignment is as follows :
|
||||||
|
* Pin 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
|
||||||
|
* GND +5V CLK0 GND GND CLK1 GND GND CLK2 GND D2 D3 D4 D5 D6 D7
|
||||||
|
* These too are flexible with what you may do with them, for the Raduino, we use them to :
|
||||||
|
* - TX_RX line : Switches between Transmit and Receive after sensing the PTT or the morse keyer
|
||||||
|
* - CW_KEY line : turns on the carrier for CW
|
||||||
|
*/
|
||||||
|
|
||||||
|
#define TX_RX (7)
|
||||||
|
#define CW_TONE (6)
|
||||||
|
#define TX_LPF_A (5)
|
||||||
|
#define TX_LPF_B (4)
|
||||||
|
#define TX_LPF_C (3)
|
||||||
|
#define CW_KEY (2)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* These are the indices where these user changable settinngs are stored in the EEPROM
|
||||||
|
*/
|
||||||
|
#define MASTER_CAL 0
|
||||||
|
#define LSB_CAL 4
|
||||||
|
#define USB_CAL 8
|
||||||
|
#define SIDE_TONE 12
|
||||||
|
//these are ids of the vfos as well as their offset into the eeprom storage, don't change these 'magic' values
|
||||||
|
#define VFO_A 16
|
||||||
|
#define VFO_B 20
|
||||||
|
#define CW_SIDETONE 24
|
||||||
|
#define CW_SPEED 28
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The uBITX is an upconnversion transceiver. The first IF is at 45 MHz.
|
||||||
|
* The first IF frequency is not exactly at 45 Mhz but about 5 khz lower,
|
||||||
|
* this shift is due to the loading on the 45 Mhz crystal filter by the matching
|
||||||
|
* L-network used on it's either sides.
|
||||||
|
* The first oscillator works between 48 Mhz and 75 MHz. The signal is subtracted
|
||||||
|
* from the first oscillator to arriive at 45 Mhz IF. Thus, it is inverted : LSB becomes USB
|
||||||
|
* and USB becomes LSB.
|
||||||
|
* The second IF of 12 Mhz has a ladder crystal filter. If a second oscillator is used at
|
||||||
|
* 57 Mhz, the signal is subtracted FROM the oscillator, inverting a second time, and arrives
|
||||||
|
* at the 12 Mhz ladder filter thus doouble inversion, keeps the sidebands as they originally were.
|
||||||
|
* If the second oscillator is at 33 Mhz, the oscilaltor is subtracated from the signal,
|
||||||
|
* thus keeping the signal's sidebands inverted. The USB will become LSB.
|
||||||
|
* We use this technique to switch sidebands. This is to avoid placing the lsbCarrier close to
|
||||||
|
* 12 MHz where its fifth harmonic beats with the arduino's 16 Mhz oscillator's fourth harmonic
|
||||||
|
*/
|
||||||
|
|
||||||
|
// the second oscillator should ideally be at 57 MHz, however, the crystal filter's center frequency
|
||||||
|
// is shifted down a little due to the loading from the impedance matching L-networks on either sides
|
||||||
|
#define SECOND_OSC_USB (56995000l)
|
||||||
|
#define SECOND_OSC_LSB (32995000l)
|
||||||
|
//these are the two default USB and LSB frequencies. The best frequencies depend upon your individual taste and filter shape
|
||||||
|
#define INIT_USB_FREQ (11996500l)
|
||||||
|
// limits the tuning and working range of the ubitx between 3 MHz and 30 MHz
|
||||||
|
#define LOWEST_FREQ (3000000l)
|
||||||
|
#define HIGHEST_FREQ (30000000l)
|
||||||
|
|
||||||
|
//we directly generate the CW by programmin the Si5351 to the cw tx frequency, hence, both are different modes
|
||||||
|
//these are the parameter passed to startTx
|
||||||
|
#define TX_SSB 0
|
||||||
|
#define TX_CW 1
|
||||||
|
|
||||||
|
char ritOn = 0;
|
||||||
|
char vfoActive = VFO_A;
|
||||||
|
int8_t meter_reading = 0; // a -1 on meter makes it invisible
|
||||||
|
unsigned long vfoA=7150000L, vfoB=14200000L, sideTone=800, usbCarrier;
|
||||||
|
unsigned long frequency, ritRxFrequency, ritTxFrequency; //frequency is the current frequency on the dial
|
||||||
|
|
||||||
|
int cwSpeed = 100; //this is actuall the dot period in milliseconds
|
||||||
|
extern int32_t calibration;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Raduino needs to keep track of current state of the transceiver. These are a few variables that do it
|
||||||
|
*/
|
||||||
|
boolean txCAT = false; //turned on if the transmitting due to a CAT command
|
||||||
|
char inTx = 0; //it is set to 1 if in transmit mode (whatever the reason : cw, ptt or cat)
|
||||||
|
char splitOn = 0; //working split, uses VFO B as the transmit frequency, (NOT IMPLEMENTED YET)
|
||||||
|
char keyDown = 0; //in cw mode, denotes the carrier is being transmitted
|
||||||
|
char isUSB = 0; //upper sideband was selected, this is reset to the default for the
|
||||||
|
//frequency when it crosses the frequency border of 10 MHz
|
||||||
|
byte menuOn = 0; //set to 1 when the menu is being displayed, if a menu item sets it to zero, the menu is exited
|
||||||
|
unsigned long cwTimeout = 0; //milliseconds to go before the cw transmit line is released and the radio goes back to rx mode
|
||||||
|
unsigned long dbgCount = 0; //not used now
|
||||||
|
unsigned char txFilter = 0; //which of the four transmit filters are in use
|
||||||
|
boolean modeCalibrate = false;//this mode of menus shows extended menus to calibrate the oscillators and choose the proper
|
||||||
|
//beat frequency
|
||||||
|
/**
|
||||||
|
* Below are the basic functions that control the uBitx. Understanding the functions before
|
||||||
|
* you start hacking around
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Select the properly tx harmonic filters
|
||||||
|
* The four harmonic filters use only three relays
|
||||||
|
* the four LPFs cover 30-21 Mhz, 18 - 14 Mhz, 7-10 MHz and 3.5 to 5 Mhz
|
||||||
|
* Briefly, it works like this,
|
||||||
|
* - When KT1 is OFF, the 'off' position routes the PA output through the 30 MHz LPF
|
||||||
|
* - When KT1 is ON, it routes the PA output to KT2. Which is why you will see that
|
||||||
|
* the KT1 is on for the three other cases.
|
||||||
|
* - When the KT1 is ON and KT2 is off, the off position of KT2 routes the PA output
|
||||||
|
* to 18 MHz LPF (That also works for 14 Mhz)
|
||||||
|
* - When KT1 is On, KT2 is On, it routes the PA output to KT3
|
||||||
|
* - KT3, when switched on selects the 7-10 Mhz filter
|
||||||
|
* - KT3 when switched off selects the 3.5-5 Mhz filter
|
||||||
|
* See the circuit to understand this
|
||||||
|
*/
|
||||||
|
|
||||||
|
void setTXFilters(unsigned long freq){
|
||||||
|
|
||||||
|
if (freq > 21000000L){ // the default filter is with 35 MHz cut-off
|
||||||
|
digitalWrite(TX_LPF_A, 0);
|
||||||
|
digitalWrite(TX_LPF_B, 0);
|
||||||
|
digitalWrite(TX_LPF_C, 0);
|
||||||
|
}
|
||||||
|
else if (freq >= 14000000L){ //thrown the KT1 relay on, the 30 MHz LPF is bypassed and the 14-18 MHz LPF is allowd to go through
|
||||||
|
digitalWrite(TX_LPF_A, 1);
|
||||||
|
digitalWrite(TX_LPF_B, 0);
|
||||||
|
digitalWrite(TX_LPF_C, 0);
|
||||||
|
}
|
||||||
|
else if (freq > 7000000L){
|
||||||
|
digitalWrite(TX_LPF_A, 1);
|
||||||
|
digitalWrite(TX_LPF_B, 1);
|
||||||
|
digitalWrite(TX_LPF_C, 0);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
digitalWrite(TX_LPF_A, 1);
|
||||||
|
digitalWrite(TX_LPF_B, 1);
|
||||||
|
digitalWrite(TX_LPF_C, 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This is the most frequently called function that configures the
|
||||||
|
* radio to a particular frequeny, sideband and sets up the transmit filters
|
||||||
|
*
|
||||||
|
* The transmit filter relays are powered up only during the tx so they dont
|
||||||
|
* draw any current during rx.
|
||||||
|
*
|
||||||
|
* The carrier oscillator of the detector/modulator is permanently fixed at
|
||||||
|
* uppper sideband. The sideband selection is done by placing the second oscillator
|
||||||
|
* either 12 Mhz below or above the 45 Mhz signal thereby inverting the sidebands
|
||||||
|
* through mixing of the second local oscillator.
|
||||||
|
*/
|
||||||
|
|
||||||
|
void setFrequency(unsigned long f){
|
||||||
|
uint64_t osc_f;
|
||||||
|
|
||||||
|
setTXFilters(f);
|
||||||
|
|
||||||
|
if (isUSB){
|
||||||
|
si5351bx_setfreq(2, SECOND_OSC_USB - usbCarrier + f);
|
||||||
|
si5351bx_setfreq(1, SECOND_OSC_USB);
|
||||||
|
}
|
||||||
|
else{
|
||||||
|
si5351bx_setfreq(2, SECOND_OSC_LSB + usbCarrier + f);
|
||||||
|
si5351bx_setfreq(1, SECOND_OSC_LSB);
|
||||||
|
}
|
||||||
|
|
||||||
|
frequency = f;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* startTx is called by the PTT, cw keyer and CAT protocol to
|
||||||
|
* put the uBitx in tx mode. It takes care of rit settings, sideband settings
|
||||||
|
* Note: In cw mode, doesnt key the radio, only puts it in tx mode
|
||||||
|
*/
|
||||||
|
|
||||||
|
void startTx(byte txMode){
|
||||||
|
unsigned long tx_freq = 0;
|
||||||
|
digitalWrite(TX_RX, 1);
|
||||||
|
inTx = 1;
|
||||||
|
|
||||||
|
if (ritOn){
|
||||||
|
//save the current as the rx frequency
|
||||||
|
ritRxFrequency = frequency;
|
||||||
|
setFrequency(ritTxFrequency);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (txMode == TX_CW){
|
||||||
|
//turn off the second local oscillator and the bfo
|
||||||
|
si5351bx_setfreq(0, 0);
|
||||||
|
si5351bx_setfreq(1, 0);
|
||||||
|
|
||||||
|
//shif the first oscillator to the tx frequency directly
|
||||||
|
//the key up and key down will toggle the carrier unbalancing
|
||||||
|
//the exact cw frequency is the tuned frequency + sidetone
|
||||||
|
if (isUSB)
|
||||||
|
si5351bx_setfreq(2, frequency + sideTone);
|
||||||
|
else
|
||||||
|
si5351bx_setfreq(2, frequency - sideTone);
|
||||||
|
}
|
||||||
|
updateDisplay();
|
||||||
|
}
|
||||||
|
|
||||||
|
void stopTx(){
|
||||||
|
inTx = 0;
|
||||||
|
|
||||||
|
digitalWrite(TX_RX, 0); //turn off the tx
|
||||||
|
si5351bx_setfreq(0, usbCarrier); //set back the carrier oscillator anyway, cw tx switches it off
|
||||||
|
|
||||||
|
if (ritOn)
|
||||||
|
setFrequency(ritRxFrequency);
|
||||||
|
else
|
||||||
|
setFrequency(frequency);
|
||||||
|
|
||||||
|
updateDisplay();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* ritEnable is called with a frequency parameter that determines
|
||||||
|
* what the tx frequency will be
|
||||||
|
*/
|
||||||
|
void ritEnable(unsigned long f){
|
||||||
|
ritOn = 1;
|
||||||
|
//save the non-rit frequency back into the VFO memory
|
||||||
|
//as RIT is a temporary shift, this is not saved to EEPROM
|
||||||
|
ritTxFrequency = f;
|
||||||
|
}
|
||||||
|
|
||||||
|
// this is called by the RIT menu routine
|
||||||
|
void ritDisable(){
|
||||||
|
if (ritOn){
|
||||||
|
ritOn = 0;
|
||||||
|
setFrequency(ritTxFrequency);
|
||||||
|
updateDisplay();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Basic User Interface Routines. These check the front panel for any activity
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The PTT is checked only if we are not already in a cw transmit session
|
||||||
|
* If the PTT is pressed, we shift to the ritbase if the rit was on
|
||||||
|
* flip the T/R line to T and update the display to denote transmission
|
||||||
|
*/
|
||||||
|
|
||||||
|
void checkPTT(){
|
||||||
|
//we don't check for ptt when transmitting cw
|
||||||
|
if (cwTimeout > 0)
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (digitalRead(PTT) == 0 && inTx == 0){
|
||||||
|
startTx(TX_SSB);
|
||||||
|
delay(50); //debounce the PTT
|
||||||
|
}
|
||||||
|
|
||||||
|
if (digitalRead(PTT) == 1 && inTx == 1)
|
||||||
|
stopTx();
|
||||||
|
}
|
||||||
|
|
||||||
|
void checkButton(){
|
||||||
|
int i, t1, t2, knob, new_knob;
|
||||||
|
|
||||||
|
//only if the button is pressed
|
||||||
|
if (!btnDown())
|
||||||
|
return;
|
||||||
|
delay(50);
|
||||||
|
if (!btnDown()) //debounce
|
||||||
|
return;
|
||||||
|
|
||||||
|
doMenu();
|
||||||
|
//wait for the button to go up again
|
||||||
|
while(btnDown())
|
||||||
|
delay(10);
|
||||||
|
delay(50);//debounce
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The tuning jumps by 50 Hz on each step when you tune slowly
|
||||||
|
* As you spin the encoder faster, the jump size also increases
|
||||||
|
* This way, you can quickly move to another band by just spinning the
|
||||||
|
* tuning knob
|
||||||
|
*/
|
||||||
|
|
||||||
|
void doTuning(){
|
||||||
|
int s;
|
||||||
|
unsigned long prev_freq;
|
||||||
|
|
||||||
|
s = enc_read();
|
||||||
|
if (s){
|
||||||
|
prev_freq = frequency;
|
||||||
|
|
||||||
|
if (s > 10)
|
||||||
|
frequency += 200000l;
|
||||||
|
if (s > 7)
|
||||||
|
frequency += 10000l;
|
||||||
|
else if (s > 4)
|
||||||
|
frequency += 1000l;
|
||||||
|
else if (s > 2)
|
||||||
|
frequency += 500;
|
||||||
|
else if (s > 0)
|
||||||
|
frequency += 50l;
|
||||||
|
else if (s > -2)
|
||||||
|
frequency -= 50l;
|
||||||
|
else if (s > -4)
|
||||||
|
frequency -= 500l;
|
||||||
|
else if (s > -7)
|
||||||
|
frequency -= 1000l;
|
||||||
|
else if (s > -9)
|
||||||
|
frequency -= 10000l;
|
||||||
|
else
|
||||||
|
frequency -= 200000l;
|
||||||
|
|
||||||
|
if (prev_freq < 10000000l && frequency > 10000000l)
|
||||||
|
isUSB = true;
|
||||||
|
|
||||||
|
if (prev_freq > 10000000l && frequency < 10000000l)
|
||||||
|
isUSB = false;
|
||||||
|
|
||||||
|
setFrequency(frequency);
|
||||||
|
updateDisplay();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* RIT only steps back and forth by 100 hz at a time
|
||||||
|
*/
|
||||||
|
void doRIT(){
|
||||||
|
unsigned long newFreq;
|
||||||
|
|
||||||
|
int knob = enc_read();
|
||||||
|
unsigned long old_freq = frequency;
|
||||||
|
|
||||||
|
if (knob < 0)
|
||||||
|
frequency -= 100l;
|
||||||
|
else if (knob > 0)
|
||||||
|
frequency += 100;
|
||||||
|
|
||||||
|
if (old_freq != frequency){
|
||||||
|
setFrequency(frequency);
|
||||||
|
updateDisplay();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The settings are read from EEPROM. The first time around, the values may not be
|
||||||
|
* present or out of range, in this case, some intelligent defaults are copied into the
|
||||||
|
* variables.
|
||||||
|
*/
|
||||||
|
void initSettings(){
|
||||||
|
//read the settings from the eeprom and restore them
|
||||||
|
//if the readings are off, then set defaults
|
||||||
|
EEPROM.get(MASTER_CAL, calibration);
|
||||||
|
EEPROM.get(USB_CAL, usbCarrier);
|
||||||
|
EEPROM.get(VFO_A, vfoA);
|
||||||
|
EEPROM.get(VFO_B, vfoB);
|
||||||
|
EEPROM.get(CW_SIDETONE, sideTone);
|
||||||
|
EEPROM.get(CW_SPEED, cwSpeed);
|
||||||
|
if (usbCarrier > 12010000l || usbCarrier < 11990000l)
|
||||||
|
usbCarrier = 11997000l;
|
||||||
|
if (vfoA > 35000000l || 3500000l > vfoA)
|
||||||
|
vfoA = 7150000l;
|
||||||
|
if (vfoB > 35000000l || 3500000l > vfoB)
|
||||||
|
vfoB = 14150000l;
|
||||||
|
if (sideTone < 100 || 2000 < sideTone)
|
||||||
|
sideTone = 800;
|
||||||
|
if (cwSpeed < 10 || 1000 < cwSpeed)
|
||||||
|
cwSpeed = 100;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
void initPorts(){
|
||||||
|
|
||||||
|
analogReference(DEFAULT);
|
||||||
|
|
||||||
|
//??
|
||||||
|
pinMode(ENC_A, INPUT_PULLUP);
|
||||||
|
pinMode(ENC_B, INPUT_PULLUP);
|
||||||
|
pinMode(FBUTTON, INPUT_PULLUP);
|
||||||
|
|
||||||
|
//configure the function button to use the external pull-up
|
||||||
|
// pinMode(FBUTTON, INPUT);
|
||||||
|
// digitalWrite(FBUTTON, HIGH);
|
||||||
|
|
||||||
|
pinMode(PTT, INPUT_PULLUP);
|
||||||
|
pinMode(ANALOG_KEYER, INPUT_PULLUP);
|
||||||
|
|
||||||
|
pinMode(CW_TONE, OUTPUT);
|
||||||
|
digitalWrite(CW_TONE, 0);
|
||||||
|
|
||||||
|
pinMode(TX_RX,OUTPUT);
|
||||||
|
digitalWrite(TX_RX, 0);
|
||||||
|
|
||||||
|
pinMode(TX_LPF_A, OUTPUT);
|
||||||
|
pinMode(TX_LPF_B, OUTPUT);
|
||||||
|
pinMode(TX_LPF_C, OUTPUT);
|
||||||
|
digitalWrite(TX_LPF_A, 0);
|
||||||
|
digitalWrite(TX_LPF_B, 0);
|
||||||
|
digitalWrite(TX_LPF_C, 0);
|
||||||
|
|
||||||
|
pinMode(CW_KEY, OUTPUT);
|
||||||
|
digitalWrite(CW_KEY, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
void setup()
|
||||||
|
{
|
||||||
|
Serial.begin(9600);
|
||||||
|
|
||||||
|
lcd.begin(16, 2);
|
||||||
|
|
||||||
|
//we print this line so this shows up even if the raduino
|
||||||
|
//crashes later in the code
|
||||||
|
printLine1("uBITX v0.20");
|
||||||
|
delay(500);
|
||||||
|
|
||||||
|
initMeter(); //not used in this build
|
||||||
|
initSettings();
|
||||||
|
initPorts();
|
||||||
|
initOscillators();
|
||||||
|
|
||||||
|
frequency = vfoA;
|
||||||
|
setFrequency(vfoA);
|
||||||
|
updateDisplay();
|
||||||
|
|
||||||
|
if (btnDown())
|
||||||
|
factory_alignment();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The loop checks for keydown, ptt, function button and tuning.
|
||||||
|
*/
|
||||||
|
|
||||||
|
byte flasher = 0;
|
||||||
|
void loop(){
|
||||||
|
|
||||||
|
cwKeyer();
|
||||||
|
if (!txCAT)
|
||||||
|
checkPTT();
|
||||||
|
checkButton();
|
||||||
|
|
||||||
|
//tune only when not tranmsitting
|
||||||
|
if (!inTx){
|
||||||
|
if (ritOn)
|
||||||
|
doRIT();
|
||||||
|
else
|
||||||
|
doTuning();
|
||||||
|
}
|
||||||
|
|
||||||
|
//we check CAT after the encoder as it might put the radio into TX
|
||||||
|
checkCAT();
|
||||||
|
}
|
231
ubitx_20/ubitx_cat.ino
Normal file
231
ubitx_20/ubitx_cat.ino
Normal file
@ -0,0 +1,231 @@
|
|||||||
|
/**
|
||||||
|
* The CAT protocol is used by many radios to provide remote control to comptuers through
|
||||||
|
* the serial port.
|
||||||
|
*
|
||||||
|
* This is very much a work in progress. Parts of this code have been liberally
|
||||||
|
* borrowed from other GPLicensed works like hamlib.
|
||||||
|
*
|
||||||
|
* WARNING : This is an unstable version and it has worked with fldigi,
|
||||||
|
* it gives time out error with WSJTX 1.8.0
|
||||||
|
*/
|
||||||
|
|
||||||
|
// The next 4 functions are needed to implement the CAT protocol, which
|
||||||
|
// uses 4-bit BCD formatting.
|
||||||
|
//
|
||||||
|
byte setHighNibble(byte b,byte v) {
|
||||||
|
// Clear the high nibble
|
||||||
|
b &= 0x0f;
|
||||||
|
// Set the high nibble
|
||||||
|
return b | ((v & 0x0f) << 4);
|
||||||
|
}
|
||||||
|
|
||||||
|
byte setLowNibble(byte b,byte v) {
|
||||||
|
// Clear the low nibble
|
||||||
|
b &= 0xf0;
|
||||||
|
// Set the low nibble
|
||||||
|
return b | (v & 0x0f);
|
||||||
|
}
|
||||||
|
|
||||||
|
byte getHighNibble(byte b) {
|
||||||
|
return (b >> 4) & 0x0f;
|
||||||
|
}
|
||||||
|
|
||||||
|
byte getLowNibble(byte b) {
|
||||||
|
return b & 0x0f;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Takes a number and produces the requested number of decimal digits, staring
|
||||||
|
// from the least significant digit.
|
||||||
|
//
|
||||||
|
void getDecimalDigits(unsigned long number,byte* result,int digits) {
|
||||||
|
for (int i = 0; i < digits; i++) {
|
||||||
|
// "Mask off" (in a decimal sense) the LSD and return it
|
||||||
|
result[i] = number % 10;
|
||||||
|
// "Shift right" (in a decimal sense)
|
||||||
|
number /= 10;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Takes a frequency and writes it into the CAT command buffer in BCD form.
|
||||||
|
//
|
||||||
|
void writeFreq(unsigned long freq,byte* cmd) {
|
||||||
|
// Convert the frequency to a set of decimal digits. We are taking 9 digits
|
||||||
|
// so that we can get up to 999 MHz. But the protocol doesn't care about the
|
||||||
|
// LSD (1's place), so we ignore that digit.
|
||||||
|
byte digits[9];
|
||||||
|
getDecimalDigits(freq,digits,9);
|
||||||
|
// Start from the LSB and get each nibble
|
||||||
|
cmd[3] = setLowNibble(cmd[3],digits[1]);
|
||||||
|
cmd[3] = setHighNibble(cmd[3],digits[2]);
|
||||||
|
cmd[2] = setLowNibble(cmd[2],digits[3]);
|
||||||
|
cmd[2] = setHighNibble(cmd[2],digits[4]);
|
||||||
|
cmd[1] = setLowNibble(cmd[1],digits[5]);
|
||||||
|
cmd[1] = setHighNibble(cmd[1],digits[6]);
|
||||||
|
cmd[0] = setLowNibble(cmd[0],digits[7]);
|
||||||
|
cmd[0] = setHighNibble(cmd[0],digits[8]);
|
||||||
|
}
|
||||||
|
|
||||||
|
// This function takes a frquency that is encoded using 4 bytes of BCD
|
||||||
|
// representation and turns it into an long measured in Hz.
|
||||||
|
//
|
||||||
|
// [12][34][56][78] = 123.45678? Mhz
|
||||||
|
//
|
||||||
|
unsigned long readFreq(byte* cmd) {
|
||||||
|
// Pull off each of the digits
|
||||||
|
byte d7 = getHighNibble(cmd[0]);
|
||||||
|
byte d6 = getLowNibble(cmd[0]);
|
||||||
|
byte d5 = getHighNibble(cmd[1]);
|
||||||
|
byte d4 = getLowNibble(cmd[1]);
|
||||||
|
byte d3 = getHighNibble(cmd[2]);
|
||||||
|
byte d2 = getLowNibble(cmd[2]);
|
||||||
|
byte d1 = getHighNibble(cmd[3]);
|
||||||
|
byte d0 = getLowNibble(cmd[3]);
|
||||||
|
return
|
||||||
|
(unsigned long)d7 * 100000000L +
|
||||||
|
(unsigned long)d6 * 10000000L +
|
||||||
|
(unsigned long)d5 * 1000000L +
|
||||||
|
(unsigned long)d4 * 100000L +
|
||||||
|
(unsigned long)d3 * 10000L +
|
||||||
|
(unsigned long)d2 * 1000L +
|
||||||
|
(unsigned long)d1 * 100L +
|
||||||
|
(unsigned long)d0 * 10L;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Responds to all the cat commands, emulates FT-817
|
||||||
|
*/
|
||||||
|
|
||||||
|
void processCATCommand(byte* cmd) {
|
||||||
|
byte response[5];
|
||||||
|
|
||||||
|
// Debugging code, enable it to fix the cat implementation
|
||||||
|
|
||||||
|
count++;
|
||||||
|
if (cmd[4] == 0x00){
|
||||||
|
response[0]=0;
|
||||||
|
Serial.write(response, 1);
|
||||||
|
}
|
||||||
|
else if (cmd[4] == 0x01) {
|
||||||
|
unsigned long f = readFreq(cmd);
|
||||||
|
setFrequency(f);
|
||||||
|
updateDisplay();
|
||||||
|
//sprintf(b, "set:%ld", f);
|
||||||
|
//printLine2(b);
|
||||||
|
|
||||||
|
}
|
||||||
|
// Get frequency
|
||||||
|
else if (cmd[4] == 0x03){
|
||||||
|
writeFreq(frequency,response); // Put the frequency into the buffer
|
||||||
|
if (isUSB)
|
||||||
|
response[4] = 0x01; //USB
|
||||||
|
else
|
||||||
|
response[4] = 0x00; //LSB
|
||||||
|
Serial.write(response,5);
|
||||||
|
printLine2("cat:getfreq");
|
||||||
|
}
|
||||||
|
else if (cmd[4] == 0x07){ // set mode
|
||||||
|
if (cmd[0] == 0x00 || cmd[0] == 0x03)
|
||||||
|
isUSB = 0;
|
||||||
|
else
|
||||||
|
isUSB = 1;
|
||||||
|
response[0] = 0x00;
|
||||||
|
Serial.write(response, 1);
|
||||||
|
setFrequency(frequency);
|
||||||
|
//printLine2("cat: mode changed");
|
||||||
|
//updateDisplay();
|
||||||
|
}
|
||||||
|
else if (cmd[4] == 0x88){
|
||||||
|
if (inTx){
|
||||||
|
stopTx();
|
||||||
|
txCAT = false;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
response[0] = 0xf0;
|
||||||
|
printLine2("tx > rx");
|
||||||
|
Serial.write(response,1);
|
||||||
|
}
|
||||||
|
else if (cmd[4] == 0x08) { // PTT On
|
||||||
|
if (!inTx) {
|
||||||
|
response[0] = 0;
|
||||||
|
txCAT = true;
|
||||||
|
startTx(TX_SSB);
|
||||||
|
updateDisplay();
|
||||||
|
} else {
|
||||||
|
response[0] = 0xf0;
|
||||||
|
}
|
||||||
|
Serial.write(response,1);
|
||||||
|
printLine2("rx > tx");
|
||||||
|
}
|
||||||
|
// Read TX keyed state
|
||||||
|
else if (cmd[4] == 0x10) {
|
||||||
|
if (!inTx) {
|
||||||
|
response[0] = 0;
|
||||||
|
} else {
|
||||||
|
response[0] = 0xf0;
|
||||||
|
}
|
||||||
|
Serial.write(response,1);
|
||||||
|
printLine2("cat;0x10");
|
||||||
|
}
|
||||||
|
// PTT Off
|
||||||
|
else if (cmd[4] == 0x88) {
|
||||||
|
byte resBuf[0];
|
||||||
|
if (inTx) {
|
||||||
|
response[0] = 0;
|
||||||
|
} else {
|
||||||
|
response[0] = 0xf0;
|
||||||
|
}
|
||||||
|
Serial.write(response,1);
|
||||||
|
printLine2("cat;0x88");
|
||||||
|
//keyed = false;
|
||||||
|
//digitalWrite(13,LOW);
|
||||||
|
}
|
||||||
|
// Read receiver status
|
||||||
|
else if (cmd[4] == 0xe7) {
|
||||||
|
response[0] = 0x09;
|
||||||
|
Serial.write(response,1);
|
||||||
|
printLine2("cat;0xe7");
|
||||||
|
}
|
||||||
|
else if (cmd[4] == 0xf5){
|
||||||
|
|
||||||
|
}
|
||||||
|
// Read receiver status
|
||||||
|
else if (cmd[4] == 0xf7) {
|
||||||
|
response[0] = 0x00;
|
||||||
|
if (inTx) {
|
||||||
|
response[0] = response[0] | 0xf0;
|
||||||
|
}
|
||||||
|
Serial.write(response,1);
|
||||||
|
printLine2("cat;0xf7");
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
//somehow, get this to print the four bytes
|
||||||
|
ultoa(*((unsigned long *)cmd), c, 16);
|
||||||
|
itoa(cmd[4], b, 16);
|
||||||
|
strcat(b, ":");
|
||||||
|
strcat(b, c);
|
||||||
|
printLine2(b);
|
||||||
|
response[0] = 0x00;
|
||||||
|
Serial.write(response[0]);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
void checkCAT(){
|
||||||
|
static byte cat[5];
|
||||||
|
byte i;
|
||||||
|
|
||||||
|
if (Serial.available() < 5)
|
||||||
|
return;
|
||||||
|
|
||||||
|
cat[4] = cat[3];
|
||||||
|
cat[3] = cat[2];
|
||||||
|
cat[2] = cat[0];
|
||||||
|
for (i = 0; i < 5; i++)
|
||||||
|
cat[i] = Serial.read();
|
||||||
|
|
||||||
|
processCATCommand(cat);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
87
ubitx_20/ubitx_factory_alignment.ino
Normal file
87
ubitx_20/ubitx_factory_alignment.ino
Normal file
@ -0,0 +1,87 @@
|
|||||||
|
|
||||||
|
/**
|
||||||
|
* This procedure is only for those who have a signal generator/transceiver tuned to exactly 7.150 and a dummy load
|
||||||
|
*/
|
||||||
|
|
||||||
|
void btnWaitForClick(){
|
||||||
|
while(!btnDown())
|
||||||
|
delay(50);
|
||||||
|
while(btnDown())
|
||||||
|
delay(50);
|
||||||
|
delay(50);
|
||||||
|
}
|
||||||
|
|
||||||
|
void factory_alignment(){
|
||||||
|
|
||||||
|
factoryCalibration(1);
|
||||||
|
|
||||||
|
if (calibration == 0){
|
||||||
|
printLine2("Setup Aborted");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
//move it away to 7.160 for an LSB signal
|
||||||
|
setFrequency(7160000l);
|
||||||
|
updateDisplay();
|
||||||
|
printLine2("#2 BFO");
|
||||||
|
delay(1000);
|
||||||
|
|
||||||
|
usbCarrier = 11994999l;
|
||||||
|
menuSetupCarrier(1);
|
||||||
|
|
||||||
|
if (usbCarrier == 11994999l){
|
||||||
|
printLine2("Setup Aborted");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
printLine2("#3:Test 3.5MHz");
|
||||||
|
isUSB = false;
|
||||||
|
setFrequency(3500000l);
|
||||||
|
updateDisplay();
|
||||||
|
|
||||||
|
while (!btnDown()){
|
||||||
|
checkPTT();
|
||||||
|
delay(100);
|
||||||
|
}
|
||||||
|
|
||||||
|
btnWaitForClick();
|
||||||
|
printLine2("#4:Test 7MHz");
|
||||||
|
|
||||||
|
setFrequency(7150000l);
|
||||||
|
updateDisplay();
|
||||||
|
while (!btnDown()){
|
||||||
|
checkPTT();
|
||||||
|
delay(100);
|
||||||
|
}
|
||||||
|
|
||||||
|
btnWaitForClick();
|
||||||
|
printLine2("#5:Test 14MHz");
|
||||||
|
|
||||||
|
isUSB = true;
|
||||||
|
setFrequency(14000000l);
|
||||||
|
updateDisplay();
|
||||||
|
while (!btnDown()){
|
||||||
|
checkPTT();
|
||||||
|
delay(100);
|
||||||
|
}
|
||||||
|
|
||||||
|
btnWaitForClick();
|
||||||
|
printLine2("#6:Test 28MHz");
|
||||||
|
|
||||||
|
setFrequency(28000000l);
|
||||||
|
updateDisplay();
|
||||||
|
while (!btnDown()){
|
||||||
|
checkPTT();
|
||||||
|
delay(100);
|
||||||
|
}
|
||||||
|
|
||||||
|
printLine2("Alignment done");
|
||||||
|
delay(1000);
|
||||||
|
|
||||||
|
isUSB = false;
|
||||||
|
setFrequency(7150000l);
|
||||||
|
updateDisplay();
|
||||||
|
|
||||||
|
}
|
||||||
|
|
155
ubitx_20/ubitx_keyer.ino
Normal file
155
ubitx_20/ubitx_keyer.ino
Normal file
@ -0,0 +1,155 @@
|
|||||||
|
/**
|
||||||
|
* CW Keyer
|
||||||
|
*
|
||||||
|
* The CW keyer handles either a straight key or an iambic / paddle key.
|
||||||
|
* They all use just one analog input line. This is how it works.
|
||||||
|
* The analog line has the internal pull-up resistor enabled.
|
||||||
|
* When a straight key is connected, it shorts the pull-up resistor, analog input is 0 volts
|
||||||
|
* When a paddle is connected, the dot and the dash are connected to the analog pin through
|
||||||
|
* a 10K and a 2.2K resistors. These produce a 4v and a 2v input to the analog pins.
|
||||||
|
* So, the readings are as follows :
|
||||||
|
* 0v - straight key
|
||||||
|
* 1-2.5 v - paddle dot
|
||||||
|
* 2.5 to 4.5 v - paddle dash
|
||||||
|
* 2.0 to 0.5 v - dot and dash pressed
|
||||||
|
*
|
||||||
|
* The keyer is written to transparently handle all these cases
|
||||||
|
*
|
||||||
|
* Generating CW
|
||||||
|
* The CW is cleanly generated by unbalancing the front-end mixer
|
||||||
|
* and putting the local oscillator directly at the CW transmit frequency.
|
||||||
|
* The sidetone, generated by the Arduino is injected into the volume control
|
||||||
|
*/
|
||||||
|
|
||||||
|
|
||||||
|
// in milliseconds, this is the parameter that determines how long the tx will hold between cw key downs
|
||||||
|
#define CW_TIMEOUT (600l)
|
||||||
|
#define PADDLE_DOT 1
|
||||||
|
#define PADDLE_DASH 2
|
||||||
|
#define PADDLE_BOTH 3
|
||||||
|
#define PADDLE_STRAIGHT 4
|
||||||
|
|
||||||
|
//we store the last padde's character
|
||||||
|
//to alternatively send dots and dashes
|
||||||
|
//when both are simultaneously pressed
|
||||||
|
char lastPaddle = 0;
|
||||||
|
|
||||||
|
|
||||||
|
//reads the analog keyer pin and reports the paddle
|
||||||
|
byte getPaddle(){
|
||||||
|
int paddle = analogRead(ANALOG_KEYER);
|
||||||
|
|
||||||
|
if (paddle > 800) // above 4v is up
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
if (paddle > 600) // 4-3v is dot
|
||||||
|
return PADDLE_DASH;
|
||||||
|
else if (paddle > 300) //1-2v is dash
|
||||||
|
return PADDLE_DOT;
|
||||||
|
else if (paddle > 50)
|
||||||
|
return PADDLE_BOTH; //both are between 1 and 2v
|
||||||
|
else
|
||||||
|
return PADDLE_STRAIGHT; //less than 1v is the straight key
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Starts transmitting the carrier with the sidetone
|
||||||
|
* It assumes that we have called cwTxStart and not called cwTxStop
|
||||||
|
* each time it is called, the cwTimeOut is pushed further into the future
|
||||||
|
*/
|
||||||
|
void cwKeydown(){
|
||||||
|
keyDown = 1; //tracks the CW_KEY
|
||||||
|
tone(CW_TONE, (int)sideTone);
|
||||||
|
digitalWrite(CW_KEY, 1);
|
||||||
|
cwTimeout = millis() + CW_TIMEOUT;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Stops the cw carrier transmission along with the sidetone
|
||||||
|
* Pushes the cwTimeout further into the future
|
||||||
|
*/
|
||||||
|
void cwKeyUp(){
|
||||||
|
keyDown = 0; //tracks the CW_KEY
|
||||||
|
noTone(CW_TONE);
|
||||||
|
digitalWrite(CW_KEY, 0);
|
||||||
|
cwTimeout = millis() + CW_TIMEOUT;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The keyer handles the straight key as well as the iambic key
|
||||||
|
* This module keeps looping until the user stops sending cw
|
||||||
|
* if the cwTimeout is set to 0, then it means, we have to exit the keyer loop
|
||||||
|
* Each time the key is hit the cwTimeout is pushed to a time in the future by cwKeyDown()
|
||||||
|
*/
|
||||||
|
|
||||||
|
void cwKeyer(){
|
||||||
|
byte paddle;
|
||||||
|
lastPaddle = 0;
|
||||||
|
|
||||||
|
while(1){
|
||||||
|
paddle = getPaddle();
|
||||||
|
|
||||||
|
// do nothing if the paddle has not been touched, unless
|
||||||
|
// we are in the cw mode and we have timed out
|
||||||
|
if (!paddle){
|
||||||
|
if (0 < cwTimeout && cwTimeout < millis()){
|
||||||
|
cwTimeout = 0;
|
||||||
|
keyDown = 0;
|
||||||
|
stopTx();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!cwTimeout)
|
||||||
|
return;
|
||||||
|
|
||||||
|
//if a paddle was used (not a straight key) we should extend the space to be a full dash
|
||||||
|
//by adding two more dots long space (one has already been added at the end of the dot or dash)
|
||||||
|
if (cwTimeout > 0 && lastPaddle != PADDLE_STRAIGHT)
|
||||||
|
delay(cwSpeed * 2);
|
||||||
|
|
||||||
|
// got back to the begining of the loop, if no further activity happens on the paddle or the straight key
|
||||||
|
// we will time out, and return out of this routine
|
||||||
|
delay(5);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
Serial.print("paddle:");Serial.println(paddle);
|
||||||
|
// if we are here, it is only because the key or the paddle is pressed
|
||||||
|
if (!inTx){
|
||||||
|
keyDown = 0;
|
||||||
|
cwTimeout = millis() + CW_TIMEOUT;
|
||||||
|
startTx(TX_CW);
|
||||||
|
updateDisplay();
|
||||||
|
}
|
||||||
|
|
||||||
|
// star the transmission)
|
||||||
|
// we store the transmitted character in the lastPaddle
|
||||||
|
cwKeydown();
|
||||||
|
if (paddle == PADDLE_DOT){
|
||||||
|
delay(cwSpeed);
|
||||||
|
lastPaddle = PADDLE_DOT;
|
||||||
|
}
|
||||||
|
else if (paddle == PADDLE_DASH){
|
||||||
|
delay(cwSpeed * 3);
|
||||||
|
lastPaddle = PADDLE_DASH;
|
||||||
|
}
|
||||||
|
else if (paddle == PADDLE_BOTH){ //both paddles down
|
||||||
|
//depending upon what was sent last, send the other
|
||||||
|
if (lastPaddle == PADDLE_DOT) {
|
||||||
|
delay(cwSpeed * 3);
|
||||||
|
lastPaddle = PADDLE_DASH;
|
||||||
|
}else{
|
||||||
|
delay(cwSpeed);
|
||||||
|
lastPaddle = PADDLE_DOT;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (paddle == PADDLE_STRAIGHT){
|
||||||
|
while (getPaddle() == PADDLE_STRAIGHT)
|
||||||
|
delay(1);
|
||||||
|
lastPaddle = PADDLE_STRAIGHT;
|
||||||
|
}
|
||||||
|
cwKeyUp();
|
||||||
|
//introduce a dot long gap between characters if the keyer was used
|
||||||
|
if (lastPaddle != PADDLE_STRAIGHT)
|
||||||
|
delay(cwSpeed);
|
||||||
|
}
|
||||||
|
}
|
572
ubitx_20/ubitx_menu.ino
Normal file
572
ubitx_20/ubitx_menu.ino
Normal file
@ -0,0 +1,572 @@
|
|||||||
|
/** Menus
|
||||||
|
* The Radio menus are accessed by tapping on the function button.
|
||||||
|
* - The main loop() constantly looks for a button press and calls doMenu() when it detects
|
||||||
|
* a function button press.
|
||||||
|
* - As the encoder is rotated, at every 10th pulse, the next or the previous menu
|
||||||
|
* item is displayed. Each menu item is controlled by it's own function.
|
||||||
|
* - Eache menu function may be called to display itself
|
||||||
|
* - Each of these menu routines is called with a button parameter.
|
||||||
|
* - The btn flag denotes if the menu itme was clicked on or not.
|
||||||
|
* - If the menu item is clicked on, then it is selected,
|
||||||
|
* - If the menu item is NOT clicked on, then the menu's prompt is to be displayed
|
||||||
|
*/
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
int menuBand(int btn){
|
||||||
|
int knob = 0;
|
||||||
|
int band;
|
||||||
|
unsigned long offset;
|
||||||
|
|
||||||
|
// band = frequency/1000000l;
|
||||||
|
// offset = frequency % 1000000l;
|
||||||
|
|
||||||
|
if (!btn){
|
||||||
|
printLine2("Band Select?");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
printLine2("Press to confirm");
|
||||||
|
//wait for the button menu select button to be lifted)
|
||||||
|
while (btnDown())
|
||||||
|
delay(50);
|
||||||
|
delay(50);
|
||||||
|
ritDisable();
|
||||||
|
|
||||||
|
while(!btnDown()){
|
||||||
|
|
||||||
|
knob = enc_read();
|
||||||
|
if (knob != 0){
|
||||||
|
/*
|
||||||
|
if (band > 3 && knob < 0)
|
||||||
|
band--;
|
||||||
|
if (band < 30 && knob > 0)
|
||||||
|
band++;
|
||||||
|
if (band > 10)
|
||||||
|
isUSB = true;
|
||||||
|
else
|
||||||
|
isUSB = false;
|
||||||
|
setFrequency(((unsigned long)band * 1000000l) + offset); */
|
||||||
|
if (knob < 0 && frequency > 3000000l)
|
||||||
|
setFrequency(frequency - 200000l);
|
||||||
|
if (knob > 0 && frequency < 30000000l)
|
||||||
|
setFrequency(frequency + 200000l);
|
||||||
|
if (frequency > 10000000l)
|
||||||
|
isUSB = true;
|
||||||
|
else
|
||||||
|
isUSB = false;
|
||||||
|
updateDisplay();
|
||||||
|
}
|
||||||
|
delay(20);
|
||||||
|
}
|
||||||
|
|
||||||
|
while(btnDown())
|
||||||
|
delay(50);
|
||||||
|
delay(50);
|
||||||
|
|
||||||
|
printLine2("");
|
||||||
|
updateDisplay();
|
||||||
|
menuOn = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
void menuVfoToggle(int btn){
|
||||||
|
|
||||||
|
if (!btn){
|
||||||
|
if (vfoActive == VFO_A)
|
||||||
|
printLine2("Select VFO B? ");
|
||||||
|
else
|
||||||
|
printLine2("Select VFO A? ");
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
if (vfoActive == VFO_B){
|
||||||
|
vfoB = frequency;
|
||||||
|
EEPROM.put(VFO_B, frequency);
|
||||||
|
vfoActive = VFO_A;
|
||||||
|
printLine2("Selected VFO A ");
|
||||||
|
frequency = vfoA;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
vfoA = frequency;
|
||||||
|
EEPROM.put(VFO_A, frequency);
|
||||||
|
vfoActive = VFO_B;
|
||||||
|
printLine2("Selected VFO B ");
|
||||||
|
frequency = vfoB;
|
||||||
|
}
|
||||||
|
|
||||||
|
ritDisable();
|
||||||
|
setFrequency(frequency);
|
||||||
|
if (frequency >= 10000000l)
|
||||||
|
isUSB = true;
|
||||||
|
else
|
||||||
|
isUSB = false;
|
||||||
|
updateDisplay();
|
||||||
|
printLine2("");
|
||||||
|
delay(1000);
|
||||||
|
//exit the menu
|
||||||
|
menuOn = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void menuRitToggle(int btn){
|
||||||
|
if (!btn){
|
||||||
|
if (ritOn == 1)
|
||||||
|
printLine2("RIT:On, Off? ");
|
||||||
|
else
|
||||||
|
printLine2("RIT:Off, On? ");
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
if (ritOn == 0){
|
||||||
|
printLine2("RIT is ON");
|
||||||
|
//enable RIT so the current frequency is used at transmit
|
||||||
|
ritEnable(frequency);
|
||||||
|
}
|
||||||
|
else{
|
||||||
|
printLine2("RIT is OFF");
|
||||||
|
ritDisable();
|
||||||
|
}
|
||||||
|
menuOn = 0;
|
||||||
|
delay(500);
|
||||||
|
printLine2("");
|
||||||
|
updateDisplay();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void menuSidebandToggle(int btn){
|
||||||
|
if (!btn){
|
||||||
|
if (isUSB == true)
|
||||||
|
printLine2("Select LSB?");
|
||||||
|
else
|
||||||
|
printLine2("Select USB?");
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
if (isUSB == true){
|
||||||
|
isUSB = false;
|
||||||
|
printLine2("LSB Selected");
|
||||||
|
delay(500);
|
||||||
|
printLine2("");
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
isUSB = true;
|
||||||
|
printLine2("USB Selected");
|
||||||
|
delay(500);
|
||||||
|
printLine2("");
|
||||||
|
}
|
||||||
|
|
||||||
|
updateDisplay();
|
||||||
|
menuOn = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The calibration routines are not normally shown in the menu as they are rarely used
|
||||||
|
* They can be enabled by choosing this menu option
|
||||||
|
*/
|
||||||
|
void menuSetup(int btn){
|
||||||
|
if (!btn){
|
||||||
|
if (!modeCalibrate)
|
||||||
|
printLine2("Setup On?");
|
||||||
|
else
|
||||||
|
printLine2("Setup Off?");
|
||||||
|
}else {
|
||||||
|
if (!modeCalibrate){
|
||||||
|
modeCalibrate = true;
|
||||||
|
printLine2("Setup:On ");
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
modeCalibrate = false;
|
||||||
|
printLine2("Setup:Off ");
|
||||||
|
}
|
||||||
|
delay(2000);
|
||||||
|
printLine2("");
|
||||||
|
menuOn = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void menuExit(int btn){
|
||||||
|
|
||||||
|
if (!btn){
|
||||||
|
printLine2("Exit Menu? ");
|
||||||
|
}
|
||||||
|
else{
|
||||||
|
printLine2("Exiting menu");
|
||||||
|
delay(300);
|
||||||
|
printLine2("");
|
||||||
|
updateDisplay();
|
||||||
|
menuOn = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
int menuCWSpeed(int btn){
|
||||||
|
int knob = 0;
|
||||||
|
int wpm;
|
||||||
|
|
||||||
|
wpm = 1200/cwSpeed;
|
||||||
|
|
||||||
|
if (!btn){
|
||||||
|
strcpy(b, "CW:");
|
||||||
|
itoa(wpm,c, 10);
|
||||||
|
strcat(b, c);
|
||||||
|
strcat(b, "WPM Change?");
|
||||||
|
printLine2(b);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
printLine1("Press PTT to set");
|
||||||
|
strcpy(b, "WPM:");
|
||||||
|
itoa(wpm,c, 10);
|
||||||
|
strcat(b, c);
|
||||||
|
printLine2(b);
|
||||||
|
delay(300);
|
||||||
|
|
||||||
|
while(!btnDown() && digitalRead(PTT) == HIGH){
|
||||||
|
|
||||||
|
knob = enc_read();
|
||||||
|
if (knob != 0){
|
||||||
|
if (wpm > 3 && knob < 0)
|
||||||
|
wpm--;
|
||||||
|
if (wpm < 50 && knob > 0)
|
||||||
|
wpm++;
|
||||||
|
|
||||||
|
strcpy(b, "WPM:");
|
||||||
|
itoa(wpm,c, 10);
|
||||||
|
strcat(b, c);
|
||||||
|
printLine2(b);
|
||||||
|
}
|
||||||
|
//abort if this button is down
|
||||||
|
if (btnDown())
|
||||||
|
//re-enable the clock1 and clock 2
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
//save the setting
|
||||||
|
if (digitalRead(PTT) == LOW){
|
||||||
|
printLine2("CW Speed set!");
|
||||||
|
cwSpeed = 1200/wpm;
|
||||||
|
EEPROM.put(CW_SPEED, cwSpeed);
|
||||||
|
delay(2000);
|
||||||
|
}
|
||||||
|
printLine2("");
|
||||||
|
menuOn = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Take a deep breath, math(ematics) ahead
|
||||||
|
* The 25 mhz oscillator is multiplied by 35 to run the vco at 875 mhz
|
||||||
|
* This is divided by a number to generate different frequencies.
|
||||||
|
* If we divide it by 875, we will get 1 mhz signal
|
||||||
|
* So, if the vco is shifted up by 875 hz, the generated frequency of 1 mhz is shifted by 1 hz (875/875)
|
||||||
|
* At 12 Mhz, the carrier will needed to be shifted down by 12 hz for every 875 hz of shift up of the vco
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
//this is used by the si5351 routines in the ubitx_5351 file
|
||||||
|
extern int32_t calibration;
|
||||||
|
extern uint32_t si5351bx_vcoa;
|
||||||
|
|
||||||
|
int factoryCalibration(int btn){
|
||||||
|
int knob = 0;
|
||||||
|
int32_t prev_calibration;
|
||||||
|
|
||||||
|
|
||||||
|
//keep clear of any previous button press
|
||||||
|
while (btnDown())
|
||||||
|
delay(100);
|
||||||
|
delay(100);
|
||||||
|
|
||||||
|
if (!btn){
|
||||||
|
printLine2("Set Calibration?");
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
prev_calibration = calibration;
|
||||||
|
calibration = 0;
|
||||||
|
|
||||||
|
isUSB = true;
|
||||||
|
|
||||||
|
//turn off the second local oscillator and the bfo
|
||||||
|
si5351_set_calibration(calibration);
|
||||||
|
startTx(TX_CW);
|
||||||
|
si5351bx_setfreq(2, 10000000l);
|
||||||
|
|
||||||
|
strcpy(b, "#1 10 MHz cal:");
|
||||||
|
ltoa(calibration/8750, c, 10);
|
||||||
|
strcat(b, c);
|
||||||
|
printLine2(b);
|
||||||
|
|
||||||
|
while (!btnDown())
|
||||||
|
{
|
||||||
|
|
||||||
|
if (digitalRead(PTT) == LOW && !keyDown)
|
||||||
|
cwKeydown();
|
||||||
|
if (digitalRead(PTT) == HIGH && keyDown)
|
||||||
|
cwKeyUp();
|
||||||
|
|
||||||
|
knob = enc_read();
|
||||||
|
|
||||||
|
if (knob > 0)
|
||||||
|
calibration += 875;
|
||||||
|
else if (knob < 0)
|
||||||
|
calibration -= 875;
|
||||||
|
else
|
||||||
|
continue; //don't update the frequency or the display
|
||||||
|
|
||||||
|
si5351_set_calibration(calibration);
|
||||||
|
si5351bx_setfreq(2, 10000000l);
|
||||||
|
strcpy(b, "#1 10 MHz cal:");
|
||||||
|
ltoa(calibration/8750, c, 10);
|
||||||
|
strcat(b, c);
|
||||||
|
printLine2(b);
|
||||||
|
}
|
||||||
|
|
||||||
|
cwTimeout = 0;
|
||||||
|
keyDown = 0;
|
||||||
|
stopTx();
|
||||||
|
|
||||||
|
printLine2("Calibration set!");
|
||||||
|
EEPROM.put(MASTER_CAL, calibration);
|
||||||
|
initOscillators();
|
||||||
|
setFrequency(frequency);
|
||||||
|
updateDisplay();
|
||||||
|
|
||||||
|
while(btnDown())
|
||||||
|
delay(50);
|
||||||
|
delay(100);
|
||||||
|
}
|
||||||
|
|
||||||
|
int menuSetupCalibration(int btn){
|
||||||
|
int knob = 0;
|
||||||
|
int32_t prev_calibration;
|
||||||
|
|
||||||
|
if (!btn){
|
||||||
|
printLine2("Set Calibration?");
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
printLine1("Set to Zero-beat,");
|
||||||
|
printLine2("press PTT to save");
|
||||||
|
delay(1000);
|
||||||
|
|
||||||
|
prev_calibration = calibration;
|
||||||
|
calibration = 0;
|
||||||
|
si5351_set_calibration(calibration);
|
||||||
|
setFrequency(frequency);
|
||||||
|
|
||||||
|
strcpy(b, "cal:");
|
||||||
|
ltoa(calibration/8750, c, 10);
|
||||||
|
strcat(b, c);
|
||||||
|
printLine2(b);
|
||||||
|
|
||||||
|
while (digitalRead(PTT) == HIGH && !btnDown())
|
||||||
|
{
|
||||||
|
knob = enc_read();
|
||||||
|
|
||||||
|
if (knob > 0){
|
||||||
|
calibration += 8750;
|
||||||
|
usbCarrier += 120;
|
||||||
|
}
|
||||||
|
else if (knob < 0){
|
||||||
|
calibration -= 8750;
|
||||||
|
usbCarrier -= 120;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
continue; //don't update the frequency or the display
|
||||||
|
|
||||||
|
si5351_set_calibration(calibration);
|
||||||
|
si5351bx_setfreq(0, usbCarrier);
|
||||||
|
setFrequency(frequency);
|
||||||
|
|
||||||
|
strcpy(b, "cal:");
|
||||||
|
ltoa(calibration/8750, c, 10);
|
||||||
|
strcat(b, c);
|
||||||
|
printLine2(b);
|
||||||
|
}
|
||||||
|
|
||||||
|
//save the setting
|
||||||
|
if (digitalRead(PTT) == LOW){
|
||||||
|
printLine1("Calibration set!");
|
||||||
|
printLine2("Set Carrier now");
|
||||||
|
EEPROM.put(MASTER_CAL, calibration);
|
||||||
|
delay(2000);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
calibration = prev_calibration;
|
||||||
|
|
||||||
|
printLine2("");
|
||||||
|
initOscillators();
|
||||||
|
//si5351_set_calibration(calibration);
|
||||||
|
setFrequency(frequency);
|
||||||
|
updateDisplay();
|
||||||
|
menuOn = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void printCarrierFreq(unsigned long freq){
|
||||||
|
|
||||||
|
memset(c, 0, sizeof(c));
|
||||||
|
memset(b, 0, sizeof(b));
|
||||||
|
|
||||||
|
ultoa(freq, b, DEC);
|
||||||
|
|
||||||
|
strncat(c, b, 2);
|
||||||
|
strcat(c, ".");
|
||||||
|
strncat(c, &b[2], 3);
|
||||||
|
strcat(c, ".");
|
||||||
|
strncat(c, &b[5], 1);
|
||||||
|
printLine2(c);
|
||||||
|
}
|
||||||
|
|
||||||
|
void menuSetupCarrier(int btn){
|
||||||
|
int knob = 0;
|
||||||
|
unsigned long prevCarrier;
|
||||||
|
|
||||||
|
if (!btn){
|
||||||
|
printLine2("Set the BFO");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
prevCarrier = usbCarrier;
|
||||||
|
printLine1("Tune to best Signal");
|
||||||
|
printLine2("PTT to confirm. ");
|
||||||
|
delay(1000);
|
||||||
|
|
||||||
|
usbCarrier = 11995000l;
|
||||||
|
si5351bx_setfreq(0, usbCarrier);
|
||||||
|
printCarrierFreq(usbCarrier);
|
||||||
|
|
||||||
|
//disable all clock 1 and clock 2
|
||||||
|
while (digitalRead(PTT) == HIGH && !btnDown())
|
||||||
|
{
|
||||||
|
knob = enc_read();
|
||||||
|
|
||||||
|
if (knob > 0)
|
||||||
|
usbCarrier -= 50;
|
||||||
|
else if (knob < 0)
|
||||||
|
usbCarrier += 50;
|
||||||
|
else
|
||||||
|
continue; //don't update the frequency or the display
|
||||||
|
|
||||||
|
si5351bx_setfreq(0, usbCarrier);
|
||||||
|
printCarrierFreq(usbCarrier);
|
||||||
|
|
||||||
|
delay(100);
|
||||||
|
}
|
||||||
|
|
||||||
|
//save the setting
|
||||||
|
if (digitalRead(PTT) == LOW){
|
||||||
|
printLine2("Carrier set! ");
|
||||||
|
EEPROM.put(USB_CAL, usbCarrier);
|
||||||
|
delay(1000);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
usbCarrier = prevCarrier;
|
||||||
|
|
||||||
|
si5351bx_setfreq(0, usbCarrier);
|
||||||
|
setFrequency(frequency);
|
||||||
|
updateDisplay();
|
||||||
|
printLine2("");
|
||||||
|
menuOn = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
void menuSetupCwTone(int btn){
|
||||||
|
int knob = 0;
|
||||||
|
int prev_sideTone;
|
||||||
|
|
||||||
|
if (!btn){
|
||||||
|
printLine2("Change CW Tone");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
prev_sideTone = sideTone;
|
||||||
|
printLine1("Tune CW tone");
|
||||||
|
printLine2("PTT to confirm. ");
|
||||||
|
delay(1000);
|
||||||
|
tone(CW_TONE, sideTone);
|
||||||
|
|
||||||
|
//disable all clock 1 and clock 2
|
||||||
|
while (digitalRead(PTT) == LOW || !btnDown())
|
||||||
|
{
|
||||||
|
knob = enc_read();
|
||||||
|
|
||||||
|
if (knob > 0 && sideTone < 2000)
|
||||||
|
sideTone += 10;
|
||||||
|
else if (knob < 0 && sideTone > 100 )
|
||||||
|
sideTone -= 10;
|
||||||
|
else
|
||||||
|
continue; //don't update the frequency or the display
|
||||||
|
|
||||||
|
tone(CW_TONE, sideTone);
|
||||||
|
itoa(sideTone, b, 10);
|
||||||
|
printLine2(b);
|
||||||
|
|
||||||
|
delay(100);
|
||||||
|
}
|
||||||
|
noTone(CW_TONE);
|
||||||
|
//save the setting
|
||||||
|
if (digitalRead(PTT) == LOW){
|
||||||
|
printLine2("Sidetone set! ");
|
||||||
|
EEPROM.put(CW_SIDETONE, usbCarrier);
|
||||||
|
delay(2000);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
sideTone = prev_sideTone;
|
||||||
|
|
||||||
|
printLine2("");
|
||||||
|
updateDisplay();
|
||||||
|
menuOn = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
void doMenu(){
|
||||||
|
int select=0, i,btnState;
|
||||||
|
|
||||||
|
//wait for the button to be raised up
|
||||||
|
while(btnDown())
|
||||||
|
delay(50);
|
||||||
|
delay(50); //debounce
|
||||||
|
|
||||||
|
menuOn = 2;
|
||||||
|
|
||||||
|
while (menuOn){
|
||||||
|
i = enc_read();
|
||||||
|
btnState = btnDown();
|
||||||
|
|
||||||
|
if (i > 0){
|
||||||
|
if (modeCalibrate && select + i < 110)
|
||||||
|
select += i;
|
||||||
|
if (!modeCalibrate && select + i < 70)
|
||||||
|
select += i;
|
||||||
|
}
|
||||||
|
if (i < 0 && select - i >= 0)
|
||||||
|
select += i; //caught ya, i is already -ve here, so you add it
|
||||||
|
|
||||||
|
if (select < 10)
|
||||||
|
menuBand(btnState);
|
||||||
|
else if (select < 20)
|
||||||
|
menuRitToggle(btnState);
|
||||||
|
else if (select < 30)
|
||||||
|
menuVfoToggle(btnState);
|
||||||
|
else if (select < 40)
|
||||||
|
menuSidebandToggle(btnState);
|
||||||
|
else if (select < 50)
|
||||||
|
menuCWSpeed(btnState);
|
||||||
|
else if (select < 60)
|
||||||
|
menuSetup(btnState);
|
||||||
|
else if (select < 70 && !modeCalibrate)
|
||||||
|
menuExit(btnState);
|
||||||
|
else if (select < 80 && modeCalibrate)
|
||||||
|
menuSetupCalibration(btnState); //crystal
|
||||||
|
else if (select < 90 && modeCalibrate)
|
||||||
|
menuSetupCarrier(btnState); //lsb
|
||||||
|
else if (select < 100 && modeCalibrate)
|
||||||
|
menuSetupCwTone(btnState);
|
||||||
|
else if (select < 110 && modeCalibrate)
|
||||||
|
menuExit(btnState);
|
||||||
|
}
|
||||||
|
|
||||||
|
//debounce the button
|
||||||
|
while(btnDown())
|
||||||
|
delay(50);
|
||||||
|
delay(50);
|
||||||
|
}
|
||||||
|
|
116
ubitx_20/ubitx_si5351.ino
Normal file
116
ubitx_20/ubitx_si5351.ino
Normal file
@ -0,0 +1,116 @@
|
|||||||
|
// ************* SI5315 routines - tks Jerry Gaffke, KE7ER ***********************
|
||||||
|
|
||||||
|
// An minimalist standalone set of Si5351 routines.
|
||||||
|
// VCOA is fixed at 875mhz, VCOB not used.
|
||||||
|
// The output msynth dividers are used to generate 3 independent clocks
|
||||||
|
// with 1hz resolution to any frequency between 4khz and 109mhz.
|
||||||
|
|
||||||
|
// Usage:
|
||||||
|
// Call si5351bx_init() once at startup with no args;
|
||||||
|
// Call si5351bx_setfreq(clknum, freq) each time one of the
|
||||||
|
// three output CLK pins is to be updated to a new frequency.
|
||||||
|
// A freq of 0 serves to shut down that output clock.
|
||||||
|
|
||||||
|
// The global variable si5351bx_vcoa starts out equal to the nominal VCOA
|
||||||
|
// frequency of 25mhz*35 = 875000000 Hz. To correct for 25mhz crystal errors,
|
||||||
|
// the user can adjust this value. The vco frequency will not change but
|
||||||
|
// the number used for the (a+b/c) output msynth calculations is affected.
|
||||||
|
// Example: We call for a 5mhz signal, but it measures to be 5.001mhz.
|
||||||
|
// So the actual vcoa frequency is 875mhz*5.001/5.000 = 875175000 Hz,
|
||||||
|
// To correct for this error: si5351bx_vcoa=875175000;
|
||||||
|
|
||||||
|
// Most users will never need to generate clocks below 500khz.
|
||||||
|
// But it is possible to do so by loading a value between 0 and 7 into
|
||||||
|
// the global variable si5351bx_rdiv, be sure to return it to a value of 0
|
||||||
|
// before setting some other CLK output pin. The affected clock will be
|
||||||
|
// divided down by a power of two defined by 2**si5351_rdiv
|
||||||
|
// A value of zero gives a divide factor of 1, a value of 7 divides by 128.
|
||||||
|
// This lightweight method is a reasonable compromise for a seldom used feature.
|
||||||
|
|
||||||
|
|
||||||
|
#define BB0(x) ((uint8_t)x) // Bust int32 into Bytes
|
||||||
|
#define BB1(x) ((uint8_t)(x>>8))
|
||||||
|
#define BB2(x) ((uint8_t)(x>>16))
|
||||||
|
|
||||||
|
#define SI5351BX_ADDR 0x60 // I2C address of Si5351 (typical)
|
||||||
|
#define SI5351BX_XTALPF 2 // 1:6pf 2:8pf 3:10pf
|
||||||
|
|
||||||
|
// If using 27mhz crystal, set XTAL=27000000, MSA=33. Then vco=891mhz
|
||||||
|
#define SI5351BX_XTAL 25000000 // Crystal freq in Hz
|
||||||
|
#define SI5351BX_MSA 35 // VCOA is at 25mhz*35 = 875mhz
|
||||||
|
|
||||||
|
// User program may have reason to poke new values into these 3 RAM variables
|
||||||
|
uint32_t si5351bx_vcoa = (SI5351BX_XTAL*SI5351BX_MSA); // 25mhzXtal calibrate
|
||||||
|
uint8_t si5351bx_rdiv = 0; // 0-7, CLK pin sees fout/(2**rdiv)
|
||||||
|
uint8_t si5351bx_drive[3] = {1, 1, 1}; // 0=2ma 1=4ma 2=6ma 3=8ma for CLK 0,1,2
|
||||||
|
uint8_t si5351bx_clken = 0xFF; // Private, all CLK output drivers off
|
||||||
|
int32_t calibration = 0;
|
||||||
|
|
||||||
|
void i2cWrite(uint8_t reg, uint8_t val) { // write reg via i2c
|
||||||
|
Wire.beginTransmission(SI5351BX_ADDR);
|
||||||
|
Wire.write(reg);
|
||||||
|
Wire.write(val);
|
||||||
|
Wire.endTransmission();
|
||||||
|
}
|
||||||
|
|
||||||
|
void i2cWriten(uint8_t reg, uint8_t *vals, uint8_t vcnt) { // write array
|
||||||
|
Wire.beginTransmission(SI5351BX_ADDR);
|
||||||
|
Wire.write(reg);
|
||||||
|
while (vcnt--) Wire.write(*vals++);
|
||||||
|
Wire.endTransmission();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void si5351bx_init() { // Call once at power-up, start PLLA
|
||||||
|
uint8_t reg; uint32_t msxp1;
|
||||||
|
Wire.begin();
|
||||||
|
i2cWrite(149, 0); // SpreadSpectrum off
|
||||||
|
i2cWrite(3, si5351bx_clken); // Disable all CLK output drivers
|
||||||
|
i2cWrite(183, SI5351BX_XTALPF << 6); // Set 25mhz crystal load capacitance
|
||||||
|
msxp1 = 128 * SI5351BX_MSA - 512; // and msxp2=0, msxp3=1, not fractional
|
||||||
|
uint8_t vals[8] = {0, 1, BB2(msxp1), BB1(msxp1), BB0(msxp1), 0, 0, 0};
|
||||||
|
i2cWriten(26, vals, 8); // Write to 8 PLLA msynth regs
|
||||||
|
i2cWrite(177, 0x20); // Reset PLLA (0x80 resets PLLB)
|
||||||
|
// for (reg=16; reg<=23; reg++) i2cWrite(reg, 0x80); // Powerdown CLK's
|
||||||
|
// i2cWrite(187, 0); // No fannout of clkin, xtal, ms0, ms4
|
||||||
|
}
|
||||||
|
|
||||||
|
void si5351bx_setfreq(uint8_t clknum, uint32_t fout) { // Set a CLK to fout Hz
|
||||||
|
uint32_t msa, msb, msc, msxp1, msxp2, msxp3p2top;
|
||||||
|
if ((fout < 500000) || (fout > 109000000)) // If clock freq out of range
|
||||||
|
si5351bx_clken |= 1 << clknum; // shut down the clock
|
||||||
|
else {
|
||||||
|
msa = si5351bx_vcoa / fout; // Integer part of vco/fout
|
||||||
|
msb = si5351bx_vcoa % fout; // Fractional part of vco/fout
|
||||||
|
msc = fout; // Divide by 2 till fits in reg
|
||||||
|
while (msc & 0xfff00000) {
|
||||||
|
msb = msb >> 1;
|
||||||
|
msc = msc >> 1;
|
||||||
|
}
|
||||||
|
msxp1 = (128 * msa + 128 * msb / msc - 512) | (((uint32_t)si5351bx_rdiv) << 20);
|
||||||
|
msxp2 = 128 * msb - 128 * msb / msc * msc; // msxp3 == msc;
|
||||||
|
msxp3p2top = (((msc & 0x0F0000) << 4) | msxp2); // 2 top nibbles
|
||||||
|
uint8_t vals[8] = { BB1(msc), BB0(msc), BB2(msxp1), BB1(msxp1),
|
||||||
|
BB0(msxp1), BB2(msxp3p2top), BB1(msxp2), BB0(msxp2)
|
||||||
|
};
|
||||||
|
i2cWriten(42 + (clknum * 8), vals, 8); // Write to 8 msynth regs
|
||||||
|
i2cWrite(16 + clknum, 0x0C | si5351bx_drive[clknum]); // use local msynth
|
||||||
|
si5351bx_clken &= ~(1 << clknum); // Clear bit to enable clock
|
||||||
|
}
|
||||||
|
i2cWrite(3, si5351bx_clken); // Enable/disable clock
|
||||||
|
}
|
||||||
|
|
||||||
|
void si5351_set_calibration(int32_t cal){
|
||||||
|
si5351bx_vcoa = (SI5351BX_XTAL * SI5351BX_MSA) + cal; // apply the calibration correction factor
|
||||||
|
si5351bx_setfreq(0, usbCarrier);
|
||||||
|
}
|
||||||
|
|
||||||
|
void initOscillators(){
|
||||||
|
//initialize the SI5351
|
||||||
|
si5351bx_init();
|
||||||
|
si5351bx_vcoa = (SI5351BX_XTAL * SI5351BX_MSA) + calibration; // apply the calibration correction factor
|
||||||
|
si5351bx_setfreq(0, usbCarrier);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
230
ubitx_20/ubitx_ui.ino
Normal file
230
ubitx_20/ubitx_ui.ino
Normal file
@ -0,0 +1,230 @@
|
|||||||
|
/**
|
||||||
|
* The user interface of the ubitx consists of the encoder, the push-button on top of it
|
||||||
|
* and the 16x2 LCD display.
|
||||||
|
* The upper line of the display is constantly used to display frequency and status
|
||||||
|
* of the radio. Occasionally, it is used to provide a two-line information that is
|
||||||
|
* quickly cleared up.
|
||||||
|
*/
|
||||||
|
|
||||||
|
//returns true if the button is pressed
|
||||||
|
int btnDown(){
|
||||||
|
if (digitalRead(FBUTTON) == HIGH)
|
||||||
|
return 0;
|
||||||
|
else
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Meter (not used in this build for anything)
|
||||||
|
* the meter is drawn using special characters. Each character is composed of 5 x 8 matrix.
|
||||||
|
* The s_meter array holds the definition of the these characters.
|
||||||
|
* each line of the array is is one character such that 5 bits of every byte
|
||||||
|
* makes up one line of pixels of the that character (only 5 bits are used)
|
||||||
|
* The current reading of the meter is assembled in the string called meter
|
||||||
|
*/
|
||||||
|
|
||||||
|
char meter[17];
|
||||||
|
|
||||||
|
byte s_meter_bitmap[] = {
|
||||||
|
B00000,B00000,B00000,B00000,B00000,B00100,B00100,B11011,
|
||||||
|
B10000,B10000,B10000,B10000,B10100,B10100,B10100,B11011,
|
||||||
|
B01000,B01000,B01000,B01000,B01100,B01100,B01100,B11011,
|
||||||
|
B00100,B00100,B00100,B00100,B00100,B00100,B00100,B11011,
|
||||||
|
B00010,B00010,B00010,B00010,B00110,B00110,B00110,B11011,
|
||||||
|
B00001,B00001,B00001,B00001,B00101,B00101,B00101,B11011
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
// initializes the custom characters
|
||||||
|
// we start from char 1 as char 0 terminates the string!
|
||||||
|
void initMeter(){
|
||||||
|
lcd.createChar(1, s_meter_bitmap);
|
||||||
|
lcd.createChar(2, s_meter_bitmap + 8);
|
||||||
|
lcd.createChar(3, s_meter_bitmap + 16);
|
||||||
|
lcd.createChar(4, s_meter_bitmap + 24);
|
||||||
|
lcd.createChar(5, s_meter_bitmap + 32);
|
||||||
|
lcd.createChar(6, s_meter_bitmap + 40);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The meter is drawn with special characters.
|
||||||
|
* character 1 is used to simple draw the blocks of the scale of the meter
|
||||||
|
* characters 2 to 6 are used to draw the needle in positions 1 to within the block
|
||||||
|
* This displays a meter from 0 to 100, -1 displays nothing
|
||||||
|
*/
|
||||||
|
void drawMeter(int8_t needle){
|
||||||
|
int16_t best, i, s;
|
||||||
|
|
||||||
|
if (needle < 0)
|
||||||
|
return;
|
||||||
|
|
||||||
|
s = (needle * 4)/10;
|
||||||
|
for (i = 0; i < 8; i++){
|
||||||
|
if (s >= 5)
|
||||||
|
meter[i] = 1;
|
||||||
|
else if (s >= 0)
|
||||||
|
meter[i] = 2 + s;
|
||||||
|
else
|
||||||
|
meter[i] = 1;
|
||||||
|
s = s - 5;
|
||||||
|
}
|
||||||
|
if (needle >= 40)
|
||||||
|
meter[i-1] = 6;
|
||||||
|
meter[i] = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// The generic routine to display one line on the LCD
|
||||||
|
void printLine(char linenmbr, char *c) {
|
||||||
|
if (strcmp(c, printBuff[linenmbr])) { // only refresh the display when there was a change
|
||||||
|
lcd.setCursor(0, linenmbr); // place the cursor at the beginning of the selected line
|
||||||
|
lcd.print(c);
|
||||||
|
strcpy(printBuff[linenmbr], c);
|
||||||
|
|
||||||
|
for (byte i = strlen(c); i < 16; i++) { // add white spaces until the end of the 16 characters line is reached
|
||||||
|
lcd.print(' ');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// short cut to print to the first line
|
||||||
|
void printLine1(char *c){
|
||||||
|
printLine(1,c);
|
||||||
|
}
|
||||||
|
// short cut to print to the first line
|
||||||
|
void printLine2(char *c){
|
||||||
|
printLine(0,c);
|
||||||
|
}
|
||||||
|
|
||||||
|
// this builds up the top line of the display with frequency and mode
|
||||||
|
void updateDisplay() {
|
||||||
|
// tks Jack Purdum W8TEE
|
||||||
|
// replaced fsprint commmands by str commands for code size reduction
|
||||||
|
|
||||||
|
memset(c, 0, sizeof(c));
|
||||||
|
memset(b, 0, sizeof(b));
|
||||||
|
|
||||||
|
ultoa(frequency, b, DEC);
|
||||||
|
|
||||||
|
if (inTx){
|
||||||
|
if (cwTimeout > 0)
|
||||||
|
strcpy(c, " CW:");
|
||||||
|
else
|
||||||
|
strcpy(c, " TX:");
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
if (ritOn)
|
||||||
|
strcpy(c, "RIT ");
|
||||||
|
else {
|
||||||
|
if (isUSB)
|
||||||
|
strcpy(c, "USB ");
|
||||||
|
else
|
||||||
|
strcpy(c, "LSB ");
|
||||||
|
}
|
||||||
|
if (vfoActive == VFO_A) // VFO A is active
|
||||||
|
strcat(c, "A:");
|
||||||
|
else
|
||||||
|
strcat(c, "B:");
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
//one mhz digit if less than 10 M, two digits if more
|
||||||
|
if (frequency < 10000000l){
|
||||||
|
c[6] = ' ';
|
||||||
|
c[7] = b[0];
|
||||||
|
strcat(c, ".");
|
||||||
|
strncat(c, &b[1], 3);
|
||||||
|
strcat(c, ".");
|
||||||
|
strncat(c, &b[4], 3);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
strncat(c, b, 2);
|
||||||
|
strcat(c, ".");
|
||||||
|
strncat(c, &b[2], 3);
|
||||||
|
strcat(c, ".");
|
||||||
|
strncat(c, &b[5], 3);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (inTx)
|
||||||
|
strcat(c, " TX");
|
||||||
|
printLine(1, c);
|
||||||
|
|
||||||
|
/*
|
||||||
|
//now, the second line
|
||||||
|
memset(c, 0, sizeof(c));
|
||||||
|
memset(b, 0, sizeof(b));
|
||||||
|
|
||||||
|
if (inTx)
|
||||||
|
strcat(c, "TX ");
|
||||||
|
else if (ritOn)
|
||||||
|
strcpy(c, "RIT");
|
||||||
|
|
||||||
|
strcpy(c, " \xff");
|
||||||
|
drawMeter(meter_reading);
|
||||||
|
strcat(c, meter);
|
||||||
|
strcat(c, "\xff");
|
||||||
|
printLine2(c);*/
|
||||||
|
}
|
||||||
|
|
||||||
|
int enc_prev_state = 3;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The A7 And A6 are purely analog lines on the Arduino Nano
|
||||||
|
* These need to be pulled up externally using two 10 K resistors
|
||||||
|
*
|
||||||
|
* There are excellent pages on the Internet about how these encoders work
|
||||||
|
* and how they should be used. We have elected to use the simplest way
|
||||||
|
* to use these encoders without the complexity of interrupts etc to
|
||||||
|
* keep it understandable.
|
||||||
|
*
|
||||||
|
* The enc_state returns a two-bit number such that each bit reflects the current
|
||||||
|
* value of each of the two phases of the encoder
|
||||||
|
*
|
||||||
|
* The enc_read returns the number of net pulses counted over 50 msecs.
|
||||||
|
* If the puluses are -ve, they were anti-clockwise, if they are +ve, the
|
||||||
|
* were in the clockwise directions. Higher the pulses, greater the speed
|
||||||
|
* at which the enccoder was spun
|
||||||
|
*/
|
||||||
|
|
||||||
|
byte enc_state (void) {
|
||||||
|
return (analogRead(ENC_A) > 500 ? 1 : 0) + (analogRead(ENC_B) > 500 ? 2: 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
int enc_read(void) {
|
||||||
|
int result = 0;
|
||||||
|
byte newState;
|
||||||
|
int enc_speed = 0;
|
||||||
|
|
||||||
|
long stop_by = millis() + 50;
|
||||||
|
|
||||||
|
while (millis() < stop_by) { // check if the previous state was stable
|
||||||
|
newState = enc_state(); // Get current state
|
||||||
|
|
||||||
|
if (newState != enc_prev_state)
|
||||||
|
delay (1);
|
||||||
|
|
||||||
|
if (enc_state() != newState || newState == enc_prev_state)
|
||||||
|
continue;
|
||||||
|
//these transitions point to the encoder being rotated anti-clockwise
|
||||||
|
if ((enc_prev_state == 0 && newState == 2) ||
|
||||||
|
(enc_prev_state == 2 && newState == 3) ||
|
||||||
|
(enc_prev_state == 3 && newState == 1) ||
|
||||||
|
(enc_prev_state == 1 && newState == 0)){
|
||||||
|
result--;
|
||||||
|
}
|
||||||
|
//these transitions point o the enccoder being rotated clockwise
|
||||||
|
if ((enc_prev_state == 0 && newState == 1) ||
|
||||||
|
(enc_prev_state == 1 && newState == 3) ||
|
||||||
|
(enc_prev_state == 3 && newState == 2) ||
|
||||||
|
(enc_prev_state == 2 && newState == 0)){
|
||||||
|
result++;
|
||||||
|
}
|
||||||
|
enc_prev_state = newState; // Record state for next pulse interpretation
|
||||||
|
enc_speed++;
|
||||||
|
delay(1);
|
||||||
|
}
|
||||||
|
return(result);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
BIN
ubitx_wiring.png
Normal file
BIN
ubitx_wiring.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 71 KiB |
BIN
ubitxv3.pdf
Normal file
BIN
ubitxv3.pdf
Normal file
Binary file not shown.
Loading…
Reference in New Issue
Block a user