Usdx Ino
Usdx Ino
com/threeme3/QCX-SSB
//
// Copyright 2019, 2020, 2021, 2022, 2023, 2024, 2025 Guido PE1NNZ
//
// 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.
#ifdef SWAP_ROTARY
#undef ROT_A
#undef ROT_B
#define ROT_A 7 //PD7 (pin 13)
#define ROT_B 6 //PD6 (pin 12)
#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
#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
float FWD;
float SWR;
float ref_V = 5 * 1.15;
static uint32_t stimer;
#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
+#define SDA 3 //PD3 (pin 5)
+#define SCL 4 //PD4 (pin 6)
+#define ROT_A 6 //PD6 (pin 12)
+#define ROT_B 7 //PD7 (pin 13)
+#define RX 8 //PB0 (pin 14)
+#define SIDETONE 9 //PB1 (pin 15)
+#define KEY_OUT 10 //PB2 (pin 16)
+#define NTX 11 //PB3 (pin 17)
+#define DAH 12 //PB4 (pin 18)
+#define DIT 13 //PB5 (pin 19)
+#define AUDIO1 14 //PC0/A0 (pin 23)
+#define AUDIO2 15 //PC1/A1 (pin 24)
+#define DVM 16 //PC2/A2 (pin 25)
+#define BUTTONS 17 //PC3/A3 (pin 26)
// In addition set:
#define OLED 1
#define ONEBUTTON 1
#define ONEBUTTON_INV 1
#undef DEBUG
adjust I2C and I2C_ 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
*/
#ifdef CAT_EXT
volatile uint8_t cat_key = 0;
uint8_t _digitalRead(uint8_t pin){ // reads pin or (via CAT) artificially
overriden pins
serialEvent(); // allows CAT update
if(cat_key){ return (pin == BUTTONS) ? ((cat_key&0x07) > 0) : (pin == DIT) ?
~cat_key&0x10 : (pin == DAH) ? ~cat_key&0x20 : 0; } // overrides digitalRead(DIT,
DAH, BUTTONS);
return digitalRead(pin);
}
#else
#define _digitalRead(x) digitalRead(x)
#endif //CAT_EXT
//#define ONEBUTTON_INV 1 // Encoder button goes from PC3 to GND (instead PC3 to
5V, with 10k pull down)
#ifdef ONEBUTTON_INV
uint8_t inv = 1;
#else
uint8_t inv = 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.
void update_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;
}
}
void loadWPM (int wpm) // 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
static uint8_t practice = false; // Practice mode
#include <avr/sleep.h>
#include <avr/wdt.h>
void begin(){};
void beginTransmission(uint8_t addr){ start(); SendByte(addr << 1); };
bool write(uint8_t byte){ SendByte(byte); return 1; };
uint8_t endTransmission(){ stop(); return 0; };
};
I2C_ Wire;
//#include <Wire.h>
uint8_t backlight = 8;
//#define RS_HIGH_ON_IDLE 1 // Experimental LCD support where RS line is high
on idle periods to comply with SDA I2C standard.
/*
class LCD : public Print { // inspired by: http://www.technoblogy.com/show?2BET
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 // PC4 - MUST have pull-up resistor
#define _rs 4 // PC4 - MUST have pull-up resistor
#define LCD_RS_HI() DDRC &= ~(1 << _rs); // RS high (pull-up)
#define LCD_RS_LO() DDRC |= 1 << _rs; // RS low (pull-down)
#define LCD_EN_LO() PORTD &= ~(1 << _en); // EN low
#define LCD_PREP_NIBBLE(b) (PORTD & ~(0xf << _dn)) | (b) << _dn | 1 << _en //
Send data and enable high
void begin(uint8_t x, uint8_t y){ // Send command
DDRD |= 0xf << _dn | 1 << _en; // Make data, EN and RS pins
outputs
PORTC &= ~(1 << _rs); // Set RS low in case to support
pull-down when DDRC is output
delayMicroseconds(50000); // * At least 40ms after power
rises above 2.7V before sending commands
LCD_RS_LO();
cmd(0x33); // Ensures display is in 8-bit
mode
cmd(0x32); // Puts display in 4-bit mode
cmd(0x0e); // Display and cursor on
cmd(0x01); // Clear display
delay(3); // Allow to execute on display
[https://www.sparkfun.com/datasheets/LCD/HD44780.pdf, p.49, p58]
}
void nib(uint8_t b){ // Send four bit nibble to
display
PORTD = LCD_PREP_NIBBLE(b); // Send data and enable high
delayMicroseconds(4);
LCD_EN_LO();
delayMicroseconds(60); // Execution time (was: 37)
}
void cmd(uint8_t b){ nib(b >> 4); nib(b & 0xf); }// Write command: send nibbles
while RS low
size_t write(uint8_t b){ // Write data: send nibbles
while RS high
uint8_t nibh = LCD_PREP_NIBBLE(b >> 4); // Prepare high nibble data and
enable high
uint8_t nibl = LCD_PREP_NIBBLE(b & 0xf); // Prepare low nibble data and
enable high
PORTD = nibh; // Send high nibble data and
enable high
LCD_RS_HI();
LCD_EN_LO();
PORTD = nibl; // Send low nibble data and
enable high
LCD_RS_LO();
LCD_RS_HI();
LCD_EN_LO();
LCD_RS_LO();
delayMicroseconds(41); // Execution time
PORTD |= 0x02; // To support serial-interface
keep LCD_D5 high, so that DVM is not pulled-down via D
return 1;
}
void setCursor(uint8_t x, uint8_t y){ cmd(0x80 | (x + y * 0x40)); }
void cursor(){ cmd(0x0e); }
void noCursor(){ cmd(0x0c); }
void noDisplay(){ cmd(0x08); }
void createChar(uint8_t l, uint8_t glyph[]){ cmd(0x40 | ((l & 0x7) << 3));
for(int i = 0; i != 8; i++) write(glyph[i]); }
};
*/
/*
#include <LiquidCrystal.h>
class LCD_ : public LiquidCrystal {
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.
//LCD_(uint8_t rs = LCD_RS, uint8_t en = LCD_EN, uint8_t d4 = LCD_D4, uint8_t d5,
= LCD_D5 uint8_t d6 = LCD_D6, uint8_t d7 = LCD_D7) : LiquidCrystal(rs, en, d4, d5,
d6, d7){ };
LCD_() : LiquidCrystal(LCD_RS, LCD_EN, LCD_D4, LCD_D5, LCD_D6, LCD_D7){ };
virtual size_t write(uint8_t value){ // overwrites LiquidCrystal::write() and re-
implements LCD data writes
pinMode(LCD_RS, INPUT); // pull-up LCD_RS
write4bits(value >> 4);
write4bits(value);
pinMode(LCD_RS, OUTPUT); // pull-down LCD_RS
return 1;
};
void write4bits(uint8_t value){
digitalWrite(LCD_D4, (value >> 0) & 0x01);
digitalWrite(LCD_D5, (value >> 1) & 0x01);
digitalWrite(LCD_D6, (value >> 2) & 0x01);
digitalWrite(LCD_D7, (value >> 3) & 0x01);
digitalWrite(LCD_EN, LOW); // pulseEnable
delayMicroseconds(1);
digitalWrite(LCD_EN, HIGH);
delayMicroseconds(1); // enable pulse must be >450ns
digitalWrite(LCD_EN, LOW);
delayMicroseconds(100); // commands need > 37us to settle
};
};
*/
// C64 real
const uint8_t font[]PROGMEM = {
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // ' '
0x00, 0x00, 0x00, 0x4f, 0x4f, 0x00, 0x00, 0x00, // !
0x00, 0x07, 0x07, 0x00, 0x00, 0x07, 0x07, 0x00, // "
0x14, 0x7f, 0x7f, 0x14, 0x14, 0x7f, 0x7f, 0x14, // #
0x00, 0x24, 0x2e, 0x6b, 0x6b, 0x3a, 0x12, 0x00, // $
0x00, 0x63, 0x33, 0x18, 0x0c, 0x66, 0x63, 0x00, // %
0x00, 0x32, 0x7f, 0x4d, 0x4d, 0x77, 0x72, 0x50, // &
0x00, 0x00, 0x00, 0x04, 0x06, 0x03, 0x01, 0x00, // '
0x00, 0x00, 0x1c, 0x3e, 0x63, 0x41, 0x00, 0x00, // (
0x00, 0x00, 0x41, 0x63, 0x3e, 0x1c, 0x00, 0x00, // )
0x08, 0x2a, 0x3e, 0x1c, 0x1c, 0x3e, 0x2a, 0x08, // *
0x00, 0x08, 0x08, 0x3e, 0x3e, 0x08, 0x08, 0x00, // +
0x00, 0x00, 0x80, 0xe0, 0x60, 0x00, 0x00, 0x00, // ,
0x00, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x00, // -
0x00, 0x00, 0x00, 0x60, 0x60, 0x00, 0x00, 0x00, // .
0x00, 0x40, 0x60, 0x30, 0x18, 0x0c, 0x06, 0x02, // /
0x00, 0x3e, 0x7f, 0x49, 0x45, 0x7f, 0x3e, 0x00, // 0
0x00, 0x40, 0x44, 0x7f, 0x7f, 0x40, 0x40, 0x00, // 1
0x00, 0x62, 0x73, 0x51, 0x49, 0x4f, 0x46, 0x00, // 2
0x00, 0x22, 0x63, 0x49, 0x49, 0x7f, 0x36, 0x00, // 3
0x00, 0x18, 0x18, 0x14, 0x16, 0x7f, 0x7f, 0x10, // 4
0x00, 0x27, 0x67, 0x45, 0x45, 0x7d, 0x39, 0x00, // 5
0x00, 0x3e, 0x7f, 0x49, 0x49, 0x7b, 0x32, 0x00, // 6
0x00, 0x03, 0x03, 0x79, 0x7d, 0x07, 0x03, 0x00, // 7
0x00, 0x36, 0x7f, 0x49, 0x49, 0x7f, 0x36, 0x00, // 8
0x00, 0x26, 0x6f, 0x49, 0x49, 0x7f, 0x3e, 0x00, // 9
0x00, 0x00, 0x00, 0x24, 0x24, 0x00, 0x00, 0x00, // :
0x00, 0x00, 0x80, 0xe4, 0x64, 0x00, 0x00, 0x00, // ;
0x00, 0x08, 0x1c, 0x36, 0x63, 0x41, 0x41, 0x00, // <
0x00, 0x14, 0x14, 0x14, 0x14, 0x14, 0x14, 0x00, // =
0x00, 0x41, 0x41, 0x63, 0x36, 0x1c, 0x08, 0x00, // >
0x00, 0x02, 0x03, 0x51, 0x59, 0x0f, 0x06, 0x00, // ?
0x00, 0x3e, 0x7f, 0x41, 0x4d, 0x4f, 0x2e, 0x00, // @
0x00, 0x7c, 0x7e, 0x0b, 0x0b, 0x7e, 0x7c, 0x00, // A
0x00, 0x7f, 0x7f, 0x49, 0x49, 0x7f, 0x36, 0x00, // B
0x00, 0x3e, 0x7f, 0x41, 0x41, 0x63, 0x22, 0x00, // C
0x00, 0x7f, 0x7f, 0x41, 0x63, 0x3e, 0x1c, 0x00, // D
0x00, 0x7f, 0x7f, 0x49, 0x49, 0x41, 0x41, 0x00, // E
0x00, 0x7f, 0x7f, 0x09, 0x09, 0x01, 0x01, 0x00, // F
0x00, 0x3e, 0x7f, 0x41, 0x49, 0x7b, 0x3a, 0x00, // G
0x00, 0x7f, 0x7f, 0x08, 0x08, 0x7f, 0x7f, 0x00, // H
0x00, 0x00, 0x41, 0x7f, 0x7f, 0x41, 0x00, 0x00, // I
0x00, 0x20, 0x60, 0x41, 0x7f, 0x3f, 0x01, 0x00, // J
0x00, 0x7f, 0x7f, 0x1c, 0x36, 0x63, 0x41, 0x00, // K
0x00, 0x7f, 0x7f, 0x40, 0x40, 0x40, 0x40, 0x00, // L
0x00, 0x7f, 0x7f, 0x06, 0x0c, 0x06, 0x7f, 0x7f, // M
0x00, 0x7f, 0x7f, 0x0e, 0x1c, 0x7f, 0x7f, 0x00, // N
0x00, 0x3e, 0x7f, 0x41, 0x41, 0x7f, 0x3e, 0x00, // O
0x00, 0x7f, 0x7f, 0x09, 0x09, 0x0f, 0x06, 0x00, // P
0x00, 0x1e, 0x3f, 0x21, 0x61, 0x7f, 0x5e, 0x00, // Q
0x00, 0x7f, 0x7f, 0x19, 0x39, 0x6f, 0x46, 0x00, // R
0x00, 0x26, 0x6f, 0x49, 0x49, 0x7b, 0x32, 0x00, // S
0x00, 0x01, 0x01, 0x7f, 0x7f, 0x01, 0x01, 0x00, // T
0x00, 0x3f, 0x7f, 0x40, 0x40, 0x7f, 0x3f, 0x00, // U
0x00, 0x1f, 0x3f, 0x60, 0x60, 0x3f, 0x1f, 0x00, // V
0x00, 0x7f, 0x7f, 0x30, 0x18, 0x30, 0x7f, 0x7f, // W
0x00, 0x63, 0x77, 0x1c, 0x1c, 0x77, 0x63, 0x00, // X
0x00, 0x07, 0x0f, 0x78, 0x78, 0x0f, 0x07, 0x00, // Y
0x00, 0x61, 0x71, 0x59, 0x4d, 0x47, 0x43, 0x00, // Z
0x00, 0x00, 0x7f, 0x7f, 0x41, 0x41, 0x00, 0x00, // [
0x00, 0x02, 0x06, 0x0c, 0x18, 0x30, 0x60, 0x40,
0x00, 0x00, 0x41, 0x41, 0x7f, 0x7f, 0x00, 0x00, // ]
0x00, 0x08, 0x0c, 0xfe, 0xfe, 0x0c, 0x08, 0x00, // ^
0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, // _
0x00, 0x01, 0x03, 0x06, 0x04, 0x00, 0x00, 0x00, // '
0x00, 0x20, 0x74, 0x54, 0x54, 0x7c, 0x78, 0x00, // a
0x00, 0x7e, 0x7e, 0x48, 0x48, 0x78, 0x30, 0x00, // b
0x00, 0x38, 0x7c, 0x44, 0x44, 0x44, 0x00, 0x00, // c
0x00, 0x30, 0x78, 0x48, 0x48, 0x7e, 0x7e, 0x00, // d
0x00, 0x38, 0x7c, 0x54, 0x54, 0x5c, 0x18, 0x00, // e
0x00, 0x00, 0x08, 0x7c, 0x7e, 0x0a, 0x0a, 0x00, // f
0x00, 0x98, 0xbc, 0xa4, 0xa4, 0xfc, 0x7c, 0x00, // g
0x00, 0x7e, 0x7e, 0x08, 0x08, 0x78, 0x70, 0x00, // h
0x00, 0x00, 0x48, 0x7a, 0x7a, 0x40, 0x00, 0x00, // i
0x00, 0x00, 0x80, 0x80, 0x80, 0xfa, 0x7a, 0x00, // j
0x00, 0x7e, 0x7e, 0x10, 0x38, 0x68, 0x40, 0x00, // k
0x00, 0x00, 0x42, 0x7e, 0x7e, 0x40, 0x00, 0x00, // l
0x00, 0x7c, 0x7c, 0x18, 0x38, 0x1c, 0x7c, 0x78, // m
0x00, 0x7c, 0x7c, 0x04, 0x04, 0x7c, 0x78, 0x00, // n
0x00, 0x38, 0x7c, 0x44, 0x44, 0x7c, 0x38, 0x00, // o
0x00, 0xfc, 0xfc, 0x24, 0x24, 0x3c, 0x18, 0x00, // p
0x00, 0x18, 0x3c, 0x24, 0x24, 0xfc, 0xfc, 0x00, // q
0x00, 0x7c, 0x7c, 0x04, 0x04, 0x0c, 0x08, 0x00, // r
0x00, 0x48, 0x5c, 0x54, 0x54, 0x74, 0x24, 0x00, // s
0x00, 0x04, 0x04, 0x3e, 0x7e, 0x44, 0x44, 0x00, // t
0x00, 0x3c, 0x7c, 0x40, 0x40, 0x7c, 0x7c, 0x00, // u
0x00, 0x1c, 0x3c, 0x60, 0x60, 0x3c, 0x1c, 0x00, // v
0x00, 0x1c, 0x7c, 0x70, 0x38, 0x70, 0x7c, 0x1c, // w
0x00, 0x44, 0x6c, 0x38, 0x38, 0x6c, 0x44, 0x00, // x
0x00, 0x9c, 0xbc, 0xa0, 0xe0, 0x7c, 0x3c, 0x00, // y
0x00, 0x44, 0x64, 0x74, 0x5c, 0x4c, 0x44, 0x00, // z
0x00, 0x08, 0x3e, 0x77, 0x41, 0x41, 0x00, 0x00, // {
0x00, 0x00, 0x00, 0xff, 0xff, 0x00, 0x00, 0x00, // |
0x00, 0x00, 0x41, 0x41, 0x77, 0x3e, 0x08, 0x00, // }
0x00, 0x04, 0x02, 0x02, 0x04, 0x04, 0x02, 0x00, // ~
#define FONT_W 8
#define FONT_H 2
#define FONT_STRETCHV 1
#define FONT_STRETCHH 0
#define FONT_W 8
#define FONT_H 2
#define FONT_STRETCHV 1
#define FONT_STRETCHH 0
*/
//#define INVERSE 1
static const uint8_t oled_init_sequence [] PROGMEM = { // Initialization Sequence
https://cdn-shop.adafruit.com/datasheets/SSD1306.pdf
//0xAE, // Display OFF (sleep mode)
0xD5, 0x80, // 0x01--set display clock divide ratio/oscillator frequency OK?
(0x80 (or >=0x10) needed when multiplex ration set to 0x3F)
#ifdef CONDENSED
0xA8, 0x3F, // Set multiplex ratio(1 to 64) 128x64
#else
0xA8, 0x1F, // Set multiplex ratio(1 to 64) 128x32
#endif
0xD3, 0x00, // Set display offset. 00 = no offset
#ifndef OLED_SH1106 // for SSD1306 only:
0x40, // Set display start line address
0x8D, 0x14, // Set charge pump, internal VCC
0x20, 0x02, // Set Memory Addressing; 0=Horizontal Mode; 1=Vertical Mode;
2=Page Mode
0xA4, // Output RAM to Display (display all on resume) 0xA4=Output follows
RAM content; 0xA5,Output ignores RAM content
#endif //!OLED_SH1106
0xA1, // Set Segment Re-map. A0=column 0 mapped to SEG0; A1=column 127
mapped to SEG0. Flip Horizontally
0xC8, // Set COM Output Scan Direction. Flip Veritically.
#ifdef CONDENSED
0xDA, 0x12, // Set com pins hardware configuration 128x64
#else
0xDA, 0x02, // Set com pins hardware configuration 128x32
#endif
0x81, 0x80, // Set contrast control register
0xDB, 0x40, // Set vcomh 0x20 = 0.77xVcc
0xD9, 0xF1, // 0xF1=brighter //0x22 Set pre-charge period
0xB0 | 0x0, // Set page address, 0-7
#ifdef OLED_SH1106
0xAD, 0x8B, // SH1106 Set pump mode: pump ON
0x30 | 0x2, // SH1106 Pump voltage 8.0V
#endif //OLED_SH1106
#ifdef INVERSE
0xA7, // Set display mode: Inverse
#else
0xA6, // Set display mode: Normal
#endif
//0x00, // Set low nibble of column address
//0x10, // Set high nibble of column address
0xAF, // Display ON
};
void newLine() {
oledY+=FONT_H;
if(oledY > OLED_PAGES - FONT_H) {
oledY = OLED_PAGES - FONT_H;
}
_setCursor(0, oledY);
}
size_t write(byte c) {
if((c == '\n') || (oledX > ((uint8_t)128 - FONT_W)) ) {
if(wrap) newLine();
return 1;
}
//if(oledY > OLED_PAGES - FONT_H) return; //needed?
c = ((c < 9) ? (c + '~') : c) - ' ';
void bitmap(uint8_t x0, uint8_t y0, uint8_t x1, uint8_t y1, const uint8_t
bitmap[]) {
uint16_t j = 0;
for (uint8_t y = y0; y < y1; y++) {
_setCursor(x0, y);
Wire.beginTransmission(OLED_ADDR); Wire.write(OLED_DATA);
for (uint8_t x = x0; x < x1; x++) {
Wire.write(pgm_read_byte(&bitmap[j++]));
}
Wire.endTransmission();
}
setCursor(0, 0);
}
};
I2C(){
I2C_PORT &= ~( I2C_SDA | I2C_SCL );
I2C_SCL_HI();
I2C_SDA_HI();
#ifndef RS_HIGH_ON_IDLE
suspend();
#endif
}
~I2C(){
I2C_PORT &= ~( I2C_SDA | I2C_SCL );
I2C_DDR &= ~( I2C_SDA | I2C_SCL );
}
inline void start(){
#ifdef RS_HIGH_ON_IDLE
I2C_SDA_LO();
#else
resume(); //prepare for I2C
#endif
I2C_SCL_LO();
I2C_SDA_HI();
}
inline void stop(){
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(); // recv ACK \
DELAY(I2C_DELAY); \
I2C_SCL_HI(); \
I2C_SCL_LO();*/
inline void SendByte(uint8_t 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(); // recv ACK
DELAY(I2C_DELAY);
I2C_SCL_HI();
I2C_SCL_LO();
}
inline uint8_t RecvBit(uint8_t mask){
I2C_SCL_HI();
uint16_t i = 60000;
for(;!(I2C_SCL_GET()) && i; i--); // wait util slave release SCL to HIGH
(meaning data valid), or timeout at 3ms
//if(!i){ lcd.setCursor(0, 1); lcd.print(F("E07 I2C timeout")); }
uint8_t data = I2C_SDA_GET();
I2C_SCL_LO();
return (data) ? mask : 0;
}
inline uint8_t RecvByte(uint8_t last){
uint8_t data = 0;
data |= RecvBit(1 << 7);
data |= RecvBit(1 << 6);
data |= RecvBit(1 << 5);
data |= RecvBit(1 << 4);
data |= RecvBit(1 << 3);
data |= RecvBit(1 << 2);
data |= RecvBit(1 << 1);
data |= RecvBit(1 << 0);
if(last){
I2C_SDA_HI(); // NACK
DELAY(I2C_DELAY);
I2C_SCL_LO();
} else {
I2C_SDA_LO(); // ACK
I2C_SCL_HI();
DELAY(I2C_DELAY);
I2C_SCL_LO(); // DELAY(I2C_DELAY);
I2C_SDA_HI();
}
return data;
}
inline void resume(){
#ifdef LCD_RS_PORTIO
I2C_PORT &= ~I2C_SDA; // pin sharing SDA/LCD_RS mitigation
#endif
}
inline void suspend(){
I2C_SDA_LO(); // pin sharing SDA/LCD_RS: pull-down LCD_RS;
QCXLiquidCrystal require this for any operation
}
void begin(){};
void beginTransmission(uint8_t addr){ start(); SendByte(addr << 1); };
bool write(uint8_t byte){ SendByte(byte); return 1; };
uint8_t endTransmission(){ stop(); return 0; };
};
// /*
I2C i2c;
class SI5351 {
public:
volatile int32_t _fout;
volatile uint8_t _div; // note: uint8_t asserts fout > 3.5MHz with R_DIV=1
volatile uint16_t _msa128min512;
volatile uint32_t _msb128;
//volatile uint32_t _mod;
volatile uint8_t pll_regs[8];
#define NEW_TX 1
#ifdef NEW_TX
inline void FAST freq_calc_fast(int16_t df) // note: relies on cached variables:
_msb128, _msa128min512, _div, _fout, fxtal
{
#define _MSC 0x10000
uint32_t msb128 = _msb128 + ((int64_t)(_div * (int32_t)df) * _MSC * 128) /
fxtal;
//#define _MSC 0xFFFFF // Old algorithm 114% CPU load, shortcut for a fixed
fxtal=27e6
//register uint32_t xmsb = (_div * (_fout + (int32_t)df)) % fxtal; // xmsb =
msb * fxtal/(128 * _MSC);
//uint32_t msb128 = xmsb * 5*(32/32) - (xmsb/32); // msb128 = xmsb * 159/32,
where 159/32 = 128 * 0xFFFFF / fxtal; fxtal=27e6
void reset(){ SendRegister(177, 0xA0); } // 0x20 reset PLLA; 0x80 reset PLLB
#ifdef x
ms(MSNA, fvcoa, fxtal);
ms(MSNB, fvcoa, fxtal);
#define F_DEV 4
ms(MS0, fvcoa, (fout + F_DEV), PLLA, 0, 0, rdiv);
ms(MS1, fvcoa, (fout + F_DEV), PLLA, 0, 0, rdiv);
ms(MS2, fvcoa, fout, PLLA, 0, 0, rdiv);
reset();
ms(MS0, fvcoa, fout, PLLA, 0, 0, rdiv);
delayMicroseconds(F_MCU/16000000 * 1000000UL/F_DEV); // Td = 1/(4 * Fdev)
phase-shift https://tj-lab.org/2020/08/27/si5351%e5%8d%98%e4%bd%93%e3%81%a73mhz
%e4%bb%a5%e4%b8%8b%e3%81%ae%e7%9b%b4%e4%ba%a4%e4%bf%a1%e5%8f%b7%e3%82%92%e5%87%ba
%e5%8a%9b%e3%81%99%e3%82%8b/
ms(MS1, fvcoa, fout, PLLA, 0, 0, rdiv);
oe(0b00000011); // output enable CLK0, CLK1
#endif
_fout = fout; // cache
_div = d;
_msa128min512 = fvcoa / fxtal * 128 - 512;
_msb128=((uint64_t)(fvcoa % fxtal)*_MSC*128) / fxtal;
//_mod = fvcoa % fxtal;
}
//*/
/*
void freq(uint32_t fout, uint16_t i, uint16_t q){ // Set a CLK0,1 to fout Hz
with phase i, q
uint16_t msa; uint32_t msb, msc, msp1, msp2, msp3;
uint8_t rdiv = 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
};
static SI5351 si5351;
// */
/*
class SI5351 : public I2C {
public:
#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
volatile uint32_t fxtal = 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
SI5351(){
init();
iqmsa = 0;
}
uint8_t RecvRegister(uint8_t reg)
{
// 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_t data = RecvByte(true);
stop();
return data;
}
void SendRegister(uint8_t reg, uint8_t data)
{
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
// r_div = 1..128 (1,2,4,8,16,32,64,128)
void SetupMultisynth(uint8_t reg, uint8_t divider, uint32_t num, uint32_t denom,
uint8_t r_div)
{
uint32_t P1; // Synth config register P1
uint32_t P2; // Synth config register P2
uint32_t P3; // Synth config register P3
// Set up specified PLL with mult, num and denom: mult is 15..90, num128 is
0..128*1,048,575 (128*0xFFFFF), denom is 0..1,048,575 (0xFFFFF)
uint32_t P1 = 128 * mult + (num128 / 0xFFFFF) - 512;
uint32_t P2 = num128 % 0xFFFFF;
//pll_regs[0] = 0xFF;
//pll_regs[1] = 0xFF;
//pll_regs[2] = (P1 >> 14) & 0x0C;
pll_regs[3] = P1 >> 8;
pll_regs[4] = P1;
pll_regs[5] = 0xF0 | (P2 >> 16);
pll_regs[6] = P2 >> 8;
pll_regs[7] = P2;
}
uint16_t div(uint32_t num, uint32_t denom, uint32_t* b, uint32_t* c)
{ // returns a + b / c = num / denom, where a is the integer part and b and c is
the optional fractional part 20 bits each (range 0..1048575)
uint16_t a = num / denom;
if(b && c){
uint64_t l = num % denom;
l <<= 20; l--; // l *= 1048575;
l /= denom; // normalize
*b = l;
*c = 0xFFFFF; // for simplicity set c to the maximum 1048575
}
return a;
}
void freq(uint32_t freq, uint8_t i, uint8_t q)
{ // Fout = Fvco / (R * [MSx_a + MSx_b/MSx_c]), Fvco = Fxtal * [MSPLLx_a +
MSPLLx_b/MSPLLx_c]; MSx as integer reduce spur
//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_t r_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()
// 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
SetupMultisynth(SI_SYNTH_PLL_A, mult, num, denom, 1);
SetupMultisynth(SI_SYNTH_PLL_B, mult, num, denom, 1);
//if(denom == 1) SendRegister(22, SI_MSx_INT); // FBA_INT: MSNA operates in
integer mode
//if(denom == 1) SendRegister(23, SI_MSx_INT); // FBB_INT: MSNB operates in
integer mode
// Set up MultiSynth 0,1,2 with the calculated divider, from 4, 6..1800.
// The final R division stage can divide by a power of two, from 1..128
// if you want to output frequencies below 1MHz, you have to use the final R
division stage
SetupMultisynth(SI_SYNTH_MS_0, divider, 0, 1, r_div);
SetupMultisynth(SI_SYNTH_MS_1, divider, 0, 1, r_div);
SetupMultisynth(SI_SYNTH_MS_2, divider, 0, 1, r_div);
//if(prev_divider != divider){ lcd.setCursor(0, 0); lcd.print(divider);
lcd.print(F(" "));
// Set I/Q phase
SendRegister(SI_CLK0_PHOFF, i * divider / 90); // one LSB equivalent to a time
delay of Tvco/4 range 0..127
SendRegister(SI_CLK1_PHOFF, q * divider / 90); // one LSB equivalent to a time
delay of Tvco/4 range 0..127
// Switch on the CLK0, CLK1 output to be PLL A and set multiSynth0, multiSynth1
input (0x0F = SI_CLK_SRC_MS | SI_CLK_IDRV_8MA)
SendRegister(SI_CLK0_CONTROL, 0x0F | SI_MS_INT | SI_CLK_SRC_PLL_A);
SendRegister(SI_CLK1_CONTROL, 0x0F | SI_MS_INT | SI_CLK_SRC_PLL_A);
// Switch on the CLK2 output to be PLL B and set multiSynth2 input
SendRegister(SI_CLK2_CONTROL, 0x0F | SI_MS_INT | SI_CLK_SRC_PLL_B);
SendRegister(SI_CLK_OE, 0b11111100); // Enable CLK1|CLK0
// Reset the PLL. This causes a glitch in the output. For small changes to
// the parameters, you don't need to reset the PLL, and there is no glitch
if((abs(pll_freq - iqmsa) > 16000000L) || divider != prev_divider){
iqmsa = pll_freq;
prev_divider = divider;
SendRegister(SI_PLL_RESET, 0xA0);
}
//SendRegister(24, 0b00000000); // CLK3-0 Disable State: CLK2=0 (BE CAREFUL TO
CHANGE THIS!!!), CLK1/0=00 -> IC4-X0 selected -> 2,5V on IC5A/3(+), when IC5/2(-)
leaks down below 2.5V -> 12V on IC5A/1, IC6A/2(-) -> 0V on IC6A/1, AUDIO2
}
void alt_clk2(uint32_t freq)
{
uint32_t num, denom;
uint16_t mult = div(pll_freq, freq, &num, &denom);
#ifdef LPF_SWITCHING_DL2MAN_USDX_REV1
class PCA9536 {
public:
#define PCA9536_ADDR 0x41 // PCA9536
https://www.ti.com/lit/ds/symlink/pca9536.pdf
inline void SendRegister(uint8_t reg, uint8_t val){ i2c.begin();
i2c.beginTransmission(PCA9536_ADDR); i2c.write(reg); i2c.write(val);
i2c.endTransmission(); }
inline void init(){ SendRegister(0x03, 0x00); } // configuration cmd: IO0-IO7 as
output
inline void write(uint8_t data){ init(); SendRegister(0x01, data); } // output
port cmd: write bits D7-D0 to IO7-IO0
};
PCA9536 ioext;
void set_latch(uint8_t io){ // reset all latches and set latch k to corresponding
GPIO, all relays share a common (ground) GPIO
#define LATCH_TIME 15 // set/reset time latch relay
for(int i = 0; i != 8; i++){ ioext.write( (~(1 << i))| 0x01);
delay(LATCH_TIME); } ioext.write(0x00); // reset all latches
ioext.write((1 << io)| 0x00); delay(LATCH_TIME); ioext.write(0x00); // set latch
wired to io port
}
#if defined(LPF_SWITCHING_DL2MAN_USDX_REV3) ||
defined(LPF_SWITCHING_DL2MAN_USDX_REV2) ||
defined(LPF_SWITCHING_DL2MAN_USDX_REV2_BETA)
class IOExpander16 {
public:
#ifdef LPF_SWITCHING_DL2MAN_USDX_REV2_BETA
#define IOEXP16_ADDR 0x74 // PCA9539 with A1..A0 set to 0
https://www.nxp.com/docs/en/data-sheet/PCA9539_PCA9539R.pdf
#endif
#ifdef LPF_SWITCHING_DL2MAN_USDX_REV2
#define IOEXP16_ADDR 0x24 // TCA/PCA9555 with A2=1 A1..A0=0
https://www.ti.com/lit/ds/symlink/tca9555.pdf
#endif
#ifdef LPF_SWITCHING_DL2MAN_USDX_REV3
#define IOEXP16_ADDR 0x20 // TCA/PCA9555 with A2=0 A1..A0=0
https://www.ti.com/lit/ds/symlink/tca9555.pdf
#endif
inline void SendRegister(uint8_t reg, uint8_t val){ i2c.begin();
i2c.beginTransmission(IOEXP16_ADDR); i2c.write(reg); i2c.write(val);
i2c.endTransmission(); }
inline void init(){ write(0U); } //IO0, IO1 as input, IO0 to 0, IO0 as output,
IO1 to 0, IO1 as output
inline void write(uint16_t data){ SendRegister(0x07, 0xff); SendRegister(0x06,
0xff);/*Common last!*/ SendRegister(0x02, data); SendRegister(0x06, 0x00);/*Common
first!*/ SendRegister(0x03, data >> 8); SendRegister(0x07, 0x00); } // output port
cmd: write bits D15-D0 to IO1.7-0.0;
};
IOExpander16 ioext;
enum gpioext_t { IO0_0, IO0_1, IO0_2, IO0_3, IO0_4, IO0_5, IO0_6, IO0_7, IO1_0,
IO1_1, IO1_2, IO1_3, IO1_4, IO1_5, IO1_6, IO1_7 };
void set_latch(uint8_t io, uint8_t common_io, bool latch = 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(int io = 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
}
}
#endif
}
#endif //LPF_SWITCHING_DL2MAN_USDX_REV3 LPF_SWITCHING_DL2MAN_USDX_REV2 REV2_BETA
#ifdef LPF_SWITCHING_WB2CBA_USDX_OCTOBAND
class MCP23008 {
public:
#define MCP23008_ADDR 0x20 // MCP23008 with A1..A0 set to 0
https://ww1.microchip.com/downloads/en/DeviceDoc/21919e.pdf
inline void SendRegister(uint8_t reg, uint8_t val){ i2c.begin();
i2c.beginTransmission(MCP23008_ADDR); i2c.write(reg); i2c.write(val);
i2c.endTransmission(); }
inline void init(){ SendRegister(0x09, 0x00); SendRegister(0x00, 0x00); } //GP0-7
to 0, GP0-7 as output
inline void write(uint16_t data){ SendRegister(0x09, data); } // output port
cmd: write bits D7-D0 to GP7-GP0
};
MCP23008 ioext;
#if defined(LPF_SWITCHING_PE1DDA_USDXDUO)
inline void set_lpf(uint8_t f){
pinMode(PD5, OUTPUT);
digitalWrite(PD5, (f >= LPF_SWITCHING_PE1DDA_USDXDUO));
}
#endif //LPF_SWITCHING_PE1DDA_USDXDUO
#ifdef DEBUG
static uint32_t sr = 0;
static uint32_t cpu_load = 0;
volatile uint16_t param_a = 0; // registers for debugging, testing and
experimental purposes
volatile int16_t param_b = 0;
volatile int16_t param_c = 0;
#endif
volatile uint8_t tx = 0;
volatile uint8_t filt = 0;
inline void _vox(bool trigger)
{
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)
const int16_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
uint8_t lut[256];
volatile uint8_t amp;
#define MORE_MIC_GAIN 1 // adds more microphone gain, improving overall SSB
quality (when speaking further away from microphone)
#ifdef MORE_MIC_GAIN
volatile uint8_t vox_thresh = (1 << 2);
#else
volatile uint8_t vox_thresh = (1 << 1); //(1 << 2);
#endif
volatile uint8_t drive = 2; // hmm.. drive>2 impacts cpu load..why?
int16_t i, q;
uint8_t j;
static int16_t v[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_t ac = 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_t ac = 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)
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)
#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;
#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)
return dp * ( _F_SAMP_TX / _UA); // calculate frequency-difference based on
phase-difference
else
return dp * (-_F_SAMP_TX / _UA);
}
#define MIC_ATTEN 0 // 0*6dB attenuation (note that the LSB bits are quite noisy)
volatile int8_t mox = 0;
volatile int8_t volume = 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.
static int16_t _adc;
void dsp_tx()
{ // jitter dependent things first
#ifdef MULTI_ADC // SSB with multiple ADC conversions:
int16_t adc; // 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
#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_t df = 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_t adc = ADC - 512; // current ADC sample 10-bits analog input, NOTE: first
ADCL, then ADCH
int16_t df = 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
#endif
#ifdef CARRIER_COMPLETELY_OFF_ON_LOW
if(tx == 1){ OCR1BL = 0; si5351.SendRegister(SI_CLK_OE, TX0RX0); } // disable
carrier
if(tx == 255){ si5351.SendRegister(SI_CLK_OE, TX1RX0); } // enable carrier
#endif
#ifdef MOX_ENABLE
if(!mox) return;
OCR1AL = (adc << (mox-1)) + 128; // TX audio monitoring
#endif
}
void dummy()
{
}
void dsp_tx_cw()
{ // jitter dependent things first
#ifdef KEY_CLICK
if(OCR1BL < lut[255]) { //check if already ramped up: ramp up of amplitude
for(uint16_t i = 31; i != 0; i--) { // soft rising slope against key-clicks
OCR1BL = lut[pgm_read_byte_near(&ramp[i-1])];
delayMicroseconds(60);
}
}
#endif // KEY_CLICK
OCR1BL = lut[255];
process_minsky();
OCR1AL = (p_sin >> (16 - volume)) + 128;
}
void dsp_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_t adc = ADC - 512; // current ADC sample 10-bits analog input, NOTE: first
ADCL, then ADCH
int16_t in = (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];
}
void dsp_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_t adc = ADC - 512; // current ADC sample 10-bits analog input, NOTE: first
ADCL, then ADCH
int16_t in = (adc >> MIC_ATTEN);
in = in << (drive);
int16_t df = in;
si5351.freq_calc_fast(df); // calculate SI5351 registers based on
frequency shift and carrier frequency
}
#ifdef SWR_METER
volatile uint8_t swrmeter = 1;
#endif
#ifdef CW_MESSAGE
#define MENU_STR 1
#ifdef CW_DECODER
volatile uint8_t cwdec = 1;
static int32_t avg = 256;
static uint8_t sym;
static uint32_t amp32 = 0;
volatile uint32_t _amp32 = 0;
static char out[] = " ";
volatile uint8_t cw_event = false;
dec2();
realstatebefore = realstate;
}
if(filteredstate == HIGH){
starttimehigh = millis();
lowduration = (millis() - startttimelow);
}
if(filteredstate == LOW){
startttimelow = millis();
highduration = (millis() - starttimehigh);
if(highduration < (2*hightimesavg) || hightimesavg == 0){
hightimesavg = (highduration+hightimesavg+hightimesavg)/3; // now we know
avg dit time ( rolling 3 avg)
}
if(highduration > (5*hightimesavg)){
hightimesavg = highduration/3; // if speed decrease fast ..
//hightimesavg = highduration+hightimesavg; // if speed decrease fast ..
}
}
}
// now we will check which kind of baud we have - dit or dah, and what kind of
pause we do have 1 - 3 or 7 pause, we think that hightimeavg = 1 bit
if(filteredstate != filteredstatebefore){
if(filteredstate == LOW){ //// we did end a HIGH
#define FAIR_WEIGHTING 1
#ifdef FAIR_WEIGHTING
if(highduration < (hightimesavg + hightimesavg/2) && highduration >
(hightimesavg*6/10)){ /// 0.6 filter out false dits
#else
if(highduration < (hightimesavg*2) && highduration > (hightimesavg*6/10)){ ///
0.6 filter out false dits
#endif
sym=(sym<<1)|(0); // insert dit (0)
}
#ifdef FAIR_WEIGHTING
if(highduration > (hightimesavg + hightimesavg/2) && highduration <
(hightimesavg*6)){
#else
if(highduration > (hightimesavg*2) && highduration < (hightimesavg*6)){
#endif
sym=(sym<<1)|(1); // insert dah (1)
wpm = (wpm + (1200/((highduration)/3) * 4/3))/2;
}
}
#ifdef FAIR_WEIGHTING
if(lowduration > (hightimesavg*(lacktime*1/10)) && lowduration <
hightimesavg*(lacktime*5/10)){ // letter space
#else
if(lowduration > (hightimesavg*(lacktime*7/80)) && lowduration <
hightimesavg*(lacktime*5/10)){ // letter space
//if(lowduration > (hightimesavg*(lacktime*2/10)) && lowduration <
hightimesavg*(lacktime*5/10)){ // letter space
#endif
printsym();
}
if(lowduration >= hightimesavg*(lacktime*5/10)){ // word space
printsym();
printsym(); // print space
}
}
}
filteredstatebefore = filteredstate;
}
#else // OLD_CW
void dec2()
{
if(filteredstate != filteredstatebefore){ // then we do want to have some
durations on high and low
if(menumode == 0){ lcd.noCursor(); lcd.setCursor(15, 1);
lcd.print((filteredstate) ? 'R' : ' '); stepsize_showcursor(); }
if(filteredstate == HIGH){
starttimehigh = millis();
lowduration = (millis() - startttimelow);
//highduration = 0;
if(filteredstate == LOW){
startttimelow = millis();
highduration = (millis() - starttimehigh);
//lowduration = 0;
if(highduration < (2*hightimesavg) || hightimesavg == 0){
hightimesavg = (highduration+hightimesavg+hightimesavg)/3; // now we know
avg dit time (rolling 3 avg)
}
if(highduration > (5*hightimesavg)){
hightimesavg = highduration/3; // if speed decrease fast ..
//hightimesavg = highduration+hightimesavg; // if speed decrease fast ..
}
if(highduration > (hightimesavg/2)){ sym=(sym<<1)|(highduration >
(hightimesavg*2)); // dit (0) or dash (1)
#if defined(CW_INTERMEDIATE) && !defined(OLED) && !defined(LCD_I2C) && (F_MCU >=
20000000)
printsym(false);
#endif
}
}
}
filteredstatebefore = filteredstate;
}
#endif //OLD_CW
#endif //CW_DECODER
#ifdef FAST_AGC
volatile uint8_t agc = 2;
#else
volatile uint8_t agc = 1;
#endif
volatile uint8_t nr = 0;
volatile uint8_t att = 0;
volatile uint8_t att2 = 2; // Minimum att2 increased, to prevent numeric overflow
on strong signals
#ifdef SWR_METER
volatile uint8_t calpwr = PWR_CALIBRATION_CONSTANT;
#ifdef INA219_POWER_METER
volatile uint16_t calshunt = CURRENT_SHUNT_CALIBRATION_CONSTANT;
#endif
#endif
// 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.
static int16_t gain = 1024;
inline int16_t process_agc_fast(int16_t in)
{
int16_t out = (gain >= 1024) ? (gain >> 10) * in : in;
int16_t accum = (1 - abs(out >> 10));
if((INT16_MAX - gain) > accum) gain = gain + accum;
if(gain < 1) gain = 1;
return out;
}
return ea1;
}
/*
int32_t _avg = 64 * avg;
// if(_avg > 4) _avg = 4; // clip
// uint16_t brs_avgsq = 1 << (_avg * _avg);
if(_avg > 14) _avg = 14; // clip
uint16_t brs_avgsq = 1 << (_avg);
int16_t inv_gain;
if(brs_avgsq > 1) inv_gain = brs_avgsq / (brs_avgsq - 1); // = 1 / (1 - 1/(1 <<
(1*avg*avg)) );
else inv_gain = 32768;*/
return ea1;
}
/*
inline int16_t process_nr(int16_t in)
{
// Exponential moving average and variance (Lyons 13.36.2)
param_b = EA(param_b, in, 1 << 4); // avg
param_c = EA(param_c, (in - param_b) * (in - param_b), 1 << 4); // variance
}
*/
#include "usdx_filter.h"
if(mode == AM) {
ac = magn(i, q);
{ static int16_t dc; // DC decoupling
dc += (ac - dc) / 2;
ac = ac - dc; }
} else if(mode == FM){
static int16_t zi;
ac = ((ac + i) * zi); // -qh = ac + i
zi =i;
/*int16_t z0 = _arctan3(q, i);
static int16_t z1;
ac = z0 - z1; // Differentiator
z1 = z0;*/
/*static int16_t _q;
_q = (_q + q) / 2;
ac = i * _q; // quadrature detector */
//ac = ((q > 0) == !(i > 0)) ? 128 : -128; // XOR I/Q zero-cross detector
} // needs: p.12
https://www.veron.nl/wp-content/uploads/2014/01/FmDemodulator.pdf
else { ; } // USB, LSB, CW
#ifdef FAST_AGC
if(agc == 2) {
ac = process_agc(ac);
ac = ac >> (16-volume);
} else if(agc == 1){
ac = process_agc_fast(ac);
ac = ac >> (16-volume);
#else
if(agc == 1){
ac = process_agc_fast(ac);
ac = ac >> (16-volume);
#endif //!FAST_AGC
} else {
//ac = ac >> (16-volume);
if(volume <= 13) // if no AGC allow volume control to boost weak signals
ac = ac >> (13-volume);
else
ac = ac << (volume-13);
}
if(nr) ac = process_nr(ac);
}
#ifdef TESTBENCH
// Sine table with 72 entries results in 868Hz sine wave at effective sampling rate
of 31250 SPS
// for each of I and Q, since thay are sampled alternately, and hence I (for
example) only
// gets 36 samples from this table before looping.
const int8_t sine[] = {
11, 22, 33, 43, 54, 64, 73, 82, 90, 97, 104, 110, 115, 119, 123, 125, 127, 127,
127, 125, 123, 119, 115, 110, 104, 97, 90, 82, 73, 64, 54, 43, 33, 22, 11, 0, -11,
-22, -33, -43, -54, -64, -73, -82, -90, -97, -104, -110, -115, -119, -123, -125, -
127, -127, -127, -125, -123, -119, -115, -110, -104, -97, -90, -82, -73, -64, -54,
-43, -33, -22, -11, 0
};
// Short Sine table with 36 entries results in 1736Hz sine wave at effective
sampling rate of 62500 SPS.
/* const int8_t sine[] = {
22, 43, 64, 82, 97, 110, 119, 125, 127, 125, 119, 110, 97, 82, 64, 43, 22, 0, -
22, -43, -64, -82, -97, -110, -119, -125, -127, -125, -119, -110, -97, -82, -64, -
43, -22, 0
}; */
uint8_t ncoIdx = 0;
int16_t NCO_Q()
{
ncoIdx++;
if(ncoIdx >= sizeof(sine))
ncoIdx = 0;
return (int16_t(sine[ncoIdx])) << 2;
}
int16_t NCO_I()
{
uint8_t i;
ncoIdx++;
if(ncoIdx >= sizeof(sine))
ncoIdx = 0;
//#define SIMPLE_RX 1
#ifndef SIMPLE_RX
volatile uint8_t admux[3];
volatile int16_t ocomb, qh;
volatile uint8_t rx_state = 0;
#pragma GCC push_options
#pragma GCC optimize ("Ofast") // compiler-optimization for speed
/* Basicdsp simulation:
# M=2 FA(z) = 1 + z^-1, FB(z) = 2
# M=3 FA(z) = 1 + 3*z^-1, FB(z) = 3 + z^-1
# M=4 FA(z) = 1 + 6*z^-1 + z^-2, FB(z) = 4 + 4*z^-1
samplerate=28000
x=x+1
clk1=mod1(x/2)*2
y=y+clk1
clk2=mod1(y/2)*2
#s1=clk1*fir(in, 1, 2, 1, 0)/16
#s2=clk2*fir(s1, 1, 0, 2, 0, 1, 0, 0)/16
#s1=clk1*fir(in, 1, 3, 3, 1, 0)/16
#s2=clk2*fir(s1, 1, 0, 3, 0, 3, 0, 1, 0, 0)/16
s1=clk1*fir(in, 1, 4, 6, 4, 1, 0)/16
s2=clk2*fir(s1, 1, 0, 4, 0, 6, 0, 4, 0, 1, 0, 0)/16
out=s2
*/
static uint8_t tc = 0;
void process(int16_t i_ac2, int16_t q_ac2)
{
static int16_t ac3;
#ifdef CAT_STREAMING
//UCSR0B &= ~(TXCIE0); // disable USART TX interrupts
//while (!( UCSR0A & (1<<UDRE0))); // wait for empty buffer
if(cat_streaming){ uint8_t out = ac3 + 128; if(out == ';') out++;
Serial.write(out); } //UDR0 = (uint8_t)(ac3 + 128); // from:
https://www.xanthium.in/how-to-avr-atmega328p-microcontroller-usart-uart-embedded-
programming-avrgcc
#endif // CAT_STREAMING
#ifdef AF_OUT
static int16_t ozd1, ozd2; // Output stage
if(_init){ ac3 = 0; ozd1 = 0; ozd2 = 0; _init = 0; } // hack: on first sample
init accumlators of further stages (to prevent instability)
int16_t od1 = ac3 - ozd1; // Comb section
ocomb = od1 - ozd2;
#endif //AF_OUT
#define OUTLET 1
#ifdef OUTLET
if(tc++ == 0) // prevent recursion
//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
#ifdef AF_OUT
ozd2 = od1;
ozd1 = ac3;
#endif //AF_OUT
int16_t qh;
{
q_ac2 >>= att2; // digital gain control
static int16_t v[14]; // Process Q (down-sampled) samples
// Hilbert transform, BasicDSP model: outi= fir(inl, 0, 0, 0, 0, 0, 0, 0,
1, 0, 0, 0, 0, 0, 0, 0, 0); outq = fir(inr, 2, 0, 8, 0, 21, 0, 79, 0, -79, 0,
-21, 0, -8, 0, -2, 0) / 128;
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] - q_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)
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] = v[7]; v[7] = v[8]; v[8] = v[9]; v[9] = v[10]; v[10] = v[11]; v[11] = v[12];
v[12] = v[13]; v[13] = q_ac2;
}
i_ac2 >>= att2; // digital gain control
static int16_t v[7]; // Post processing I and Q (down-sampled) results
i = i_ac2; q = q_ac2; // tbd: this can be more efficient
int16_t 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] = i_ac2; // Delay to match Hilbert transform on Q branch
ac3 = slow_dsp(-i - qh); //inverting I and Q helps dampening a feedback-loop
between PWM out and ADC inputs
#ifdef OUTLET
tc--;
#endif
}
/*
// # M=3 .. = i0 + 3*(i2 + i3) + i1
int16_t i0, i1, i2, i3, i4, i5, i6, i7, i8;
int16_t q0, q1, q2, q3, q4, q5, q6, q7, q8;
#define M_SR 1
//#define EXPANDED_CIC
#ifdef EXPANDED_CIC
void sdr_rx_00(){ i0 = sdr_rx_common_i(); func_ptr = sdr_rx_01; i4 = (i0
+ (i2 + i3) * 3 + i1) >> M_SR; }
void sdr_rx_02(){ i1 = sdr_rx_common_i(); func_ptr = sdr_rx_03; i8 = (i4
+ (i6 + i7) * 3 + i5) >> M_SR; }
void sdr_rx_04(){ i2 = sdr_rx_common_i(); func_ptr = sdr_rx_05; i5 = (i2
+ (i0 + i1) * 3 + i3) >> M_SR; }
void sdr_rx_06(){ i3 = sdr_rx_common_i(); func_ptr = sdr_rx_07; }
void sdr_rx_08(){ i0 = sdr_rx_common_i(); func_ptr = sdr_rx_09; i6 = (i0
+ (i2 + i3) * 3 + i1) >> M_SR; }
void sdr_rx_10(){ i1 = sdr_rx_common_i(); func_ptr = sdr_rx_11; i8 = (i6
+ (i4 + i5) * 3 + i7) >> M_SR; }
void sdr_rx_12(){ i2 = sdr_rx_common_i(); func_ptr = sdr_rx_13; i7 = (i2
+ (i0 + i1) * 3 + i3) >> M_SR; }
void sdr_rx_14(){ i3 = sdr_rx_common_i(); func_ptr = sdr_rx_15; }
void sdr_rx_15(){ q0 = sdr_rx_common_q(); func_ptr = sdr_rx_00; q4 = (q0
+ (q2 + q3) * 3 + q1) >> M_SR; }
void sdr_rx_01(){ q1 = sdr_rx_common_q(); func_ptr = sdr_rx_02; q8 = (q4
+ (q6 + q7) * 3 + q5) >> M_SR; }
void sdr_rx_03(){ q2 = sdr_rx_common_q(); func_ptr = sdr_rx_04; q5 = (q2
+ (q0 + q1) * 3 + q3) >> M_SR; }
void sdr_rx_05(){ q3 = sdr_rx_common_q(); func_ptr = sdr_rx_06; process(i8,
q8); }
void sdr_rx_07(){ q0 = sdr_rx_common_q(); func_ptr = sdr_rx_08; q6 = (q0
+ (q2 + q3) * 3 + q1) >> M_SR; }
void sdr_rx_09(){ q1 = sdr_rx_common_q(); func_ptr = sdr_rx_10; q8 = (q6
+ (q4 + q5) * 3 + q7) >> M_SR; }
void sdr_rx_11(){ q2 = sdr_rx_common_q(); func_ptr = sdr_rx_12; q7 = (q2
+ (q0 + q1) * 3 + q3) >> M_SR; }
void sdr_rx_13(){ q3 = sdr_rx_common_q(); func_ptr = sdr_rx_14; process(i8,
q8); }
#else
void sdr_rx_00(){ i0 = sdr_rx_common_i(); func_ptr = sdr_rx_01; i4 = (i0
+ (i2 + i3) * 3 + i1) >> M_SR; }
void sdr_rx_02(){ i1 = sdr_rx_common_i(); func_ptr = sdr_rx_03; i8 = (i4
+ (i6 + i7) * 3 + i5) >> M_SR; }
void sdr_rx_04(){ i2 = sdr_rx_common_i(); func_ptr = sdr_rx_05; i5 = (i2
+ (i0 + i1) * 3 + i3) >> M_SR; }
void sdr_rx_06(){ i3 = sdr_rx_common_i(); func_ptr = sdr_rx_07; i6 = i4;
i7 = i5; q6 = q4; q7 = q5; }
void sdr_rx_07(){ q0 = sdr_rx_common_q(); func_ptr = sdr_rx_00; q4 = (q0
+ (q2 + q3) * 3 + q1) >> M_SR; }
void sdr_rx_01(){ q1 = sdr_rx_common_q(); func_ptr = sdr_rx_02; q8 = (q4
+ (q6 + q7) * 3 + q5) >> M_SR; }
void sdr_rx_03(){ q2 = sdr_rx_common_q(); func_ptr = sdr_rx_04; q5 = (q2
+ (q0 + q1) * 3 + q3) >> M_SR; }
void sdr_rx_05(){ q3 = sdr_rx_common_q(); func_ptr = sdr_rx_06; process(i8,
q8); }
#endif
*/
// /*
static int16_t i_s0za1, i_s0zb0, i_s0zb1, i_s1za1, i_s1zb0, i_s1zb1;
static int16_t q_s0za1, q_s0zb0, q_s0zb1, q_s1za1, q_s1zb0, q_s1zb1, q_ac2;
/*
static int16_t i_s0za1, i_s0za2, i_s0zb0, i_s0zb1, i_s1za1, i_s1za2, i_s1zb0,
i_s1zb1;
static int16_t q_s0za1, q_s0za2, q_s0zb0, q_s0zb1, q_s1za1, q_s1za2, q_s1zb0,
q_s1zb1, q_ac2;
/*
#define M_SR 2 // CIC N=3
static uint8_t nested = false;
void sdr_rx()
{
#ifdef TESTBENCH
int16_t adc = NCO_I();
#else
ADMUX = admux[1]; // set MUX for next conversion
ADCSRA |= (1 << ADSC); // start next ADC conversion
int16_t adc = ADC - 511; // current ADC sample 10-bits analog input, NOTE: first
ADCL, then ADCH
#endif
func_ptr = sdr_rx_q; // processing function for next conversion
sdr_rx_common();
{
q_ac2 >>= att2; // digital gain control
static int16_t v[14]; // Process Q (down-sampled) samples
q = v[7];
// Hilbert transform, BasicDSP model: outi= fir(inl, 0, 0, 0, 0, 0, 0,
0, 1, 0, 0, 0, 0, 0, 0, 0, 0); outq = fir(inr, 2, 0, 8, 0, 21, 0, 79, 0, -79,
0, -21, 0, -8, 0, -2, 0) / 128;
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] - q_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)
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] = v[7]; v[7] = v[8]; v[8] = v[9]; v[9] = v[10]; v[10] = v[11]; v[11] =
v[12]; v[12] = v[13]; v[13] = q_ac2;
}
ac2 >>= att2; // digital gain control
static int16_t v[7]; // Post processing I and Q (down-sampled) results
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
ac3 = slow_dsp(i + qh);
//nested--;
return;
}
} else { s1zb1 = s1zb0; s1zb0 = s1za0; } // rx_state == 4 // *4
} else { s0zb1 = s0zb0; s0zb0 = ac; } // rx_state == 2 || rx_state == 6 // *4
rx_state++;
}
void sdr_rx_q()
{
#ifdef TESTBENCH
int16_t adc = NCO_Q();
#else
ADMUX = admux[0]; // set MUX for next conversion
ADCSRA |= (1 << ADSC); // start next ADC conversion
int16_t adc = ADC - 511; // current ADC sample 10-bits analog input, NOTE: first
ADCL, then ADCH
#endif
func_ptr = sdr_rx; // processing function for next conversion
//sdr_rx_common(); //necessary? YES!... Maybe NOT!
rx_state++;
}
void sdr_rx()
{
// process I for even samples [75% CPU@R=4;Fs=62.5k] (excluding the Comb branch
and output stage)
ADMUX = admux[1]; // set MUX for next conversion
ADCSRA |= (1 << ADSC); // start next ADC conversion
int16_t adc = ADC - 511; // current ADC sample 10-bits analog input, NOTE: first
ADCL, then ADCH
func_ptr = sdr_rx_q; // processing function for next conversion
sdr_rx_common();
int16_t ac2;
static int16_t z1;
if(rx_state == 0 || rx_state == 4){ // 1st stage: down-sample by 2
static int16_t za1;
int16_t _ac = ac + za1 + z1 * 2; // 1st stage: FA + FB
za1 = ac;
static int16_t _z1;
if(rx_state == 0){ // 2nd stage: down-sample by 2
static int16_t _za1;
ac2 = _ac + _za1 + _z1 * 2; // 2nd stage: FA + FB
_za1 = _ac;
{
ac2 >>= att2; // digital gain control
// post processing I and Q (down-sampled) results
static int16_t v[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_t ac = i + qh;
ac = slow_dsp(ac);
// Output stage
static int16_t ozd1, ozd2;
if(_init){ ac = 0; ozd1 = 0; ozd2 = 0; _init = 0; } // hack: on first
sample init accumlators of further stages (to prevent instability)
#define SECOND_ORDER_DUC 1
#ifdef SECOND_ORDER_DUC
int16_t od1 = ac - ozd1; // Comb section
ocomb = od1 - ozd2;
ozd2 = od1;
#else
ocomb = ac - ozd1; // Comb section
#endif
ozd1 = ac;
}
} else _z1 = _ac;
} else z1 = ac;
rx_state++;
}
void sdr_rx_q()
{
// process Q for odd samples [75% CPU@R=4;Fs=62.5k] (excluding the Comb branch
and output stage)
#ifdef TESTBENCH
int16_t adc = NCO_Q();
#else
ADMUX = admux[0]; // set MUX for next conversion
ADCSRA |= (1 << ADSC); // start next ADC conversion
int16_t adc = ADC - 511; // current ADC sample 10-bits analog input, NOTE: first
ADCL, then ADCH
#endif
func_ptr = sdr_rx; // processing function for next conversion
#ifdef SECOND_ORDER_DUC
// sdr_rx_common(); //necessary? YES!... Maybe NOT!
#endif
int16_t ac2;
static int16_t z1;
if(rx_state == 3 || rx_state == 7){ // 1st stage: down-sample by 2
static int16_t za1;
int16_t _ac = ac + za1 + z1 * 2; // 1st stage: FA + FB
za1 = ac;
static int16_t _z1;
if(rx_state == 7){ // 2nd stage: down-sample by 2
static int16_t _za1;
ac2 = _ac + _za1 + _z1 * 2; // 2nd stage: FA + FB
_za1 = _ac;
{
ac2 >>= att2; // digital gain control
// Process Q (down-sampled) samples
static int16_t v[14];
q = v[7];
// Hilbert transform, BasicDSP model: outi= fir(inl, 0, 0, 0, 0, 0, 0, 0, 1,
0, 0, 0, 0, 0, 0, 0, 0); outq = fir(inr, 2, 0, 8, 0, 21, 0, 79, 0, -79, 0, -21,
0, -8, 0, -2, 0) / 128;
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)
//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_t j = 0; j != 13; j++) v[j] = v[j + 1]; v[13] = ac2;
//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] = v[7]; v[7] = v[8]; v[8] = v[9]; v[9] = v[10]; v[10] = v[11]; v[11] =
v[12]; v[12] = v[13]; v[13] = ac2;
}
rx_state = 0; return;
} else _z1 = _ac;
} else z1 = ac;
rx_state++;
}
#endif //!SIMPLE_RX
#ifdef SIMPLE_RX
volatile uint8_t admux[3];
static uint8_t rx_state = 0;
static struct rx {
int16_t z1;
int16_t za1;
int16_t _z1;
int16_t _za1;
} rx_inst[2];
void sdr_rx()
{
static int16_t ocomb;
static int16_t qh;
//sdr_common
static int16_t ozi1, ozi2;
if(_init){ ocomb=0; ozi1 = 0; ozi2 = 0; } // hack
// Output stage [25% CPU@R=4;Fs=62.5k]
#define SECOND_ORDER_DUC 1
#ifdef SECOND_ORDER_DUC
ozi2 = ozi1 + ozi2; // Integrator section
#endif
ozi1 = ocomb + ozi1;
#ifdef SECOND_ORDER_DUC
OCR1AL = min(max((ozi2>>5) + 128, 0), 255); // OCR1AL = min(max((ozi2>>5) +
ICR1L/2, 0), ICR1L); // center and clip wrt PWM working range
#else
OCR1AL = (ozi1>>5) + 128;
//OCR1AL = min(max((ozi1>>5) + 128, 0), 255); // OCR1AL = min(max((ozi2>>5) +
ICR1L/2, 0), ICR1L); // center and clip wrt PWM working range
#endif
// Only for I: correct I/Q sample delay by means of linear interpolation
static int16_t prev_adc;
int16_t corr_adc = (prev_adc + ac) / 2;
prev_adc = ac;
ac = corr_adc;
_rx_state = ~rx_state;
} else {
ADMUX = admux[0]; // set MUX for next conversion
ADCSRA |= (1 << ADSC); // start next ADC conversion
ac = ADC - 512; // current ADC sample 10-bits analog input, NOTE: first ADCL,
then ADCH
_rx_state = rx_state;
}
int16_t ac = i + qh;
ac = slow_dsp(ac);
// Output stage
static int16_t ozd1, 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_t od1 = 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
static int16_t v[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_t j = 0; j != 13; j++) v[j] = v[j + 1]; v[13] = ac2;
}
} else p->_z1 = _ac;
} else p->z1 = ac; // rx_state == I: 2, 6 Q: 1, 5
rx_state++;
}
//#pragma GCC push_options
//#pragma GCC optimize ("Ofast") // compiler-optimization for speed
//#pragma GCC pop_options // end of DSP section
// */
#endif //SIMPLE_RX
void adc_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)
}
void timer1_stop()
{
OCR1AL = 0x00;
OCR1BL = 0x00;
}
void timer2_stop()
{ // Stop Timer 2 interrupt
TIMSK2 &= ~(1 << OCIE2A); // disable timer compare interrupt
delay(1); // wait until potential in-flight interrupts are finished
}
///////////////////////////////////////////////////////////////////////////////////
/////////////////////////
// Below a radio-specific implementation based on the above components (seperation
of concerns)
//
// Feel free to replace it with your own custom radio implementation :-)
void inline lcd_blanks(){ lcd.print(F(" ")); }
#define N_FONTS 8
const byte fonts[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
int analogSafeRead(uint8_t pin, bool ref1v1 = 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_t adcsra = ADCSRA;
uint8_t admux = 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)
else ADMUX = (1 << REFS0); // restore reference voltage AREF (5V)
delay(1); // settle
int val = analogRead(pin);
ADCSRA = adcsra;
ADMUX = admux;
interrupts();
return val;
}
#else //VSS_METER
uint16_t analogSafeRead(uint8_t adcpin, bool ref1v1 = false){
noInterrupts();
uint8_t oldmux = ADMUX;
ADMUX = (3 & 0x0f) | ((ref1v1) ? (1 << REFS1) : 0) | (1 << REFS0); // set MUX
for next conversion note: hardcoded for BUTTONS adcpin
for(;!(ADCSRA & (1 << ADIF));); // wait until (a potential previous) ADC
conversion is completed
delayMicroseconds(16); // settle
ADCSRA |= (1 << ADSC); // start next ADC conversion
for(;!(ADCSRA & (1 << ADIF));); // wait until ADC conversion is completed
ADMUX = oldmux;
uint16_t adc = ADC;
interrupts();
return adc;
}
#endif
uint16_t analogSampleMic()
{
uint16_t adc;
noInterrupts();
ADCSRA = (1 << ADEN) | (((uint8_t)log2((uint8_t)(F_CPU / 13 / (192307/1)))) &
0x07); // hack: faster conversion rate necessary for VOX
// We measure the average amplitude of the signal (see slow_dsp()) but the S-meter
should be based on RMS value.
// So we multiply by 0.707/0.639 in an attempt to roughly compensate, although that
only really works if the input
// is a sine wave
uint8_t smode = 1;
uint32_t max_absavg256 = 0;
int16_t dbm;
lcd.noCursor();
if(smode == 1){ // dBm meter
lcd.setCursor(9, 0); lcd.print((int16_t)dbm); lcd.print(F("dBm "));
}
if(smode == 2){ // S-meter
uint8_t s = (dbm < -63) ? ((dbm - -127) / 6) : (((uint8_t)(dbm - -73)) / 10)
* 10; // dBm to S (modified to work correctly above S9)
lcd.setCursor(14, 0); if(s < 10){ lcd.print('S'); } lcd.print(s);
}
if(smode == 3){ // S-bar
int8_t s = (dbm < -63) ? ((dbm - -127) / 6) : (((uint8_t)(dbm - -73)) / 10) *
10; // dBm to S (modified to work correctly above S9)
char tmp[5];
for(uint8_t i = 0; i != 4; i++){ tmp[i] = max(2, min(5, s + 1)); s = s - 3; }
tmp[4] = 0;
lcd.setCursor(12, 0); lcd.print(tmp);
}
#ifdef CW_DECODER
if(smode == 4){ // wpm-indicator
lcd.setCursor(14, 0); if(mode == CW) lcd.print(wpm); lcd.print(" ");
}
#endif //CW_DECODER
#ifdef VSS_METER
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_t vss10 = (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)
lcd.setCursor(10, 0); lcd.print(vss10/10); lcd.print('.');
lcd.print(vss10%10); lcd.print("V ");
}
#endif //VSS_METER
#ifdef CLOCK
if(smode == 6){ // clock-indicator
uint32_t _s = (millis() * 16000000ULL / F_MCU) / 1000;
uint8_t h = (_s / 3600) % 24;
uint8_t m = (_s / 60) % 60;
uint8_t s = (_s) % 60;
lcd.setCursor(8, 0); lcd.print(h / 10); lcd.print(h % 10); lcd.print(':');
lcd.print(m / 10); lcd.print(m % 10); lcd.print(':'); lcd.print(s / 10);
lcd.print(s % 10); lcd.print(" ");
}
#endif //CLOCK
stepsize_showcursor();
max_absavg256 /= 2; // Implement peak hold/decay for all meter types
}
return dbm;
}
void start_rx()
{
_init = 1;
rx_state = 0;
func_ptr = sdr_rx_00; //enable RX DSP/SDR
adc_start(2, true, F_ADC_CONV*4); admux[2] = ADMUX; // Note that conversion-rate
for TX is factors more
if(dsp_cap == SDR){
//#define SWAP_RX_IQ 1 // Swap I/Q ADC inputs, flips RX sideband
#ifdef SWAP_RX_IQ
adc_start(1, !(att == 1)/*true*/, F_ADC_CONV); admux[0] = ADMUX;
adc_start(0, !(att == 1)/*true*/, F_ADC_CONV); admux[1] = ADMUX;
#else
adc_start(0, !(att == 1)/*true*/, F_ADC_CONV); admux[0] = ADMUX;
adc_start(1, !(att == 1)/*true*/, F_ADC_CONV); admux[1] = ADMUX;
#endif //SWAP_RX_IQ
} else { // ANALOG, DSP
adc_start(0, false, F_ADC_CONV); admux[0] = ADMUX; admux[1] = ADMUX;
}
timer1_start(F_SAMP_PWM);
timer2_start(F_SAMP_RX);
TCCR1A &= ~(1 << COM1B1); digitalWrite(KEY_OUT, LOW); // disable KEY_OUT PWM
}
int16_t _centiGain = 0;
uint8_t txdelay = 0;
uint8_t semi_qsk = false;
uint32_t semi_qsk_timeout = 0;
if(tx_enable){ // tx
if(practice){
digitalWrite(RX, LOW); // TX (disable RX)
lcd.setCursor(15, 1); lcd.print('P');
si5351.SendRegister(SI_CLK_OE, TX0RX0);
// Do not enable PWM (KEY_OUT), do not enble CLK2
} else
{
digitalWrite(RX, LOW); // TX (disable RX)
#ifdef NTX
digitalWrite(NTX, LOW); // TX (enable TX)
#endif //NTX
#ifdef PTX
digitalWrite(PTX, HIGH); // TX (enable TX)
#endif //PTX
lcd.setCursor(15, 1); lcd.print('T');
if(mode == CW){ si5351.freq_calc_fast(-cw_offset);
si5351.SendPLLRegisterBulk(); } // for CW, TX at freq
#ifdef RIT_ENABLE
else if(rit){ si5351.freq_calc_fast(0); si5351.SendPLLRegisterBulk(); }
#endif //RIT_ENABLE
si5351.SendRegister(SI_CLK_OE, TX1RX0);
OCR1AL = 0x80; // make sure SIDETONE is set at 2.5V
if((!mox) && (mode != CW)) TCCR1A &= ~(1 << COM1A1); // disable SIDETONE,
prevent interference during SSB TX
TCCR1A |= (1 << COM1B1); // enable KEY_OUT PWM
#ifdef _SERIAL
if(cat_active){ DDRC &= ~(1<<2); } // disable PC2, so that ADC2 can be used
as mic input
#endif
}
} else { // rx
#ifdef KEY_CLICK
if(OCR1BL != 0) {
for(uint16_t i = 0; i != 31; i++) { // ramp down of amplitude: soft
falling edge to prevent key clicks
OCR1BL = lut[pgm_read_byte_near(&ramp[i])];
delayMicroseconds(60);
}
}
#endif //KEY_CLICK
TCCR1A |= (1 << COM1A1); // enable SIDETONE (was disabled to prevent
interference during ssb tx)
TCCR1A &= ~(1 << COM1B1); digitalWrite(KEY_OUT, LOW); // disable KEY_OUT PWM,
prevents interference during RX
OCR1BL = 0; // make sure PWM (KEY_OUT) is set to 0%
#ifdef QUAD
#ifdef TX_CLK0_CLK1
si5351.SendRegister(16, 0x0f); // disable invert on CLK0
si5351.SendRegister(17, 0x0f); // disable invert on CLK1
#else
si5351.SendRegister(18, 0x0f); // disable invert on CLK2
#endif //TX_CLK0_CLK1
#endif //QUAD
si5351.SendRegister(SI_CLK_OE, TX0RX1);
#ifdef SEMI_QSK
if((!semi_qsk_timeout) || (!semi_qsk)) // enable RX when no longer in semi-
qsk phase; so RX and NTX/PTX outputs are switching only when in RX mode
#endif //SEMI_QSK
{
digitalWrite(RX, !(att == 2)); // RX (enable RX when attenuator not on)
#ifdef NTX
digitalWrite(NTX, HIGH); // RX (disable TX)
#endif //NTX
#ifdef PTX
digitalWrite(PTX, LOW); // TX (disable TX)
#endif //PTX
}
#ifdef RIT_ENABLE
si5351.freq_calc_fast(rit); si5351.SendPLLRegisterBulk(); // restore
original PLL RX frequency
#else
si5351.freq_calc_fast(0); si5351.SendPLLRegisterBulk(); // restore original
PLL RX frequency
#endif //RIT_ENABLE
#ifdef SWR_METER
if(swrmeter > 0) { show_banner(); lcd.print(" "); }
#endif
lcd.setCursor(15, 1); lcd.print((vox) ? 'V' : 'R');
#ifdef _SERIAL
if(!vox) if(cat_active){ DDRC |= (1<<2); } // enable PC2, so that ADC2 is
pulled-down so that CAT TX is not disrupted via mic input
#endif
}
OCR2A = ((F_CPU / 64) / ((tx_enable) ? F_SAMP_TX : F_SAMP_RX)) - 1;
TIMSK2 |= (1 << OCIE2A); // enable timer compare interrupt TIMER2_COMPA_vect
}
#ifdef QCX
#define CAL_IQ 1
#ifdef CAL_IQ
int16_t cal_iq_dummy = 0;
// 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
void calibrate_iq()
{
smode = 1;
lcd.setCursor(0, 0); lcd_blanks(); lcd_blanks();
digitalWrite(SIG_OUT, true); // loopback on
si5351.freq(freq, 0, 90); // RX in USB
si5351.SendRegister(SI_CLK_OE, TX1RX1);
float dbc;
si5351.freqb(freq+700); delay(100);
dbc = smeter();
si5351.freqb(freq-700); delay(100);
lcd.setCursor(0, 1); lcd.print("I-Q bal. 700Hz"); lcd_blanks();
for(; !_digitalRead(BUTTONS);){ wdt_reset(); smeter(dbc); } for(;
_digitalRead(BUTTONS);) wdt_reset();
si5351.freqb(freq+600); delay(100);
dbc = smeter();
si5351.freqb(freq-600); delay(100);
lcd.setCursor(0, 1); lcd.print("Phase Lo 600Hz"); lcd_blanks();
for(; !_digitalRead(BUTTONS);){ wdt_reset(); smeter(dbc); } for(;
_digitalRead(BUTTONS);) wdt_reset();
si5351.freqb(freq+800); delay(100);
dbc = smeter();
si5351.freqb(freq-800); delay(100);
lcd.setCursor(0, 1); lcd.print("Phase Hi 800Hz"); lcd_blanks();
for(; !_digitalRead(BUTTONS);){ wdt_reset(); smeter(dbc); } for(;
_digitalRead(BUTTONS);) wdt_reset();
uint8_t prev_bandval = 3;
uint8_t bandval = 3;
#define N_BANDS 11
#ifdef CW_FREQS_QRP
uint32_t band[N_BANDS] = { /*472000,*/ 1810000, 3560000, 5351500, 7030000,
10106000, 14060000, 18096000, 21060000, 24906000, 28060000, 50096000/*, 70160000,
144060000*/ }; // CW QRP freqs
#else
#ifdef CW_FREQS_FISTS
uint32_t band[N_BANDS] = { /*472000,*/ 1818000, 3558000, 5351500, 7028000,
10118000, 14058000, 18085000, 21058000, 24908000, 28058000, 50058000/*, 70158000,
144058000*/ }; // CW FISTS freqs
#else
uint32_t band[N_BANDS] = { /*472000,*/ 1840000, 3573000, 5357000, 7074000,
10136000, 14074000, 18100000, 21074000, 24915000, 28074000, 50313000/*, 70101000,
144125000*/ }; // FT8 freqs
#endif
#endif
void stepsize_showcursor()
{
lcd.setCursor(stepsize+1, 1); // display stepsize with cursor
lcd.cursor();
}
void powerDown()
{ // Reduces power from 110mA to 70mA (back-light on) or 30mA (back-light off),
remaining current is probably opamp quiescent current
lcd.setCursor(0, 1); lcd.print(F("Power-off 73 :-)")); lcd_blanks();
timer2_stop();
timer1_stop();
adc_stop();
si5351.powerDown();
delay(1500);
// Power-down sub-systems
PRR = 0xff;
lcd.noDisplay();
PORTD &= ~0x08; // disable backlight
set_sleep_mode(SLEEP_MODE_PWR_DOWN);
sleep_enable();
interrupts();
sleep_bod_disable();
//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 show_banner(){
lcd.setCursor(0, 0);
#ifdef QCX
lcd.print(F("QCX"));
const char* cap_label[] = { "SSB", "DSP", "SDR" };
if(ssb_cap || dsp_cap){ lcd.print('-'); lcd.print(cap_label[dsp_cap]); }
#else
lcd.print(F("uSDX"));
#endif //QCX
lcd.print('\x01'); lcd_blanks(); lcd_blanks();
}
int32_t scale=10e6;
if(rit){
f = rit;
scale=1e3; // RIT frequency
lcd.print(F("RIT ")); lcd.print(rit < 0 ? '-' : '+');
} else {
if(f/scale == 0){ lcd.print(' '); scale/=10; } // Initial space instead of
zero
}
for(; scale!=1; f%=scale, scale/=10){
lcd.print(abs(f/scale));
if(scale == (int32_t)1e3 || scale == (int32_t)1e6) lcd.print(','); //
Thousands separator
}
uint8_t eeprom_version;
#define EEPROM_OFFSET 0x150 // avoid collision with QCX settings, overwrites text
settings though
int eeprom_addr;
lcd.noCursor();
printlabel(action, menuid, label); // print normal/menu label
if(enumArray == NULL){ // print value
if((_min < 0) && (value >= 0)) lcd.print('+'); // add + sign for positive
values, in case negative values are supported
lcd.print(value);
} else {
lcd.print(enumArray[value]);
}
lcd_blanks(); lcd_blanks(); //lcd.setCursor(0, 1);
//if(action == UPDATE) paramAction(SAVE, value, menuid, label, enumArray,
_min, _max, continuous, init_val);
break;
default:
actionCommon(action, (uint8_t *)&value, sizeof(value));
break;
}
}
#ifdef MENU_STR
static uint8_t pos = 0;
void paramAction(uint8_t action, char* value, uint8_t menuid, const
__FlashStringHelper* label, uint8_t size){
const uint8_t _min = ' '; const uint8_t _max = 'Z';
switch(action){
case NEXT_CH:
if(pos < size) pos++; // allow to go to next character when string size
allows and when current character is not string end
action = UPDATE_MENU; //fall-through next case
case UPDATE:
case UPDATE_MENU:
if(menumode != 3) pos = 0;
if(menumode == 2) menumode = 3; // hack: for strings enter in edit mode
if(((value[pos] + encoder_val) < _min) || ((value[pos] + encoder_val) == 0))
value[pos] = _min;
else if((value[pos] + encoder_val) > _max) value[pos] = _max;
else value[pos] = value[pos] + encoder_val;
encoder_val = 0;
static uint8_t pwm_min = 0; // PWM value for which PA reaches its minimum: 29
when C31 installed; 0 when C31 removed; 0 for biasing BS170 directly
#ifdef QCX
static uint8_t pwm_max = 255; // PWM value for which PA reaches its maximum: 96
when C31 installed; 255 when C31 removed;
#else
static uint8_t pwm_max = 128; // PWM value for which PA reaches its maximum:
128 for biasing BS170 directly
#endif
enum params_t {_NULL, VOLUME, MODE, FILTER, BAND, STEP, VFOSEL, RIT, AGC, NR, ATT,
ATT2, SMETER, SWRMETER, CALPWR, CALSHUNT, CWDEC, CWTONE, CWOFF, SEMIQSK, KEY_WPM,
KEY_MODE, KEY_PIN, KEY_TX, VOX, VOXGAIN, DRIVE, TXDELAY, MOX, CWINTERVAL, CWMSG1,
CWMSG2, CWMSG3, CWMSG4, CWMSG5, CWMSG6, PWM_MIN, PWM_MAX, SIFXTAL, IQ_ADJ, CALIB,
SR, CPULOAD, PARAM_A, PARAM_B, PARAM_C, BACKL, FREQA, FREQB, MODEA, MODEB, VERS,
ALL=0xff};
// Non-parameters
case _NULL: menumode = 0; show_banner(); change = true; break;
default: if((action == NEXT_MENU) && (id != N_PARAMS)) id =
paramAction(action, max(1 /*0*/, min(N_PARAMS, id + ((encoder_val > 0) ? 1 : -
1))) ); break; // keep iterating util menu item found
}
return id;
}
void initPins(){
// initialize
digitalWrite(SIG_OUT, LOW);
digitalWrite(RX, HIGH);
digitalWrite(KEY_OUT, LOW);
digitalWrite(SIDETONE, LOW);
// pins
pinMode(SIDETONE, OUTPUT);
pinMode(SIG_OUT, OUTPUT);
pinMode(RX, OUTPUT);
pinMode(KEY_OUT, OUTPUT);
#ifdef ONEBUTTON
pinMode(BUTTONS, INPUT_PULLUP); // rotary button
#else
pinMode(BUTTONS, INPUT); // L/R/rotary button
#endif
pinMode(DIT, INPUT_PULLUP);
pinMode(DAH, INPUT); // pull-up DAH 10k via AVCC
//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);
#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
// https://www.kenwood.com/i/products/info/amateur/ts_480/pdf/ts_480_pc.pdf
#define CATCMD_SIZE 32
char CATcmd[CATCMD_SIZE];
void analyseCATcmd()
{
if((CATcmd[0] == 'F') && (CATcmd[1] == 'A') && (CATcmd[2] == ';'))
Command_GETFreqA();
else if((CATcmd[0] == 'A') && (CATcmd[1] == 'G') && (CATcmd[2] == '0')) // add
Command_AG0();
else if((CATcmd[0] == 'X') && (CATcmd[1] == 'T') && (CATcmd[2] == '1')) // add
Command_XT1();
else if((CATcmd[0] == 'R') && (CATcmd[1] == 'T') && (CATcmd[2] == '1')) // add
Command_RT1();
else if((CATcmd[0] == 'R') && (CATcmd[1] == 'C') && (CATcmd[2] == ';')) // add
Command_RC();
else if((CATcmd[0] == 'F') && (CATcmd[1] == 'L') && (CATcmd[2] == '0')) // need?
Command_FL0();
/*
The following CAT extensions are available to support remote operatons. Use a
baudrate of 115200 when enabling CAT_STREAMING config switch:
UA1; enable audio streaming; an 8-bit audio stream nnn at 4812 samples/s is then
sent within CAT response USnnn; here nnn is of undefined length, it will end as
soon other CAT requests are processed and resume with a new USnnn; response
UA0; disable audio streaming
UD; requests display contents
UKnn; control keys, where nn is a sum of the following hexadecimal values: 0x80
encoder left, 0x40 encoder right, 0x20 DIT/PTT, 0x10 DAH, 0x04 left-button, 0x02
encoder button, 0x01 right button
The following Linux script may be useful to stream audio over CAT, play the audio
and redirect CAT to /tmp/ttyS0:
socat -d -d pty,link=/tmp/ttyS0,echo=0,ignoreeof,b115200,raw,perm=0777
pty,link=/tmp/ttyS1,echo=0,ignoreeof,b115200,raw,perm=0777 &
3. You should now hear audio, use pavucontrol. Now start your favorite
digimode/logbook application, and use /tmp/ttyS0 as CAT port.
clear; echo ";UA1;UD;" > /tmp/ttyS0; cat /tmp/ttyS0 | while IFS= read -d \; c; do
echo "${c}" |sed -E 's/^UD..(.+)$|^[^U][^D](.*)$/\1/g'| sed -E 's/^(.{16})(.+)$/\
x1B[;1H\x1B[1m\x1B[44m\x1B[97m\1\x1B[0m\x1B[K\n\x1B[1m\x1B[44m\x1B[97m\2\x1B[0m\
x1B[K\n\x1B[K\n\x1B[K/g'; echo ";UD;UD;" >> /tmp/ttyS0; sleep 1; done
For an Arduino board at 16 MHz, the following simple script will start streaming
audio:
*/
#ifdef CAT_EXT
else if((CATcmd[0] == 'U') && (CATcmd[1] == 'K') && (CATcmd[4] == ';')) //
remote key press
Command_UK(CATcmd[2], CATcmd[3]);
#ifdef CAT_STREAMING
else if((CATcmd[0] == 'U') && (CATcmd[1] == 'A') && (CATcmd[3] == ';')) // audio
streaming enable/disable
Command_UA(CATcmd[2]);
#endif //CAT_STREAMING
else {
Serial.print("?;");
#ifdef DEBUG
{ lcd.setCursor(0, 0); lcd.print(CATcmd); lcd_blanks(); } // Print error cmd
#else
//{ lcd.setCursor(15, 1); lcd.print('E'); }
#endif
}
}
#ifdef CAT
volatile uint8_t cat_ptr = 0;
void serialEvent(){
if(Serial.available()){
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;
char data = 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
#endif
#ifdef CAT_STREAMING
if(cat_streaming){ noInterrupts(); cat_streaming = false;
Serial.print(';'); } // terminate CAT stream
analyseCATcmd(); // process CAT cmd
if(_cat_streaming){ Serial.print("US"); cat_streaming = true; } // resume
CAT stream
interrupts();
#else
analyseCATcmd();
#endif // CAT_STREAMING
delay(10);
} else if(cat_ptr > (CATCMD_SIZE - 1)){ Serial.print("E;"); cat_ptr = 0; } //
overrun
}
}
#endif //CAT
#ifdef CAT_EXT
void Command_UK(char k1, char k2)
{
cat_key = ((k1 - '0') << 4) | (k2 - '0');
if(cat_key & 0x40){ encoder_val--; cat_key &= 0x3f; }
if(cat_key & 0x80){ encoder_val++; cat_key &= 0x3f; }
char Catbuffer[16];
sprintf(Catbuffer, "UK%c%c;", k1, k2);
Serial.print(Catbuffer);
}
void Command_UD()
{
char Catbuffer[40];
sprintf(Catbuffer, "UD%02u%s;", (lcd.curs) ? lcd.y*16+lcd.x : 16*2+1, lcd.text);
Serial.print(Catbuffer);
}
void Command_GETFreqA()
{
#ifdef _SERIAL
if(!cat_active) return;
#endif
char Catbuffer[32];
unsigned int g,m,k,h;
uint32_t tf;
tf=freq;
g=(unsigned int)(tf/1000000000lu);
tf-=g*1000000000lu;
m=(unsigned int)(tf/1000000lu);
tf-=m*1000000lu;
k=(unsigned int)(tf/1000lu);
tf-=k*1000lu;
h=(unsigned int)tf;
sprintf(Catbuffer,"FA%02u%03u",g,m);
Serial.print(Catbuffer);
sprintf(Catbuffer,"%03u%03u;",k,h);
Serial.print(Catbuffer);
}
void Command_SETFreqA()
{
char Catbuffer[16];
strncpy(Catbuffer,CATcmd+2,11);
Catbuffer[11]='\0';
freq=(uint32_t)atol(Catbuffer);
change=true;
}
void Command_IF()
{
#ifdef _SERIAL
if(!cat_active) return;
#endif
char Catbuffer[32];
unsigned int g,m,k,h;
uint32_t tf;
tf=freq;
g=(unsigned int)(tf/1000000000lu);
tf-=g*1000000000lu;
m=(unsigned int)(tf/1000000lu);
tf-=m*1000000lu;
k=(unsigned int)(tf/1000lu);
tf-=k*1000lu;
h=(unsigned int)tf;
sprintf(Catbuffer,"IF%02u%03u%03u%03u",g,m,k,h);
Serial.print(Catbuffer);
sprintf(Catbuffer,"00000+000000");
Serial.print(Catbuffer);
sprintf(Catbuffer,"0000");
Serial.print(Catbuffer);
Serial.print(mode + 1);
sprintf(Catbuffer,"0000000;");
Serial.print(Catbuffer);
}
void Command_AI()
{
Serial.print("AI0;");
}
void Command_AG0()
{
Serial.print("AG0;");
}
void Command_XT1()
{
Serial.print("XT1;");
}
void Command_RT1()
{
Serial.print("RT1;");
}
void Command_RC()
{
rit = 0;
Serial.print("RC;");
}
void Command_FL0()
{
Serial.print("FL0;");
}
void Command_GetMD()
{
Serial.print("MD");
Serial.print(mode + 1);
Serial.print(';');
}
void Command_SetMD()
{
mode = CATcmd[2] - '1';
vfomode[vfosel%2] = mode;
change = true;
si5351.iqmsa = 0; // enforce PLL reset
}
void Command_AI0()
{
Serial.print("AI0;");
}
void Command_RX()
{
#ifdef TX_ENABLE
switch_rxtx(0);
semi_qsk_timeout = 0; // hack: fix for multiple RX cmds
#endif
Serial.print("RX0;");
}
void Command_TX0()
{
#ifdef TX_ENABLE
switch_rxtx(1);
#endif
}
void Command_TX1()
{
#ifdef TX_ENABLE
switch_rxtx(1);
#endif
}
void Command_TX2()
{
#ifdef TX_ENABLE
switch_rxtx(1);
#endif
}
void Command_RS()
{
Serial.print("RS0;");
}
void Command_ID()
{
Serial.print("ID020;");
}
void Command_PS()
{
Serial.print("PS1;");
}
void Command_PS1()
{
}
#endif //CAT
#ifdef SWR_METER
#ifdef INA219_POWER_METER
/* measurement using an addon INA219 board from Adafruit or one of very similar
boards but without the Adafruit logo
all of these boards have a 0.1ohm resistor on board
the trace to the PA should be cut and the board shunt resistor inserted there
SCL/SDA go to the SCL/SDA pins on the atmega (same as SI5351, TCA9555 etc)
currently i've connected Vcc to the +5V trace, but it doesn't seem to do any
harm to the i2c. will have to find a +3.3V trace on the board
The current measurement can be calibrated using the "Cal Current" menu entry:
- connect an ammeter in series with the PA
- change SWR meter to I-U-P, change mode to CW
- preferably transmit into a dummy load, not into an antenna
- press the key, change the "Cal Current" value so that the current value shown
reflects the current shown by the meter
Ideally Cal Current should be 4096 for a 0.1ohm shunt resistor, however for mine
4010 gives same results as my meter
#include "ina219.h"
// TODO: try to move these into a separate library, not easy because i would also
need to move the i2c stuff into a separate library too --sq5bpf
void ina219_write(uint8_t reg, uint16_t val) {
i2c.start();
i2c.SendByte(INA219_ADDR << 1);
i2c.SendByte(reg);
i2c.SendByte(val>>8);
i2c.SendByte(val&0xff);
i2c.stop();
}
void ina219_init() {
ina219_write(INA219_REG_CALIBRATION,calshunt); //actually the lowest bit is
insignificant according to the INA219 docs, maybe i should &0xfffe --sq5bpf
/* nifty calculator in javascript here:
https://forums.adafruit.com/download/file.php?id=84820 */
ina219_write(INA219_REG_CONFIG,INA219_CONFIG_BVOLTAGERANGE_32V |
INA219_CONFIG_GAIN_8_320MV |
INA219_CONFIG_BADCRES_12BIT |
INA219_CONFIG_SADCRES_12BIT_1S_532US |
INA219_CONFIG_MODE_SANDBVOLT_CONTINUOUS);
//ina219_write(INA219_REG_CALIBRATION,4096);
//ina219_write(INA219_REG_CALIBRATION,calshunt);
}
#endif //INA219_POWER_METER
void readSWR()
// reads FWD / REF values from A6 and A7 and computes SWR
// credit Duwayne, KV4QB
/* This should similar as PE1DDA's (more direct) approach:
busvoltage = ina219.getBusVoltage_V();
current_mA = ina219.getCurrent_mA();
power_mW = ina219.getPower_mW();
Vinc = analogRead(3);
Vref = analogRead(2);
SWR = (Vinc + Vref) / (Vinc - Vref);
Vinc = ((Vinc * 5.0) / 1024.0) + 0.5;
pwr = ((((Vinc) * (Vinc)) - 0.25 ) * k);
Eff = (pwr) / ((power_mW) / 1000) * 100; */
{
#define SWR_AVERAGING_NUM 8 //how much measurements to average --sq5bpf
#ifdef INA219_POWER_METER
float busvoltage=0;
float current_mA;
float power_mW;
ina219_init(); // comment in Adafruit library says to always initialize before
measurement, because a current spike may reset the board --sq5bpf
#endif
float v_FWD = 0;
float v_REF = 0;
for (int i = 0; i < SWR_AVERAGING_NUM ; i++) {
v_FWD = v_FWD + (ref_V / 1023) * (int) analogRead(PIN_FWD);
v_REF = v_REF + (ref_V / 1023) * (int) analogRead(PIN_REF);
delay(5);
}
v_FWD = v_FWD / SWR_AVERAGING_NUM;
v_REF = v_REF / SWR_AVERAGING_NUM;
/* actually this seems a bit wrong, because we should take into account the 0.6V
voltage drop accross the diode
* so for 0.6V voltage drop and 50 ohms load something like
p_FWD=((v_FWD+0.6)/sqrt(2))^2 * some_calibration; //P=V^2/R
* but we'll use the original code with a calibration coefficient for now until i
get a better power meter --sq5bpf
*
* TODO: correct the p_FWD and P_REV calculation because it seems wrong or figure
out what i'm missing --sq5bpf
*/
float p_FWD = sq(v_FWD);
float p_REV = sq(v_REF);
#ifdef INA219_POWER_METER
busvoltage=(ina219_read(INA219_REG_BUSVOLTAGE)>>3)*0.004;
current_mA=(int16_t)ina219_read(INA219_REG_CURRENT)/10.0;
power_mW=ina219_read(INA219_REG_POWER)*2.0;
// TODO: maybe we should disable TX when the current is over some limit or the swr
is too high?
#endif
#ifdef INA219_POWER_METER
if (p_FWD != FWD || VSWR != SWR || swrmeter==4 || swrmeter==5)
#else
if (p_FWD != FWD || VSWR != SWR)
#endif
{
lcd.noCursor();
lcd.setCursor(0,0);
switch(swrmeter) {
case 1:
/* this used to be floor(100*p_FWD)/100, and i've just added a calibration
coefficient, but it just seems wrong, see above comment --sq5bpf */
lcd.print(" "); lcd.print(floor(calpwr*p_FWD)/100); lcd.print("W SWR:");
lcd.print(floor(100*VSWR)/100);
break;
case 2:
lcd.print(" F:"); lcd.print(floor(calpwr*p_FWD)/100); lcd.print("W R:");
lcd.print(floor(calpwr*p_REV)/100); lcd.print("W");
break;
case 3:
lcd.print(" F:"); lcd.print(floor(100*v_FWD)/100); lcd.print("V R:");
lcd.print(floor(100*v_REF)/100); lcd.print("V");
break;
#ifdef INA219_POWER_METER
case 4: //prints output power , efficiency, voltage. this just fits in 20
characters on the LCD, might have to modified for the OLED displays --sq5bpf
case 5: //prints output power , current, voltage. this just fits in 20
characters on the LCD, might have to modified for the OLED displays --sq5bpf
float eff=100.0*(calpwr*p_FWD*10.0)/power_mW;
lcd.print(floor(calpwr*p_FWD)/100);
lcd.print("W ");
if (swrmeter==4) {
lcd.print(uint8_t(eff));
lcd.print("% ");
} else {
lcd.print(int16_t(current_mA));
lcd.print("mA"); // there is no space after mA, it looks ugly, but one
more digit of voltage will fit, the V after the voltage won't fit
}
lcd.print(floor(busvoltage*100)/100); lcd.print("V ");
break;
#endif
}
FWD = p_FWD;
SWR = VSWR;
}
}
#endif
void setup()
{
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)
encoder_setup();
initPins();
show_banner();
lcd.setCursor(7, 0); lcd.print(F(" R")); lcd.print(F(VERSION)); lcd_blanks();
#ifdef QCX
// Test if QCX has DSP/SDR capability: SIDETONE output disconnected from AUDIO2
si5351.SendRegister(SI_CLK_OE, TX0RX0); // Mute QSD
digitalWrite(RX, HIGH); // generate pulse on SIDETONE and test if it can be seen
on AUDIO2
delay(1); // settle
digitalWrite(SIDETONE, LOW);
int16_t v1 = analogRead(AUDIO2);
digitalWrite(SIDETONE, HIGH);
int16_t v2 = analogRead(AUDIO2);
digitalWrite(SIDETONE, LOW);
dsp_cap = !(abs(v2 - v1) > (0.05 * 1024.0 / 5.0)); // DSP capability?
if(dsp_cap){ // Test if QCX has SDR capability: AUDIO2 is disconnected from
AUDIO1 (only in case of DSP capability)
delay(400); wdt_reset(); // settle: the following test only works well 400ms
after startup
v1 = analogRead(AUDIO1);
digitalWrite(AUDIO2, HIGH); // generate pulse on AUDIO2 and test if it can be
seen on AUDIO1
pinMode(AUDIO2, OUTPUT);
delay(1);
digitalWrite(AUDIO2, LOW);
delay(1);
digitalWrite(AUDIO2, HIGH);
v2 = analogRead(AUDIO1);
pinMode(AUDIO2, INPUT);
if(!(abs(v2 - v1) > (0.125 * 1024.0 / 5.0))) dsp_cap = SDR; // SDR capacility?
}
// Test if QCX has SSB capability: DAH is connected to DVM
delay(1); // settle
pinMode(DAH, OUTPUT);
digitalWrite(DAH, LOW);
v1 = analogRead(DVM);
digitalWrite(DAH, HIGH);
v2 = analogRead(DVM);
digitalWrite(DAH, LOW);
//pinMode(DAH, INPUT_PULLUP);
pinMode(DAH, INPUT);
ssb_cap = (abs(v2 - v1) > (0.05 * 1024.0 / 5.0)); // SSB capability?
#ifdef DEBUG
/*if((mcusr & WDRF) && (!(mcusr & EXTRF)) && (!(mcusr & BORF))){
lcd.setCursor(0, 1); lcd.print(F("!!Watchdog RESET")); lcd_blanks();
delay(1500); wdt_reset();
}
if((mcusr & BORF) && (!(mcusr & WDRF))){
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)
delay(1500); wdt_reset();
}
if(mcusr & PORF){
lcd.setCursor(0, 1); lcd.print(F("!!Power-On RESET")); lcd_blanks();
delay(1500); wdt_reset();
}*/
/*if(mcusr & EXTRF){
lcd.setCursor(0, 1); lcd.print(F("Power-On")); lcd_blanks();
delay(1); wdt_reset();
}*/
#ifdef DIAG
// Measure VDD (+5V); should be ~5V
si5351.SendRegister(SI_CLK_OE, TX0RX0); // Mute QSD
digitalWrite(KEY_OUT, LOW);
digitalWrite(RX, LOW); // mute RX
delay(100); // settle
float vdd = 2.0 * (float)analogRead(AUDIO2) * 5.0 / 1024.0;
digitalWrite(RX, HIGH);
if(!(vdd > 4.8 && vdd < 5.2)){
fatal(F("V5.0"), vdd, 'V');
}
// Measure AVCC via AREF and using internal 1.1V reference fed to ADC; should be
~5V
analogRead(6); // setup almost proper ADC readout
bitSet(ADMUX, 3); // Switch to channel 14 (Vbg=1.1V)
delay(1); // delay improves accuracy
bitSet(ADCSRA, ADSC);
for(; bit_is_set(ADCSRA, ADSC););
float avcc = 1.1 * 1023.0 / ADC;
if(!(avcc > 4.6 && avcc < 5.2)){
fatal(F("Vavcc"), avcc, 'V');
}
#ifdef TX_ENABLE
// Measure I2C Bus speed for Bulk Transfers
//si5351.freq(freq, 0, 90);
wdt_reset();
t0 = micros();
for(i = 0; i != 1000; i++) si5351.SendPLLRegisterBulk();
t1 = micros();
uint32_t speed = (1000000 * 8 * 7) / (t1 - t0); // speed in kbit/s
if(false) {
fatal(F("i2cspeed"), speed, 'k');
}
// Measure I2C Bit-Error Rate (BER); should be error free for a thousand random
bulk PLLB writes
si5351.freq(freq, 0, 90); // freq needs to be set in order to use
freq_calc_fast()
wdt_reset();
uint16_t i2c_error = 0; // number of I2C byte transfer errors
for(i = 0; i != 1000; i++){
si5351.freq_calc_fast(i);
//for(int j = 0; j != 8; j++) si5351.pll_regs[j] = rand();
si5351.SendPLLRegisterBulk();
#define SI_SYNTH_PLL_A 26
#ifdef NEW_TX
for(int j = 4; j != 8; j++) if(si5351.RecvRegister(SI_SYNTH_PLL_A + j) !=
si5351.pll_regs[j]) i2c_error++;
#else
for(int j = 3; j != 8; j++) if(si5351.RecvRegister(SI_SYNTH_PLL_A + j) !=
si5351.pll_regs[j]) i2c_error++;
#endif //NEW_TX
}
wdt_reset();
if(i2c_error){
fatal(F("BER_i2c"), i2c_error, ' ');
}
#endif //TX_ENABLE
#endif // DIAG
// 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();
//for(int n = 0; n != 1024; n++){ eeprom_write_byte((uint8_t *) n, 0);
wdt_reset(); } //clean EEPROM
//eeprom_write_dword((uint32_t *)EEPROM_OFFSET/3, 0x000000);
paramAction(SAVE); // save default parameter values
lcd.setCursor(0, 1); lcd.print(F("Reset settings.."));
delay(500); wdt_reset();
} else {
paramAction(LOAD); // load all parameters
}
//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 = 0; // disable NR
rit = false; // disable RIT
freq = vfo[vfosel%2];
mode = vfomode[vfosel%2];
#ifdef TX_ENABLE
build_lut();
#endif
start_rx();
#ifdef KEYER
keyerState = IDLE;
keyerControl = IAMBICB; // Or 0 for IAMBICA
loadWPM(keyer_speed); // Fix speed at 15 WPM
#endif //KEYER
void loop()
{
#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;
}
} else if(!tx){ // VOX activated, no audio detected -> RX
switch_rxtx(0);
vox_tx = 0;
delay(32); //delay(10);
//vox_adc = 0; for(i = 0; i != 32; i++) ssb(0); //clean buffers
//for(int i = 0; i != 32; i++) ssb((analogSampleMic() - 512) >>
MIC_ATTEN); // clear internal buffer
//tx = 0; // make sure tx is off (could have been triggered by rubbish in
above statement)
}
}
#endif //VOX_ENABLE
#ifdef CW_DECODER
//if((mode == CW) && cwdec) cw_decode(); // if(!(semi_qsk_timeout)) cw_decode();
else dec2();
if((mode == CW) && cwdec && ((!tx) && (!semi_qsk_timeout))) cw_decode(); // CW
decoder only active during RX
#endif //CW_DECODER
} else {
#endif //KEYER
#ifdef TX_ENABLE
uint8_t pin = ((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(!_digitalRead(pin)){ // PTT/DIT keys transmitter
#ifdef CW_MESSAGE
cw_msg_event = 0; // clear cw message event
#endif //CW_MESSAGE
switch_rxtx(1);
do {
wdt_reset();
delay((mode == CW) ? 10 : 100); // keep the tx keyed for a while before
sensing (helps against RFI issues on DAH/DAH line)
#ifdef SWR_METER
if(swrmeter > 0 && mode == CW && millis() >= stimer) { readSWR(); stimer =
millis() + 500; }
#endif
if(inv ^ _digitalRead(BUTTONS)) break; // break if button is pressed (to
prevent potential lock-up)
} while(!_digitalRead(pin)); // until released
switch_rxtx(0);
}
#endif //TX_ENABLE
#ifdef KEYER
}
#endif //KEYER
#ifdef SEMI_QSK
if((semi_qsk_timeout) && (millis() > semi_qsk_timeout)){ switch_rxtx(0); } //
delayed QSK RX
#endif
enum event_t { BL=0x10, BR=0x20, BE=0x30, SC=0x01, DC=0x02, PL=0x04, PLC=0x05,
PT=0x0C }; // button-left, button-right and button-encoder; single-click, double-
click, push-long, push-and-turn
if(inv ^ _digitalRead(BUTTONS)){ // Left-/Right-/Rotary-button (while not
already pressed)
if(!((event & PL) || (event & PLC))){ // hack: if there was long-push before,
then fast forward
uint16_t v = analogSafeRead(BUTTONS);
#ifdef CAT_EXT
if(cat_key){ v = (cat_key&0x04) ? 512 : (cat_key&0x01) ? 870 : (cat_key&0x02)
? 1024 : 0; } // override analog value exercised by BUTTONS press
#endif //CAT_EXT
event = SC;
int32_t t0 = millis();
for(; inv ^ _digitalRead(BUTTONS);){ // until released or long-press
if((millis() - t0) > 300){ event = PL; break; }
wdt_reset();
}
delay(10); //debounce
for(; (event != PL) && ((millis() - t0) < 500);){ // until 2nd press or
timeout
if(inv ^ _digitalRead(BUTTONS)){ event = DC; break; }
wdt_reset();
}
for(; inv ^ _digitalRead(BUTTONS);){ // until released, or encoder is turned
while longpress
if(encoder_val && event == PL){ event = PT; break; }
#ifdef ONEBUTTON
if(event == PL) break; // do not lock on longpress, so that L and R
buttons can be used for tuning
#endif
wdt_reset();
} // Max. voltages at ADC3 for buttons L,R,E: 3.76V;4.55V;5V, thresholds are
in center
event |= (v < (uint16_t)(4.2 * 1024.0 / 5.0)) ? BL : (v < (uint16_t)(4.8 *
1024.0 / 5.0)) ? BR : BE; // determine which button pressed based on threshold
levels
} else { // hack: fast forward handling
event = (event&0xf0) | ((encoder_val) ? PT : PLC/*PL*/); // only alternate
between push-long/turn when applicable
}
switch(event){
#ifndef ONEBUTTON
case BL|PL: // Called when menu button pressed
case BL|PLC: // or kept pressed
menumode = 2;
break;
case BL|PT:
menumode = 1;
//if(menu == 0) menu = 1;
break;
case BL|SC:
#ifdef CW_MESSAGE
if((menumode == 1) && (menu >= CWMSG1) && (menu <= CWMSG6)){
cw_msg_event = millis();
cw_msg_id = menu - CWMSG1;
menumode = 0;
break;
}
#endif //CW_MESSAGE
int8_t _menumode;
if(menumode == 0){ _menumode = 1; if(menu == 0) menu = 1; } // short left-
click while in default screen: enter menu mode
if(menumode == 1){ _menumode = 2; } // short left-
click while in menu: enter value selection screen
if(menumode >= 2){ _menumode = 0; paramAction(SAVE, menu); } // short left-
click while in value selection screen: save, and return to default screen
menumode = _menumode;
break;
case BL|DC:
break;
case BR|SC:
if(!menumode){
int8_t prev_mode = mode;
if(rit){ rit = 0; stepsize = prev_stepsize[mode == CW]; change = true;
break; }
mode += 1;
//encoder_val = 1;
//paramAction(UPDATE, MODE); // Mode param //paramAction(UPDATE, mode,
NULL, F("Mode"), mode_label, 0, _N(mode_label), true);
//#define MODE_CHANGE_RESETS 1
#ifdef MODE_CHANGE_RESETS
if(mode != CW) stepsize = STEP_1k; else stepsize = STEP_500; // sets
suitable stepsize
#endif
if(mode > CW) mode = LSB; // skip all other modes (only LSB, USB, CW)
#ifdef MODE_CHANGE_RESETS
if(mode == CW) { filt = 4; nr = 0; } else filt = 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
#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;
case BR|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(mode == CW && (filt == 5 || filt == 6) && stepsize < STEP_100) stepsize
= STEP_100; // for CW BW 200, 100 -> step = 100 Hz
if(mode == CW && filt == 7 && stepsize < STEP_10) stepsize = STEP_10;
// for CW BW 50 -> step = 10 Hz
if(mode != CW && filt > 3) filt = 0;
encoder_val = 0;
paramAction(UPDATE, FILTER);
paramAction(SAVE, FILTER);
wdt_reset(); delay(1500); wdt_reset();
change = true; // refresh display
break;
case BR|PL:
#ifdef SIMPLE_RX
// Experiment: ISR-less sdr_rx():
smode = 0;
TIMSK2 &= ~(1 << OCIE2A); // disable timer compare interrupt
delay(100);
lcd.setCursor(15, 1); lcd.print('X');
static uint8_t x = 0;
uint32_t next = 0;
for(;;){
func_ptr();
#ifdef DEBUG
numSamples++;
#endif
if(!rx_state){
x++;
if(x > 16){
loop();
//lcd.setCursor(9, 0); lcd.print((int16_t)100); lcd.print(F("dBm
")); // delays are taking too long!
x= 0;
}
}
//for(;micros() < next;); next = micros() + 16; // sync every
1000000/62500=16ms (or later if missed)
} //
#endif //SIMPLE_RX
#ifdef RIT_ENABLE
rit = !rit;
stepsize = (rit) ? STEP_10 : prev_stepsize[mode == CW];
if(!rit){ // after RIT comes VFO A/B swap
#else
{
#endif //RIT_ENABLE
vfosel = !vfosel;
freq = vfo[vfosel%2]; // todo: share code with menumode
mode = vfomode[vfosel%2];
// make more generic:
if(mode != CW) stepsize = STEP_1k; else stepsize = STEP_500;
if(mode == CW) { filt = 4; nr = 0; } else filt = 0;
}
change = true;
break;
//#define TUNING_DIAL 1
#ifdef TUNING_DIAL
case BR|PLC: // while pressed long continues
case BE|PLC:
freq = freq + ((_step > 0) ? 1 : -1) * pow(2, abs(_step)); change=true;
break;
case BR|PT:
_step += encoder_val; encoder_val = 0;
lcd.setCursor(0, 0); lcd.print(_step); lcd_blanks();
break;
#endif //TUNING_DIAL
case BE|SC:
if(!menumode){
stepsize_change(+1);
} else {
int8_t _menumode;
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;
case BE|DC:
//delay(100);
bandval++;
//if(bandval >= N_BANDS) bandval = 0;
if(bandval >= (N_BANDS-1)) bandval = 1; // excludes 6m, 160m
stepsize = STEP_1k;
change = true;
break;
case BE|PL: stepsize_change(-1); break;
case BE|PT:
for(; _digitalRead(BUTTONS);){ // process encoder changes until released
wdt_reset();
if(encoder_val){
paramAction(UPDATE, VOLUME);
if(volume < 0){ volume = 10; paramAction(SAVE, VOLUME); powerDown(); }
// powerDown when volume < 0
paramAction(SAVE, VOLUME);
}
}
change = true; // refresh display
break;
#else //ONEBUTTON
case BE|SC:
int8_t _menumode;
if(menumode == 0){ _menumode = 1; if(menu == 0) menu = 1; } // short enc-
click while in default screen: enter menu mode
if(menumode == 1){ _menumode = 2; } // short enc-
click while in menu: enter value selection screen
if(menumode == 2){ _menumode = 0; paramAction(SAVE, menu); } // short enc-
click while in value selection screen: save, and return to default 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;
case BE|DC:
if(!menumode){
int8_t prev_mode = mode;
if(rit){ rit = 0; stepsize = prev_stepsize[mode == CW]; change = true;
break; }
mode += 1;
//encoder_val = 1;
//paramAction(UPDATE, MODE); // Mode param //paramAction(UPDATE, mode,
NULL, F("Mode"), mode_label, 0, _N(mode_label), true);
//#define MODE_CHANGE_RESETS 1
#ifdef MODE_CHANGE_RESETS
if(mode != CW) stepsize = STEP_1k; else stepsize = STEP_500; // sets
suitable stepsize
#endif
if(mode > CW) mode = LSB; // skip all other modes (only LSB, USB, CW)
#ifdef MODE_CHANGE_RESETS
if(mode == CW) { filt = 4; nr = 0; } else filt = 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;
case BE|PL:
stepsize += 1;
if(stepsize < STEP_1k) stepsize = STEP_10;
if(stepsize > STEP_10) stepsize = STEP_1k;
stepsize_showcursor();
break;
case BE|PLC: // or kept pressed
menumode = 2;
break;
case BE|PT:
menumode = 1;
//if(menu == 0) menu = 1;
break;
case BL|SC:
case BL|DC:
case BL|PL:
case BL|PLC:
encoder_val++;
break;
case BR|SC:
case BR|DC:
case BR|PL:
case BR|PLC:
encoder_val--;
break;
#endif //ONEBUTTON
}
} else event = 0; // no button pressed: reset event
if(menumode == 0){
if(encoder_val){ // process encoder tuning steps
process_encoder_tuning_step(encoder_val);
encoder_val = 0;
}
}
if(menumode == 0){
display_vfo(freq);
stepsize_showcursor();
#ifdef CAT
//Command_GETFreqA();
#endif
//noInterrupts();
uint8_t f = 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, ...
#ifdef RIT_ENABLE
if(rit){ si5351.freq_calc_fast(rit); si5351.SendPLLRegisterBulk(); }
#endif //RIT_ENABLE
//interrupts();
}
#ifdef CW_MESSAGE
if((mode == CW) && (cw_msg_event) && (millis() > cw_msg_event)){ // if it is
time to send a 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) cw_msg_event = millis() + 1000 *
cw_msg_interval; else cw_msg_event = 0; // then send message, if not interrupted
and its a CQ msg and there is an interval set, then schedule new event
}
#endif //CW_MESSAGE
wdt_reset();
/* BACKLOG:
code definitions and re-use for comb, integrator, dc decoupling, arctan
refactor main()
agc based on rms256, agc/smeter after filter
noisefree integrator (rx audio out) in lower range
raised cosine tx amp for cw, 4ms tau seems enough:
http://fermi.la.asu.edu/w9cf/articles/click/index.html
32 bin fft
dynamic range cw
att extended agc
Split
undersampling, IF-offset
K2/TS480 CAT control
faster RX-TX switch to support CW
usdx API demo code
scan
move last bit of arrays into flash?
https://web.archive.org/web/20180324010832/https://www.microchip.com/webdoc/
AVRLibcReferenceManual/FAQ_1faq_rom_array.html
u-law in RX path?: http://dystopiancode.blogspot.com/2012/02/pcm-law-and-u-law-
companding-algorithms.html
Arduino library?
1. RX bias offset correction by measurement avg, 2. charge decoupling cap. by
resetting to 0V and setting 5V for a certain amount of (charge) time
add 1K (500R?) to GND at TTL RF output to keep zero-level below BS170 threshold
additional PWM output for potential BOOST conversion
squelch gating
more buttons
s-meter offset vs DC bal.
keyer with interrupt-driven timers (to reduce jitter)
Analyse assembly:
/home/guido/Downloads/arduino-1.8.10/hardware/tools/avr/bin/avr-g++ -S -g -Os -w -
std=gnu++11 -fpermissive -fno-exceptions -ffunction-sections -fdata-sections -fno-
threadsafe-statics -Wno-error=narrowing -MMD -mmcu=atmega328p -DF_CPU=16000000L -
DARDUINO=10810 -DARDUINO_AVR_UNO -DARDUINO_ARCH_AVR
-I/home/guido/Downloads/arduino-1.8.10/hardware/arduino/avr/cores/arduino
-I/home/guido/Downloads/arduino-1.8.10/hardware/arduino/avr/variants/standard
/tmp/arduino_build_483134/sketch/QCX-SSB.ino.cpp -o
/tmp/arduino_build_483134/sketch/QCX-SSB.ino.cpp.txt
Rewire/code I/Q clk pins so that a Div/1 and Div/2 scheme is used instead of 0 and
90 degrees phase shift
10,11,13,12 10,11,12,13 (pin)
Q- I+ Q+ I- Q- I+ Q+ I-
90 deg.shift div/2@S1(pin2)
// https://www.ti.com/lit/ds/symlink/ina226.pdf
#include <Wire.h>
#include <LiquidCrystal_I2C.h>
LiquidCrystal_I2C lcd(0x27, 20, 4);
#include <Adafruit_INA219.h>
Adafruit_INA219 ina219;
float pwr;
float Eff;
float Vinc, Vref = 0, SWR;
float k = 0.85;
float busvoltage = 0;
float current_mA = 0;
float power_mW = 0;
void setup() {
ina219.begin();
lcd.init();
lcd.backlight();
}
void loop() {
busvoltage = ina219.getBusVoltage_V();
current_mA = ina219.getCurrent_mA();
power_mW = ina219.getPower_mW();
Vinc = analogRead(3);
Vref = analogRead(2);
SWR = (Vinc + Vref) / (Vinc - Vref);
Vinc = ((Vinc * 5.0) / 1024.0) + 0.5;
pwr = ((((Vinc) * (Vinc)) - 0.25 ) * k);
Eff = (pwr) / ((power_mW) / 1000) * 100;
if (pwr > 0 ) (pwr = pwr + 0.25);
lcd.setCursor(0, 0);
lcd.print("SWR :1 / P W");
lcd.setCursor(4, 0);
lcd.print(SWR);
lcd.setCursor(15, 0);
lcd.print(pwr);
lcd.setCursor(8, 2);
lcd.print (-((current_mA) / 1000));
lcd.print("A ");
lcd.setCursor(15, 2);
lcd.print((power_mW) / 1000);
lcd.print("W ");
lcd.setCursor(0, 1);
lcd.print("Efficiency = ");
lcd.print(Eff);
lcd.setCursor(17, 1);
lcd.print("% ");
delay(300);
}
*/