// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
// Copyright all additions 2022-2023 Rob Colclough GW8RDI, use of the additions and changes is permitted for all private, non-commecial use at user´s risk. No responsibility is accepted for any losses that may occur through the use of this modified code.
// THIS CODE SUPERCEEDS VERSIONS 1.02X AND 1.03. 1.03 HAS UPDATES FOR OLED CHPSETS BUT DOES NOT HAVE FUNCTIONAL OR DSP CHANGES COMPARED TO 1.02w.
// GW8RDI IMPORTANT NOTES: *** DO NOT RUSH - READ THE NOTES BELOW SEVERAL TIMES!
// *** ISP DATA CORRUPTION WARNING1!! ALWAYS REMOVE C24 (C27?) (or as marked) it's a 10nF on the ISP HEADER'S MOSI line (PA ctrl out) (PB3) of the ISP header,
// and disconnect the internal mic by plugging in a disconnected Jack plug. FAILING TO DO THIS CAN CORRUPT YOUR MCU CHIP!!
// *** CHECK WITH ME FIRST PLEASE ***
// PLEASE DO NOT PUBLISH WHAT YOU THINK ARE FAULTS WITH THIS RELEASE *** BEFORE CHECKING WITH ME, GW8RDI ***.
// 99% OF REPORTED PROBLEMS ARE JUST CONFIGURATION RELATED, NOT BUGS OR LIMITATIONS.
// SO IF YOU USE THIS SOFTWARE, YOU AGREE NOT TO PUBLISH UNTIL YOU´VE CONTACTED ME, GW8RDI
// THIS AVOIDS CONFUSIONS.
// Supports up to 9 bands (using 8 filters, 15 and 17M share). To increase change N_BANDS, and add frequencies to array "band[N_BANDS] ="
// SSB TX quality tests, see below MORE_MIC_GAIN, QUAD
/* WELCOME TO THE OPEN SOURCE USDX PROJECT 2022 AND ONWARDS!
#define VERSION "4.00c" // Fixed format "9.99z" : Additions and changes Copyright 2022-2023 GW8RDI - You can use and distribute if you maintain the copyright message, commercial use is prohibited.
// 2022/03/04 - Added delay to show serial number at start - G8RDI mod
// Added band change direction based on last freq step directions. See "case BE | DC:" - GW8RDI mod
// Set PB3/PB5 to output in init to drive LCD backlight
// 2022/03/05 - Release 1.02wA2 2022/03/20 : Added Cat 8.6 ad QUAD enable 8.7 menu items
// 2022/03/06 - With MORE_MIC_GAIN enabled & QUAD disabled, SSB TX voice is sounding much better!
// 2022/04/24 - Release 1.02wA3: Fixed band dir. bug
// 2022/05/08 - Added KEEP_BAND_DATA to maintain last freq and mode set on each band.
// 2022/05/09 - Release 1.02wA4 : Maintains last freq and mode set for each band, up to 9 bands set. Added error code display.
// 2022/05/11 - Changed menu to cycle end-start, start-end
// 2022/07/19 - Added full CW mode update on band change, minor fixes
// 2022/07/19 - Release 1.02wA5 : Maintains last freq and mode set for each band, up to 9 bands set. Added error code display.
// 2022/07/28 - Release 1.02wA6 : Minor changes. Reduced CW message duplication to release more memory.
// 2022/08/16 - Release 1.02wA7 : Directional band-change went in the wrong direction if SWAP_ROTARY was not enabled. This fix solves that issue.
// 2022/08/19 - Release 1.02wA8 : Directional band-change saved last band-mode to Flash, and not mode of band changed to, causing incorrect mode restore if shutdown without band change. Minor other changes, DEBUG modes changes callsign.
// 2022/08/22 - Release 1.02wA8a : Red Corners Unit: CW messages updated, Band menu fix as was showing 6m in the list. Added WHITE_CORNERS to config list.
// 2022/08/22 - Release 2.00a : Jumped version to avoid confusion with prev. releases. Added SWR, 115200 baud CAT, experimental changes to AM and FM, and minor tidy up
// 2022/10/12 - Release 2.00b : NR stays as last used.
// 2022/11/06 - Release 2.00c : #define KEYER CW keyer for Iambic -> NOTE: Auto CW msg sending aborts if not installed, and 700Hz filter selection gives reduced gain.
// : Added new post mag IQ filter, added BlackBrick config.
// 2023/02/09 - Release 2.00d : Added CAT freq. error handling, in RIT mode the TX freq. is now displayed, plus FIR noise filter, see #define NR_FIR below. NR 0-2 is 1.02x method, 3-8 is now FIR DSP filter method to be used with Full b/w and Att2 = 2
// 2023/02/09 - Release 2.00e : Ammended code to support +/- 99 KHz RIT receiver offset, and added CAT command to set the RIT offset.
// 2023/03/14 - Release 2.00f : Ammended code with new "TT" CAT command to set a TX offset frequency; see CAT_XO_CMD. Minor improvements for setting mode display, etc.
// 2023/03/14 - Update to 2.00f : Ammended code as missing #ifdef KEEP_BAND_DATA on line 5922
// 2023/04/01 - Release 4.00a : Changed to version 4.00 as DL2MAN jumped his version up to 2.00! CAT now enables directly from the menu without needing a reboot to activate! CAT MDx; now refreshes LCD fully after CW mode.
// Re-coded KEEP_BAND_DATA switch statements which freed 328 bytes!!
// 2023/04/11 - Release 4.00b : Fixed CAT mode change to allow it to select AM and FM, AM important to correctly track IQ flip. Also commented-out redundant BE | DC for mode change, as now used for directional band change.
// : Added new post mag IQ filter, added BlackBrick config.
// : todo see "// xyzzy Test with i_d"
// NOTE update #define VERSION "????" above!
// See "BACKLOG:" FOR PENDING WORK CARRIED FORWARD
// *** Use of this modified software is at the users risk *** PLEASE READ THE INSTRUCTIONS AVAILABLE IN THE FB GROUP "uSDX uSDR Radios" or uSDX Group IO online.
// Notes: To have CW tone in the menu, enable #define FILTER_700HZ. You´ll need to find 28 bytes depending on your config, i.e. disabling #define DIAG
// Configuration switches; remove/add a double-slash at line-start to enable/disable a feature; to save space disable e.g. CAT, DIAG, KEYER
// *** BEFORE ALL, READ THIS BLOCK !!!!!!!!
/* THIS CONFIG: / To change, ADD or DELETE the "//" in front of #define lines below
//#define RED_CORNERS 1 // Backlight control PortD is PD3 0x08, or PD5 0x20 for Red Corners rig. Disable for Red and White buttons and most black brick uSDX.
//#define TRUSDX 1 // Small USDX clone in 3D printed case marked "DL2MAN & PE1NNZ". CHECK WITH GW8RDI BEFORE USING THIS FOR UPDATES AND CONFIG DETAILS! SWR protection via PA sensing resistor can be added if needed.
// NOTE: DL2MAN claims (as of 17 April 2023) that his license blocks users from installing other software (Microsoft vs Linux et al), but it is understood that this violates consumer rights laws in the USA, UK and European Union.
// *** NOTE ***: If none of the above are enabled, configuration may match other units, but if tuning direction is reversed, backlight or frequency wrong, adjust as needed.
// Put your callsigne below and remove the "///" in front to activate.
///#define MY_CALLSIGN "G8RDI" // <----- Add your callsign here or enable line below, replacing G8RDI! If you don´t want the LCD to show your callsign, enable the line "uSDR+" below.
///#define MY_CALLSIGN_PADDED "G8RDI " // <----- Also add your callsign here BUT keep the 2 spaces at the end!
/// Disable below line if using your own callsign by adding // in front.
#define MY_CALLSIGN_PADDED "uSDR+ " // Ensure two spaces at end of heading and that it is under 7 characters (including the 2 spaces), or this program may not work correctly.
#define CALLSIGN_LENGTH 5 // Change length to match your callsign but remember the LCD isn't very wide!
// *** MEMORY LIMITATION OF ATMEGA328 *** This means you may have to mix and match functions option defines. CAT requires considerable memory, so use only if needed.
// If your dial goes the wrong way, change SWAP_ROTARY
#if defined(RED_CORNERS) || defined(BLACK_BRICK)
// SWAP_ROTARY is isually required for Red Corners unless Rotary type changed, like mine!
#ifdef MY_RED_CORNERS
#define REVERSE_BAND_CHANGE 1 //If your freq. change is correct, but band jump goes backwards, define REVERSE_BAND_CHANGE
#else
#define SWAP_ROTARY 1 // Swap rotary direction (enable for WB2CBA-uSDX) NOTE: To enable SWAP without RED_CORNERS enabled, comment out the lines above and below with // character, i.e. //#ifdef RED_CORNERS and //#endif
//#define REVERSE_BAND_CHANGE 1 //If your freq. change is correct, but band jump goes backwards, define REVERSE_BAND_CHANGE
#endif
// :( No space for SWR with both CW Msgs and CAT
#define SWR_METER 1 // Supports SWR meter with bridge on A6/A7 (LQPF ATMEGA328P) by Alain, K1FM, see: https://groups.io/g/ucx/message/6262 and https://groups.io/g/ucx/message/6361
//#define OLED_SH1106 1 // OLED display (SH1106 1.3" inch display), connect SDA (PD2), SCL (PD3), NOTE that this display is pretty slow
#define SWR_METER 1 // Supports SWR meter with bridge on A6/A7 (LQPF ATMEGA328P) by Alain, K1FM, see: https://groups.io/g/ucx/message/6262 and https://groups.io/g/ucx/message/6361
#define SWR_METER 1 // Supports SWR meter with bridge on A6/A7 (LQPF ATMEGA328P) by Alain, K1FM, see: https://groups.io/g/ucx/message/6262 and https://groups.io/g/ucx/message/6361
#endif
//#define FAST_AGC 1 // Adds fast AGC option (good for CW) Slow mode not recommended. Remove for CAT if memory errors.
#define CAT 1 // CAT-interface - OTHER OPTIONS, SUCH AS CW_MESSAGES and KEEP_BAND_DATA MAY TO BE DISABLED TO MAKE SPACE FOR CAT
//#define CAT_EXT 1 // Extended CAT support: remote button and screen control commands over CAT
//#define CAT_STREAMING 1 // Streams audio and IQ, only 8KHz b/w, & needs faster 115200 baud RS232
#define CAT_FAST 1 // Uses faster 115200 baud (can be changed to 57600), else 38400, 8, 1, N.
// If short of memory on compile and not using Spectrum display, disable CAT_XO_CMD:- Like this:-> //#define CAT_XO_CMD
#ifdef CAT
#define CAT_TX_CMD 1 // GW8RDI mod - added - Send TX and RX status CAT cmds as PTT is pressed and released
// Lines below NEEDED FOR CW, removed to make space for CAT
//#define KEYER 1 // CW keyer for Iambic - NOTE: Auto CW msg sending aborts if not installed as changes dit timing. Can be removed to save memory for CAT
//#define KEY_CLICK 1 // G8RDI mod - may be removed to free memory for CAT - NEEDED FOR CW msg sending else CW TX sounds mushy & CW msg sending stops after one peep! // Reduce key clicks by envelope shaping
//#define FILTER_700HZ 1 // G8RDI mod - Moved here - Enabled shows in Menu
// CW Messages: Note: If CAT is enabled, CW messages may cause a program memory overflow. KEEP_BAND_DATA can be disabled to release memory for CW at cost of losing band frequency memory.
// NOTE: DO NOT CHANGE THE CW_MESSAGE LINES BELOW AS THEY ARE INCORPORATED OR NOT BASED ON CW_MESSAGE and CW_MESSAGE_EXT above.
// Note: !!!Do not exceed CW_MESSAGE_LENGTH when ammending messages!!!
#ifdef CW_MESSAGE_EXT
#define CW_MESSAGE_LENGTH 48 //48/32
#else
#define CW_MESSAGE_LENGTH 48 //48/32/16
#endif
// CHANGE THE CW MESSAGE TEXT BELOW AS YOU LIKE BUT LESS THAN CW_MESSAGE_LENGTH+1 CHARS! THE ## MEANS +, USED TO CONCATANTE STRINGS.
// DO NOT COMMENT OUT LINES BELOW, THESE TEXTS ARE NOT INCLUDED WHEN CW_MESSAGES/EXT ARE DISABLED
#define CW_STD_MSG "CQ CQ DE " MY_CALLSIGN " +" // 16 chars, change in code of size changes.
#define CW_MSG1 "CQ CQ DE " MY_CALLSIGN " +"
#define CW_MSG2 "CQ CQ DE " MY_PREFIX MY_CALLSIGN " +"
#define CW_MSG3 MY_PREFIX MY_CALLSIGN
#define CW_MSG4 "GE TKS 5NN 5NN NAME IS " MY_NAME " HW?"
#define CW_MSG5 "FB RPTR TX 5W ANT EFW 73 CUAGN"
#define CW_MSG6 "73 GL TU EE"
// Examples:
//"CQ" MY_CALLSIGN " +", "CQ CQ DE " MY_CALLSIGN + MY_CALLSIGN " +", "GE TKS 5NN 5NN NAME IS ROB ROB HW?", "FB RPTR TX 5W 5W ANT ENDFED 73 CUAGN", "73 TU E E", "G8RDI"
//"CQ CQ DE " MY_CALLSIGN " " MY_CALLSIGN " +", "GE TKS 5NN 5NN NAME IS " MY_NAME "" MY_NAME " HW?", "FB RPTR TX 5W 5W ANT ENDFED 73 CUAGN", "73 TU E E", MY_CALLSIGN
//#define CW_MSG2 '"CQ CQ DE " MY_CALLSIGN " +"' // Remove/add your Area prefit
//#define CW_MSG3 MY_CALLSIGN
//#define NR_FIR 1 // GW8RDI mod. Usually this won´t fit with CAT, but removing other options, such as CW messages, etc., can make enough space
///G8RDI comment out FAST_AGC & DIAG below to save mem space for CAT
//#define DIAG 1 // Hardware diagnostics on startup (use to debug problems)
#ifndef CAT_XO_CMD // Undefined CW_VOLUME to make space for CAT_XO_CMD
#define CW_VOLUME 1 // Enable separate CW tone volume in the menu
#endif
#define CW_DECODER 1 // CW decoder
//#define CW_INTERMEDIATE 1 // CW decoder shows intermediate characters (only available for LCD and F_MCU at 20M), sequences like: EIS[HV] EIUF EAW[JP] EARL TMO TMG[ZQ] TND[BX] TNK[YC], may be good to learn CW; a full list of possible sequences: EISH5 EISV3 EIUF EIUU2 EAWJ1 EAWP EARL TMOO0 TMOO9 TMOO8 TMGZ7 TMGQ TNDB6 TNDX TNKY TNKC
//#define CW_FREQS_QRP 1 // Defaults to CW QRP frequencies when changing bands
//#define CW_FREQS_FISTS 1 // Defaults to CW FISTS frequencies when changing bands
// NOTE: Make sure you have the correct xtal frequency enabled. This is the xtal near the SI5351/SI3253 chip, not the one near the Atmega MCU.
//#define F_XTAL 27005000 // 27MHz SI5351 crystal
//#define F_XTAL 25004000 // 25MHz SI5351 crystal (enable for WB2CBA-uSDX, SI5351 break-out board or uSDXDuO)
#define F_XTAL 27000000 // !!!! SET YOUR EXACT XTAL FREQ OR 27000000 !!!! 27MHz usually on black bricks, Red Buttons (27001400 is my calibration offset!!!) and White buttons versions
// GW8RDI NOTE: Enable to have battery voltage shown on the LCD.
// GW8RDI WARNING!!! The problem with the original code is that it switches the ADC VREF up to 5V to read the bat, V, this causes some noise on the IQ sampling which enters the audio,
// and can interfere with the reading of buttons which are sensed through an ADC.
// A better solution is to simply use a 2 resistor voltage divider and not change VREF, calculating the bar. voltage based on the res. divider ration. Alternatively, only show voltage in a menu function. todo - change code.
//#define LPF_SWITCHING_DL2MAN_USDX_REV3 1 // Enable 8-band filter bank switching: latching relays wired to a TCA/PCA9555 GPIO extender on the PC4/PC5 I2C bus; relays are using IO0.0 as common (ground), IO1.0..7 used by the individual latches K0-7 switching respectively LPFs for 10m, 15m, 17m, 20m, 30m, 40m, 60m, 80m
//#define LPF_SWITCHING_DL2MAN_USDX_REV3_NOLATCH 1 // Enable 8-band filter bank switching: non-latching relays wired to a TCA/PCA9555 GPIO extender on the PC4/PC5 I2C bus; relays are using IO0.0 as common (ground), IO1.0..7 used by the individual latches K0-7 switching respectively LPFs for 10m, 15m, 17m, 20m, 30m, 40m, 60m, 80m. Enable this if you are using 8-band non-latching version for the relays, the radio will draw extra 15mA current but will work ity any relay (Tnx OH2UDS/TA7W Baris)
//#define LPF_SWITCHING_DL2MAN_USDX_REV2 1 // Enable 5-band filter bank switching: latching relays wired to a TCA/PCA9555 GPIO extender on the PC4/PC5 I2C bus; relays are using IO0.1 as common (ground), IO0.3, IO0.5, IO0.7, IO1.1, IO1.3 used by the individual latches K1-5 switching respectively LPFs for 20m, 30m, 40m, 60m, 80m
//#define LPF_SWITCHING_DL2MAN_USDX_REV2_BETA 1 // Enable 5-band filter bank switching: latching relays wired to a PCA9539PW GPIO extender on the PC4/PC5 I2C bus; relays are using IO0.1 as common (ground), IO0.3, IO0.5, IO0.7, IO1.1, IO1.3 used by the individual latches K1-5 switching respectively LPFs for 20m, 30m, 40m, 60m, 80m
//#define LPF_SWITCHING_DL2MAN_USDX_REV1 1 // Enable 3-band filter bank switching: latching relays wired to a PCA9536D GPIO extender on the PC4/PC5 I2C bus; relays are using IO0 as common (ground), IO1-IO3 used by the individual latches K1-3 switching respectively LPFs for 20m, 40m, 80m
//#define LPF_SWITCHING_WB2CBA_USDX_OCTOBAND 1 // Enable 8-band filter bank switching: non-latching relays wired to a MCP23008 GPIO extender on the PC4/PC5 I2C bus; relays are using GND as common (ground), GP0..7 used by the individual latches K1-8 switching respectively LPFs for 80m, 60m, 40m, 30m, 20m, 17m, 15m, 10m
//#define LPF_SWITCHING_PE1DDA_USDXDUO 14 // Enable 2-band filter bank switching: non-latching relay wired to pin PD5 (pin 11); specify as value the frequency in MHz for which (and above) the relay should be altered (e.g. put 14 to enable the relay at 14MHz and above to use the 20m LPF).
#define SI5351_ADDR 0x60 // SI5351A I2C address: 0x60 for SI5351A-B-GT, Si5351A-B04771-GT, MS5351M; 0x62 for SI5351A-B-04486-GT; 0x6F for SI5351A-B02075-GT; see here for other variants: https://www.silabs.com/TimingUtility/timing-download-document.aspx?OPN=Si5351A-B02075-GT&OPNRevision=0&FileType=PublicAddendum
//#define F_MCU 16000000 // 16MHz ATMEGA328P crystal (enable for unmodified Arduino Uno/Nano boards with 16MHz crystal). You may change this value to any other crystal frequency (up to 28MHz may work)
// Advanced configuration switches
//#define CONDENSED 1 // Display in 4 line mode (for OLED and LCD2004 modules)
#define TX_ENABLE 1 // Disable this for RX only (no transmit), e.g. to support uSDX for kids idea: https://groups.io/g/ucx/topic/81030243#6276
#define SEMI_QSK 1 // Just after keying the transmitter, keeps the RX muted for a short amount of time in the anticipation for continued keying
#define RIT_ENABLE 1 // Receive-In-Transit alternates the receiving frequency with an user-defined offset to compensate for any necessary tuning needed on receive
#define VOX_ENABLE 1 // Voice-On-Xmit which is switching the transceiver into transmit as soon audio is detected (above noise gate level)
//#define MOX_ENABLE 1 // Monitor-On-Xmit which is audio monitoring on speaker during transmit
//#define ONEBUTTON 1 // Use single (encoder) button to control full the rig; optionally use L/R buttons to completely replace rotory encoder function
//#define DEBUG 1 // for development purposes only (adds debugging features such as CPU, sample-rate measurement, additional parameters)
//#define TESTBENCH 1 // Tests RX chain by injection of sine wave, measurements results are sent over serial
// G8RDI removed for memory due to CAT
//#define TX_DELAY 1 // Enables a delay in the actual transmission to allow relay-switching to be completed before the power is applied (see also NTX, PTX definitions below for GPIO that can switch relay/PA)
//#define NTX 11 // Enables LOW on TX, used as PTT out to enable external PAs (a value of 11 means PB3 is used)
#define PTX 11 // Enables HIGH on TX, used as PTT out to enable external PAs (a value of 11 means PB3 is used)
//#define CLOCK 1 // Enables clock
// G8RDI removed to save memory #define:
//#define F_XTAL 20000000 // Enable this for uSDXDuO, 20MHz SI5351 crystal
//#define TX_CLK0_CLK1 1 // Enable this for uSDXDuO, i.e. when PA is driven by CLK0, CLK1 (not CLK2); NTX pin may be used for enabling the TX path (this is like RX pin, except that RX may also be used as attenuator)
//#define F_CLK2 12000000 // Enables a fixed CLK2 clock output of choice (only applicable when TX_CLK0_CLK1 is enabled), e.g. for up-converter or to clock UART USB device
#define _SERIAL 1 // Coexistence support for serial port and LCD on the same pins
#endif
#ifdef LPF_SWITCHING_DL2MAN_USDX_REV3_NOLATCH
#define LPF_SWITCHING_DL2MAN_USDX_REV3 1
#endif
#ifdef TX_CLK0_CLK1
#ifdef F_CLK2
#define TX1RX0 0b11111000
#define TX1RX1 0b11111000
#define TX0RX1 0b11111000
#define TX0RX0 0b11111011
#else //!F_CLK2
#define TX1RX0 0b11111100
#define TX1RX1 0b11111100
#define TX0RX1 0b11111100
#define TX0RX0 0b11111111
#endif //F_CLK2
#else //!TX_CLK0_CLK1
#define TX1RX0 0b11111011
#define TX1RX1 0b11111000
#define TX0RX1 0b11111100
#define TX0RX0 0b11111111
#endif //TX_CLK0_CLK1
#if defined(F_CLK2) && !defined(TX_CLK0_CLK1)
#error "TX_CLK0_CLK1 must be enabled in order to use F_CLK2."
#endif
#ifndef TX_ENABLE
#undef KEYER
#undef TX_DELAY
#undef SEMI_QSK
#undef RIT_ENABLE
#undef VOX_ENABLE
#undef MOX_ENABLE
#endif //!TX_ENABLE
#ifdef SWR_METER
floatFWD;
floatSWR;
floatref_V=5*1.15;
staticuint32_tstimer;
#define PIN_FWD A6
#define PIN_REF A7
#endif
/*
// UCX installation: On blank chip, use (standard Arduino Uno) fuse settings (E:FD, H:DE, L:FF), and use customized Optiboot bootloader for 20MHz clock, then upload via serial interface (with RX, TX and DTR lines connected to pin 1, 2, 3 respectively)
// UCX pin defintions
+#defineSDA3//PD3 (pin 5)
+#defineSCL4//PD4 (pin 6)
+#defineROT_A6//PD6 (pin 12)
+#defineROT_B7//PD7 (pin 13)
+#defineRX8//PB0 (pin 14)
+#defineSIDETONE9//PB1 (pin 15)
+#defineKEY_OUT10//PB2 (pin 16)
+#defineNTX11//PB3 (pin 17)
+#defineDAH12//PB4 (pin 18)
+#defineDIT13//PB5 (pin 19)
+#defineAUDIO114//PC0/A0 (pin 23)
+#defineAUDIO215//PC1/A1 (pin 24)
+#defineDVM16//PC2/A2 (pin 25)
+#defineBUTTONS17//PC3/A3 (pin 26)
// In addition set:
#define OLED 1
#define ONEBUTTON 1
#define ONEBUTTON_INV 1
#undef DEBUG
adjustI2CandI2C_ports,
ssb_cap=1;dsp_cap=2;
#define _DELAY() for(uint8_t i = 0; i != 5; i++) asm("nop");
#define F_XTAL 20004000
#define F_CPU F_XTAL
*/
//FUSES = { .low = 0xFF, .high = 0xD6, .extended = 0xFD }; // Fuse settings should be set at programming (Arduino IDE > Tools > Burn bootloader)
#if(ARDUINO < 10810)
#if (ARDUINO != 10607) // G8RDI mod - IDE 2.0.1 has its version set to 10607, an IDE bug since fixed.
# error "Unsupported Arduino IDE version, use Arduino IDE 1.8.10 or later from https://www.arduino.cc/en/software"
#define F_CPU 20007000 // Actual crystal frequency of 20MHz XTAL1, note that this declaration is just informative and does not correct the timing in Arduino functions like delay(); hence a 1.25 factor needs to be added for correction.
//#define ONEBUTTON_INV 1 // Encoder button goes from PC3 to GND (instead PC3 to 5V, with 10k pull down)
#ifdef ONEBUTTON_INV
uint8_tinv=1;
#else
uint8_tinv=0;
#endif
//#ifdef KEYER
// Iambic Morse Code Keyer Sketch, Contribution by Uli, DL2DBG. Copyright (c) 2009 Steven T. Elliott Source: http://openqrp.org/?p=343, Trimmed by Bill Bishop - wrb[at]wrbishop.com. This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details: Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
// keyerControl bit definitions
#define DIT_L 0x01 // Dit latch
#define DAH_L 0x02 // Dah latch
#define DIT_PROC 0x04 // Dit is being processed
#define PDLSWAP 0x08 // 0 for normal, 1 for swap
#define IAMBICB 0x10 // 0 for Iambic A, 1 for Iambic B
#define IAMBICA 0x00 // 0 for Iambic A, 1 for Iambic B
staticunsignedlongditTime;// No. milliseconds per dit
staticuint8_tkeyerControl;
staticuint8_tkeyerState;
staticuint8_tkeyer_mode=2;//-> SINGLE
staticuint8_tkeyer_swap=0;//-> DI/DAH
staticuint32_tktimer;
staticintKey_state;
intdebounce;
enumKSTYPE{IDLE,CHK_DIT,CHK_DAH,KEYED_PREP,KEYED,INTER_ELEMENT};// State machine states
voidupdate_PaddleLatch()// Latch dit and/or dah press, called by keyer routine
{
if(_digitalRead(DIT)==LOW){
keyerControl|=keyer_swap?DAH_L:DIT_L;
}
if(_digitalRead(DAH)==LOW){
keyerControl|=keyer_swap?DIT_L:DAH_L;
}
}
voidloadWPM(intwpm)// Calculate new time constants based on wpm value
{
#if(F_MCU != 20000000)
ditTime=(1200ULL*F_MCU/16000000)/wpm;//ditTime = 1200/wpm; compensated for F_CPU clock (running in a 16MHz Arduino environment)
#else
ditTime=(1200*5/4)/wpm;//ditTime = 1200/wpm; compensated for 20MHz clock (running in a 16MHz Arduino environment)
#endif
}
//#endif //KEYER
staticuint8_tpractice=false;// Practice mode
staticint8_tprev_mode;
volatileuint8_tcat_active=0;// Run-time set when serial data being processed to keep shared LCD pins controlled
volatileuint32_trxend_event=0;
volatileuint8_tvox=0;
#include<avr/sleep.h>
#include<avr/wdt.h>
//#define _I2C_DIRECT_IO 1 // Enables communications that is not using the standard I/O pull-down approach with pull-up resistors, instead I/O is directly driven with 0V/5V
classI2C_{// Secundairy I2C class used by I2C LCD/OLED, uses alternate pins: PD2 (SDA) and PD3 (SCL)
public:
#if(F_MCU > 20900000)
#ifdef OLED_SH1106
#define _DELAY() for(uint8_t i = 0; i != 9; i++) asm("nop");
#else
#ifdef OLED_SSD1306
#define _DELAY() for(uint8_t i = 0; i != 6; i++) asm("nop");
#else // other (I2C_LCD)
#define _DELAY() for(uint8_t i = 0; i != 7; i++) asm("nop");
#endif
#endif
#else // slow F_MCU
#ifdef OLED_SH1106
#define _DELAY() for(uint8_t i = 0; i != 8; i++) asm("nop");
#else
#ifdef OLED_SSD1306
#define _DELAY() for(uint8_t i = 0; i != 4; i++) asm("nop"); // 4=731kb/s
#else // other (I2C_LCD)
#define _DELAY() for(uint8_t i = 0; i != 5; i++) asm("nop");
#endif
#endif
#endif // F_MCU
#define _I2C_SDA (1<<2) // PD2
#define _I2C_SCL (1<<3) // PD3
#ifdef _I2C_DIRECT_IO
#define _I2C_INIT() _I2C_SDA_HI(); _I2C_SCL_HI(); DDRD |= (_I2C_SDA | _I2C_SCL); // direct I/O (no need for pull-ups)
public:// LCD1602 display in 4-bit mode, RS is pull-up and kept low when idle to prevent potential display RFI via RS line
#define _dn 0 // PD0 to PD3 connect to D4 to D7 on the display
#define _en 4 // PD4 - MUST have pull-up resistor
#define _rs 4 // PC4 - MUST have pull-up resistor
#define RS_PULLUP 1 // Use pullup on RS line, ensures compliancy to the absolute maximum ratings for the si5351 sda input that is shared with rs pin of lcd
delayMicroseconds(4);// enable pulse must be >450ns
Wire.write(b);// write command EN LO
delayMicroseconds(60);// commands need > 37us to settle
Wire.write(b);// must write for some unknown reason
}
voidcmd(uint8_tb){
Wire.beginTransmission(PCF_ADDR);
nib(b>>4,false);nib(b,false);
Wire.endTransmission();
}
size_twrite(uint8_tb){
Wire.beginTransmission(PCF_ADDR);
nib((b>>4),true);nib((b),true);
Wire.endTransmission();
}
#else //!LCD_I2C
// Since LCD is using PD0(RXD), PD1(TXD) pins in the data-path, some co-existence feature is required when using the serial port.
// The following functions are temporarily disabling the serial port when LCD writes happen, and make sure that serial transmission is ended.
// To prevent that LCD writes are received by the serial receiver, PC2 is made HIGH during writes to pull-up TXD via a diode.
// The RXD, TXD lines are connected to the host via 1k resistors, a 1N4148 is placed between PC2 (anode) and the TXD resistor.
// NOISE LEAK INTO RX!!! G8RDI NOTE
// There are two drawbacks when continuous LCD writes happen:
// 1. noise is leaking via the AREF pull-ups into the receiver
// 2. serial data cannot be received
voidpre(){
#ifdef _SERIAL
if(!vox)if(cat_active){Serial.flush();for(;millis()<rxend_event;)wdt_reset();PORTC|=1<<2;DDRC|=1<<2;}UCSR0B&=~((1<<RXEN0)|(1<<TXEN0));// Complete serial TX and RX; mask PD1 LCD data-exchange by pulling-up TXD via PC2 HIGH; enable PD0/PD1, disable serial port
#endif
noInterrupts();// ***!!!*** do not allow LCD tranfer to be interrupted, to prevent backlight to lighten-up
UCSR0B|=(1<<RXEN0)|(1<<TXEN0);if(!vox)if(cat_active){PORTC&=~(1<<2);}// Enable serial port, disable PD0, PD1; PC2 LOW to prevent CAT TX disruption via MIC input
#endif
interrupts();
}
#ifdef RS_HIGH_ON_IDLE
voidcmd(uint8_tb){
pre();
uint8_tnibh=LCD_PREP_NIBBLE(b>>4);// Prepare high nibble data and enable high
PORTD=nibh;// Send high nibble data and enable high
uint8_tnibl=LCD_PREP_NIBBLE(b&0xf);// Prepare low nibble data and enable high
LCD_RS_LO();
LCD_EN_LO();
PORTD=nibl;// Send low nibble data and enable high
asm("nop");asm("nop");// Keep RS low, but complete enable cycle (should be 500ns)
LCD_EN_LO();
LCD_RS_HI();
post();
delayMicroseconds(60);// Execution time (37+4)*1.25 us
}
size_twrite(uint8_tb){// Write data: send nibbles while RS high
pre();
uint8_tnibh=LCD_PREP_NIBBLE(b>>4);// Prepare high nibble data and enable high
PORTD=nibh;// Send high nibble data and enable high
uint8_tnibl=LCD_PREP_NIBBLE(b&0xf);// Prepare low nibble data and enable high
LCD_RS_HI();
LCD_EN_LO();
PORTD=nibl;// Send low nibble data and enable high
asm("nop");asm("nop");// Keep RS high, but complete enable cycle (should be 500ns)
LCD_EN_LO();
post();
delayMicroseconds(60);// Execution time (37+4)*1.25 us
return1;
}
#else //!RS_HIGH_ON_IDLE
voidnib(uint8_tb){// Send four bit nibble to display
pre();
PORTD=LCD_PREP_NIBBLE(b);// Send data and enable high
//asm("nop"); // Enable high pulse width must be at least 230ns high, data-setup time 80ns
delayMicroseconds(4);
LCD_EN_LO();
post();
//delayMicroseconds(52); // Execution time
delayMicroseconds(60);// Execution time
}
voidcmd(uint8_tb){nib(b>>4);nib(b&0xf);}// Write command: send nibbles while RS low
size_twrite(uint8_tb){// Write data: send nibbles while RS high
pre();
//LCD_EN_HI(); // Complete Enable cycle must be at least 500ns (so start early)
uint8_tnibh=LCD_PREP_NIBBLE(b>>4);// Prepare high nibble data and enable high
PORTD=nibh;// Send high nibble data and enable high
uint8_tnibl=LCD_PREP_NIBBLE(b&0xf);// Prepare low nibble data and enable high
//asm("nop"); // Enable high pulse width must be at least 230ns high, data-setup time 80ns; ATMEGA clock-cycle is 50ns (so at least 5 cycles)
LCD_RS_HI();
LCD_EN_LO();
PORTD=nibl;// Send low nibble data and enable high
LCD_RS_LO();
//asm("nop"); asm("nop"); // Complete Enable cycle must be at least 500ns
//PORTD = nibl; // Send low nibble data and enable high
//asm("nop"); // Enable high pulse width must be at least 230ns high, data-setup time 80ns; ATMEGA clock-cycle is 50ns (so at least 5 cycles)
LCD_RS_HI();
LCD_EN_LO();
LCD_RS_LO();
post();
delayMicroseconds(60);// Execution time (37+4)*1.25 us
return1;
}
#endif // RS_HIGH_ON_IDLE
#endif //!LCD_I2C
#ifdef CONDENSED
voidsetCursor(uint8_tx,uint8_ty){cmd(0x80|(x+(uint8_t[]){0x00,0x40,0x00+20,0x40+20}[y]));}// ONLY for LCD2004 display
public:// QCXLiquidCrystal extends LiquidCrystal library for pull-up driven LCD_RS, as done on QCX. LCD_RS needs to be set to LOW in advance of calling any operation.
PCMSK2|=(1<<PCINT22)|(1<<PCINT23);// interrupt-enable for ROT_A, ROT_B pin changes; see https://github.com/EnviroDIY/Arduino-SDI-12/wiki/2b.-Overview-of-Interrupts
PCMSK2|=(1<<PCINT22)|(1<<PCINT23);// interrupt-enable for ROT_A, ROT_B pin changes; see https://github.com/EnviroDIY/Arduino-SDI-12/wiki/2b.-Overview-of-Interrupts
ISR(PCINT2_vect){// Interrupt on rotary encoder turn
enc.event();
}*/
// I2C communication starts with a START condition, multiple single byte-transfers (MSB first) followed by an ACK/NACK and stops with a STOP condition;
// during data-transfer SDA may only change when SCL is LOW, during a START/STOP condition SCL is HIGH and SDA goes DOWN for a START and UP for a STOP.
// https://www.ti.com/lit/an/slva704/slva704.pdf
classI2C{
public:
#if(F_MCU > 20900000)
#define I2C_DELAY 6
#else
#define I2C_DELAY 4 // Determines I2C Speed (2=939kb/s (too fast!!); 3=822kb/s; 4=731kb/s; 5=658kb/s; 6=598kb/s). Increase this value when you get I2C tx errors (E05); decrease this value when you get a CPU overload (E01). An increment eats ~3.5% CPU load; minimum value is 3 on my QCX, resulting in 84.5% CPU load
#endif
#define I2C_DDR DDRC // Pins for the I2C bit banging
#define I2C_PIN PINC
#define I2C_PORT PORTC
#define I2C_SDA (1 << 4) // PC4
#define I2C_SCL (1 << 5) // PC5
#define DELAY(n) for(uint8_t i = 0; i != n; i++) asm("nop");
I2C_SDA_LO();// ensure SDA is LO so STOP-condition can be initiated by pulling SCL HI (in case of ACK it SDA was already LO, but for a delayed ACK or NACK it is not!)
I2C_SCL_HI();
I2C_SDA_HI();
I2C_DDR&=~(I2C_SDA|I2C_SCL);// prepare for a start: pull-up both SDA, SCL
#ifndef RS_HIGH_ON_IDLE
suspend();
#endif
}
#define SendBit(data, mask) \
if(data&mask){ \
I2C_SDA_HI(); \
}else{ \
I2C_SDA_LO(); \
} \
I2C_SCL_HI(); \
I2C_SCL_LO();
/*#define SendByte(data) \
SendBit(data,1<<7) \
SendBit(data,1<<6) \
SendBit(data,1<<5) \
SendBit(data,1<<4) \
SendBit(data,1<<3) \
SendBit(data,1<<2) \
SendBit(data,1<<1) \
SendBit(data,1<<0) \
I2C_SDA_HI();//recvACK \
DELAY(I2C_DELAY); \
I2C_SCL_HI(); \
I2C_SCL_LO();*/
inlinevoidSendByte(uint8_tdata){
SendBit(data,1<<7);
SendBit(data,1<<6);
SendBit(data,1<<5);
SendBit(data,1<<4);
SendBit(data,1<<3);
SendBit(data,1<<2);
SendBit(data,1<<1);
SendBit(data,1<<0);
I2C_SDA_HI();// recv ACK
DELAY(I2C_DELAY);
I2C_SCL_HI();
I2C_SCL_LO();
}
inlineuint8_tRecvBit(uint8_tmask){
I2C_SCL_HI();
uint16_ti=60000;
for(;!(I2C_SCL_GET())&&i;i--);// wait util slave release SCL to HIGH (meaning data valid), or timeout at 3ms
uint16_tmsp2=msb128;// = msb128 % _MSC; assuming MSC is covering exact uint16_t so the mod operation can dissapear (and the upper BB2 byte) // = msb128 - msb128/_MSC * _MSC;
//pll_regs[0] = BB1(msc); // 3 regs are constant
//pll_regs[1] = BB0(msc);
//pll_regs[2] = BB2(msp1);
//pll_regs[3] = BB1(msp1);
pll_regs[4]=BB0(msp1);
pll_regs[5]=((_MSC&0xF0000)>>(16-4))/*|BB2(msp2)*/;// top nibble MUST be same as top nibble of _MSC ! assuming that BB2(msp2) is always 0 -> so reg is constant
msa=div_nom/div_denom;// integer part: msa must be in range 15..90 for PLL, 8+1/1048575..900 for MS
if(msa==4)_int=1;// To satisfy the MSx_INT=1 requirement of AN619, section 4.1.3 which basically says that for MS divider a value of 4 and integer mode must be used
msb=(_int)?0:(((uint64_t)(div_nom%div_denom)*_MSC)/div_denom);// fractional part
SendRegister(n+16,((pll)*0x20)|0x0C|3|(0x40*_int));// MSx CLKn: 0x0C=PLLA,0x2C=PLLB local msynth; 3=8mA; 0x40=MSx_INT; 0x80=CLKx_PDN
SendRegister(n+165,(!_int)*phase*msa/90);// when using: make sure to configure MS in fractional-mode, perform reset afterwards
}
}
voidphase(int8_tn,uint32_tdiv_nom,uint32_tdiv_denom,uint16_tphase){SendRegister(n+165,phase*(div_nom/div_denom)/90);}// when using: make sure to configure MS in fractional-mode!, perform reset afterwards
voidfreq(int32_tfout,uint16_ti,uint16_tq){// Set a CLK0,1,2 to fout Hz with phase i, q (on PLLA)
uint8_trdiv=0;// CLK pin sees fout/(2^rdiv)
if(fout>300000000){i/=3;q/=3;fout/=3;}// for higher freqs, use 3rd harmonic
if(fout<500000){rdiv=7;fout*=128;}// Divide by 128 for fout 4..500kHz
uint16_td;if(fout<30000000)d=(16*fxtal)/fout;elsed=(32*fxtal)/fout;// Integer part .. maybe 44?
if(fout<3500000)d=(7*fxtal)/fout;// PLL at 189MHz to cover 160m (freq>1.48MHz) when using 27MHz crystal
if(fout>140000000)d=4;// for f=140..300MHz; AN619; 4.1.3, this implies integer mode
if(d%2)d++;// even numbers preferred for divider (AN619 p.4 and p.6)
if((d*(fout-5000)/fxtal)!=(d*(fout+5000)/fxtal))d+=2;// Test if multiplier remains same for freq deviation +/- 5kHz, if not use different divider to make same
uint32_tfvcoa=d*fout;// Variable PLLA VCO frequency at integer multiple of fout at around 27MHz*16 = 432MHz
voidfreqb(uint32_tfout){// Set a CLK2 to fout Hz (on PLLB)
uint16_td=(16*fxtal)/fout;
if(d%2)d++;// even numbers preferred for divider (AN619 p.4 and p.6)
uint32_tfvcoa=d*fout;// Variable PLLA VCO frequency at integer multiple of fout at around 27MHz*16 = 432MHz
ms(MSNB,fvcoa,fxtal);
ms(MS2,fvcoa,fout,PLLB,0,0,0);
}
//*/
/*
voidfreq(uint32_tfout,uint16_ti,uint16_tq){// Set a CLK0,1 to fout Hz with phase i, q
uint16_tmsa;uint32_tmsb,msc,msp1,msp2,msp3;
uint8_trdiv=0;// CLK pin sees fout/(2^rdiv)
if(fout>300000000){i/=3;q/=3;fout/=3;}// for higher freqs, use 3rd harmonic
if(fout<500000){rdiv=7;fout*=128;}// Divide by 128 for fout 4..500kHz
uint16_td=(16*fxtal)/fout;// Integer part
//if(fout > 7000000) d = (33 * fxtal) / fout;
if(fout<3500000)d=(7*fxtal)/fout;// PLL at 189MHz to cover 160m (freq>1.48MHz) when using 27MHz crystal
if((d*(fout-5000)/fxtal)!=(d*(fout+5000)/fxtal))d++;// Test if multiplier remains same for freq deviation +/- 5kHz, if not use different divider to make same
if(d%2)d++;// even numbers preferred for divider (AN619 p.4 and p.6)
booldivby4=0;if(fout>140000000){d=4;divby4=1;}// for f=140..300MHz; AN619; 4.1.3
uint32_tfvcoa=d*fout;// Variable PLLA VCO frequency at integer multiple of fout at around 27MHz*16 = 432MHz
msa=fvcoa/fxtal;// Integer part of vco/fxtal. msa must be in range 15..90
msb=((uint64_t)(fvcoa%fxtal)*_MSC)/fxtal;// fractional part
#define SI_I2C_ADDR 0x60 // SI5351A I2C address: 0x60 for SI5351A-B-GT; 0x62 for SI5351A-B-04486-GT; 0x6F for SI5351A-B02075-GT; see here for other variants: https://www.silabs.com/TimingUtility/timing-download-document.aspx?OPN=Si5351A-B02075-GT&OPNRevision=0&FileType=PublicAddendum
#define SI_CLK_OE 3 // Register definitions
#define SI_CLK0_CONTROL 16
#define SI_CLK1_CONTROL 17
#define SI_CLK2_CONTROL 18
#define SI_SYNTH_PLL_A 26
#define SI_SYNTH_PLL_B 34
#define SI_SYNTH_MS_0 42
#define SI_SYNTH_MS_1 50
#define SI_SYNTH_MS_2 58
#define SI_CLK0_PHOFF 165
#define SI_CLK1_PHOFF 166
#define SI_CLK2_PHOFF 167
#define SI_PLL_RESET 177
#define SI_MS_INT 0b01000000 // Clock control
#define SI_CLK_SRC_PLL_A 0b00000000
#define SI_CLK_SRC_PLL_B 0b00100000
#define SI_CLK_SRC_MS 0b00001100
#define SI_CLK_IDRV_8MA 0b00000011
#define SI_CLK_INV 0b00010000
volatileuint32_tfxtal=27004300;//myqcx1:27003980 myqcx2:27004900 Actual crystal frequency of 27MHz XTAL2 for CL = 10pF (default), calibrate your QCX 27MHz crystal frequency here
#define SI_PLL_FREQ (16*fxtal) //900000000, with 432MHz(=16*27M) PLL freq, usable range is 3.46..100MHz
volatileuint8_tprev_divider;
volatileint32_traw_freq;
volatileuint8_tdivider;// note: because of int8 only freq > 3.6MHz can be covered for R_DIV=1
volatileuint8_tmult;
volatileuint8_tpll_regs[8];
volatileint32_tiqmsa;
volatileint32_tpll_freq;// temporary
SI5351(){
init();
iqmsa=0;
}
uint8_tRecvRegister(uint8_treg)
{
// Data write to set the register address
start();
SendByte(SI_I2C_ADDR<<1);
SendByte(reg);
stop();
// Data read to retrieve the data from the set address
start();
SendByte((SI_I2C_ADDR<<1)|1);
uint8_tdata=RecvByte(true);
stop();
returndata;
}
voidSendRegister(uint8_treg,uint8_tdata)
{
start();
SendByte(SI_I2C_ADDR<<1);
SendByte(reg);
SendByte(data);
stop();
}
// Set up MultiSynth for register reg=MSNA, MNSB, MS0-5 with fractional divider, num and denom and R divider (for MSn, not for MSNA, MSNB)
// divider is 15..90 for MSNA, MSNB, divider is 8..900 (and in addition 4,6 for integer mode) for MS[0-5]
// num is 0..1,048,575 (0xFFFFF)
// denom is 0..1,048,575 (0xFFFFF)
// num = 0, denom = 1 forces an integer value for the divider
//uint8_t r_div = (freq > (SI_PLL_FREQ/256/1)) ? 1 : (freq > (SI_PLL_FREQ/256/32)) ? 32 : 128; // helps divider to be in range
uint8_tr_div=(freq<500000)?128:1;
freq*=r_div;// take r_div into account, now freq is in the range 1MHz to 150MHz
raw_freq=freq;// cache frequency generated by PLL and MS stages (excluding R divider stage); used by freq_calc_fast()
divider=SI_PLL_FREQ/freq;// Calculate the division ratio. 900,000,000 is the maximum internal PLL freq (official range 600..900MHz but can be pushed to 300MHz..~1200Mhz)
if(divider%2)divider--;// divider in range 8.. 900 (including 4,6 for integer mode), even numbers preferred. Note that uint8 datatype is used, so 254 is upper limit
if((divider*(freq-5000)/fxtal)!=(divider*(freq+5000)/fxtal))divider-=2;// Test if multiplier remains same for freq deviation +/- 5kHz, if not use different divider to make same
pll_freq=divider*freq;// Calculate the pll_freq: the divider * desired output freq
uint32_tnum,denom;
mult=div(pll_freq,fxtal,&num,&denom);// Determine the mult to get to the required pll_freq (in the range 15..90)
// Set up specified PLL with mult, num and denom: mult is 15..90, num is 0..1,048,575 (0xFFFFF), denom is 0..1,048,575 (0xFFFFF)
// Set up PLL A and PLL B with the calculated multiplication ratio
voidset_latch(uint8_tio,uint8_tcommon_io,boollatch=true){// reset all latches and set latch k to corresponding GPIO, all relays share a common (ground) GPIO
#define LATCH_TIME 30 // set/reset time latch relay
if(latch){
ioext.write((1U<<io)|0x0000);delay(LATCH_TIME);ioext.write(0x0000);// set latch wired to io port
}
else{
if(io==0xff){ioext.init();for(intio=0;io!=16;io++)set_latch(io,common_io,latch);}// reset all latches
else{ioext.write((~(1U<<io))|(1U<<common_io));delay(LATCH_TIME);ioext.write(0x0000);}// reset latch wired to io port
}
}
staticuint8_tprev_lpf_io=0xff;// inits and resets all latches
inlinevoidset_lpf(uint8_tf){
#ifdef LPF_SWITCHING_DL2MAN_USDX_REV3
uint8_tlpf_io=(f>26)?IO1_3:(f>20)?IO1_4:(f>17)?IO1_2:(f>12)?IO1_5:(f>8)?IO1_1:(f>5)?IO1_6:(f>4)?IO1_0:/*(f <= 4)*/IO1_7;// cut-off freq in MHz to IO port of LPF relay
#ifndef LPF_SWITCHING_DL2MAN_USDX_REV3_NOLATCH
if(prev_lpf_io!=lpf_io){set_latch(prev_lpf_io,IO0_0,false);set_latch(lpf_io,IO0_0);prev_lpf_io=lpf_io;};// set relay (latched)
#else
if(prev_lpf_io!=lpf_io){ioext.write(1U<<lpf_io);prev_lpf_io=lpf_io;};// set relay (non-latched)
volatileuint16_tparam_a=0;// registers for debugging, testing and experimental purposes
volatileint16_tparam_b=0;
volatileint16_tparam_c=0;
#endif
enumdsp_cap_t{ANALOG,DSP,SDR};
#ifdef QCX
uint8_tdsp_cap=0;
uint8_tssb_cap=0;
#else
// force SSB and SDR capability
constuint8_tssb_cap=1;
constuint8_tdsp_cap=SDR;
#endif
enummode_t{LSB,USB,CW,FM,AM};
volatileuint8_tmode=USB;
volatileuint16_tnumSamples=0;
volatileuint8_ttx=0;
volatileuint8_tfilt=0;
inlinevoid_vox(booltrigger)
{
if(trigger){
tx=(tx)?254:255;// hangtime = 255 / 4402 = 58ms (the time that TX at least stays on when not triggered again). tx == 255 when triggered first, 254 follows for subsequent triggers, until tx is off.
}
else{
if(tx)tx--;
}
}
#define F_SAMP_TX 4800 //4810 //4805 // 4402 // (Design) ADC sample-rate; is best a multiple of _UA and fits exactly in OCR2A = ((F_CPU / 64) / F_SAMP_TX) - 1 , should not exceed CPU utilization
#if(F_MCU != 20000000)
constint16_t_F_SAMP_TX=(F_MCU*4800LL/20000000);// Actual ADC sample-rate; used for phase calculations
#else
#define _F_SAMP_TX F_SAMP_TX
#endif
#define _UA 600 //=(_FSAMP_TX)/8 //(_F_SAMP_TX) //360 // unit angle; integer representation of one full circle turn or 2pi radials or 360 degrees, should be a integer divider of F_SAMP_TX and maximized to have higest precision
#define MAX_DP ((filt == 0) ? _UA : (filt == 3) ? _UA/4 : _UA/2) //(_UA/2) // the occupied SSB bandwidth can be further reduced by restricting the maximum phase change (set MAX_DP to _UA/2).
#define CARRIER_COMPLETELY_OFF_ON_LOW 1 // disable oscillator on low amplitudes, to prevent potential unwanted biasing/leakage through PA circuit
#define MULTI_ADC 1 // multiple ADC conversions for more sensitive (+12dB) microphone input
#define QUAD 1 // invert TX signal for phase changes > 180
#define MORE_MIC_GAIN 1 // 1.02w adds more microphone gain, improving overall SSB quality (when speaking further away from microphone)
#ifdef MORE_MIC_GAIN
volatileuint8_tvox_thresh=(1<<2);
#else
volatileuint8_tvox_thresh=(1<<1);//(1 << 2);
#endif
volatileuint8_tdrive=2;// hmm.. drive>2 impacts cpu load..why?
staticuint8_tcat_enabled=false;// G8RDI mod - added
staticuint8_tquad_enabled=false;// G8RDI mod - added run time enabling
staticuint8_terror_code=0;// G8RDI mod - added LCD error code
volatileuint8_tquad=0;
inlineint16_tssb(int16_tin)
{
staticint16_tdc,z1;
int16_ti,q;
uint8_tj;
staticint16_tv[16];
for(j=0;j!=15;j++)v[j]=v[j+1];
#ifdef MORE_MIC_GAIN
//#define DIG_MODE // optimization for digital modes: for super flat TX spectrum, (only down < 100Hz to cut-off DC components)
#ifdef DIG_MODE
int16_tac=in;
dc=(ac+(7)*dc)/(7+1);// hpf: slow average
v[15]=(ac-dc)/2;// hpf (dc decoupling) (-6dB gain to compensate for DC-noise)
#else
int16_tac=in*2;// 6dB gain (justified since lpf/hpf is losing -3dB)
ac=ac+z1;// lpf
z1=(in-(2)*z1)/(2+1);// lpf: notch at Fs/2 (alias rejecting)
dc=(ac+(2)*dc)/(2+1);// hpf: slow average
v[15]=(ac-dc);// hpf (dc decoupling)
#endif //DIG_MODE
i=v[7]*2;// 6dB gain for i, q (to prevent quanitization issues in hilbert transformer and phase calculation, corrected for magnitude calc)
q=((v[0]-v[14])*2+(v[2]-v[12])*8+(v[4]-v[10])*21+(v[6]-v[8])*16)/64+(v[6]-v[8]);// Hilbert transform, 40dB side-band rejection in 400..1900Hz (@4kSPS) when used in image-rejection scenario; (Hilbert transform require 5 additional bits)
uint16_t_amp=magn(i/2,q/2);// -6dB gain (correction)
v[15]=(ac+z1);// / 2; // low-pass filter with notch at Fs/2
z1=ac;
i=v[7];
q=((v[0]-v[14])*2+(v[2]-v[12])*8+(v[4]-v[10])*21+(v[6]-v[8])*15)/128+(v[6]-v[8])/2;// Hilbert transform, 40dB side-band rejection in 400..1900Hz (@4kSPS) when used in image-rejection scenario; (Hilbert transform require 5 additional bits)
uint16_t_amp=magn(i,q);
#endif // MORE_MIC_GAIN
#ifdef CARRIER_COMPLETELY_OFF_ON_LOW
_vox(_amp>vox_thresh);
#else
if(vox)_vox(_amp>vox_thresh);
#endif
//_amp = (_amp > vox_thresh) ? _amp : 0; // vox_thresh = 4 is a good setting
//if(!(_amp > vox_thresh)) return 0;
_amp=_amp<<(drive);
_amp=((_amp>255)||(drive==8))?255:_amp;// clip or when drive=8 use max output
amp=(tx)?lut[_amp]:0;
staticint16_tprev_phase;
int16_tphase=arctan3(q,i);
int16_tdp=phase-prev_phase;// phase difference and restriction
//dp = (amp) ? dp : 0; // dp = 0 when amp = 0
prev_phase=phase;
if(dp<0)
dp=dp+_UA;// make negative phase shifts positive: prevents negative frequencies and will reduce spurs on other sideband
#ifdef QUAD // G8RDI: This worsens TX SSB voice quality, more Dalex sounding.
if(dp>=(_UA/2))
{
if(quad_enabled)// G8RDI mod - added
{
dp=dp-_UA/2;
quad=!quad;
}
}
#endif
#ifdef MAX_DP
if(dp>MAX_DP){// dp should be less than half unit-angle in order to keep frequencies below F_SAMP_TX/2
prev_phase=phase-(dp-MAX_DP);// substract restdp
dp=MAX_DP;
}
#endif
if(mode==USB)
returndp*(_F_SAMP_TX/_UA);// calculate frequency-difference based on phase-difference
else
returndp*(-_F_SAMP_TX/_UA);
}
#define MIC_ATTEN 0 // 0*6dB attenuation (note that the LSB bits are quite noisy)
volatileint8_tmox=0;
volatileint8_tvolume=12;
// This is the ADC ISR, issued with sample-rate via timer1 compb interrupt.
// It performs in real-time the ADC sampling, calculation of SSB phase-differences, calculation of SI5351 frequency registers and send the registers to SI5351 over I2C.
staticint16_t_adc;
voiddsp_tx()
{// jitter dependent things first
#ifdef MULTI_ADC // SSB with multiple ADC conversions:
int16_tadc;// current ADC sample 10-bits analog input, NOTE: first ADCL, then ADCH
adc=ADC;
ADCSRA|=(1<<ADSC);
//OCR1BL = amp; // submit amplitude to PWM register (actually this is done in advance (about 140us) of phase-change, so that phase-delays in key-shaping circuit filter can settle)
si5351.SendPLLRegisterBulk();// submit frequency registers to SI5351 over 731kbit/s I2C (transfer takes 64/731 = 88us, then PLL-loopfilter probably needs 50us to stabalize)
#ifdef QUAD
if(quad_enabled)// G8RDI mod - added
{
#ifdef TX_CLK0_CLK1
si5351.SendRegister(16,(quad)?0x1f:0x0f);// Invert/non-invert CLK0 in case of a huge phase-change
si5351.SendRegister(17,(quad)?0x1f:0x0f);// Invert/non-invert CLK1 in case of a huge phase-change
#else
si5351.SendRegister(18,(quad)?0x1f:0x0f);// Invert/non-invert CLK2 in case of a huge phase-change
#endif
}
#endif //QUAD
OCR1BL=amp;// submit amplitude to PWM register (takes about 1/32125 = 31us+/-31us to propagate) -> amplitude-phase-alignment error is about 30-50us
adc+=ADC;
ADCSRA|=(1<<ADSC);// causes RFI on QCX-SSB units (not on units with direct biasing); ENABLE this line when using direct biasing!!
int16_tdf=ssb(_adc>>MIC_ATTEN);// convert analog input into phase-shifts (carrier out by periodic frequency shifts)
adc+=ADC;
ADCSRA|=(1<<ADSC);
si5351.freq_calc_fast(df);// calculate SI5351 registers based on frequency shift and carrier frequency
adc+=ADC;
ADCSRA|=(1<<ADSC);
//_adc = (adc/4 - 512);
#define AF_BIAS 32
_adc=(adc/4-(512-AF_BIAS));// now make sure that we keep a postive bias offset (to prevent the phase swapping 180 degrees and potentially causing negative feedback (RFI)
#else // SSB with single ADC conversion:
ADCSRA|=(1<<ADSC);// start next ADC conversion (trigger ADC interrupt if ADIE flag is set)
//OCR1BL = amp; // submit amplitude to PWM register (actually this is done in advance (about 140us) of phase-change, so that phase-delays in key-shaping circuit filter can settle)
si5351.SendPLLRegisterBulk();// submit frequency registers to SI5351 over 731kbit/s I2C (transfer takes 64/731 = 88us, then PLL-loopfilter probably needs 50us to stabalize)
OCR1BL=amp;// submit amplitude to PWM register (takes about 1/32125 = 31us+/-31us to propagate) -> amplitude-phase-alignment error is about 30-50us
int16_tadc=ADC-512;// current ADC sample 10-bits analog input, NOTE: first ADCL, then ADCH
int16_tdf=ssb(adc>>MIC_ATTEN);// convert analog input into phase-shifts (carrier out by periodic frequency shifts)
si5351.freq_calc_fast(df);// calculate SI5351 registers based on frequency shift and carrier frequency
if(OCR1BL<lut[255]){//check if already ramped up: ramp up of amplitude
for(uint16_ti=31;i!=0;i--){// soft rising slope against key-clicks
OCR1BL=lut[pgm_read_byte_near(ramp[i])];
delayMicroseconds(60);
}
}
#endif // KEY_CLICK
OCR1BL=lut[255];
process_minsky();
#ifdef CW_VOLUME
OCR1AL=(tone_vol?(p_sin>>(16-tone_vol)):0)+128;// xyzzy G8RDI mod - added for CW tone volume
#else
OCR1AL=(p_sin>>(16-volume))+128;
#endif
}
voiddsp_tx_am()
{// jitter dependent things first
ADCSRA|=(1<<ADSC);// start next ADC conversion (trigger ADC interrupt if ADIE flag is set)
OCR1BL=amp;// submit amplitude to PWM register (actually this is done in advance (about 140us) of phase-change, so that phase-delays in key-shaping circuit filter can settle)
int16_tadc=ADC-512;// current ADC sample 10-bits analog input, NOTE: first ADCL, then ADCH
int16_tin=(adc>>MIC_ATTEN);
in=in<<(drive-4);
//static int16_t dc;
//dc += (in - dc) / 2;
//in = in - dc; // DC decoupling
#define AM_BASE 32
in=max(0,min(255,(in+AM_BASE)));
amp=in;// lut[in];
}
voiddsp_tx_fm()
{// jitter dependent things first
ADCSRA|=(1<<ADSC);// start next ADC conversion (trigger ADC interrupt if ADIE flag is set)
OCR1BL=lut[255];// submit amplitude to PWM register (actually this is done in advance (about 140us) of phase-change, so that phase-delays in key-shaping circuit filter can settle)
si5351.SendPLLRegisterBulk();// submit frequency registers to SI5351 over 731kbit/s I2C (transfer takes 64/731 = 88us, then PLL-loopfilter probably needs 50us to stabalize)
int16_tadc=ADC-512;// current ADC sample 10-bits analog input, NOTE: first ADCL, then ADCH
int16_tin=(adc>>MIC_ATTEN);
in=in<<(drive);
int16_tdf=in;
si5351.freq_calc_fast(df);// calculate SI5351 registers based on frequency shift and carrier frequency
uint8_tcw_msg_interval=5;// number of seconds CW message is repeated
uint32_tcw_msg_event=0;
uint8_tcw_msg_id=0;// selected message
intcw_tx(charch){// *** CW Transmit msg ***
charsym;
for(uint8_tj=0;(sym=pgm_read_byte_near(m2c+j));j++){// lookup msg[i] in m2c, skip if not found
if(sym==ch){// found -> transmit CW character j
wdt_reset();
uint8_tk=0x80;for(;!(j&k);k>>=1);k>>=1;// shift start of cw code to MSB
if(k==0)delay(ditTime*4);// space -> add word space
else{
for(;k;k>>=1){// send dit/dah one by one, until everythng is sent
switch_rxtx(1);// key-on tx
if(delayWithKeySense(ditTime*((j&k)?3:1))){switch_rxtx(0);return1;}// symbol: dah or dih length
switch_rxtx(0);// key-off tx
if(delayWithKeySense(ditTime))return1;// add symbol space
}
if(delayWithKeySense(ditTime*2))return1;// add letter space
}
break;// next character
}
}
return0;
}
intcw_tx(char*msg){
for(uint8_ti=0;msg[i];i++){// loop over message
lcd.setCursor(0,0);lcd.print(i);lcd.print("");
if(cw_tx(msg[i]))// Sent morse char
return1;
}
return0;
}
#endif // CW_MESSAGE
volatileuint8_tmenumode=0;// 0=not in menu, 1=selects menu item, 2=selects parameter value
#ifdef CW_DECODER
volatileuint8_tcwdec=1;
staticint32_tavg=256;
staticuint8_tsym;
staticuint32_tamp32=0;
volatileuint32_t_amp32=0;
staticcharout[]="";
volatileuint8_tcw_event=false;
voidprintsym(boolsubmit=true){
if(sym<128){
charch=pgm_read_byte_near(m2c+sym);if(ch!='*'){
#ifdef CW_INTERMEDIATE
out[15]=ch;cw_event=true;if(submit){for(inti=0;i!=15;i++){out[i]=out[i+1];}out[15]='';}// update LCD, only shift when submit is true, otherwise update last char only
// here we clean up the state with a noise blanker
if(realstate!=realstatebefore){
laststarttime=millis();
}
//#define NB_SCALED_TO_WPM 1 // Scales noise-blanker timing the actual CW speed; this should reduce errors from noise at low speeds; this may have side-effect with fast speed changes that fast CW will be filtered out
#define F_ADC_CONV (192307/2) //was 192307/1, but as noted this produces clicks in audio stream. Slower ADC clock cures this (but is a problem for VOX when sampling mic-input simulatanously).
#ifdef FAST_AGC
volatileuint8_tagc=2;
#else
volatileuint8_tagc=1;
#endif
volatileuint8_tnr=2;// G8RDI mod
volatileuint8_tatt=0;
volatileuint8_tatt2=2;// Minimum att2 increased, to prevent numeric overflow on strong signals
volatileuint8_t_init=0;
// Old AGC algorithm which only increases gain, but does not decrease it for very strong signals.
// Maximum possible gain is x32 (in practice, x31) so AGC range is x1 to x31 = 30dB approx.
// Decay time is fine (about 1s) but attack time is much slower than I like.
// For weak/medium signals it aims to keep the sample value between 1024 and 2048.
staticint16_tgain=1024;
inlineint16_tprocess_agc_fast(int16_tin)
{
int16_tout=(gain>=1024)?(gain>>10)*in:in;
int16_taccum=(1-abs(out>>10));
if((INT16_MAX-gain)>accum)gain=gain+accum;
if(gain<1)gain=1;
returnout;
}
// Contribution by Alan, M0PUB: Experimental new AGC algorithm.
// ASSUMES: Input sample values are constrained to a maximum of +/-4096 to avoid integer overflow in earlier
// calculations.
//
// This algorithm aims to keep signals between a peak sample value of 1024 - 1536, with fast attack but slow
// decay.
//
// The variable centiGain actually represents the applied gain x 128 - i.e. the numeric gain applied is centiGain/128
//
// Since the largest valid input sample has a value of +/- 4096, centiGain should never be less than 32 (i.e.
// a 'gain' of 0.25). The maximum value for centiGain is 32767, and hence a gain of 255. So the AGC range
// is 0.25:255, or approx. 60dB.
//
// Variable 'slowdown' allows the decay time to be slowed down so that it is not directly related to the value
centiGain-=(centiGain>>4);// Fast attack time when big signal encountered (relies on CentiGain >= 16)
}
else{
if(HI(abs(out))>HI(1024))
small=false;
if(--decayCount==0){// But slow ramp up of gain when signal disappears
if(small){// 400 samples below lower threshold - increase gain
if(centiGain<(INT16_MAX-(INT16_MAX>>4)))
centiGain+=(centiGain>>4);
else
centiGain=INT16_MAX;
}
decayCount=DECAY_FACTOR;
small=true;
}
}
returnout;
}
inlineint16_tprocess_nr_old(int16_tac)
{
ac=ac>>(6-abs(ac));// non-linear below amp of 6; to reduce noise (switchoff agc and tune-up volume until noise dissapears, todo:extra volume control needed)
// Hilbert transform, 43dB side-band rejection in 650..3400Hz (@8kSPS) when used in image-rejection scenario; (Hilbert transform require 4 additional bits)
// Hilbert transform, 40dB side-band rejection in 400..1900Hz (@4kSPS) when used in image-rejection scenario; (Hilbert transform require 5 additional bits)
ac3=0;ozd1=0;ozd2=0;_init=0;// G8RDI mod - todo move to Setup() // hack: on first sample init accumlators of further stages (to prevent instability)
}
int16_tod1=ac3-ozd1;// Comb section
ocomb=od1-ozd2;
#endif //AF_OUT
#define OUTLET 1
#ifdef OUTLET
if(tc++==0)// prevent recursion : If tc == 0 reneable interrupts, below we dec back to zero on exit, but if another interrupt enters b4, it will not reenable ints.
//if(tc++ > 16) // prevent recursion
#endif
interrupts();// hack, since slow_dsp process exceeds rx sample-time, allow subsequent 7 interrupts for further rx sampling while processing, prevent nested interrupts with tc
qh=((v[0]-q_ac2)+(v[2]-v[12])*4)/64+((v[4]-v[10])+(v[6]-v[8]))/8+((v[4]-v[10])*5-(v[6]-v[8]))/128+(v[6]-v[8])/2;// Hilbert transform, 43dB side-band rejection in 650..3400Hz (@8kSPS) when used in image-rejection scenario; (Hilbert transform require 4 additional bits)
qh=((v[0]-ac2)+(v[2]-v[12])*4)/64+((v[4]-v[10])+(v[6]-v[8]))/8+((v[4]-v[10])*5-(v[6]-v[8]))/128+(v[6]-v[8])/2;// Hilbert transform, 43dB side-band rejection in 650..3400Hz (@8kSPS) when used in image-rejection scenario; (Hilbert transform require 4 additional bits)
int16_tac2=_ac+p->_za1+p->_z1*2;// 2nd stage: FA + FB
p->_za1=_ac;
if(b){
// post processing I and Q (down-sampled) results
ac2>>=att2;// digital gain control
// post processing I and Q (down-sampled) results
staticint16_tv[7];
i=v[0];v[0]=v[1];v[1]=v[2];v[2]=v[3];v[3]=v[4];v[4]=v[5];v[5]=v[6];v[6]=ac2;// Delay to match Hilbert transform on Q branch
int16_tac=i+qh;
ac=slow_dsp(ac);
// Output stage
staticint16_tozd1,ozd2;
if(_init){ac=0;ozd1=0;ozd2=0;_init=0;}// hack: on first sample init accumlators of further stages (to prevent instability)
#ifdef SECOND_ORDER_DUC
int16_tod1=ac-ozd1;// Comb section
ocomb=od1-ozd2;
ozd2=od1;
#else
ocomb=ac-ozd1;// Comb section
#endif
ozd1=ac;
}
else{
ac2>>=att2;// digital gain control
// Process Q (down-sampled) samples
staticint16_tv[14];
q=v[7];
qh=((v[0]-ac2)*2+(v[2]-v[12])*8+(v[4]-v[10])*21+(v[6]-v[8])*15)/128+(v[6]-v[8])/2;// Hilbert transform, 40dB side-band rejection in 400..1900Hz (@4kSPS) when used in image-rejection scenario; (Hilbert transform require 5 additional bits)
for(uint8_tj=0;j!=13;j++)v[j]=v[j+1];v[13]=ac2;
}
}
elsep->_z1=_ac;
}
elsep->z1=ac;// rx_state == I: 2, 6 Q: 1, 5
rx_state++;
}
//#pragma GCC push_options
//#pragma GCC optimize ("Ofast") // compiler-optimization for speed
// set_sleep_mode(SLEEP_MODE_ADC); // ADC NR sleep destroys the timer2 integrity, therefore Idle sleep is better alternative (keeping clkIO as an active clock domain)
set_sleep_mode(SLEEP_MODE_IDLE);
sleep_enable();
#endif
}
voidadc_stop()
{
//ADCSRA &= ~(1 << ADATE); // disable auto trigger
ADCSRA&=~(1<<ADIE);// disable interrupts when measurement complete
ADCSRA|=(1<<ADPS2)|(1<<ADPS1)|(1<<ADPS0);// 128 prescaler for 9.6kHz
#ifdef ADC_NR
sleep_disable();
#endif
ADMUX=(1<<REFS0);// restore reference voltage AREF (5V)
}
voidtimer1_start(uint32_tfs)
{// Timer 1: OC1A and OC1B in PWM mode
TCCR1A=0;
TCCR1B=0;
TCCR1A|=(1<<COM1A1)|(1<<COM1B1)|(1<<WGM11);// Clear OC1A/OC1B on compare match, set OC1A/OC1B at BOTTOM (non-inverting mode)
TCCR1B|=(1<<CS10)|(1<<WGM13)|(1<<WGM12);// Mode 14 - Fast PWM; CS10: clkI/O/1 (No prescaling)
ICR1H=0x00;
ICR1L=min(255,F_CPU/fs);// PWM value range (fs>78431): Fpwm = F_CPU / [Prescaler * (1 + TOP)]
//TCCR1A |= (1 << COM1A1) | (1 << COM1B1) | (1 << WGM10); // Clear OC1A/OC1B on compare match, set OC1A/OC1B at BOTTOM (non-inverting mode)
//TCCR1B |= (1 << CS10) | (1 << WGM12); // Mode 5 - Fast PWM, 8-bit; CS10: clkI/O/1 (No prescaling)
OCR1AH=0x00;
OCR1AL=0x00;// OC1A (SIDETONE) PWM duty-cycle (span defined by ICR).
OCR1BH=0x00;
OCR1BL=0x00;// OC1B (KEY_OUT) PWM duty-cycle (span defined by ICR).
}
voidtimer1_stop()
{
OCR1AL=0x00;
OCR1BL=0x00;
}
voidtimer2_start(uint32_tfs)
{// Timer 2: interrupt mode
ASSR&=~(1<<AS2);// Timer 2 clocked from CLK I/O (like Timer 0 and 1)
// Below a radio-specific implementation based on the above components (seperation of concerns)
//
// Feel free to replace it with your own custom radio implementation :-)
voidinlinelcd_blanks(){lcd.print(F(""));}
#define N_FONTS 8
constbytefonts[N_FONTS][8]PROGMEM={
{0b01000,// 1; logo
0b00100,
0b01010,
0b00101,
0b01010,
0b00100,
0b01000,
0b00000},
{0b00000,// 2; s-meter, 0 bars
0b00000,
0b00000,
0b00000,
0b00000,
0b00000,
0b00000,
0b00000},
{0b10000,// 3; s-meter, 1 bars
0b10000,
0b10000,
0b10000,
0b10000,
0b10000,
0b10000,
0b10000},
{0b10000,// 4; s-meter, 2 bars
0b10000,
0b10100,
0b10100,
0b10100,
0b10100,
0b10100,
0b10100},
{0b10000,// 5; s-meter, 3 bars
0b10000,
0b10101,
0b10101,
0b10101,
0b10101,
0b10101,
0b10101},
{0b01100,// 6; vfo-a
0b10010,
0b11110,
0b10010,
0b10010,
0b00000,
0b00000,
0b00000},
{0b11100,// 7; vfo-b
0b10010,
0b11100,
0b10010,
0b11100,
0b00000,
0b00000,
0b00000},
{0b00000,// 8; TBD
0b00000,
0b00000,
0b00000,
0b00000,
0b00000,
0b00000,
0b00000}
};
#ifndef VSS_METER
intanalogSafeRead(uint8_tpin,boolref1v1=false){// performs classical analogRead with default Arduino sample-rate and analog reference setting; restores previous settings
noInterrupts();
for(;!(ADCSRA&(1<<ADIF)););// wait until (a potential previous) ADC conversion is completed
uint8_tadcsra=ADCSRA;
uint8_tadmux=ADMUX;
ADCSRA&=~(1<<ADIE);// disable interrupts when measurement complete
ADCSRA|=(1<<ADPS2)|(1<<ADPS1)|(1<<ADPS0);// 128 prescaler for 9.6kHz
if(ref1v1)ADMUX&=~(1<<REFS0);// restore reference voltage AREF (1V1)
elseADMUX=(1<<REFS0);// restore reference voltage AREF (5V)
if((smode)&&((++smeter_cnt%2048)==0)){// slowed down display slightly
floatrms=(float)max_absavg256*(float)(1<<att2);
if(dsp_cap==SDR)rms/=(256.0*1024.0*(float)R*8.0*500.0*1.414/(0.707*1.1));// = -98.8dB 1 rx gain stage: rmsV = ADC value * AREF / [ADC DR * processing gain * receiver gain * "RMS compensation"]
if(smode==5){// Supply-voltage indicator; add resistor of value R_VSS (see below) between 12V supply input and pin 26 (PC3) Contribution by Jeff WB4LCG: https://groups.io/g/ucx/message/4470
#define R_VSS 1000 // for 1000kOhm from VSS to PC3 (and 10kOhm to GND). Correct this value until VSS is matching
uint8_tvss10=(uint32_t)analogSafeRead(BUTTONS,true)*(R_VSS+10)*11/(10*1024);// use for a 1.1V ADC range VSS measurement
//uint8_t vss10 = (uint32_t)analogSafeRead(BUTTONS, false) * (R_VSS + 10) * 50 / (10 * 1024); // use for a 5V ADC range VSS measurement (use for 100k value of R_VSS)
// Set RX or TX mode, with RIT support and CW offset
voidswitch_rxtx(uint8_ttx_enable)
{
//GW8RDI - NOTE: Best use the PA line to trigger the Spectrum mode instead of CAT as gives near instant audio control. TX RFI can interfere with RS232 as TX starts to xmit.
#ifdef CAT_TX_CMD
if(cat_enabled&&mode!=CW)// To mute Spectrum DSP audio in CW mode connect the PA line to the Spectrum module PA input.
{
if(tx_enable)
{
Serial.print("TX0;");// GW8RDI mod - advise going to Xmit
Serial.print("TX0;");// Send again as TX can cause RFI ?????? too check needed
}
else
{
Serial.print("RX0;");// GW8RDI mod - advise going to Rx
// RX I/Q calibration procedure: terminate with 50 ohm, enable CW filter, adjust R27, R24, R17 subsequently to its minimum side-band rejection value in dB
MCUSR=~(1<<WDRF);// MSY be done before wdt_disable()
wdt_disable();// WDTON Fuse High bit need to be 1 (0xD1), if NOT it will override and set WDE=1; WDIE=0, meaning MCU will reset when watchdog timer is zero, and this seems to happen when wdt_disable() is called
//MCUCR |= (1<<BODS) | (1<<BODSE); // turn bod off by settings BODS, BODSE; note BODS is reset after three clock-cycles, so quickly go to sleep before it is too late
//MCUCR &= ~(1<<BODSE); // must be done right before sleep
sleep_cpu();// go to sleep mode, wake-up by either INT0, INT1, Pin Change, TWI Addr Match, WDT, BOD
sleep_disable();
//void(* reset)(void) = 0; reset(); // soft reset by calling reset vector (does not reset registers to defaults)
do{wdt_enable(WDTO_15MS);for(;;);}while(0);// soft reset by trigger watchdog timeout
}
char*szStation=(char*)MY_CALLSIGN_PADDED;// If callsign is different length, change [5] and [6] below to match 2 spaces at end.
sprintf(&szStation[CALLSIGN_LENGTH],"%02X",error_code);// G8RDI mod - added
#endif
lcd.print(szStation);// "uSDX"
#endif //QCX
lcd.print('\x01');lcd_blanks();lcd_blanks();
}
constchar*vfosel_label[]={"A","B"/*, "Split"*/};
///const char* vfosel_label[] = { "A", "B", "Split" }; // GW8RDI note - to add Split to the menu, will need a control adding to show mode, and change receive offset (int16_t rit)
caseBAND:paramAction(action,bandval,0x14,F("Band"),band_label,1,_N(band_label)-2,false);break;// G8RDI mod - changed min to 1 as 160M not in use, and _N(band_label) - 1 to -2 as 6m also
caseBACKL:paramAction(action,backlight,0xA1,F("Light"),offon_label,0,1,false);break;// GW8RDI "Backlight" workaround for varying N_PARAM and not being able to overflowing default cases properly
//pinMode(DAH, INPUT_PULLUP); // Could this replace D4? But leaks noisy VCC into mic input!
digitalWrite(AUDIO1,LOW);// when used as output, help can mute RX leakage into AREF
digitalWrite(AUDIO2,LOW);
pinMode(AUDIO1,INPUT);
pinMode(AUDIO2,INPUT);
#ifdef NTX
digitalWrite(NTX,HIGH);
pinMode(NTX,OUTPUT);
#endif //NTX
#ifdef PTX
digitalWrite(PTX,LOW);
pinMode(PTX,OUTPUT);
#endif //PTX
#ifdef SWR_METER
pinMode(PIN_FWD,INPUT);
pinMode(PIN_REF,INPUT);
#endif
#ifdef OLED // assign unused LCD pins
pinMode(PD4,OUTPUT);
pinMode(PD5,OUTPUT);
#else
#if defined(RED_CORNERS) || defined(BLACK_BRICK)
pinMode(PD5,OUTPUT);// G8RDI mod as drives LCD 1602 backlight
#else
pinMode(PD3,OUTPUT);// G8RDI mod - uSDX+ have backlight control
#endif
#endif
}
#ifdef CAT
// CAT support inspired by Charlie Morris, ZL2CTM, contribution by Alex, PE1EVX, source: http://zl2ctm.blogspot.com/2020/06/digital-modes-transceiver.html?m=1
rxend_event=millis()+10;// block display until this moment, to prevent CAT cmds that initiate display changes to interfere with the next CAT cmd e.g. Hamlib: FA00007071000;ID;
chardata=Serial.read();
CATcmd[cat_ptr++]=data;
if(data==';'){
CATcmd[cat_ptr]='\0';// terminate the array
cat_ptr=0;// reset for next CAT command
#ifdef _SERIAL
if(!cat_active){cat_active=1;smode=0;}// disable smeter to reduce display activity
digitalWrite(KEY_OUT,LOW);// for safety: to prevent exploding PA MOSFETs, in case there was something still biasing them.
si5351.powerDown();// disable all CLK outputs (especially needed for si5351 variants that has CLK2 enabled by default, such as Si5351A-B04486-GT)
//uint8_t mcusr = MCUSR;
MCUSR=0;
//wdt_disable();
wdt_enable(WDTO_4S);// Enable watchdog
uint32_tt0,t1;
#ifdef DEBUG
// Benchmark dsp_tx() ISR (this needs to be done in beginning of setup() otherwise when VERSION containts 5 chars, mis-alignment impact performance by a few percent)
lcd.setCursor(0,1);lcd.print(F("!!Brownout RESET"));lcd_blanks();// Brow-out reset happened, CPU voltage not stable or make sure Brown-Out threshold is set OK (make sure E fuse is set to FD)
mode_last[4]=mode_last[5]=mode_last[6]=mode_last[7]=mode_last[8]=USB;// Set for up to 9 bands only xyzzy
#endif
// Load parameters from EEPROM, reset to factory defaults when stored values are from a different version
paramAction(LOAD,VERS);
if((eeprom_version!=get_version_id())||_digitalRead(BUTTONS)){// EEPROM clean: if rotary-key pressed or version signature in EEPROM does NOT corresponds with this firmware
eeprom_version=get_version_id();
// G8RDI mod - reduce EEPROM writes
//for(int n = 0; n != 1024; n++){ eeprom_write_byte((uint8_t *) n, 0); wdt_reset(); } //clean EEPROM
//if(abs((int32_t)F_XTAL - (int32_t)si5351.fxtal) > 50000){ si5351.fxtal = F_XTAL; } // if F_XTAL frequency deviates too much with actual setting -> use default
si5351.iqmsa=0;// enforce PLL reset
change=true;
prev_bandval=bandval;
vox=false;// disable VOX
//nr = 2; // set 2 default / 0 disable NR
rit=false;// disable RIT
freq=vfo[vfosel%2];
mode=vfomode[vfosel%2];
#ifdef NR_FIR
if(nr>2)
FirFilterSetup(7+(((nr-2)-1)*2),filt_val[filt],F_SAMP_RX/8);// GW8RDI mod
#endif
#ifdef TX_ENABLE
build_lut();
#endif
delay(800);// G8RDI mod added so visible
show_banner();// remove release number
start_rx();// Start radio receiver
#if defined(CAT) || defined(TESTBENCH)
#ifdef CAT_STREAMING
#define BAUD 115200 // Baudrate used for serial communications
#else
#ifdef CAT_FAST
#define BAUD 115200 // 57600/115200 Fast comms, see if reduces audio glitching on CAT polling G8RDI mod
#else
#define BAUD 38400 // 38400 57600//115200//4800 //Baudrate used for serial communications (CAT, TESTBENCH)
#endif
#endif
if(cat_enabled)// G8RDI mod
{
Serial.begin(16000000ULL*BAUD/F_MCU);// corrected for F_CPU=20M
Command_IF();
#if !defined(OLED) && defined(TESTBENCH)
smode=0;// In case of LCD, turn off smeter
#endif
}
#endif //CAT TESTBENCH
#ifdef KEYER
keyerState=IDLE;
keyerControl=IAMBICB;// Or 0 for IAMBICA
loadWPM(keyer_speed);// Fix speed at 15 WPM
#endif //KEYER
for(;!_digitalRead(DIT)||((mode==CW&&keyer_mode!=SINGLE)&&(!_digitalRead(DAH)));){fatal(F("Check PTT/key"));}// wait until DIH/DAH/PTT is released to prevent TX on startup
}
staticint32_t_step=0;
//static int8_t prev_mode;
voidloop()
{
#ifdef VOX_ENABLE
if((vox)&&((mode==LSB)||(mode==USB))){// If VOX enabled (and in LSB/USB mode), then take mic samples and feed ssb processing function, to derive amplitude, and potentially detect cross vox_threshold to detect a TX or RX event: this is expressed in tx variable
if(!vox_tx){// VOX not active
#ifdef MULTI_ADC
if(vox_sample++==16){// take N sample, then process
ssb(((int16_t)(vox_adc/16)-(512-AF_BIAS))>>MIC_ATTEN);// sampling mic
vox_sample=0;
vox_adc=0;
}
else{
vox_adc+=analogSampleMic();
}
#else
ssb(((int16_t)(analogSampleMic())-512)>>MIC_ATTEN);// sampling mic
#endif
if(tx){// TX triggered by audio -> TX
vox_tx=1;
switch_rxtx(255);
//for(;(tx);) wdt_reset(); // while in tx (workaround for RFI feedback related issue)
//delay(100); tx = 255;
}
}
elseif(!tx){// VOX activated, no audio detected -> RX
if((mode==CW)&&cwdec&&((!tx)&&(!semi_qsk_timeout)))cw_decode();// CW decoder only active during RX
#endif //CW_DECODER
if(menumode==0){// in main
#ifdef CW_DECODER
if(cw_event){
constcharoffv[]={0,7,3,5,3,7,8};// G8RDI mod - adjusted to meet standard
uint8_toffset=offv[smode];// depending on smeter more/less cw-text
//illegal format in Viz : G8RDI uint8_t offset = (uint8_t[7]){ 0, 7, 3, 5, 3, 7, 8 }[smode]; // depending on smeter more/less cw-text
lcd.noCursor();
#ifdef OLED
//cw_event = false; for(int i = 0; out[offset + i] != '\0'; i++){ lcd.setCursor(i, 0); lcd.print(out[offset + i]); if((!tx) && (!semi_qsk_timeout)) cw_decode(); } // like 'lcd.print(out + offset);' but then in parallel calling cw_decoding() to handle long OLED writes
//uint8_t i = cw_event - 1; if(out[offset + i]){ lcd.setCursor(i, 0); lcd.print(out[offset + i]); cw_event++; } else cw_event = false; // since an oled string write would hold-up reliable decoding/keying, write only a single char each time and continue
elsecw_event=false;// since an oled string write would hold-up reliable decoding/keying, write only a single char each time and continue
#else
cw_event=false;
lcd.setCursor(0,0);lcd.print(out+offset);
#endif
stepsize_showcursor();
}
else
#endif //CW_DECODER
if((!semi_qsk_timeout)&&(!vox_tx))
smeter();
}
#ifdef KEYER //Keyer
if(mode==CW&&keyer_mode!=SINGLE)// check DIT/DAH keys for CW
{
switch(keyerState){// Basic Iambic Keyer, keyerControl contains processing flags and keyer mode bits, Supports Iambic A and B, State machine based, uses calls to millis() for timing.
caseIDLE:// Wait for direct or latched paddle press
if((_digitalRead(DAH)==LOW)||
(_digitalRead(DIT)==LOW)||
(keyerControl&0x03))
{
#ifdef CW_MESSAGE
cw_msg_event=0;// clear cw message event
#endif //CW_MESSAGE
update_PaddleLatch();
keyerState=CHK_DIT;
}
break;
caseCHK_DIT:// See if the dit paddle was pressed
if(keyerControl&DIT_L){
keyerControl|=DIT_PROC;
ktimer=ditTime;
keyerState=KEYED_PREP;
}
else{
keyerState=CHK_DAH;
}
break;
caseCHK_DAH:// See if dah paddle was pressed
if(keyerControl&DAH_L){
ktimer=ditTime*3;
keyerState=KEYED_PREP;
}
else{
keyerState=IDLE;
}
break;
caseKEYED_PREP:// Assert key down, start timing, state shared for dit or dah
Key_state=HIGH;
switch_rxtx(Key_state);
ktimer+=millis();// set ktimer to interval end time
keyerControl&=~(DIT_L+DAH_L);// clear both paddle latch bits
keyerState=KEYED;// next state
break;
caseKEYED:// Wait for timer to expire
if(millis()>ktimer){// are we at end of key down ?
Key_state=LOW;
switch_rxtx(Key_state);
ktimer=millis()+ditTime;// inter-element time
keyerState=INTER_ELEMENT;// next state
}
elseif(keyerControl&IAMBICB){
update_PaddleLatch();// early paddle latch in Iambic B mode
}
break;
caseINTER_ELEMENT:
// Insert time between dits/dahs
update_PaddleLatch();// latch paddle state
if(millis()>ktimer){// are we at end of inter-space ?
if(keyerControl&DIT_PROC){// was it a dit or dah ?
keyerControl&=~(DIT_L+DIT_PROC);// clear two bits
keyerState=CHK_DAH;// dit done, check for dah
}
else{
keyerControl&=~(DAH_L);// clear dah latch
keyerState=IDLE;// go idle
}
}
break;
}
}
else{
#endif //KEYER
#ifdef TX_ENABLE
uint8_tpin=((mode==CW)&&(keyer_swap))?DAH:DIT;
if(!vox_tx)// ONLY if VOX not active, then check DIT/DAH (fix for VOX to prevent RFI feedback through EMI on DIT or DAH line)
if(mode>AM)// G8RDI mod - *changed from > CW so that all modes can be accessed
#endif
mode=LSB;// *now shows all / skip all other modes (only LSB (0), USB, CW(2))
#ifdef MODE_CHANGE_RESETS
if(mode!=CW)
{
stepsize=STEP_1k;
else
stepsize=STEP_500;// sets suitable stepsize
filt=0;// resets filter (to most BW) and NR on mode change
}
else{filt=4;nr=0;}
#else
if(mode==CW){nr=0;}
prev_stepsize[prev_mode==CW]=stepsize;stepsize=prev_stepsize[mode==CW];// backup stepsize setting for previous mode, restore previous stepsize setting for current selected mode; filter settings captured for either CQ or other modes.
prev_filt[prev_mode==CW]=filt;
filt=prev_filt[mode==CW];// backup filter setting for previous mode, restore previous filter setting for current selected mode; filter settings captured for either CQ or other modes.
#endif
//paramAction(UPDATE, MODE);
vfomode[vfosel%2]=mode;
paramAction(SAVE,(vfosel%2)?MODEB:MODEA);// save vfoa/b changes
paramAction(SAVE,MODE);
paramAction(SAVE,FILTER);
si5351.iqmsa=0;// enforce PLL reset
#ifdef CW_DECODER
if(prev_mode==CW&&cwdec)
show_banner();
#endif
change=true;
}
else{
if(menumode==1){menumode=0;}// short right-click while in menu: enter value selection screen
if(menumode>=2){menumode=1;change=true;paramAction(SAVE,menu);}// short right-click while in value selection screen: save, and return to menu screen
}*/
break;
caseBR|DC:
filt++;
_init=true;
if(mode==CW&&filt>N_FILT)filt=4;
if(mode==CW&&filt==4)stepsize=STEP_500;// reset stepsize for 500Hz filter
if(menumode==1){_menumode=2;}// short encoder-click while in menu: enter value selection screen
if(menumode==2){_menumode=1;change=true;paramAction(SAVE,menu);}// short encoder-click while in value selection screen: save, and return to menu screen
#ifdef MENU_STR
if(menumode==3){_menumode=3;paramAction(NEXT_CH,menu);}// short encoder-click while in string edit mode: change position to next character
#endif
menumode=_menumode;
}
break;
caseBE|DC:// Button Encoder and DC double-click for Band change (( G8RDI mod - now bi-directional and restores freq & mode)
#ifdef KEEP_BAND_DATA // G8RDI mod
prev_bandval=bandval;
prev_mode=mode;
if(bandval>0&&bandval<=BANDCOUNT)// bandval 1-8 (0 is 6m, 9 is 160m)
{
freq_last[bandval-1]=freq;//vfo[vfosel % 2] // G8RDI mod - Save freq and mode last used on this band
#if defined(RED_BUTTONS) || defined(WHITE_BUTTONS) || defined(BLACK_BRICK) // For some reason, even without SWAP_ROTARY, Red Buttons is reversed
#if defined(REVERSE_BAND_CHANGE)
if(last_state==0x13||last_state==0x32||last_state==0x20||last_state==0x01)// --Dir last freq step - G8RDI mod
bandval++;
else
bandval--;
#else
if(last_state==0x13||last_state==0x32||last_state==0x20||last_state==0x01)// --Dir last freq step - G8RDI mod
bandval--;
else
bandval++;// G8RDI mod to make last freq change control and change dir
#endif
#else
#if defined(SWAP_ROTARY) || defined(REVERSE_BAND_CHANGE) // G8RDI mod A7. If your freq. change is correct, but band jump goes backwards, define REVERSE_BAND_CHANGE
if(last_state==0x13||last_state==0x32||last_state==0x20||last_state==0x01)// --Dir last freq step - G8RDI mod
bandval--;
else
bandval++;// G8RDI mod to make last freq change control and change dir
#else
if(last_state==0x13||last_state==0x32||last_state==0x20||last_state==0x01)// --Dir last freq step - G8RDI mod
bandval++;
else
bandval--;// G8RDI mod to make last freq change control and change dir
mode=LSB;// skip all other modes (only LSB, USB, CW)
#ifdef MODE_CHANGE_RESETS
if(mode==CW){filt=4;nr=0;}
elsefilt=0;// resets filter (to most BW) and NR on mode change
#else
if(mode==CW){nr=0;}
prev_stepsize[prev_mode==CW]=stepsize;stepsize=prev_stepsize[mode==CW];// backup stepsize setting for previous mode, restore previous stepsize setting for current selected mode; filter settings captured for either CQ or other modes.
prev_filt[prev_mode==CW]=filt;filt=prev_filt[mode==CW];// backup filter setting for previous mode, restore previous filter setting for current selected mode; filter settings captured for either CQ or other modes.
#endif
//paramAction(UPDATE, MODE);
vfomode[vfosel%2]=mode;
paramAction(SAVE,(vfosel%2)?MODEB:MODEA);// save vfoa/b changes
paramAction(SAVE,MODE);
paramAction(SAVE,FILTER);
si5351.iqmsa=0;// enforce PLL reset
if((prev_mode==CW)&&(cwdec))show_banner();
change=true;
}
else{
if(menumode==1){menumode=0;}// short right-click while in menu: enter value selection screen
if(menumode>=2){menumode=1;change=true;paramAction(SAVE,menu);}// short right-click while in value selection screen: save, and return to menu screen
}
break;
*/
caseBE|PL:
stepsize+=1;
if(stepsize<STEP_1k)stepsize=STEP_10;
if(stepsize>STEP_10)stepsize=STEP_1k;
stepsize_showcursor();
break;
caseBE|PLC:// or kept pressed
menumode=2;
break;
caseBE|PT:
menumode=1;
//if(menu == 0) menu = 1;
break;
caseBL|SC:
caseBL|DC:
caseBL|PL:
caseBL|PLC:
encoder_val++;
break;
caseBR|SC:
caseBR|DC:
caseBR|PL:
caseBR|PLC:
encoder_val--;
break;
#endif //ONEBUTTON
}
}
elseevent=0;// no button pressed: reset event
if(changedMode||changedModeCAT)// 230401 GW8RDI
{
changedMode=false;
if(!menumode)
{
/*if (rit) // If in RIT mode ignore mode change - original uSDX behaviour
{
rit=0;stepsize=prev_stepsize[mode==CW];
change=true;
}
else*/// GW8RDI mod - modulation mode changes now supported in RIT mode
filt=0;// resets filter (to most BW) and NR on mode change
}
else
{filt=4;nr=0;}
#else
if(mode==CW){nr=0;}
prev_stepsize[prev_mode==CW]=stepsize;stepsize=prev_stepsize[mode==CW];// backup stepsize setting for previous mode, restore previous stepsize setting for current selected mode; filter settings captured for either CQ or other modes.
prev_filt[prev_mode==CW]=filt;
filt=prev_filt[mode==CW];// backup filter setting for previous mode, restore previous filter setting for current selected mode; filter settings captured for either CQ or other modes.
#endif
//paramAction(UPDATE, MODE);
vfomode[vfosel%2]=mode;
paramAction(SAVE,(vfosel%2)?MODEB:MODEA);// save vfoa/b changes
paramAction(SAVE,MODE);
paramAction(SAVE,FILTER);
si5351.iqmsa=0;// enforce PLL reset
#ifdef CW_DECODER
if(prev_mode==CW&&cwdec)
show_banner();
#endif
change=true;
}
}
else
{
if(menumode==1){menumode=0;}// short right-click while in menu: enter value selection screen
if(menumode>=2){menumode=1;change=true;paramAction(SAVE,menu);}// short right-click while in value selection screen: save, and return to menu screen
}
changedModeCAT=false;
}
if((menumode)||(prev_menumode!=menumode)){// Show parameter and value
int8_tencoder_change=encoder_val;
if((menumode==1)&&encoder_change){
menu+=encoder_val;// Navigate through menu
#ifdef ONEBUTTON
menu=max(0,min(menu,N_PARAMS));
#else
// G8RDI mod to cycle menu //menu = max(1 /* 0 */, min(menu, N_PARAMS));
if(menu>N_PARAMS)
menu=1;
else
if(menu<1)
menu=N_PARAMS;
#endif
menu=paramAction(NEXT_MENU,menu);// auto probe next menu item (gaps may exist)
encoder_val=0;
}
if(encoder_change||(prev_menumode!=menumode))paramAction(UPDATE_MENU,(menumode)?menu:0);// update param with encoder change and display
prev_menumode=menumode;
if(menumode==2){
if(encoder_change){
lcd.setCursor(0,1);lcd.cursor();// edits menu item value; make cursor visible
if(menu==MODE){// post-handling Mode parameter
vfomode[vfosel%2]=mode;
paramAction(SAVE,(vfosel%2)?MODEB:MODEA);// save vfoa/b changes
delay(F_MCU*500UL/16000000);// delay 0.5s (in reality because F_CPU=20M instead of 16M, delay() is running 1.25x faster therefore we need to multiply with 1.25)
sr=numSamples*2;// samples per second
paramAction(UPDATE_MENU,menu);// refresh
}
if(menu==CPULOAD){// measure CPU-load
uint32_ti=0;
uint32_tprev_time=millis();
for(i=0;i!=300000;i++)wdt_reset();// fixed CPU-load 132052*1.25us delay under 0% load condition; is 132052*1.25 * 20M = 3301300 CPU cycles fixed load
if((change)&&(!tx)&&(!vox_tx))// Only change if TX is OFF, prevent simultaneous I2C bus access
{
change=false;
if(prev_bandval!=bandval){// If band changed
prev_bandval=bandval;
#ifdef KEEP_BAND_DATA // G8RDI mod
if(freq_last[bandval-1]!=0)// G8RDI mod
{
freq=freq_last[bandval-1];// Change to last freq used on this band
if(freq>60000000)// Keep in range to avoid lots of twiddling
{
freq=band[bandval];
freq_last[bandval-1]=freq;
}
}
else
freq=band[bandval];// Load default
if(mode_last[bandval-1]>AM)
{
mode_last[bandval-1]=LSB;// Should never happen, error! G8RDI mod
error_code=3;
}
mode=mode_last[bandval-1];// Change to last used mode on this band
// G8RDI mod 2022/07/19 updated filter, step and NR as per mode change. NOTE: TODO We could save previous filter and maybe noise settings, CW decode needs to be disable for non-CW modes. We could also save for both VFO A & B:-
elsefilt=0;// resets filter (to most BW) and NR on mode change
#else
if(mode==CW){nr=0;}
prev_stepsize[prev_mode==CW]=stepsize;stepsize=prev_stepsize[mode==CW];// backup stepsize setting for previous mode, restore previous stepsize setting for current selected mode; filter settings captured for either CQ or other modes.
prev_filt[prev_mode==CW]=filt;filt=prev_filt[mode==CW];// backup filter setting for previous mode, restore previous filter setting for current selected mode; filter settings captured for either CQ or other modes.
#endif
#ifdef CW_DECODER
if(prev_mode==CW&&cwdec)
show_banner();
#endif
#else
freq=band[bandval];// Change to new band freq start
#endif
//paramAction(UPDATE, MODE);
vfomode[vfosel%2]=mode;
paramAction(SAVE,(vfosel%2)?MODEB:MODEA);// save vfoa/b changes
paramAction(SAVE,MODE);
paramAction(SAVE,FILTER);
si5351.iqmsa=0;// enforce PLL reset
}
vfo[vfosel%2]=freq;
//save_event_time = millis() + 1000; // schedule time to save freq (no save while tuning, hence no EEPROM wear out - G8RDI "Datasheet: Write/erase cycles: 10,000 flash/100,000 EEPROM")
save_event_time=millis()+2000;// G8RDI mod - increased to 2 seconds // schedule time to save freq (no save while tuning, hence no EEPROM wear out - G8RDI "Datasheet: Write/erase cycles: 10,000 flash/100,000 EEPROM")
if(menumode==0){
display_vfo(freq);
stepsize_showcursor();
#ifdef CAT
//Command_GETFreqA();
#endif
// The following is a hack for SWR measurement:
//si5351.alt_clk2(freq + 2400);
//si5351.SendRegister(SI_CLK_OE, TX1RX1);
//digitalWrite(SIG_OUT, HIGH); // inject CLK2 on antenna input via 120K
}
//noInterrupts();
uint8_tf=freq/1000000UL;
set_lpf(f);
bandval=(f>32)?10:(f>26)?9:(f>22)?8:(f>20)?7:(f>16)?6:(f>12)?5:(f>8)?4:(f>6)?3:(f>4)?2:(f>2)?1:0;prev_bandval=bandval;// align bandval with freq
if(mode==CW){
si5351.freq(freq+cw_offset,rx_ph_q,0/*90, 0*/);// RX in CW-R (=LSB), correct for CW-tone offset
}
else
if(mode==LSB)
si5351.freq(freq,rx_ph_q,0/*90, 0*/);// RX in LSB
else
si5351.freq(freq,0,rx_ph_q/*0, 90*/);// RX in USB, ...
if((mode==CW)&&(cw_msg_event)&&(millis()>cw_msg_event)){// if it is time, send CW message
if((cw_tx(cw_msg[cw_msg_id])==0)&&((cw_msg[cw_msg_id][0]=='C')&&(cw_msg[cw_msg_id][1]=='Q'))&&cw_msg_interval)// If msg starts CQ, schedule repeat send at set interval
cw_msg_event=millis()+(1000*cw_msg_interval);
else
cw_msg_event=0;// Done/aborted by key press (if CW aborts can also be caused by noise getting onto keys ADC line)
if(mode>CW)mode=LSB;// skip all other modes (only LSB, USB, CW)
#ifdef MODE_CHANGE_RESETS
if(mode==CW){filt=4;nr=0;}
elsefilt=0;// resets filter (to most BW) and NR on mode change
#else
if(mode==CW){nr=0;}
prev_stepsize[prev_mode==CW]=stepsize;stepsize=prev_stepsize[mode==CW];// backup stepsize setting for previous mode, restore previous stepsize setting for current selected mode; filter settings captured for either CQ or other modes.
prev_filt[prev_mode==CW]=filt;filt=prev_filt[mode==CW];// backup filter setting for previous mode, restore previous filter setting for current selected mode; filter settings captured for either CQ or other modes.
#endif
//paramAction(UPDATE, MODE);
vfomode[vfosel%2]=mode;
paramAction(SAVE,(vfosel%2)?MODEB:MODEA);// save vfoa/b changes
paramAction(SAVE,MODE);
paramAction(SAVE,FILTER);
si5351.iqmsa=0;// enforce PLL reset
if((prev_mode==CW)&&(cwdec))
show_banner();
change=true;
}
else
{
if(menumode==1){menumode=0;}// short right-click while in menu: enter value selection screen
if(menumode>=2){menumode=1;change=true;paramAction(SAVE,menu);}// short right-click while in value selection screen: save, and return to menu screen