From ac2de2aa24d517b15e4d8d0b3ad1112c7cae234a Mon Sep 17 00:00:00 2001 From: Allard PE1NWL Date: Thu, 21 Dec 2017 00:31:09 +0100 Subject: [PATCH] Update raduino_diag_v3.ino --- raduino_diag_v3.ino | 1982 +++++++++++++++++++++---------------------- 1 file changed, 991 insertions(+), 991 deletions(-) diff --git a/raduino_diag_v3.ino b/raduino_diag_v3.ino index 6759444..2f04ec6 100644 --- a/raduino_diag_v3.ino +++ b/raduino_diag_v3.ino @@ -1,992 +1,992 @@ -/** - Raduino_diag v3 for BITX40 - Allard Munters PE1NWL (pe1nwl@gooddx.net) - This source file is under General Public License version 3. - - This sketch tests some basic hardware functions: - tuning pot reading - Function Button/ SPOT button switches - PTT switch - capacitive touch sensors - - Results are shown on the LCD display - additional messages are output to the serial monitor - (in the IDE, select tools => serial monitor) - -*/ - -#define DEBUG_BOOT //uncomment this to print debug messages to the serial monitor -#define DEBUG_RUN //uncomment this to print debug messages to the serial monitor - -// *** USER PARAMETERS *** - -// tuning range parameters -#define MIN_FREQ 7000000UL // absolute minimum tuning frequency in Hz -#define MAX_FREQ 7300000UL // absolute maximum tuning frequency in Hz -#define TUNING_POT_SPAN 50 // tuning pot span in kHz [accepted range 10-500 kHz] -// recommended pot span for a 1-turn pot: 50kHz, for a 10-turn pot: 100 to 200kHz -// recommended pot span when radio is used mainly for CW: 10 to 25 kHz - -// USB/LSB parameters -#define CAL_VALUE 1575 // VFO calibration value (180 ppm) -#define OFFSET_USB 1500 // USB offset in Hz [accepted range -10000Hz to 10000Hz] -#define VFO_DRIVE_LSB 4 // VFO drive level in LSB mode in mA [accepted values 2,4,6,8 mA] -#define VFO_DRIVE_USB 8 // VFO drive level in USB mode in mA [accepted values 2,4,6,8 mA] - -// CW parameters -#define CW_SHIFT 800 // RX shift in CW mode in Hz, equal to sidetone pitch [accepted range 200-1200 Hz] -#define SEMI_QSK true // whether we use semi-QSK (true) or manual PTT (false) -#define CW_TIMEOUT 350 // time delay in ms before radio goes back to receive [accepted range 10-1000 ms] - -// CW keyer parameters -#define CW_KEY_TYPE 0 // type of CW-key (0:straight, 1:paddle, 2:rev. paddle, 3:bug, 4:rev. bug) -#define CW_SPEED 16 // CW keyer speed in words per minute [accepted range 1-50 WPM] -#define AUTOSPACE false // whether or not auto-space is enabled [accepted values: true or false] -#define DIT_DELAY 5 // debounce delay (ms) for DIT contact (affects the DIT paddle sensitivity) -#define DAH_DELAY 15 // debounce delay (ms) for DAH contact (affects the DAH paddle sensitivity) - -// Capacitive touch keyer -#define CAP_SENSITIVITY 22 // capacitive touch keyer sensitivity 0 (OFF) [accepted range 0-25] - -// frequency scanning parameters -#define SCAN_START 7100 // Scan start frequency in kHz [accepted range MIN_FREQ - MAX_FREQ, see above] -#define SCAN_STOP 7150 // Scan stop frequency in kHz [accepted range SCAN_START - MAX_FREQ, see above] -#define SCAN_STEP 1000 // Scan step size in Hz [accepted range 50Hz to 10000Hz] -#define SCAN_STEP_DELAY 500 // Scan step delay in ms [accepted range 0-2000 ms] - -// Function Button -#define CLICK_DELAY 750 // max time (in ms) between function button clicks - -// RX-TX burst prevention -#define TX_DELAY 65 // delay (ms) to prevent spurious burst that is emitted when switching from RX to TX - -// all variables that will be stored in EEPROM are contained in this 'struct': -struct userparameters { - byte raduino_version; // version identifier - byte wpm = CW_SPEED; // CW keyer speed (words per minute) - int USB_OFFSET = OFFSET_USB; // VFO offset in Hz for USB mode - int cal = CAL_VALUE; // VFO calibration value - byte LSBdrive = VFO_DRIVE_LSB; // VFO drive level in LSB mode - byte USBdrive = VFO_DRIVE_USB; // VFO drive level in USB mode - bool semiQSK = SEMI_QSK; // whether semi QSK is ON or OFF - unsigned int POT_SPAN = TUNING_POT_SPAN; // tuning pot span (kHz) - unsigned int CW_OFFSET = CW_SHIFT; // RX shift (Hz) in CW mode, equal to side tone pitch - unsigned long vfoA = 7125000UL; // frequency of VFO A - unsigned long vfoB = 7125000UL; // frequency of VFO B - byte mode_A = 0; // operating mode of VFO A - byte mode_B = 0; // operating mode of VFO B - bool vfoActive = false; // currently active VFO (A = false, B = true) - bool splitOn = false; // whether SPLIT is ON or OFF - unsigned int scan_start_freq = SCAN_START; // scan start frequency (kHz) - unsigned int scan_stop_freq = SCAN_STOP; // scan stop frequency (kHz) - unsigned int scan_step_freq = SCAN_STEP; // scan step size (Hz) - unsigned int scan_step_delay = SCAN_STEP_DELAY; // scan step delay (ms) - unsigned int QSK_DELAY = CW_TIMEOUT; // word [accepted range 10-1000 ms] - byte key_type = CW_KEY_TYPE; // CW key type (0:straight, 1:paddle, 2:rev. paddle, 3:bug, 4:rev. bug) - unsigned long LOWEST_FREQ = MIN_FREQ; // absolute minimum dial frequency (Hz) - unsigned long HIGHEST_FREQ = MAX_FREQ; // absolute maximum dial frequency (Hz) - bool autospace = AUTOSPACE; // whether auto character space is ON or OFF - byte cap_sens = CAP_SENSITIVITY; // sensitivity of the touch keyer sensors -}; - -// we can access each of the variables inside the above struct by "u.variablename" -struct userparameters u; - -#ifdef DEBUG_BOOT -#define DEBUG_PRINT(x) Serial.print (x) -#define DEBUG_PRINTLN(x) Serial.println (x) -#else -#define DEBUG_PRINT(x) -#define DEBUG_PRINTLN(x) -#endif - -#ifdef DEBUG_RUN -#define DEBUG_PRINT2(x) Serial.print (x) -#define DEBUG_PRINTLN2(x) Serial.println (x) -#else -#define DEBUG_PRINT2(x) -#define DEBUG_PRINTLN2(x) -#endif - -/** - Below are the libraries to be included for building the Raduino - The EEPROM library is used to store settings like the frequency memory, calibration data, etc. -*/ - -#include - -/** - 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 - -/** - The PinChangeInterrupt.h library is used for handling interrupts -*/ -#include // https://github.com/NicoHood/PinChangeInterrupt - -/** - 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 requirement 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: -*/ - -#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 - -/** - 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. - A0 A1 A2 A3 GND +5V A6 A7 - BLACK BROWN RED ORANGE YELLOW GREEN BLUE VIOLET (same color coding as used for resistors) - 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 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[17], b[10], printBuff[2][17]; - -/** - 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, GND, +5V, A6, A7 - pin 8 7 6 5 4 3 2 1 (connector P1) - BLACK BROWN RED ORANGE YELLW GREEN BLUE VIOLET - (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: - A0 (digital input) for sensing the PTT. Connect to the output of U3 (LM7805) of the BITX40. - This way the A0 input will see 0V (LOW) when PTT is not pressed, +5V (HIGH) when PTT is pressed. - A1 (digital input) is to connect to a straight key, or to the 'Dit' contact of a paddle keyer. Open (HIGH) during key up, switch to ground (LOW) during key down. - A2 (digital input) can be used for calibration by grounding this line (not required when you have the Function Button at A3) - A3 (digital input) is connected to a push button that can momentarily ground this line. This Function Button will be used to switch between different modes, etc. - A4 (already in use for talking to the SI5351) - A5 (already in use for talking to the SI5351) - A6 (analog input) is not currently used - A7 (analog input) 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 implements the tuning mechanism. -*/ - -#define PTT_SENSE (A0) -#define KEY (A1) - -#define FBUTTON (A3) -#define CLARIFIER (A6) -#define ANALOG_TUNING (A7) - -bool PTTsense_installed; //whether or not the PTT sense line is installed (detected automatically during startup) - -/** - The second set of 16 pins on the bottom connector P3 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 (connector P3) - +12V +12V CLK2 GND GND CLK1 GND GND CLK0 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 : - output D2 - PULSE : is used for the capacitive touch keyer - input D3 - DAH : is connected to the 'Dah' contact of an paddle keyer (switch to ground). - input D4 - SPOT : is connected to a push button that can momentarily ground this line. When the SPOT button is pressed a sidetone will be generated for zero beat tuning. - output D5 - CW_TONE : Side tone output - output D6 - CW_CARRIER line : turns on the carrier for CW - output D7 - TX_RX line : Switches between Transmit and Receive in CW mode -*/ - -#define PULSE (2) -#define DAH (A2) -#define SPOT (4) -#define CW_TONE (5) -#define CW_CARRIER (6) -#define TX_RX (7) - -bool TXRX_installed = true; // whether or not the TX_RX mod (PTT bypass transistor) is installed (set automatically at startup) - -/** - Raduino has 4 modes of operation: -*/ - -#define LSB (0) -#define USB (1) -#define CWL (2) -#define CWU (3) - -/** - Raduino needs to keep track of current state of the transceiver. These are a few variables that do it -*/ -unsigned long vfoA; // the frequency (Hz) of VFO A -unsigned long vfoB; // the frequency (Hz) of VFO B -byte mode = LSB; // mode of the currently active VFO -bool inTx = false; // whether or not we are in transmit mode -volatile bool TXstart = false; // this flag will be set by the ISR as soon as the PTT is keyed -int clar, clar_old; - - -// some variable used by the capacitive touch keyer: - -bool CapTouch_installed = true; // whether or not the capacitive touch modification is installed (detected automatically during startup) -byte base_sens_KEY; // base delay (in us) when DIT pad is NOT touched (measured automatically by touch sensor calibration routine) -byte base_sens_DAH; // base delay (in us) when DAH pad is NOT touched (measured automatically by touch sensor calibration routine) -bool capaKEY, capaDAH; - -#define bfo_freq (11998800UL) -unsigned long baseTune = 7100000UL; // frequency (Hz) when tuning pot is at minimum position -int old_knob = 0; -unsigned long frequency; // the 'dial' frequency as shown on the display - - -// ************* 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. - -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 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(); -} - -// *********** End of Jerry's si5315bx routines ********************************************************* - -/** - Display Routine - This display routine prints a line of characters to the upper or lower line of the 16x2 display - linenmbr = 0 is the upper line - linenmbr = 1 is the lower line -*/ - -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(' '); - } - } -} - -/** - Building upon the previous routine, - update Display paints the first line as per current state of the radio -*/ - -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 + 50), b, DEC); // construct the frequency string - if (!u.vfoActive) - strcpy(c, "A "); // display which VFO is active (A or B) - else - strcpy(c, "B "); - - - byte p = strlen(b); // the length of the frequency string (<10 Mhz: 7, >10 MHz: 8) - - strncat(c, &b[0], p - 6); // display the megahertzes - strcat(c, "."); - strncat(c, &b[p - 6], 3); // display the kilohertzes - strcat(c, "."); - - strncat(c, &b[p - 3], 1); // display the frequency at 100 Hz precision - - switch (mode) { // show the operating mode - case LSB: - strcat(c, " LSB"); - break; - case USB: - strcat(c, " USB"); - break; - case CWL: - strcat(c, " CWL"); - break; - case CWU: - strcat(c, " CWU"); - break; - } - - if (inTx) // show the state (TX, SPLIT, or nothing) - strcat(c, " TX"); - else if (u.splitOn) - strcat(c, " SP"); - - c[16] = '\0'; // cut off any excess characters (string length is max 16 postions) - printLine(0, c); // finally print the constructed string to the first line of the display -} - -// routine to generate a bleep sound (FB menu) -void bleep(int pitch, int duration, byte repeat) { - for (byte i = 0; i < repeat; i++) { - tone(CW_TONE, pitch); - delay(duration); - noTone(CW_TONE); - delay(duration); - } -} - -void setFrequency() { - - DEBUG_PRINT("BFO "); - DEBUG_PRINT(bfo_freq); - DEBUG_PRINT(", Si5351: "); - if (mode & 1) { // if we are in UPPER side band mode - si5351bx_setfreq(2, (bfo_freq + frequency - u.USB_OFFSET)); - DEBUG_PRINT(bfo_freq + frequency - u.USB_OFFSET); - DEBUG_PRINT(", VFO "); - DEBUG_PRINT(frequency); - } - else { // if we are in LOWER side band mode - si5351bx_setfreq(2, (bfo_freq - frequency)); - DEBUG_PRINT(bfo_freq - frequency); - DEBUG_PRINT(", VFO "); - DEBUG_PRINT(frequency); - } - DEBUG_PRINT(", tuning pot "); - DEBUG_PRINT(analogRead(ANALOG_TUNING)); - DEBUG_PRINT(", CLAR pot "); - DEBUG_PRINTLN(analogRead(CLARIFIER) - 512); - updateDisplay(); -} - -/** - The checkTX toggles the T/R line. If you would like to make use of RIT, etc, - you must connect pin A0 (black wire) via a 10K resistor to the output of U3 - This is a voltage regulator LM7805 which goes on during TX. We use the +5V output - as a PTT sense line (to tell the Raduino that we are in TX). -*/ - -void SetSideBand(byte drivelevel) { - - set_drive_level(drivelevel); - setFrequency(); - if (!u.vfoActive) // if VFO A is active - u.mode_A = mode; - else // if VFO B is active - u.mode_B = mode; -} - -/* interrupt service routine - When the PTT is keyed (either manually or by the semiQSK function), normal program execution - will be interrupted and the interrupt service routine (ISR) will be executed. After that, normal - program execution will continue from the point it was interrupted. - The only thing this ISR does, is setting the TXstart flag. This flag is frequently polled by the - knob_position routine. - The knob_position routine takes 100 readings from the tuning pot for extra precision. However - this takes more time (about 10ms). Normally this is not a problem, however when we key the PTT - we need to quickly turn off the VFO in order to prevent spurious emission. In that case 10ms is - too much delay. - In order to prevent this delay, the knob-position routine keeps checking the TXstart flag - and when it is set it will terminate immediately. -*/ - -void ISRptt(void) { - TXstart = true; // the TXstart flag will be set as soon as the PTT is keyed -} - -// routine to read the position of the tuning knob at high precision (Allard, PE1NWL) -int knob_position() { - unsigned long knob = 0; - // the knob value normally ranges from 0 through 1023 (10 bit ADC) - // in order to increase the precision by a factor 10, we need 10^2 = 100x oversampling - for (byte i = 0; i < 100; i++) { // it takes about 10 ms to run this loop 100 times - if (TXstart) { // when the PTT was keyed, in order to prevent 10 ms delay: - DEBUG_PRINTLN2("RX => TX"); - TXstart = false; // reset the TXstart flag that was set by the interrupt service routine - return old_knob; // then exit this routine immediately and return the old knob value - } - else - knob = knob + analogRead(ANALOG_TUNING); // take 100 readings from the ADC - } - knob = (knob + 5L) / 10L; // take the average of the 100 readings and multiply the result by 10 - //now the knob value ranges from 0 through 10,230 (10x more precision) - knob = knob * 10000L / 10230L; // scale the knob range down to 0-10,000 - return (int)knob; -} - -/* - Many BITX40's suffer from a strong birdie at 7199 kHz (LSB). - This birdie may be eliminated by using a different VFO drive level in LSB mode. - In USB mode, a high drive level may be needed to compensate for the attenuation of - higher VFO frequencies. - The drive level for each mode can be set in the SETTINGS menu -*/ - -void set_drive_level(byte level) { - si5351bx_drive[2] = level / 2 - 1; - setFrequency(); -} - -/* - routine to align the current knob position with the current frequency - If we switch between VFO's A and B, the frequency will change but the tuning knob - is still in the same position. We need to apply some offset so that the new frequency - corresponds with the current knob position. - This routine reads the current knob position, then it shifts the baseTune value up or down - so that the new frequency matches again with the current knob position. -*/ - -void shiftBase() { - setFrequency(); - unsigned long knob = knob_position(); // get the current tuning knob position - baseTune = frequency - (knob * (unsigned long)u.POT_SPAN / 10UL); -} - -/* - The Tuning mechansim of the Raduino works in a very innovative way. It uses a tuning potentiometer. - The tuning potentiometer that a voltage between 0 and 5 volts at ANALOG_TUNING pin of the control connector. - This is read as a value between 0 and 1023. By 100x oversampling this range is expanded by a factor 10. - Hence, the tuning pot gives you 10,000 steps from one end to the other end of its rotation. Each step is 50 Hz, - thus giving maximum 500 Khz of tuning range. The tuning range is scaled down depending on the POT_SPAN value. - The standard tuning range (for the standard 1-turn pot) is 50 Khz. But it is also possible to use a 10-turn pot - to tune accross the entire 40m band. The desired POT_SPAN can be set via the Function Button in the SETTINGS menu. - When the potentiometer is moved to either end of the range, the frequency starts automatically moving - up or down in 10 Khz increments, so it is still possible to tune beyond the range set by POT_SPAN. -*/ - -void doTuning() { - int knob = analogRead(ANALOG_TUNING); // get the current tuning knob position - if (digitalRead(PTT_SENSE)) { - inTx = true; - printLine(1, (char *)"PTT is ON"); - DEBUG_PRINTLN("PTT is ON"); - updateDisplay(); - noTone(CW_TONE); // stop generating the sidetone - delay(250); - } - else if (!digitalRead(SPOT)) { - inTx = false; - printLine(1, (char *)"SPOT pressed"); - DEBUG_PRINTLN("SPOT pressed"); - updateDisplay(); - noTone(CW_TONE); // stop generating the sidetone - delay(250); - } - else if (!digitalRead(FBUTTON)) { - inTx = false; - printLine(1, (char *)"F-Button pressed"); - DEBUG_PRINTLN("F-Button pressed"); - updateDisplay(); - noTone(CW_TONE); // stop generating the sidetone - delay(250); - } - else if (!CapTouch_installed && !digitalRead(KEY)) { - inTx = false; - printLine(1, (char *)"KEY/DIT is ON"); - DEBUG_PRINTLN("KEY/DIT is ON"); - updateDisplay(); - tone(CW_TONE, 800); // generate sidetone - delay(250); - } - else if (!CapTouch_installed && !digitalRead(DAH)) { - inTx = false; - printLine(1, (char *)"DAH is ON"); - DEBUG_PRINTLN("DAH is ON"); - updateDisplay(); - tone(CW_TONE, 800); // generate sidetone - delay(250); - } - else if (CapTouch_installed && capaKEY) { - inTx = false; - printLine(1, (char *)"DIT sensor"); - DEBUG_PRINTLN("DIT sensor touched"); - updateDisplay(); - tone(CW_TONE, 800); // generate sidetone - delay(250); - } - else if (CapTouch_installed && capaDAH) { - inTx = false; - printLine(1, (char *)"DAH sensor"); - DEBUG_PRINTLN("DAH sensor touched"); - updateDisplay(); - tone(CW_TONE, 800); // generate sidetone - delay(250); - } - else { - inTx = false; - itoa(knob, b, DEC); - strcpy(c, "tuning pot "); - strcat(c, b); - printLine(1, c); - noTone(CW_TONE); // stop generating the sidetone - updateDisplay(); - } - knob = knob * 100000 / 10230; // get the current tuning knob position - clar = analogRead(CLARIFIER) - 512; - - // tuning is disabled during TX (only when PTT sense line is installed) - if (inTx && abs(knob - old_knob) > 20) { - printLine(1, (char *)"dial is locked"); - shiftBase(); - return; - } - else if (inTx) { // no tuning in TX or when dial is locked - return; - } - else if (abs(knob - old_knob) < 20) - save_frequency(); // only save VFO frequencies to EEPROM when tuning pot is not being turned - - knob = knob_position(); // get the precise tuning knob position - // the knob is fully on the low end, do fast tune: move down by 10 Khz and wait for 300 msec - // if the POT_SPAN is very small (less than 25 kHz) then use 1 kHz steps instead - - if (knob == 0) { - if (frequency > u.LOWEST_FREQ) { - if (u.POT_SPAN < 25) - baseTune = baseTune - 1000UL; // fast tune down in 1 kHz steps - else - baseTune = baseTune - 10000UL; // fast tune down in 10 kHz steps - frequency = baseTune + (unsigned long)knob * (unsigned long)u.POT_SPAN / 10UL; - delay(300); - } - if (frequency <= u.LOWEST_FREQ) - baseTune = frequency = u.LOWEST_FREQ; - setFrequency(); - old_knob = 0; - } - - // the knob is full on the high end, do fast tune: move up by 10 Khz and wait for 300 msec - // if the POT_SPAN is very small (less than 25 kHz) then use 1 kHz steps instead - - else if (knob == 10000) { - if (frequency < u.HIGHEST_FREQ) { - if (u.POT_SPAN < 25) - baseTune = baseTune + 1000UL; // fast tune up in 1 kHz steps - else - baseTune = baseTune + 10000UL; // fast tune up in 10 kHz steps - frequency = baseTune + (unsigned long)knob * (unsigned long)u.POT_SPAN / 10UL; - delay(300); - } - if (frequency >= u.HIGHEST_FREQ) { - baseTune = u.HIGHEST_FREQ - (u.POT_SPAN * 1000UL); - frequency = u.HIGHEST_FREQ; - } - setFrequency(); - old_knob = 10000; - } - - // the tuning knob is at neither extremities, tune the signals as usual - else { - if (abs(knob - old_knob) > 4 || abs(clar - clar_old) > 1) { // improved "flutter fix": only change frequency when the current knob position is more than 4 steps away from the previous position - knob = (knob + old_knob) / 2; // tune to the midpoint between current and previous knob reading - old_knob = knob; - frequency = constrain(baseTune + (unsigned long)knob * (unsigned long)u.POT_SPAN / 10UL, u.LOWEST_FREQ, u.HIGHEST_FREQ); - setFrequency(); - delay(10); - } - } - clar_old = clar; - if (!u.vfoActive) // VFO A is active - vfoA = frequency; - else - vfoB = frequency; -} - -void factory_settings() { - - printLine(0, (char *)"loading standard"); - printLine(1, (char *)"settings..."); - - EEPROM.put(0, u); // save all user parameters to EEPROM - delay(1000); -} - -// routine to save both VFO frequencies when they haven't changed for more than 500Hz in the past 30 seconds - -void save_frequency() { - static unsigned long t3; - if (abs(vfoA - u.vfoA) > 500UL || abs(vfoB - u.vfoB) > 500UL) { - if (millis() - t3 > 30000UL) { - u.vfoA = vfoA; - u.vfoB = vfoB; - t3 = millis(); - } - } - else - t3 = millis(); -} - -/* - Routine to calibrate the touch key pads - (measure the time it takes the capacitance to discharge while the touch pads are NOT touched) - Even when the touch pads are NOT touched, there is some internal capacitance - The internal capacitance may vary depending on the length and routing of the wiring - We measure the base capacitance so that we can use it as a baseline reference - (threshold delay when the pads are not touched). -*/ - -void calibrate_touch_pads() { - // disable the internal pullups - pinMode(KEY, INPUT); - pinMode(DAH, INPUT); - - bool triggered; - // first we calibrate the KEY (DIT) touch pad - base_sens_KEY = 0; // base capacity of the KEY (DIT) touch pad - do { - base_sens_KEY++; // increment the delay time until the KEY touch pad is no longer triggered by the base capacitance - triggered = false; - for (int i = 0; i < 100; i++) { - digitalWrite(PULSE, HIGH); // bring the KEY input to a digital HIGH level - delayMicroseconds(50); // wait a short wile to allow voltage on the KEY input to stabalize - digitalWrite(PULSE, LOW); // now bring the KEY input to a digital LOW value - delayMicroseconds(base_sens_KEY); // wait few microseconds - if (digitalRead(KEY)) { // check 100 times if KEY input is still HIGH - triggered = true; // if KEY is still high, then it was triggered by the base capacitance - i = 100; - } - } - } while (triggered && base_sens_KEY != 255); // keep trying until KEY input is no longer triggered - DEBUG_PRINT("DIT touch sensor: "); - DEBUG_PRINT(base_sens_KEY); - DEBUG_PRINTLN(" us"); - - // Next we calibrate the DAH pad - base_sens_DAH = 0; // base capacity of the DAH touch pad - do { - base_sens_DAH++; // increment the delay time until the DAH touch pad is no longer triggered by the base capacitance - triggered = false; - for (int i = 0; i < 100; i++) { - digitalWrite(PULSE, HIGH); // bring the KEY input to a digital HIGH level - delayMicroseconds(50); // wait a short wile to allow voltage on the DAH input to stabalize - digitalWrite(PULSE, LOW); // now bring the DAH input to a digital LOW value - delayMicroseconds(base_sens_DAH); // wait few microseconds - if (digitalRead(DAH)) { // check 100 times if DAH input is still HIGH - triggered = true; // if KEY is still high, then it was triggered by the base capacitance - i = 100; - } - } - } while (triggered && base_sens_DAH != 255); // keep trying until KEY input is no longer triggered - DEBUG_PRINT("DAH touch sensor: "); - DEBUG_PRINT(base_sens_DAH); - DEBUG_PRINTLN(" us"); - DEBUG_PRINT("touch sensor detected: "); - - if (base_sens_KEY == 255 || base_sens_DAH == 255 || base_sens_KEY == 1 || base_sens_DAH == 1) { // if inputs are still triggered even with max delay (255 us) - CapTouch_installed = false; // then the base capacitance is too high (or the mod is not installed) so we can't use the touch keyer - u.cap_sens = 0; // turn capacitive touch keyer OFF - printLine(0, (char *)"touch sensors"); - printLine(1, (char *)"not detected"); - DEBUG_PRINTLN("NO"); - } - else if (u.cap_sens > 0) { - DEBUG_PRINTLN("YES"); - printLine(0, (char *)"touch key calibr"); - strcpy(c, "DIT "); - itoa(base_sens_KEY, b, DEC); - strcat(c, b); - strcat(c, ", DAH "); - itoa(base_sens_DAH, b, DEC); - strcat(c, b); - strcat(c, " us"); - printLine(1, c); - } - else { - printLine(1, (char *)"touch keyer OFF"); - DEBUG_PRINTLN("YES"); - } - - delay(2000); - updateDisplay(); - - //configure the morse keyer inputs - if (u.cap_sens == 0) { // enable the internal pull-ups if touch keyer is disabled - pinMode(KEY, INPUT_PULLUP); - pinMode(DAH, INPUT_PULLUP); - } -} - -void touch_key() { - static unsigned long KEYup = 0; - static unsigned long KEYdown = 0; - static unsigned long DAHup = 0; - static unsigned long DAHdown = 0; - - digitalWrite(PULSE, LOW); // send LOW to the PULSE output - delayMicroseconds(30); - //delayMicroseconds(base_sens_KEY + 25 - u.cap_sens); // wait few microseconds for the KEY capacitance to discharge - if (digitalRead(KEY)) { // test if KEY input is still HIGH - KEYdown = millis(); // KEY touch pad was touched - if (!capaKEY && millis() - KEYup > 1) { - capaKEY = true; - } - } - else { // KEY touch pad was not touched - KEYup = millis(); - if (capaKEY && millis() - KEYdown > 10) - capaKEY = false; - } - - digitalWrite(PULSE, HIGH); // send HIGH to the PULSE output - delayMicroseconds(50); // allow the capacitance to recharge - - digitalWrite(PULSE, LOW); // send LOW to the PULSE output - delayMicroseconds(base_sens_DAH + 25 - u.cap_sens); // wait few microseconds for the DAH capacitance to discharge - - if (digitalRead(DAH)) { // test if DAH input is still HIGH - DAHdown = millis(); // DAH touch pad was touched - if (!capaDAH && millis() - DAHup > 1) { - capaDAH = true; - } - } - else { // DAH touch pad was not touched - DAHup = millis(); - if (capaDAH && millis() - DAHdown > 10) - capaDAH = false; - } - - digitalWrite(PULSE, HIGH); // send HIGH to the PULSE output - delayMicroseconds(50); // allow the capacitance to recharge - -} - -/* - setup is called on boot up - It setups up the modes for various pins as inputs or outputs - initiliaizes the Si5351 and sets various variables to initial state - Just in case the LCD display doesn't work well, the debug log is dumped on the serial monitor - Choose Serial Monitor from Arduino IDE's Tools menu to see the Serial.print messages -*/ - -void setup() { - u.raduino_version = 27; - strcpy (c, "Raduino Diag v3"); - factory_settings(); - lcd.begin(16, 2); - - Serial.begin(9600); - DEBUG_PRINTLN(c); - printLine(0, c); - delay(1000); - - - analogReference(DEFAULT); - DEBUG_PRINTLN("starting up..."); - - //configure the function button to use the internal pull-up - pinMode(FBUTTON, INPUT_PULLUP); - - //configure the SPOT button to use the internal pull-up - pinMode(SPOT, INPUT_PULLUP); - - pinMode(TX_RX, INPUT_PULLUP); - DEBUG_PRINT("TX-RX line installed: "); - if (digitalRead(TX_RX)) { // Test if TX_RX line is installed - pinMode(TX_RX, OUTPUT); - digitalWrite(TX_RX, 0); - TXRX_installed = false; - u.semiQSK = false; - u.QSK_DELAY = 10; - DEBUG_PRINTLN("NO"); - } - else { - pinMode(TX_RX, OUTPUT); - digitalWrite(TX_RX, 0); - DEBUG_PRINTLN("YES"); - printLine(0, (char *)"TX-RX line"); - printLine(1, (char *)"installed"); - delay(1000); - } - - pinMode(CW_CARRIER, OUTPUT); - digitalWrite(CW_CARRIER, 0); - pinMode(CW_TONE, OUTPUT); - digitalWrite(CW_TONE, 0); - pinMode(PULSE, OUTPUT); - digitalWrite(PULSE, 0); - - - - //configure the PTT SENSE to use the internal pull-up - pinMode(PTT_SENSE, INPUT_PULLUP); - delay(100); - // check if PTT sense line is installed - PTTsense_installed = !digitalRead(PTT_SENSE); - pinMode(PTT_SENSE, INPUT); //disable the internal pull-up - - // attach interrupt to the PTT_SENSE input - // when PTT_SENSE goes from LOW to HIGH (so when PTT is keyed), execute the ISRptt routine - attachPCINT(digitalPinToPCINT(PTT_SENSE), ISRptt, RISING); - delay(1000); - - //retrieve user settings from EEPROM - EEPROM.get(0, u); - - DEBUG_PRINT("PTTsense installed: "); - if (PTTsense_installed) { - DEBUG_PRINTLN("YES"); - printLine(0, (char *)"PTT_SENSE mod"); - printLine(1, (char *)"installed"); - delay(1000); - calibrate_touch_pads(); // measure the base capacitance of the touch pads while they're not being touched - } - else - DEBUG_PRINTLN("NO"); -#ifdef DEBUG_BOOT - DEBUG_PRINT("Function button pressed: "); - if (digitalRead(FBUTTON)) - DEBUG_PRINTLN("NO"); - else - DEBUG_PRINTLN("YES"); - DEBUG_PRINT("SPOT button pressed: "); - if (digitalRead(SPOT)) - DEBUG_PRINTLN("NO"); - else - DEBUG_PRINTLN("YES"); - DEBUG_PRINT("analog reading tuning pot: "); - DEBUG_PRINTLN(analogRead(ANALOG_TUNING)); -#endif - - - //initialize the SI5351 - si5351bx_init(); - si5351bx_vcoa = (SI5351BX_XTAL * SI5351BX_MSA) + u.cal * 100L; // apply the calibration correction factor - - vfoA = u.vfoA; - vfoB = u.vfoB; - - frequency = vfoA; - mode = LSB; - - SetSideBand(u.LSBdrive); - - shiftBase(); //align the current knob position with the current frequency - DEBUG_PRINT("display line 1: "); - DEBUG_PRINTLN(c); - - DEBUG_PRINT("VFO: "); - DEBUG_PRINTLN(frequency); - DEBUG_PRINT("BFO: "); - DEBUG_PRINTLN(bfo_freq); - - DEBUG_PRINT("Si5351: "); - DEBUG_PRINTLN(bfo_freq - frequency); - - bleep(u.CW_OFFSET, 60, 3); - bleep(u.CW_OFFSET, 180, 1); - - DEBUG_PRINTLN(); - DEBUG_PRINTLN("boot completed"); - - si5351bx_drive[0] = 0; // lowest BFO drive level for less tuning clicks - si5351bx_setfreq(0, bfo_freq); -} - -void loop() { - touch_key(); - doTuning(); +/** + Raduino_diag v3 for BITX40 - Allard Munters PE1NWL (pe1nwl@gooddx.net) + This source file is under General Public License version 3. + + This sketch tests some basic hardware functions: + tuning pot reading + Function Button/ SPOT button switches + PTT switch + capacitive touch sensors + + Results are shown on the LCD display + additional messages are output to the serial monitor + (in the IDE, select tools => serial monitor) + +*/ + +#define DEBUG_BOOT //uncomment this to print debug messages to the serial monitor +#define DEBUG_RUN //uncomment this to print debug messages to the serial monitor + +// *** USER PARAMETERS *** + +// tuning range parameters +#define MIN_FREQ 7000000UL // absolute minimum tuning frequency in Hz +#define MAX_FREQ 7300000UL // absolute maximum tuning frequency in Hz +#define TUNING_POT_SPAN 50 // tuning pot span in kHz [accepted range 10-500 kHz] +// recommended pot span for a 1-turn pot: 50kHz, for a 10-turn pot: 100 to 200kHz +// recommended pot span when radio is used mainly for CW: 10 to 25 kHz + +// USB/LSB parameters +#define CAL_VALUE 1575 // VFO calibration value (180 ppm) +#define OFFSET_USB 1500 // USB offset in Hz [accepted range -10000Hz to 10000Hz] +#define VFO_DRIVE_LSB 4 // VFO drive level in LSB mode in mA [accepted values 2,4,6,8 mA] +#define VFO_DRIVE_USB 8 // VFO drive level in USB mode in mA [accepted values 2,4,6,8 mA] + +// CW parameters +#define CW_SHIFT 800 // RX shift in CW mode in Hz, equal to sidetone pitch [accepted range 200-1200 Hz] +#define SEMI_QSK true // whether we use semi-QSK (true) or manual PTT (false) +#define CW_TIMEOUT 350 // time delay in ms before radio goes back to receive [accepted range 10-1000 ms] + +// CW keyer parameters +#define CW_KEY_TYPE 0 // type of CW-key (0:straight, 1:paddle, 2:rev. paddle, 3:bug, 4:rev. bug) +#define CW_SPEED 16 // CW keyer speed in words per minute [accepted range 1-50 WPM] +#define AUTOSPACE false // whether or not auto-space is enabled [accepted values: true or false] +#define DIT_DELAY 5 // debounce delay (ms) for DIT contact (affects the DIT paddle sensitivity) +#define DAH_DELAY 15 // debounce delay (ms) for DAH contact (affects the DAH paddle sensitivity) + +// Capacitive touch keyer +#define CAP_SENSITIVITY 22 // capacitive touch keyer sensitivity 0 (OFF) [accepted range 0-25] + +// frequency scanning parameters +#define SCAN_START 7100 // Scan start frequency in kHz [accepted range MIN_FREQ - MAX_FREQ, see above] +#define SCAN_STOP 7150 // Scan stop frequency in kHz [accepted range SCAN_START - MAX_FREQ, see above] +#define SCAN_STEP 1000 // Scan step size in Hz [accepted range 50Hz to 10000Hz] +#define SCAN_STEP_DELAY 500 // Scan step delay in ms [accepted range 0-2000 ms] + +// Function Button +#define CLICK_DELAY 750 // max time (in ms) between function button clicks + +// RX-TX burst prevention +#define TX_DELAY 65 // delay (ms) to prevent spurious burst that is emitted when switching from RX to TX + +// all variables that will be stored in EEPROM are contained in this 'struct': +struct userparameters { + byte raduino_version; // version identifier + byte wpm = CW_SPEED; // CW keyer speed (words per minute) + int USB_OFFSET = OFFSET_USB; // VFO offset in Hz for USB mode + int cal = CAL_VALUE; // VFO calibration value + byte LSBdrive = VFO_DRIVE_LSB; // VFO drive level in LSB mode + byte USBdrive = VFO_DRIVE_USB; // VFO drive level in USB mode + bool semiQSK = SEMI_QSK; // whether semi QSK is ON or OFF + unsigned int POT_SPAN = TUNING_POT_SPAN; // tuning pot span (kHz) + unsigned int CW_OFFSET = CW_SHIFT; // RX shift (Hz) in CW mode, equal to side tone pitch + unsigned long vfoA = 7125000UL; // frequency of VFO A + unsigned long vfoB = 7125000UL; // frequency of VFO B + byte mode_A = 0; // operating mode of VFO A + byte mode_B = 0; // operating mode of VFO B + bool vfoActive = false; // currently active VFO (A = false, B = true) + bool splitOn = false; // whether SPLIT is ON or OFF + unsigned int scan_start_freq = SCAN_START; // scan start frequency (kHz) + unsigned int scan_stop_freq = SCAN_STOP; // scan stop frequency (kHz) + unsigned int scan_step_freq = SCAN_STEP; // scan step size (Hz) + unsigned int scan_step_delay = SCAN_STEP_DELAY; // scan step delay (ms) + unsigned int QSK_DELAY = CW_TIMEOUT; // word [accepted range 10-1000 ms] + byte key_type = CW_KEY_TYPE; // CW key type (0:straight, 1:paddle, 2:rev. paddle, 3:bug, 4:rev. bug) + unsigned long LOWEST_FREQ = MIN_FREQ; // absolute minimum dial frequency (Hz) + unsigned long HIGHEST_FREQ = MAX_FREQ; // absolute maximum dial frequency (Hz) + bool autospace = AUTOSPACE; // whether auto character space is ON or OFF + byte cap_sens = CAP_SENSITIVITY; // sensitivity of the touch keyer sensors +}; + +// we can access each of the variables inside the above struct by "u.variablename" +struct userparameters u; + +#ifdef DEBUG_BOOT +#define DEBUG_PRINT(x) Serial.print (x) +#define DEBUG_PRINTLN(x) Serial.println (x) +#else +#define DEBUG_PRINT(x) +#define DEBUG_PRINTLN(x) +#endif + +#ifdef DEBUG_RUN +#define DEBUG_PRINT2(x) Serial.print (x) +#define DEBUG_PRINTLN2(x) Serial.println (x) +#else +#define DEBUG_PRINT2(x) +#define DEBUG_PRINTLN2(x) +#endif + +/** + Below are the libraries to be included for building the Raduino + The EEPROM library is used to store settings like the frequency memory, calibration data, etc. +*/ + +#include + +/** + 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 + +/** + The PinChangeInterrupt.h library is used for handling interrupts +*/ +#include // https://github.com/NicoHood/PinChangeInterrupt + +/** + 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 requirement 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: +*/ + +#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 + +/** + 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. + A0 A1 A2 A3 GND +5V A6 A7 + BLACK BROWN RED ORANGE YELLOW GREEN BLUE VIOLET (same color coding as used for resistors) + 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 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[17], b[10], printBuff[2][17]; + +/** + 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, GND, +5V, A6, A7 + pin 8 7 6 5 4 3 2 1 (connector P1) + BLACK BROWN RED ORANGE YELLW GREEN BLUE VIOLET + (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: + A0 (digital input) for sensing the PTT. Connect to the output of U3 (LM7805) of the BITX40. + This way the A0 input will see 0V (LOW) when PTT is not pressed, +5V (HIGH) when PTT is pressed. + A1 (digital input) is to connect to a straight key, or to the 'Dit' contact of a paddle keyer. Open (HIGH) during key up, switch to ground (LOW) during key down. + A2 (digital input) can be used for calibration by grounding this line (not required when you have the Function Button at A3) + A3 (digital input) is connected to a push button that can momentarily ground this line. This Function Button will be used to switch between different modes, etc. + A4 (already in use for talking to the SI5351) + A5 (already in use for talking to the SI5351) + A6 (analog input) is not currently used + A7 (analog input) 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 implements the tuning mechanism. +*/ + +#define PTT_SENSE (A0) +#define KEY (A1) + +#define FBUTTON (A3) +#define CLARIFIER (A6) +#define ANALOG_TUNING (A7) + +bool PTTsense_installed; //whether or not the PTT sense line is installed (detected automatically during startup) + +/** + The second set of 16 pins on the bottom connector P3 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 (connector P3) + +12V +12V CLK2 GND GND CLK1 GND GND CLK0 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 : + output D2 - PULSE : is used for the capacitive touch keyer + input D3 - DAH : is connected to the 'Dah' contact of an paddle keyer (switch to ground). + input D4 - SPOT : is connected to a push button that can momentarily ground this line. When the SPOT button is pressed a sidetone will be generated for zero beat tuning. + output D5 - CW_TONE : Side tone output + output D6 - CW_CARRIER line : turns on the carrier for CW + output D7 - TX_RX line : Switches between Transmit and Receive in CW mode +*/ + +#define PULSE (2) +#define DAH (3) +#define SPOT (4) +#define CW_TONE (5) +#define CW_CARRIER (6) +#define TX_RX (7) + +bool TXRX_installed = true; // whether or not the TX_RX mod (PTT bypass transistor) is installed (set automatically at startup) + +/** + Raduino has 4 modes of operation: +*/ + +#define LSB (0) +#define USB (1) +#define CWL (2) +#define CWU (3) + +/** + Raduino needs to keep track of current state of the transceiver. These are a few variables that do it +*/ +unsigned long vfoA; // the frequency (Hz) of VFO A +unsigned long vfoB; // the frequency (Hz) of VFO B +byte mode = LSB; // mode of the currently active VFO +bool inTx = false; // whether or not we are in transmit mode +volatile bool TXstart = false; // this flag will be set by the ISR as soon as the PTT is keyed +int clar, clar_old; + + +// some variable used by the capacitive touch keyer: + +bool CapTouch_installed = true; // whether or not the capacitive touch modification is installed (detected automatically during startup) +byte base_sens_KEY; // base delay (in us) when DIT pad is NOT touched (measured automatically by touch sensor calibration routine) +byte base_sens_DAH; // base delay (in us) when DAH pad is NOT touched (measured automatically by touch sensor calibration routine) +bool capaKEY, capaDAH; + +#define bfo_freq (11998800UL) +unsigned long baseTune = 7100000UL; // frequency (Hz) when tuning pot is at minimum position +int old_knob = 0; +unsigned long frequency; // the 'dial' frequency as shown on the display + + +// ************* 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. + +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 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(); +} + +// *********** End of Jerry's si5315bx routines ********************************************************* + +/** + Display Routine + This display routine prints a line of characters to the upper or lower line of the 16x2 display + linenmbr = 0 is the upper line + linenmbr = 1 is the lower line +*/ + +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(' '); + } + } +} + +/** + Building upon the previous routine, + update Display paints the first line as per current state of the radio +*/ + +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 + 50), b, DEC); // construct the frequency string + if (!u.vfoActive) + strcpy(c, "A "); // display which VFO is active (A or B) + else + strcpy(c, "B "); + + + byte p = strlen(b); // the length of the frequency string (<10 Mhz: 7, >10 MHz: 8) + + strncat(c, &b[0], p - 6); // display the megahertzes + strcat(c, "."); + strncat(c, &b[p - 6], 3); // display the kilohertzes + strcat(c, "."); + + strncat(c, &b[p - 3], 1); // display the frequency at 100 Hz precision + + switch (mode) { // show the operating mode + case LSB: + strcat(c, " LSB"); + break; + case USB: + strcat(c, " USB"); + break; + case CWL: + strcat(c, " CWL"); + break; + case CWU: + strcat(c, " CWU"); + break; + } + + if (inTx) // show the state (TX, SPLIT, or nothing) + strcat(c, " TX"); + else if (u.splitOn) + strcat(c, " SP"); + + c[16] = '\0'; // cut off any excess characters (string length is max 16 postions) + printLine(0, c); // finally print the constructed string to the first line of the display +} + +// routine to generate a bleep sound (FB menu) +void bleep(int pitch, int duration, byte repeat) { + for (byte i = 0; i < repeat; i++) { + tone(CW_TONE, pitch); + delay(duration); + noTone(CW_TONE); + delay(duration); + } +} + +void setFrequency() { + + DEBUG_PRINT("BFO "); + DEBUG_PRINT(bfo_freq); + DEBUG_PRINT(", Si5351: "); + if (mode & 1) { // if we are in UPPER side band mode + si5351bx_setfreq(2, (bfo_freq + frequency - u.USB_OFFSET)); + DEBUG_PRINT(bfo_freq + frequency - u.USB_OFFSET); + DEBUG_PRINT(", VFO "); + DEBUG_PRINT(frequency); + } + else { // if we are in LOWER side band mode + si5351bx_setfreq(2, (bfo_freq - frequency)); + DEBUG_PRINT(bfo_freq - frequency); + DEBUG_PRINT(", VFO "); + DEBUG_PRINT(frequency); + } + DEBUG_PRINT(", tuning pot "); + DEBUG_PRINT(analogRead(ANALOG_TUNING)); + DEBUG_PRINT(", CLAR pot "); + DEBUG_PRINTLN(analogRead(CLARIFIER) - 512); + updateDisplay(); +} + +/** + The checkTX toggles the T/R line. If you would like to make use of RIT, etc, + you must connect pin A0 (black wire) via a 10K resistor to the output of U3 + This is a voltage regulator LM7805 which goes on during TX. We use the +5V output + as a PTT sense line (to tell the Raduino that we are in TX). +*/ + +void SetSideBand(byte drivelevel) { + + set_drive_level(drivelevel); + setFrequency(); + if (!u.vfoActive) // if VFO A is active + u.mode_A = mode; + else // if VFO B is active + u.mode_B = mode; +} + +/* interrupt service routine + When the PTT is keyed (either manually or by the semiQSK function), normal program execution + will be interrupted and the interrupt service routine (ISR) will be executed. After that, normal + program execution will continue from the point it was interrupted. + The only thing this ISR does, is setting the TXstart flag. This flag is frequently polled by the + knob_position routine. + The knob_position routine takes 100 readings from the tuning pot for extra precision. However + this takes more time (about 10ms). Normally this is not a problem, however when we key the PTT + we need to quickly turn off the VFO in order to prevent spurious emission. In that case 10ms is + too much delay. + In order to prevent this delay, the knob-position routine keeps checking the TXstart flag + and when it is set it will terminate immediately. +*/ + +void ISRptt(void) { + TXstart = true; // the TXstart flag will be set as soon as the PTT is keyed +} + +// routine to read the position of the tuning knob at high precision (Allard, PE1NWL) +int knob_position() { + unsigned long knob = 0; + // the knob value normally ranges from 0 through 1023 (10 bit ADC) + // in order to increase the precision by a factor 10, we need 10^2 = 100x oversampling + for (byte i = 0; i < 100; i++) { // it takes about 10 ms to run this loop 100 times + if (TXstart) { // when the PTT was keyed, in order to prevent 10 ms delay: + DEBUG_PRINTLN2("RX => TX"); + TXstart = false; // reset the TXstart flag that was set by the interrupt service routine + return old_knob; // then exit this routine immediately and return the old knob value + } + else + knob = knob + analogRead(ANALOG_TUNING); // take 100 readings from the ADC + } + knob = (knob + 5L) / 10L; // take the average of the 100 readings and multiply the result by 10 + //now the knob value ranges from 0 through 10,230 (10x more precision) + knob = knob * 10000L / 10230L; // scale the knob range down to 0-10,000 + return (int)knob; +} + +/* + Many BITX40's suffer from a strong birdie at 7199 kHz (LSB). + This birdie may be eliminated by using a different VFO drive level in LSB mode. + In USB mode, a high drive level may be needed to compensate for the attenuation of + higher VFO frequencies. + The drive level for each mode can be set in the SETTINGS menu +*/ + +void set_drive_level(byte level) { + si5351bx_drive[2] = level / 2 - 1; + setFrequency(); +} + +/* + routine to align the current knob position with the current frequency + If we switch between VFO's A and B, the frequency will change but the tuning knob + is still in the same position. We need to apply some offset so that the new frequency + corresponds with the current knob position. + This routine reads the current knob position, then it shifts the baseTune value up or down + so that the new frequency matches again with the current knob position. +*/ + +void shiftBase() { + setFrequency(); + unsigned long knob = knob_position(); // get the current tuning knob position + baseTune = frequency - (knob * (unsigned long)u.POT_SPAN / 10UL); +} + +/* + The Tuning mechansim of the Raduino works in a very innovative way. It uses a tuning potentiometer. + The tuning potentiometer that a voltage between 0 and 5 volts at ANALOG_TUNING pin of the control connector. + This is read as a value between 0 and 1023. By 100x oversampling this range is expanded by a factor 10. + Hence, the tuning pot gives you 10,000 steps from one end to the other end of its rotation. Each step is 50 Hz, + thus giving maximum 500 Khz of tuning range. The tuning range is scaled down depending on the POT_SPAN value. + The standard tuning range (for the standard 1-turn pot) is 50 Khz. But it is also possible to use a 10-turn pot + to tune accross the entire 40m band. The desired POT_SPAN can be set via the Function Button in the SETTINGS menu. + When the potentiometer is moved to either end of the range, the frequency starts automatically moving + up or down in 10 Khz increments, so it is still possible to tune beyond the range set by POT_SPAN. +*/ + +void doTuning() { + int knob = analogRead(ANALOG_TUNING); // get the current tuning knob position + if (digitalRead(PTT_SENSE)) { + inTx = true; + printLine(1, (char *)"PTT is ON"); + DEBUG_PRINTLN("PTT is ON"); + updateDisplay(); + noTone(CW_TONE); // stop generating the sidetone + delay(250); + } + else if (!digitalRead(SPOT)) { + inTx = false; + printLine(1, (char *)"SPOT pressed"); + DEBUG_PRINTLN("SPOT pressed"); + updateDisplay(); + noTone(CW_TONE); // stop generating the sidetone + delay(250); + } + else if (!digitalRead(FBUTTON)) { + inTx = false; + printLine(1, (char *)"F-Button pressed"); + DEBUG_PRINTLN("F-Button pressed"); + updateDisplay(); + noTone(CW_TONE); // stop generating the sidetone + delay(250); + } + else if (!CapTouch_installed && !digitalRead(KEY)) { + inTx = false; + printLine(1, (char *)"KEY/DIT is ON"); + DEBUG_PRINTLN("KEY/DIT is ON"); + updateDisplay(); + tone(CW_TONE, 800); // generate sidetone + delay(250); + } + else if (!CapTouch_installed && !digitalRead(DAH)) { + inTx = false; + printLine(1, (char *)"DAH is ON"); + DEBUG_PRINTLN("DAH is ON"); + updateDisplay(); + tone(CW_TONE, 800); // generate sidetone + delay(250); + } + else if (CapTouch_installed && capaKEY) { + inTx = false; + printLine(1, (char *)"DIT sensor"); + DEBUG_PRINTLN("DIT sensor touched"); + updateDisplay(); + tone(CW_TONE, 800); // generate sidetone + delay(250); + } + else if (CapTouch_installed && capaDAH) { + inTx = false; + printLine(1, (char *)"DAH sensor"); + DEBUG_PRINTLN("DAH sensor touched"); + updateDisplay(); + tone(CW_TONE, 800); // generate sidetone + delay(250); + } + else { + inTx = false; + itoa(knob, b, DEC); + strcpy(c, "tuning pot "); + strcat(c, b); + printLine(1, c); + noTone(CW_TONE); // stop generating the sidetone + updateDisplay(); + } + knob = knob * 100000 / 10230; // get the current tuning knob position + clar = analogRead(CLARIFIER) - 512; + + // tuning is disabled during TX (only when PTT sense line is installed) + if (inTx && abs(knob - old_knob) > 20) { + printLine(1, (char *)"dial is locked"); + shiftBase(); + return; + } + else if (inTx) { // no tuning in TX or when dial is locked + return; + } + else if (abs(knob - old_knob) < 20) + save_frequency(); // only save VFO frequencies to EEPROM when tuning pot is not being turned + + knob = knob_position(); // get the precise tuning knob position + // the knob is fully on the low end, do fast tune: move down by 10 Khz and wait for 300 msec + // if the POT_SPAN is very small (less than 25 kHz) then use 1 kHz steps instead + + if (knob == 0) { + if (frequency > u.LOWEST_FREQ) { + if (u.POT_SPAN < 25) + baseTune = baseTune - 1000UL; // fast tune down in 1 kHz steps + else + baseTune = baseTune - 10000UL; // fast tune down in 10 kHz steps + frequency = baseTune + (unsigned long)knob * (unsigned long)u.POT_SPAN / 10UL; + delay(300); + } + if (frequency <= u.LOWEST_FREQ) + baseTune = frequency = u.LOWEST_FREQ; + setFrequency(); + old_knob = 0; + } + + // the knob is full on the high end, do fast tune: move up by 10 Khz and wait for 300 msec + // if the POT_SPAN is very small (less than 25 kHz) then use 1 kHz steps instead + + else if (knob == 10000) { + if (frequency < u.HIGHEST_FREQ) { + if (u.POT_SPAN < 25) + baseTune = baseTune + 1000UL; // fast tune up in 1 kHz steps + else + baseTune = baseTune + 10000UL; // fast tune up in 10 kHz steps + frequency = baseTune + (unsigned long)knob * (unsigned long)u.POT_SPAN / 10UL; + delay(300); + } + if (frequency >= u.HIGHEST_FREQ) { + baseTune = u.HIGHEST_FREQ - (u.POT_SPAN * 1000UL); + frequency = u.HIGHEST_FREQ; + } + setFrequency(); + old_knob = 10000; + } + + // the tuning knob is at neither extremities, tune the signals as usual + else { + if (abs(knob - old_knob) > 4 || abs(clar - clar_old) > 1) { // improved "flutter fix": only change frequency when the current knob position is more than 4 steps away from the previous position + knob = (knob + old_knob) / 2; // tune to the midpoint between current and previous knob reading + old_knob = knob; + frequency = constrain(baseTune + (unsigned long)knob * (unsigned long)u.POT_SPAN / 10UL, u.LOWEST_FREQ, u.HIGHEST_FREQ); + setFrequency(); + delay(10); + } + } + clar_old = clar; + if (!u.vfoActive) // VFO A is active + vfoA = frequency; + else + vfoB = frequency; +} + +void factory_settings() { + + printLine(0, (char *)"loading standard"); + printLine(1, (char *)"settings..."); + + EEPROM.put(0, u); // save all user parameters to EEPROM + delay(1000); +} + +// routine to save both VFO frequencies when they haven't changed for more than 500Hz in the past 30 seconds + +void save_frequency() { + static unsigned long t3; + if (abs(vfoA - u.vfoA) > 500UL || abs(vfoB - u.vfoB) > 500UL) { + if (millis() - t3 > 30000UL) { + u.vfoA = vfoA; + u.vfoB = vfoB; + t3 = millis(); + } + } + else + t3 = millis(); +} + +/* + Routine to calibrate the touch key pads + (measure the time it takes the capacitance to discharge while the touch pads are NOT touched) + Even when the touch pads are NOT touched, there is some internal capacitance + The internal capacitance may vary depending on the length and routing of the wiring + We measure the base capacitance so that we can use it as a baseline reference + (threshold delay when the pads are not touched). +*/ + +void calibrate_touch_pads() { + // disable the internal pullups + pinMode(KEY, INPUT); + pinMode(DAH, INPUT); + + bool triggered; + // first we calibrate the KEY (DIT) touch pad + base_sens_KEY = 0; // base capacity of the KEY (DIT) touch pad + do { + base_sens_KEY++; // increment the delay time until the KEY touch pad is no longer triggered by the base capacitance + triggered = false; + for (int i = 0; i < 100; i++) { + digitalWrite(PULSE, HIGH); // bring the KEY input to a digital HIGH level + delayMicroseconds(50); // wait a short wile to allow voltage on the KEY input to stabalize + digitalWrite(PULSE, LOW); // now bring the KEY input to a digital LOW value + delayMicroseconds(base_sens_KEY); // wait few microseconds + if (digitalRead(KEY)) { // check 100 times if KEY input is still HIGH + triggered = true; // if KEY is still high, then it was triggered by the base capacitance + i = 100; + } + } + } while (triggered && base_sens_KEY != 255); // keep trying until KEY input is no longer triggered + DEBUG_PRINT("DIT touch sensor: "); + DEBUG_PRINT(base_sens_KEY); + DEBUG_PRINTLN(" us"); + + // Next we calibrate the DAH pad + base_sens_DAH = 0; // base capacity of the DAH touch pad + do { + base_sens_DAH++; // increment the delay time until the DAH touch pad is no longer triggered by the base capacitance + triggered = false; + for (int i = 0; i < 100; i++) { + digitalWrite(PULSE, HIGH); // bring the KEY input to a digital HIGH level + delayMicroseconds(50); // wait a short wile to allow voltage on the DAH input to stabalize + digitalWrite(PULSE, LOW); // now bring the DAH input to a digital LOW value + delayMicroseconds(base_sens_DAH); // wait few microseconds + if (digitalRead(DAH)) { // check 100 times if DAH input is still HIGH + triggered = true; // if KEY is still high, then it was triggered by the base capacitance + i = 100; + } + } + } while (triggered && base_sens_DAH != 255); // keep trying until KEY input is no longer triggered + DEBUG_PRINT("DAH touch sensor: "); + DEBUG_PRINT(base_sens_DAH); + DEBUG_PRINTLN(" us"); + DEBUG_PRINT("touch sensor detected: "); + + if (base_sens_KEY == 255 || base_sens_DAH == 255 || base_sens_KEY == 1 || base_sens_DAH == 1) { // if inputs are still triggered even with max delay (255 us) + CapTouch_installed = false; // then the base capacitance is too high (or the mod is not installed) so we can't use the touch keyer + u.cap_sens = 0; // turn capacitive touch keyer OFF + printLine(0, (char *)"touch sensors"); + printLine(1, (char *)"not detected"); + DEBUG_PRINTLN("NO"); + } + else if (u.cap_sens > 0) { + DEBUG_PRINTLN("YES"); + printLine(0, (char *)"touch key calibr"); + strcpy(c, "DIT "); + itoa(base_sens_KEY, b, DEC); + strcat(c, b); + strcat(c, ", DAH "); + itoa(base_sens_DAH, b, DEC); + strcat(c, b); + strcat(c, " us"); + printLine(1, c); + } + else { + printLine(1, (char *)"touch keyer OFF"); + DEBUG_PRINTLN("YES"); + } + + delay(2000); + updateDisplay(); + + //configure the morse keyer inputs + if (u.cap_sens == 0) { // enable the internal pull-ups if touch keyer is disabled + pinMode(KEY, INPUT_PULLUP); + pinMode(DAH, INPUT_PULLUP); + } +} + +void touch_key() { + static unsigned long KEYup = 0; + static unsigned long KEYdown = 0; + static unsigned long DAHup = 0; + static unsigned long DAHdown = 0; + + digitalWrite(PULSE, LOW); // send LOW to the PULSE output + delayMicroseconds(30); + //delayMicroseconds(base_sens_KEY + 25 - u.cap_sens); // wait few microseconds for the KEY capacitance to discharge + if (digitalRead(KEY)) { // test if KEY input is still HIGH + KEYdown = millis(); // KEY touch pad was touched + if (!capaKEY && millis() - KEYup > 1) { + capaKEY = true; + } + } + else { // KEY touch pad was not touched + KEYup = millis(); + if (capaKEY && millis() - KEYdown > 10) + capaKEY = false; + } + + digitalWrite(PULSE, HIGH); // send HIGH to the PULSE output + delayMicroseconds(50); // allow the capacitance to recharge + + digitalWrite(PULSE, LOW); // send LOW to the PULSE output + delayMicroseconds(base_sens_DAH + 25 - u.cap_sens); // wait few microseconds for the DAH capacitance to discharge + + if (digitalRead(DAH)) { // test if DAH input is still HIGH + DAHdown = millis(); // DAH touch pad was touched + if (!capaDAH && millis() - DAHup > 1) { + capaDAH = true; + } + } + else { // DAH touch pad was not touched + DAHup = millis(); + if (capaDAH && millis() - DAHdown > 10) + capaDAH = false; + } + + digitalWrite(PULSE, HIGH); // send HIGH to the PULSE output + delayMicroseconds(50); // allow the capacitance to recharge + +} + +/* + setup is called on boot up + It setups up the modes for various pins as inputs or outputs + initiliaizes the Si5351 and sets various variables to initial state + Just in case the LCD display doesn't work well, the debug log is dumped on the serial monitor + Choose Serial Monitor from Arduino IDE's Tools menu to see the Serial.print messages +*/ + +void setup() { + u.raduino_version = 27; + strcpy (c, "Raduino Diag v3"); + factory_settings(); + lcd.begin(16, 2); + + Serial.begin(9600); + DEBUG_PRINTLN(c); + printLine(0, c); + delay(1000); + + + analogReference(DEFAULT); + DEBUG_PRINTLN("starting up..."); + + //configure the function button to use the internal pull-up + pinMode(FBUTTON, INPUT_PULLUP); + + //configure the SPOT button to use the internal pull-up + pinMode(SPOT, INPUT_PULLUP); + + pinMode(TX_RX, INPUT_PULLUP); + DEBUG_PRINT("TX-RX line installed: "); + if (digitalRead(TX_RX)) { // Test if TX_RX line is installed + pinMode(TX_RX, OUTPUT); + digitalWrite(TX_RX, 0); + TXRX_installed = false; + u.semiQSK = false; + u.QSK_DELAY = 10; + DEBUG_PRINTLN("NO"); + } + else { + pinMode(TX_RX, OUTPUT); + digitalWrite(TX_RX, 0); + DEBUG_PRINTLN("YES"); + printLine(0, (char *)"TX-RX line"); + printLine(1, (char *)"installed"); + delay(1000); + } + + pinMode(CW_CARRIER, OUTPUT); + digitalWrite(CW_CARRIER, 0); + pinMode(CW_TONE, OUTPUT); + digitalWrite(CW_TONE, 0); + pinMode(PULSE, OUTPUT); + digitalWrite(PULSE, 0); + + + + //configure the PTT SENSE to use the internal pull-up + pinMode(PTT_SENSE, INPUT_PULLUP); + delay(100); + // check if PTT sense line is installed + PTTsense_installed = !digitalRead(PTT_SENSE); + pinMode(PTT_SENSE, INPUT); //disable the internal pull-up + + // attach interrupt to the PTT_SENSE input + // when PTT_SENSE goes from LOW to HIGH (so when PTT is keyed), execute the ISRptt routine + attachPCINT(digitalPinToPCINT(PTT_SENSE), ISRptt, RISING); + delay(1000); + + //retrieve user settings from EEPROM + EEPROM.get(0, u); + + DEBUG_PRINT("PTTsense installed: "); + if (PTTsense_installed) { + DEBUG_PRINTLN("YES"); + printLine(0, (char *)"PTT_SENSE mod"); + printLine(1, (char *)"installed"); + delay(1000); + calibrate_touch_pads(); // measure the base capacitance of the touch pads while they're not being touched + } + else + DEBUG_PRINTLN("NO"); +#ifdef DEBUG_BOOT + DEBUG_PRINT("Function button pressed: "); + if (digitalRead(FBUTTON)) + DEBUG_PRINTLN("NO"); + else + DEBUG_PRINTLN("YES"); + DEBUG_PRINT("SPOT button pressed: "); + if (digitalRead(SPOT)) + DEBUG_PRINTLN("NO"); + else + DEBUG_PRINTLN("YES"); + DEBUG_PRINT("analog reading tuning pot: "); + DEBUG_PRINTLN(analogRead(ANALOG_TUNING)); +#endif + + + //initialize the SI5351 + si5351bx_init(); + si5351bx_vcoa = (SI5351BX_XTAL * SI5351BX_MSA) + u.cal * 100L; // apply the calibration correction factor + + vfoA = u.vfoA; + vfoB = u.vfoB; + + frequency = vfoA; + mode = LSB; + + SetSideBand(u.LSBdrive); + + shiftBase(); //align the current knob position with the current frequency + DEBUG_PRINT("display line 1: "); + DEBUG_PRINTLN(c); + + DEBUG_PRINT("VFO: "); + DEBUG_PRINTLN(frequency); + DEBUG_PRINT("BFO: "); + DEBUG_PRINTLN(bfo_freq); + + DEBUG_PRINT("Si5351: "); + DEBUG_PRINTLN(bfo_freq - frequency); + + bleep(u.CW_OFFSET, 60, 3); + bleep(u.CW_OFFSET, 180, 1); + + DEBUG_PRINTLN(); + DEBUG_PRINTLN("boot completed"); + + si5351bx_drive[0] = 0; // lowest BFO drive level for less tuning clicks + si5351bx_setfreq(0, bfo_freq); +} + +void loop() { + touch_key(); + doTuning(); }