900 lines
24 KiB
Arduino
900 lines
24 KiB
Arduino
|
/**
|
||
|
* This source file is under General Public License version 3.
|
||
|
*
|
||
|
* 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 firt 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 .
|
||
|
*/
|
||
|
|
||
|
#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.
|
||
|
* 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 <si5351.h>
|
||
|
Si5351 si5351;
|
||
|
/**
|
||
|
* The Radiono 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 serial_in[32], c[30], b[30], printBuff[32];
|
||
|
int count = 0;
|
||
|
unsigned char serial_in_count = 0;
|
||
|
|
||
|
/**
|
||
|
* 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:
|
||
|
* A0, A1, A2, A3, +5v, GND, A6, A7
|
||
|
* (while holding the board up so that back of the board faces you)
|
||
|
*
|
||
|
* 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 S_METER (A0)
|
||
|
#define PTT (A1)
|
||
|
#define FBUTTON (A3)
|
||
|
#define ANALOG_KEYER (A7)
|
||
|
#define ANALOG_TUNING (A6)
|
||
|
|
||
|
/**
|
||
|
* The second set of 16 pins on the 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_SEL (5)
|
||
|
#define CW_KEY (4)
|
||
|
|
||
|
/**
|
||
|
* The raduino has a number of timing parameters, all specified in milliseconds
|
||
|
* CW_TIMEOUT : how many milliseconds between consecutive keyup and keydowns before switching back to receive?
|
||
|
* The next set of three parameters determine what is a tap, a double tap and a hold time for the funciton button
|
||
|
* TAP_DOWN_MILLIS : upper limit of how long a tap can be to be considered as a button_tap
|
||
|
* TAP_UP_MILLIS : upper limit of how long a gap can be between two taps of a button_double_tap
|
||
|
* TAP_HOLD_MILIS : many milliseconds of the buttonb being down before considering it to be a button_hold
|
||
|
*/
|
||
|
|
||
|
#define TAP_UP_MILLIS (500)
|
||
|
#define TAP_DOWN_MILLIS (600)
|
||
|
#define TAP_HOLD_MILLIS (2000)
|
||
|
#define CW_TIMEOUT (2000l) // in milliseconds, this is the parameter that determines how long the tx will hold between cw key downs
|
||
|
|
||
|
/**
|
||
|
* The Raduino supports two VFOs : A and B and receiver incremental tuning (RIT).
|
||
|
* we define a variables to hold the frequency of the two VFOs, RITs
|
||
|
* the rit offset as well as status of the RIT
|
||
|
*/
|
||
|
#define VFO_A 0
|
||
|
#define VFO_B 1
|
||
|
char ritOn = 0;
|
||
|
char vfoActive = VFO_A;
|
||
|
unsigned long vfoA=7100000L, vfoB=14200000L, ritA, ritB, sideTone=800, lsbCarrier, usbCarrier;
|
||
|
|
||
|
#define MASTER_CAL 0
|
||
|
#define LSB_CAL 4
|
||
|
#define USB_CAL 8
|
||
|
#define SIDE_TONE 12
|
||
|
|
||
|
/**
|
||
|
* Raduino needs to keep track of current state of the transceiver. These are a few variables that do it
|
||
|
*/
|
||
|
char inTx = 0;
|
||
|
char keyDown = 0;
|
||
|
char isUSB = 0;
|
||
|
unsigned long cwTimeout = 0;
|
||
|
unsigned char txFilter = 0;
|
||
|
|
||
|
/** Tuning Mechanism of the Raduino
|
||
|
* We use a linear pot that has two ends connected to +5 and the ground. the middle wiper
|
||
|
* is connected to ANALOG_TUNNING pin. Depending upon the position of the wiper, the
|
||
|
* reading can be anywhere from 0 to 1024.
|
||
|
* The tuning control works in steps of 50Hz each for every increment between 50 and 950.
|
||
|
* Hence the turning the pot fully from one end to the other will cover 50 x 900 = 45 KHz.
|
||
|
* At the two ends, that is, the tuning starts slowly stepping up or down in 10 KHz steps
|
||
|
* To stop the scanning the pot is moved back from the edge.
|
||
|
* To rapidly change from one band to another, you press the function button and then
|
||
|
* move the tuning pot. Now, instead of 50 Hz, the tuning is in steps of 50 KHz allowing you
|
||
|
* rapidly use it like a 'bandset' control.
|
||
|
* To implement this, we fix a 'base frequency' to which we add the offset that the pot
|
||
|
* points to. We also store the previous position to know if we need to wake up and change
|
||
|
* the frequency.
|
||
|
*/
|
||
|
|
||
|
unsigned long baseTune = 7100000L;
|
||
|
int old_knob = 0;
|
||
|
|
||
|
#define FIRST_IF (45000000l)
|
||
|
#define SECOND_OSC (57000000l)
|
||
|
#define INIT_USB_FREQ (11996500l)
|
||
|
#define INIT_LSB_FREQ (11998500l)
|
||
|
#define LOWEST_FREQ (3000000l)
|
||
|
#define HIGHEST_FREQ (30000000l)
|
||
|
|
||
|
long frequency, stepSize=100000;
|
||
|
|
||
|
/**
|
||
|
* The raduino can be booted into multiple modes:
|
||
|
* MODE_NORMAL : works the radio normally
|
||
|
* MODE_CALIBRATION : used to calibrate Raduino.
|
||
|
* To enter this mode, hold the function button down and power up. Tune to exactly 10 MHz on clock0 and release the function button
|
||
|
*/
|
||
|
#define MODE_NORMAL (0)
|
||
|
#define MODE_CALIBRATE (1)
|
||
|
char mode = MODE_NORMAL;
|
||
|
|
||
|
char meter[17];
|
||
|
byte s_meter[] = {
|
||
|
B0,B0,B0,B0,B0,B00000,B0,B10101,
|
||
|
B0,B0,B0,B0,B10000,B10000,B0,B10101,
|
||
|
B0,B0,B0,B0,B11000,B11000,B0,B10101,
|
||
|
B0,B0,B0,B0,B11100,B11100,B0,B10101,
|
||
|
B0,B0,B0,B0,B11110,B11110,B0,B10101,
|
||
|
B0,B0,B0,B0,B11111,B11111,B0,B10101
|
||
|
};
|
||
|
|
||
|
void setupSmeter(){
|
||
|
lcd.createChar(1, s_meter);
|
||
|
lcd.createChar(2, s_meter + 8);
|
||
|
lcd.createChar(3, s_meter + 16);
|
||
|
lcd.createChar(4, s_meter + 24);
|
||
|
lcd.createChar(5, s_meter + 32);
|
||
|
lcd.createChar(6, s_meter + 40);
|
||
|
}
|
||
|
|
||
|
/* display routines */
|
||
|
void printLine1(char *c){
|
||
|
if (strcmp(c, printBuff)){
|
||
|
lcd.setCursor(0, 0);
|
||
|
lcd.print(c);
|
||
|
strcpy(printBuff, c);
|
||
|
count++;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
void printLine2(char *c){
|
||
|
if (strlen(c) > 16)
|
||
|
c[16] = 0;
|
||
|
lcd.setCursor(0, 1);
|
||
|
lcd.print(c);
|
||
|
}
|
||
|
|
||
|
void displayFrequency(unsigned long f){
|
||
|
int mhz, khz, hz;
|
||
|
|
||
|
mhz = f / 1000000l;
|
||
|
khz = (f % 1000000l)/1000;
|
||
|
hz = f % 1000l;
|
||
|
sprintf(b, "[%02d.%03d.%03d]", mhz, khz, hz);
|
||
|
printLine1(b);
|
||
|
}
|
||
|
|
||
|
void updateMeter(){
|
||
|
int16_t best, i, s;
|
||
|
|
||
|
best = 0;
|
||
|
//take 100 readings, take the peaks
|
||
|
for (i = 0; i < 100; i++){
|
||
|
s = analogRead(S_METER);
|
||
|
if (s > best)
|
||
|
best = s;
|
||
|
}
|
||
|
//now, use the s to get the signal
|
||
|
s = best *2;
|
||
|
sprintf(meter, "%3d", s);
|
||
|
for (i = 3; i < 14; i++){
|
||
|
if (s >= 5)
|
||
|
meter[i] = 6;
|
||
|
else if (s > 0)
|
||
|
meter[i] = 1 + s;
|
||
|
else
|
||
|
meter[i] = 1;
|
||
|
s = s - 5;
|
||
|
}
|
||
|
meter[i] = 0;
|
||
|
printLine2(meter);
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Defines for menus
|
||
|
*/
|
||
|
byte menuOn = 0;
|
||
|
|
||
|
int ritToggle(int btn){
|
||
|
if (!btn){
|
||
|
if (ritOn == 1)
|
||
|
printLine2("RIT:On, Off? ");
|
||
|
else
|
||
|
printLine2("RIT:Off, On? ");
|
||
|
}
|
||
|
else {
|
||
|
if (ritOn == 0){
|
||
|
ritOn = 1;
|
||
|
printLine2("RIT is On. ");
|
||
|
}
|
||
|
else {
|
||
|
ritOn = 0;
|
||
|
printLine2("RIT Is Off. ");
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
int vfoToggle(int btn){
|
||
|
|
||
|
if (!btn){
|
||
|
if (vfoActive == VFO_A)
|
||
|
printLine2("Select VFO B? ");
|
||
|
else
|
||
|
printLine2("Select VFO A? ");
|
||
|
}
|
||
|
else {
|
||
|
if (vfoActive == VFO_A){
|
||
|
vfoActive = VFO_A;
|
||
|
printLine1("Selected VFO A ");
|
||
|
frequency = vfoA;
|
||
|
}
|
||
|
else {
|
||
|
vfoActive = VFO_B;
|
||
|
printLine1("Selected VFO B ");
|
||
|
frequency = vfoB;
|
||
|
}
|
||
|
setFrequency(frequency);
|
||
|
updateDisplay();
|
||
|
resetBasefrequency();
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Load the new correction and resets the clock 0 to 10 MHz
|
||
|
*/
|
||
|
|
||
|
void recalibrate(int32_t correction){
|
||
|
|
||
|
si5351.set_correction(correction);
|
||
|
|
||
|
si5351.set_pll(SI5351_PLL_FIXED, SI5351_PLLA);
|
||
|
si5351.set_pll(SI5351_PLL_FIXED, SI5351_PLLB);
|
||
|
|
||
|
si5351.output_enable(SI5351_CLK0, 1);
|
||
|
si5351.output_enable(SI5351_CLK1, 0);
|
||
|
si5351.output_enable(SI5351_CLK2, 0);
|
||
|
|
||
|
si5351.set_freq(1000000000ULL, SI5351_CLK0);
|
||
|
}
|
||
|
|
||
|
void calibrateMaster(int btn){
|
||
|
int knob = 0;
|
||
|
int32_t correction;
|
||
|
|
||
|
if (!btn){
|
||
|
printLine2("Set Calibration?");
|
||
|
return;
|
||
|
}
|
||
|
printLine1("Set to 10.000.000");
|
||
|
printLine2("PTT to confirm. ");
|
||
|
delay(2000);
|
||
|
|
||
|
recalibrate(0);
|
||
|
|
||
|
//disable all clock 1 and clock 2
|
||
|
while(digitalRead(PTT) == HIGH && !btnDown()){
|
||
|
knob = analogRead(ANALOG_TUNING);
|
||
|
correction = (knob - 500) * 500ULL;
|
||
|
recalibrate(correction);
|
||
|
//abort if this button is down
|
||
|
if (btnDown()){
|
||
|
//re-enable the clock1 and clock 2
|
||
|
break;
|
||
|
}
|
||
|
sprintf(c, "%3d ", knob);
|
||
|
printLine2(c);
|
||
|
}
|
||
|
|
||
|
//save the setting
|
||
|
if (digitalRead(PTT) == LOW){
|
||
|
printLine2("Calibration set!");
|
||
|
EEPROM.put(MASTER_CAL, correction);
|
||
|
delay(2000);
|
||
|
}
|
||
|
else {
|
||
|
EEPROM.get(MASTER_CAL, correction);
|
||
|
}
|
||
|
|
||
|
si5351.set_correction(correction);
|
||
|
si5351.set_pll(SI5351_PLL_FIXED, SI5351_PLLA);
|
||
|
si5351.set_pll(SI5351_PLL_FIXED, SI5351_PLLB);
|
||
|
|
||
|
si5351.output_enable(SI5351_CLK0, 1);
|
||
|
si5351.output_enable(SI5351_CLK1, 1);
|
||
|
si5351.output_enable(SI5351_CLK2, 1);
|
||
|
|
||
|
resetBasefrequency();
|
||
|
setFrequency(frequency);
|
||
|
updateDisplay();
|
||
|
menuOn = 0;
|
||
|
}
|
||
|
|
||
|
void setBFO(int btn, byte isLSB){
|
||
|
int knob = 0;
|
||
|
int32_t lsb_Freq;
|
||
|
|
||
|
if (!btn){
|
||
|
if (isLSB)
|
||
|
printLine2("Set LSB Carrier ");
|
||
|
else
|
||
|
printLine2("Set USB Carrier ");
|
||
|
return;
|
||
|
}
|
||
|
if (isLSB)
|
||
|
printLine1("Tune to best LSB");
|
||
|
else
|
||
|
printLine1("Tune to best USB");
|
||
|
printLine2("PTT to confirm. ");
|
||
|
delay(2000);
|
||
|
|
||
|
//disable all clock 1 and clock 2
|
||
|
while(digitalRead(PTT) == HIGH && !btnDown()){
|
||
|
knob = analogRead(ANALOG_TUNING);
|
||
|
if (isLSB){
|
||
|
lsbCarrier = 12000000l - knob * 10;
|
||
|
si5351.set_freq(lsbCarrier * 100ULL, SI5351_CLK0);
|
||
|
}
|
||
|
else {
|
||
|
usbCarrier = 12000000l - knob * 10;
|
||
|
si5351.set_freq(usbCarrier * 100ULL, SI5351_CLK0);
|
||
|
}
|
||
|
//abort if this button is down
|
||
|
if (btnDown()){
|
||
|
break;
|
||
|
}
|
||
|
sprintf(c, "%3d ", knob);
|
||
|
printLine2(c);
|
||
|
}
|
||
|
//save the setting
|
||
|
if (digitalRead(PTT) == LOW){
|
||
|
printLine2("Carrier set! ");
|
||
|
if (isLSB)
|
||
|
EEPROM.put(LSB_CAL, lsbCarrier);
|
||
|
else
|
||
|
EEPROM.put(USB_CAL, usbCarrier);
|
||
|
delay(2000);
|
||
|
}
|
||
|
else {
|
||
|
EEPROM.get(LSB_CAL, lsbCarrier);
|
||
|
EEPROM.get(USB_CAL, usbCarrier);
|
||
|
}
|
||
|
|
||
|
resetBasefrequency();
|
||
|
setFrequency(frequency);
|
||
|
updateDisplay();
|
||
|
menuOn = 0;
|
||
|
}
|
||
|
|
||
|
void resetBasefrequency(){
|
||
|
int knob = analogRead(ANALOG_TUNING);
|
||
|
baseTune = frequency - (50l * knob);
|
||
|
}
|
||
|
|
||
|
int exitMenu(int btn){
|
||
|
|
||
|
if (!btn){
|
||
|
printLine2("Exit Menu? ");
|
||
|
}
|
||
|
else{
|
||
|
menuOn = 0;
|
||
|
resetBasefrequency();
|
||
|
}
|
||
|
}
|
||
|
|
||
|
void doMenu(){
|
||
|
int select, btnState;
|
||
|
menuOn = 1;
|
||
|
|
||
|
while(menuOn){
|
||
|
select = analogRead(ANALOG_TUNING);
|
||
|
//downscale the selection
|
||
|
select = (select-50)/50;
|
||
|
btnState = btnDown();
|
||
|
delay(200);
|
||
|
switch(select){
|
||
|
case 0:
|
||
|
ritToggle(btnState);
|
||
|
break;
|
||
|
case 1:
|
||
|
vfoToggle(btnState);
|
||
|
break;
|
||
|
case 2:
|
||
|
calibrateMaster(btnState);
|
||
|
break;
|
||
|
case 3:
|
||
|
setBFO(btnState, 1);
|
||
|
break;
|
||
|
case 4:
|
||
|
setBFO(btnState, 0);
|
||
|
break;
|
||
|
|
||
|
default:
|
||
|
exitMenu(btnState);
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
void updateDisplay(){
|
||
|
sprintf(b, "%8ld", frequency);
|
||
|
sprintf(c, "%s:%.2s.%.3s.%1s", vfoActive == VFO_A ? "A" : "B" , b, b+2, b+5);
|
||
|
if (isUSB)
|
||
|
strcat(c, " USB");
|
||
|
else
|
||
|
strcat(c, " LSB");
|
||
|
|
||
|
if (inTx)
|
||
|
strcat(c, " TX");
|
||
|
else if (ritOn)
|
||
|
strcat(c, " +R");
|
||
|
else
|
||
|
strcat(c, " ");
|
||
|
|
||
|
|
||
|
printLine1(c);
|
||
|
/* sprintf(c, "%s %s %d", isLSB ? "LSB" : "USB", inTx ? " TX" : " RX", digitalRead(FBUTTON));
|
||
|
printLine2(c); */
|
||
|
}
|
||
|
|
||
|
void setSidebandAndTXFilters(unsigned long freq){
|
||
|
|
||
|
if (freq > 10000000L){
|
||
|
isUSB = 1;
|
||
|
si5351.set_freq(usbCarrier * 100ULL, SI5351_CLK0);
|
||
|
digitalWrite(TX_LPF_SEL, 1);
|
||
|
}
|
||
|
else{
|
||
|
isUSB = 0;
|
||
|
digitalWrite(TX_LPF_SEL, 0);
|
||
|
si5351.set_freq(lsbCarrier * 100ULL, SI5351_CLK0);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
void setFrequency(unsigned long f){
|
||
|
uint64_t osc_f;
|
||
|
|
||
|
setSidebandAndTXFilters(f);
|
||
|
|
||
|
if (isUSB)
|
||
|
si5351.set_freq((SECOND_OSC - usbCarrier + f) * 100ULL, SI5351_CLK2);
|
||
|
else
|
||
|
si5351.set_freq((SECOND_OSC - lsbCarrier + f) * 100ULL, SI5351_CLK2);
|
||
|
|
||
|
frequency = f;
|
||
|
}
|
||
|
|
||
|
void checkTX(){
|
||
|
|
||
|
//we don't check for ptt when transmitting cw
|
||
|
if (cwTimeout > 0)
|
||
|
return;
|
||
|
|
||
|
if (digitalRead(PTT) == 0 && inTx == 0){
|
||
|
inTx = 1;
|
||
|
digitalWrite(TX_RX, 1);
|
||
|
updateDisplay();
|
||
|
}
|
||
|
|
||
|
if (digitalRead(PTT) == 1 && inTx == 1){
|
||
|
inTx = 0;
|
||
|
digitalWrite(TX_RX, 0);
|
||
|
updateDisplay();
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
byte prevWasDot = 0;
|
||
|
|
||
|
void keyer(){
|
||
|
int key = analogRead(ANALOG_KEYER);
|
||
|
|
||
|
if (key < 50) //straight key
|
||
|
keyDown();
|
||
|
else if (key < 300) // both
|
||
|
if (prevWasDot){
|
||
|
keyDown(dotPeriod * 3);
|
||
|
prevWasDot = 0;
|
||
|
}
|
||
|
else {
|
||
|
keyDown(dotPeriod);
|
||
|
prevWasDot = 1;
|
||
|
}
|
||
|
else if (key < 600){ //dash
|
||
|
keyDown(dotPeriod * 3);
|
||
|
prevWasDot = 0;
|
||
|
}
|
||
|
else if (key > 900){ //dot
|
||
|
keyUp();
|
||
|
}
|
||
|
}
|
||
|
*/
|
||
|
|
||
|
|
||
|
void checkCW2(){
|
||
|
|
||
|
if (keyDown == 0 && analogRead(ANALOG_KEYER) < 50){
|
||
|
//switch to transmit mode if we are not already in it
|
||
|
if (inTx == 0){
|
||
|
digitalWrite(TX_RX, 1);
|
||
|
// selectTransmitFilter();
|
||
|
//give the relays a few ms to settle the T/R relays
|
||
|
delay(50);
|
||
|
}
|
||
|
inTx = 1;
|
||
|
keyDown = 1;
|
||
|
tone(CW_TONE, (int)sideTone);
|
||
|
updateDisplay();
|
||
|
}
|
||
|
|
||
|
//reset the timer as long as the key is down
|
||
|
if (keyDown == 1){
|
||
|
cwTimeout = CW_TIMEOUT + millis();
|
||
|
}
|
||
|
|
||
|
//if we have a keyup
|
||
|
if (keyDown == 1 && analogRead(ANALOG_KEYER) > 150){
|
||
|
keyDown = 0;
|
||
|
noTone(CW_TONE);
|
||
|
cwTimeout = millis() + CW_TIMEOUT;
|
||
|
}
|
||
|
|
||
|
//if we are in cw-mode and have a keyuup for a longish time
|
||
|
if (cwTimeout > 0 && inTx == 1 && cwTimeout < millis()){
|
||
|
//move the radio back to receive
|
||
|
digitalWrite(TX_RX, 0);
|
||
|
inTx = 0;
|
||
|
cwTimeout = 0;
|
||
|
updateDisplay();
|
||
|
}
|
||
|
}
|
||
|
|
||
|
void checkCW3(){
|
||
|
|
||
|
if (keyDown == 0 && analogRead(ANALOG_KEYER) < 50){
|
||
|
//switch to transmit mode if we are not already in it
|
||
|
if (inTx == 0){
|
||
|
|
||
|
if (isUSB)
|
||
|
si5351.set_freq((frequency + sideTone) * 100ULL, SI5351_CLK2);
|
||
|
else
|
||
|
si5351.set_freq((frequency - sideTone) * 100ULL, SI5351_CLK2);
|
||
|
//switch off the second oscillator and the bfo
|
||
|
si5351.output_enable(SI5351_CLK0, 0);
|
||
|
si5351.output_enable(SI5351_CLK1, 0);
|
||
|
si5351.output_enable(SI5351_CLK2, 1);
|
||
|
|
||
|
digitalWrite(TX_RX, 1);
|
||
|
// selectTransmitFilter();
|
||
|
//give the relays a few ms to settle the T/R relays
|
||
|
delay(50);
|
||
|
}
|
||
|
inTx = 1;
|
||
|
keyDown = 1;
|
||
|
tone(CW_TONE, (int)sideTone);
|
||
|
digitalWrite(CW_KEY, 1);
|
||
|
|
||
|
updateDisplay();
|
||
|
}
|
||
|
|
||
|
//reset the timer as long as the key is down
|
||
|
if (keyDown == 1){
|
||
|
cwTimeout = CW_TIMEOUT + millis();
|
||
|
}
|
||
|
|
||
|
//if we have a keyup
|
||
|
if (keyDown == 1 && analogRead(ANALOG_KEYER) > 150){
|
||
|
keyDown = 0;
|
||
|
noTone(CW_TONE);
|
||
|
digitalWrite(CW_KEY, 0);
|
||
|
cwTimeout = millis() + CW_TIMEOUT;
|
||
|
}
|
||
|
|
||
|
//if we are in cw-mode and have a keyuup for a longish time
|
||
|
if (cwTimeout > 0 && inTx == 1 && cwTimeout < millis()){
|
||
|
//move the radio back to receive
|
||
|
digitalWrite(TX_RX, 0);
|
||
|
inTx = 0;
|
||
|
cwTimeout = 0;
|
||
|
updateDisplay();
|
||
|
|
||
|
//switch off the second oscillator and the bfo
|
||
|
si5351.output_enable(SI5351_CLK0, 1);
|
||
|
si5351.output_enable(SI5351_CLK1, 1);
|
||
|
si5351.output_enable(SI5351_CLK2, 1);
|
||
|
setFrequency(frequency);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
|
||
|
void checkCW(){
|
||
|
|
||
|
if (keyDown == 0 && analogRead(ANALOG_KEYER) < 50){
|
||
|
//switch to transmit mode if we are not already in it
|
||
|
if (inTx == 0){
|
||
|
digitalWrite(TX_RX, 1);
|
||
|
delay(50);
|
||
|
inTx = 1;
|
||
|
keyDown = 1;
|
||
|
}
|
||
|
if (isUSB)
|
||
|
si5351.set_freq((frequency + sideTone) * 100ULL, SI5351_CLK2);
|
||
|
else
|
||
|
si5351.set_freq((frequency - sideTone) * 100ULL, SI5351_CLK2);
|
||
|
//switch off the second oscillator and the bfo
|
||
|
si5351.output_enable(SI5351_CLK0, 0);
|
||
|
si5351.output_enable(SI5351_CLK1, 0);
|
||
|
si5351.output_enable(SI5351_CLK2, 1);
|
||
|
|
||
|
digitalWrite(CW_KEY, 1);
|
||
|
tone(CW_TONE, sideTone);
|
||
|
updateDisplay();
|
||
|
}
|
||
|
|
||
|
//reset the timer as long as the key is down
|
||
|
if (keyDown == 1){
|
||
|
cwTimeout = CW_TIMEOUT + millis();
|
||
|
}
|
||
|
|
||
|
//if we have a keyup
|
||
|
if (keyDown == 1 && analogRead(ANALOG_KEYER) > 150){
|
||
|
keyDown = 0;
|
||
|
noTone(CW_TONE);
|
||
|
digitalWrite(CW_KEY, 0);
|
||
|
cwTimeout = millis() + CW_TIMEOUT;
|
||
|
}
|
||
|
|
||
|
//if we are in cw-mode and have a keyuup for a longish time
|
||
|
if (cwTimeout > 0 && inTx == 1 && cwTimeout < millis()){
|
||
|
//move the radio back to receive
|
||
|
digitalWrite(TX_RX, 0);
|
||
|
inTx = 0;
|
||
|
cwTimeout = 0;
|
||
|
//switch off the second oscillator and the bfo
|
||
|
si5351.output_enable(SI5351_CLK0, 1);
|
||
|
si5351.output_enable(SI5351_CLK1, 1);
|
||
|
si5351.output_enable(SI5351_CLK2, 1);
|
||
|
setFrequency(frequency);
|
||
|
updateDisplay();
|
||
|
}
|
||
|
}
|
||
|
|
||
|
int btnDown(){
|
||
|
if (digitalRead(FBUTTON) == HIGH)
|
||
|
return 0;
|
||
|
else
|
||
|
return 1;
|
||
|
}
|
||
|
|
||
|
void checkButton(){
|
||
|
int i, t1, t2, knob, new_knob, duration;
|
||
|
|
||
|
//only if the button is pressed
|
||
|
if (!btnDown())
|
||
|
return;
|
||
|
|
||
|
//wait for 50 ms before declaring the button to be really down
|
||
|
delay(50);
|
||
|
if (!btnDown())
|
||
|
return;
|
||
|
|
||
|
t1 = millis();
|
||
|
knob = analogRead(ANALOG_TUNING);
|
||
|
duration = 0;
|
||
|
|
||
|
/* keep measuring how long the duration of btn down has been to a max of 3 seconds */
|
||
|
while (btnDown() && duration < 3000){
|
||
|
/* if the tuning knob is moved while the btn is down,
|
||
|
then track the bandset until the button is up and return
|
||
|
*/
|
||
|
new_knob = analogRead(ANALOG_TUNING);
|
||
|
if (abs(new_knob - knob) > 10){
|
||
|
int count = 0;
|
||
|
/* track the tuning and return */
|
||
|
while (btnDown()){
|
||
|
frequency = baseTune = ((analogRead(ANALOG_TUNING) * 30000l) + 1000000l);
|
||
|
setFrequency(frequency);
|
||
|
updateDisplay();
|
||
|
count++;
|
||
|
delay(200);
|
||
|
}
|
||
|
delay(1000);
|
||
|
return;
|
||
|
} /* end of handling the bandset */
|
||
|
|
||
|
delay(100);
|
||
|
duration += 100;
|
||
|
}
|
||
|
|
||
|
if (duration < 1000) {
|
||
|
printLine2("Menu.");
|
||
|
doMenu();
|
||
|
}
|
||
|
}
|
||
|
|
||
|
void doTuning(){
|
||
|
unsigned long newFreq;
|
||
|
|
||
|
int knob = analogRead(ANALOG_TUNING);
|
||
|
unsigned long old_freq = frequency;
|
||
|
|
||
|
if (knob < 10 && frequency > LOWEST_FREQ) {
|
||
|
baseTune = baseTune - 1000l;
|
||
|
frequency = baseTune;
|
||
|
updateDisplay();
|
||
|
setFrequency(frequency);
|
||
|
delay(50);
|
||
|
}
|
||
|
else if (knob > 1010 && frequency < HIGHEST_FREQ) {
|
||
|
baseTune = baseTune + 1000l;
|
||
|
frequency = baseTune + 50000l;
|
||
|
setFrequency(frequency);
|
||
|
updateDisplay();
|
||
|
delay(50);
|
||
|
}
|
||
|
// in the middle, it is business as usual
|
||
|
else if (knob != old_knob){
|
||
|
frequency = baseTune + (50l * knob);
|
||
|
old_knob = knob;
|
||
|
setFrequency(frequency);
|
||
|
updateDisplay();
|
||
|
}
|
||
|
}
|
||
|
|
||
|
|
||
|
void setup()
|
||
|
{
|
||
|
int32_t cal;
|
||
|
|
||
|
lcd.begin(16, 2);
|
||
|
setupSmeter();
|
||
|
printBuff[0] = 0;
|
||
|
printLine1("HFuino v0.01 ");
|
||
|
printLine2(" ");
|
||
|
|
||
|
EEPROM.get(MASTER_CAL, cal);
|
||
|
EEPROM.get(LSB_CAL, lsbCarrier);
|
||
|
EEPROM.get(USB_CAL, usbCarrier);
|
||
|
//set the lsb and usb to defaults
|
||
|
if (lsbCarrier == 0)
|
||
|
lsbCarrier = INIT_LSB_FREQ;
|
||
|
if (usbCarrier == 0)
|
||
|
usbCarrier = INIT_USB_FREQ;
|
||
|
|
||
|
// Start serial and initialize the Si5351
|
||
|
Serial.begin(9600);
|
||
|
analogReference(DEFAULT);
|
||
|
Serial.println("*HFuino v0.01\n");
|
||
|
Serial.println("*Searching Si5351\n");
|
||
|
|
||
|
//configure the function button to use the external pull-up
|
||
|
pinMode(FBUTTON, INPUT);
|
||
|
digitalWrite(FBUTTON, HIGH);
|
||
|
|
||
|
pinMode(PTT, INPUT);
|
||
|
digitalWrite(PTT, HIGH);
|
||
|
|
||
|
digitalWrite(ANALOG_KEYER, HIGH);
|
||
|
|
||
|
pinMode(CW_TONE, OUTPUT);
|
||
|
digitalWrite(CW_TONE, 0);
|
||
|
pinMode(TX_RX,OUTPUT);
|
||
|
digitalWrite(TX_RX, 0);
|
||
|
pinMode(TX_LPF_SEL, OUTPUT);
|
||
|
digitalWrite(TX_LPF_SEL, 0);
|
||
|
pinMode(CW_KEY, OUTPUT);
|
||
|
digitalWrite(CW_KEY, 0);
|
||
|
EEPROM.get(0,cal);
|
||
|
|
||
|
si5351.init(SI5351_CRYSTAL_LOAD_8PF,25000000l,0);
|
||
|
|
||
|
si5351.set_correction(cal);
|
||
|
si5351.set_pll(SI5351_PLL_FIXED, SI5351_PLLA);
|
||
|
si5351.set_pll(SI5351_PLL_FIXED, SI5351_PLLB);
|
||
|
|
||
|
si5351.output_enable(SI5351_CLK0, 1);
|
||
|
si5351.output_enable(SI5351_CLK1, 1);
|
||
|
si5351.output_enable(SI5351_CLK2, 1);
|
||
|
// printLine1("check freq ");
|
||
|
// si5351.set_freq(1000000000l, SI5351_CLK2);
|
||
|
|
||
|
// delay(20000);
|
||
|
|
||
|
si5351.set_freq(lsbCarrier * 100ULL, SI5351_CLK0);
|
||
|
si5351.set_freq(SECOND_OSC * 100ULL, SI5351_CLK1);
|
||
|
si5351.set_freq(5900000000l, SI5351_CLK2);
|
||
|
printLine2(b);
|
||
|
delay(2000);
|
||
|
|
||
|
// Set CLK0 to output 7 MHz with a fixed PLL frequency
|
||
|
|
||
|
si5351.drive_strength(SI5351_CLK0, SI5351_DRIVE_8MA);
|
||
|
si5351.drive_strength(SI5351_CLK1, SI5351_DRIVE_8MA);
|
||
|
si5351.drive_strength(SI5351_CLK2, SI5351_DRIVE_8MA);
|
||
|
|
||
|
|
||
|
Serial.println("*Si5350 ON");
|
||
|
delay(10);
|
||
|
}
|
||
|
|
||
|
void loop(){
|
||
|
|
||
|
//generateCW(10000);
|
||
|
//the order of testing first for cw and then for ptt is important.
|
||
|
checkCW3();
|
||
|
checkTX();
|
||
|
checkButton();
|
||
|
//tune only when not tranmsitting
|
||
|
if (!inTx)
|
||
|
doTuning();
|
||
|
updateMeter();
|
||
|
delay(50);
|
||
|
}
|
||
|
|