Skip to content

Instantly share code, notes, and snippets.

@threeme3
Last active March 14, 2021 12:27
Show Gist options
  • Star 2 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save threeme3/dc6e02a3a1c2ba49e717ac622719e720 to your computer and use it in GitHub Desktop.
Save threeme3/dc6e02a3a1c2ba49e717ac622719e720 to your computer and use it in GitHub Desktop.
// SDR RX
// Copyright 2021, Guido <pe1nnz@amsat.org>
//
// 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.
//#define OLED 1
// QCX pin defintion
#define LCD_D4 0 //PD0
#define LCD_D5 1 //PD1
#define LCD_D6 2 //PD2
#define LCD_D7 3 //PD3
#define LCD_EN 4 //PD4
#define FREQCNT 5 //PD5
#define ROT_A 6 //PD6
#define ROT_B 7 //PD7
#define RX 8 //PB0
#define SIDETONE 9 //PB1
#define KEY_OUT 10 //PB2
#define SIG_OUT 11 //PB3
#define DAH 12 //PB4
#define DIT 13 //PB5
#define AUDIO1 PIN_A0 //PC0
#define AUDIO2 PIN_A1 //PC1
#define DVM PIN_A2 //PC2
#define BUTTONS PIN_A3 //PC3
#define LCD_RS 18 //PC4
#define SDA 18 //PC4
#define SCL 19 //PC5
class I2C_ {
public:
#define _DELAY() for(uint8_t i = 0; i != 4; i++) asm("nop"); // 4=731kb/s
#define _I2C_SDA (1<<2) // PD2
#define _I2C_SCL (1<<3) // PD3
#define _I2C_INIT() PORTD &= ~_I2C_SDA; PORTD &= ~_I2C_SCL; _I2C_SDA_HI(); _I2C_SCL_HI(); // open-drain
#define _I2C_SDA_HI() DDRD &= ~_I2C_SDA; _DELAY();
#define _I2C_SDA_LO() DDRD |= _I2C_SDA; _DELAY();
#define _I2C_SCL_HI() DDRD &= ~_I2C_SCL; _DELAY();
#define _I2C_SCL_LO() DDRD |= _I2C_SCL; _DELAY();
#define _I2C_START() _I2C_SDA_LO(); _I2C_SCL_LO(); _I2C_SDA_HI();
#define _I2C_STOP() _I2C_SCL_HI(); _I2C_SDA_HI();
#define _I2C_SUSPEND() //_I2C_SDA_LO(); // SDA_LO to allow re-use as output port
#define _SendBit(data, bit) \
if(data & 1 << bit){ \
_I2C_SDA_HI(); \
} else { \
_I2C_SDA_LO(); \
} \
_I2C_SCL_HI(); \
_I2C_SCL_LO();
inline void start(){ _I2C_INIT(); _I2C_START(); };
inline void stop() { _I2C_STOP(); _I2C_SUSPEND(); };
inline void SendByte(uint8_t data){
_SendBit(data, 7);
_SendBit(data, 6);
_SendBit(data, 5);
_SendBit(data, 4);
_SendBit(data, 3);
_SendBit(data, 2);
_SendBit(data, 1);
_SendBit(data, 0);
_I2C_SDA_HI(); // recv ACK
_DELAY(); //
_I2C_SCL_HI();
_I2C_SCL_LO();
}
void SendRegister(uint8_t addr, uint8_t* data, uint8_t n){
start();
SendByte(addr << 1);
while(n--) SendByte(*data++);
stop();
}
//void SendRegister(uint8_t addr, uint8_t val){ SendRegister(addr, &val, 1); }
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>
const uint8_t font[]PROGMEM = {
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x06,0x5f,0x5f,0x06,0x00,0x00,
0x00,0x07,0x07,0x00,0x07,0x07,0x00,0x00,0x14,0x7f,0x7f,0x14,0x7f,0x7f,0x14,0x00,
0x24,0x2e,0x2a,0x6b,0x6b,0x3a,0x12,0x00,0x46,0x66,0x30,0x18,0x0c,0x66,0x62,0x00,
0x30,0x7a,0x4f,0x5d,0x37,0x7a,0x48,0x00,0x00,0x04,0x07,0x03,0x00,0x00,0x00,0x00,
0x00,0x1c,0x3e,0x63,0x41,0x00,0x00,0x00,0x00,0x41,0x63,0x3e,0x1c,0x00,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,0x60,0x30,0x18,0x0c,0x06,0x03,0x01,0x00,
0x3e,0x7f,0x59,0x4d,0x47,0x7f,0x3e,0x00,0x40,0x42,0x7f,0x7f,0x40,0x40,0x00,0x00,
0x62,0x73,0x59,0x49,0x6f,0x66,0x00,0x00,0x22,0x63,0x49,0x49,0x7f,0x36,0x00,0x00,
0x18,0x1c,0x16,0x53,0x7f,0x7f,0x50,0x00,0x27,0x67,0x45,0x45,0x7d,0x39,0x00,0x00,
0x3c,0x7e,0x4b,0x49,0x79,0x30,0x00,0x00,0x03,0x03,0x71,0x79,0x0f,0x07,0x00,0x00,
0x36,0x7f,0x49,0x49,0x7f,0x36,0x00,0x00,0x06,0x4f,0x49,0x69,0x3f,0x1e,0x00,0x00,
0x00,0x00,0x00,0x66,0x66,0x00,0x00,0x00,0x00,0x00,0x80,0xe6,0x66,0x00,0x00,0x00,
0x08,0x1c,0x36,0x63,0x41,0x00,0x00,0x00,0x00,0x14,0x14,0x14,0x14,0x14,0x14,0x00,
0x00,0x41,0x63,0x36,0x1c,0x08,0x00,0x00,0x00,0x02,0x03,0x59,0x5d,0x07,0x02,0x00,
0x3e,0x7f,0x41,0x5d,0x5d,0x5f,0x0e,0x00,0x7c,0x7e,0x13,0x13,0x7e,0x7c,0x00,0x00,
0x41,0x7f,0x7f,0x49,0x49,0x7f,0x36,0x00,0x1c,0x3e,0x63,0x41,0x41,0x63,0x22,0x00,
0x41,0x7f,0x7f,0x41,0x63,0x3e,0x1c,0x00,0x41,0x7f,0x7f,0x49,0x5d,0x41,0x63,0x00,
0x41,0x7f,0x7f,0x49,0x1d,0x01,0x03,0x00,0x1c,0x3e,0x63,0x41,0x51,0x33,0x72,0x00,
0x7f,0x7f,0x08,0x08,0x7f,0x7f,0x00,0x00,0x00,0x41,0x7f,0x7f,0x41,0x00,0x00,0x00,
0x30,0x70,0x40,0x41,0x7f,0x3f,0x01,0x00,0x41,0x7f,0x7f,0x08,0x1c,0x77,0x63,0x00,
0x41,0x7f,0x7f,0x41,0x40,0x60,0x70,0x00,0x7f,0x7f,0x0e,0x1c,0x0e,0x7f,0x7f,0x00,
0x7f,0x7f,0x06,0x0c,0x18,0x7f,0x7f,0x00,0x1c,0x3e,0x63,0x41,0x63,0x3e,0x1c,0x00,
0x41,0x7f,0x7f,0x49,0x09,0x0f,0x06,0x00,0x1e,0x3f,0x21,0x31,0x61,0x7f,0x5e,0x00,
0x41,0x7f,0x7f,0x09,0x19,0x7f,0x66,0x00,0x26,0x6f,0x4d,0x49,0x59,0x73,0x32,0x00,
0x03,0x41,0x7f,0x7f,0x41,0x03,0x00,0x00,0x7f,0x7f,0x40,0x40,0x7f,0x7f,0x00,0x00,
0x1f,0x3f,0x60,0x60,0x3f,0x1f,0x00,0x00,0x3f,0x7f,0x60,0x30,0x60,0x7f,0x3f,0x00,
0x63,0x77,0x1c,0x08,0x1c,0x77,0x63,0x00,0x07,0x4f,0x78,0x78,0x4f,0x07,0x00,0x00,
0x47,0x63,0x71,0x59,0x4d,0x67,0x73,0x00,0x00,0x7f,0x7f,0x41,0x41,0x00,0x00,0x00,
0x01,0x03,0x06,0x0c,0x18,0x30,0x60,0x00,0x00,0x41,0x41,0x7f,0x7f,0x00,0x00,0x00,
0x08,0x0c,0x06,0x03,0x06,0x0c,0x08,0x00,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,
0x00,0x00,0x03,0x07,0x04,0x00,0x00,0x00,0x20,0x74,0x54,0x54,0x3c,0x78,0x40,0x00,
0x41,0x7f,0x3f,0x48,0x48,0x78,0x30,0x00,0x38,0x7c,0x44,0x44,0x6c,0x28,0x00,0x00,
0x30,0x78,0x48,0x49,0x3f,0x7f,0x40,0x00,0x38,0x7c,0x54,0x54,0x5c,0x18,0x00,0x00,
0x48,0x7e,0x7f,0x49,0x03,0x06,0x00,0x00,0x98,0xbc,0xa4,0xa4,0xf8,0x7c,0x04,0x00,
0x41,0x7f,0x7f,0x08,0x04,0x7c,0x78,0x00,0x00,0x44,0x7d,0x7d,0x40,0x00,0x00,0x00,
0x60,0xe0,0x80,0x84,0xfd,0x7d,0x00,0x00,0x41,0x7f,0x7f,0x10,0x38,0x6c,0x44,0x00,
0x00,0x41,0x7f,0x7f,0x40,0x00,0x00,0x00,0x7c,0x7c,0x18,0x78,0x1c,0x7c,0x78,0x00,
0x7c,0x78,0x04,0x04,0x7c,0x78,0x00,0x00,0x38,0x7c,0x44,0x44,0x7c,0x38,0x00,0x00,
0x84,0xfc,0xf8,0xa4,0x24,0x3c,0x18,0x00,0x18,0x3c,0x24,0xa4,0xf8,0xfc,0x84,0x00,
0x44,0x7c,0x78,0x4c,0x04,0x0c,0x18,0x00,0x48,0x5c,0x54,0x74,0x64,0x24,0x00,0x00,
0x04,0x04,0x3e,0x7f,0x44,0x24,0x00,0x00,0x3c,0x7c,0x40,0x40,0x3c,0x7c,0x40,0x00,
0x1c,0x3c,0x60,0x60,0x3c,0x1c,0x00,0x00,0x3c,0x7c,0x60,0x30,0x60,0x7c,0x3c,0x00,
0x44,0x6c,0x38,0x10,0x38,0x6c,0x44,0x00,0x9c,0xbc,0xa0,0xa0,0xfc,0x7c,0x00,0x00,
0x4c,0x64,0x74,0x5c,0x4c,0x64,0x00,0x00,0x08,0x08,0x3e,0x77,0x41,0x41,0x00,0x00,
0x00,0x00,0x00,0x77,0x77,0x00,0x00,0x00,0x41,0x41,0x77,0x3e,0x08,0x08,0x00,0x00,
0x02,0x03,0x01,0x03,0x02,0x03,0x01,0x00,0x70,0x78,0x4c,0x46,0x4c,0x78,0x70,0x00};
#define FONT_W 8
#define FONT_H 2
#define FONT_STRETCH 1
static const uint8_t ssd1306_init_sequence [] PROGMEM = { // Initialization Sequence
// 0xAE, // Display OFF (sleep mode)
0x20, 0b10, // Set Memory Addressing Mode
// 00=Horizontal Addressing Mode; 01=Vertical Addressing Mode;
// 10=Page Addressing Mode (RESET); 11=Invalid
0xB0, // Set Page Start Address for Page Addressing Mode, 0-7
0xC8, // Set COM Output Scan Direction. Flip Veritically.
0x00, // Set low nibble of column address
0x10, // Set high nibble of column address
0x40, // Set display start line address
0x81, /*32*/ 0x7F, // Set contrast control register
0xA1, // Set Segment Re-map. A0=column 0 mapped to SEG0; A1=column 127 mapped to SEG0. Flip Horizontally
0xA6, // Set display mode. A6=Normal; A7=Inverse
0xA8, 0x1F, // Set multiplex ratio(1 to 64)
0xA4, // Output RAM to Display
// 0xA4=Output follows RAM content; 0xA5,Output ignores RAM content
0xD3, 0x00, // Set display offset. 00 = no offset
0xD5, 0x01, // 0x80--set display clock divide ratio/oscillator frequency
0xD9, 0xF1, // 0xF1=brighter //0x22 Set pre-charge period
#ifdef CONDENSED
0xDA, 0x12, // Set com pins hardware configuration
#else
0xDA, 0x02, // Set com pins hardware configuration
#endif
0xDB, 0x05, //0x20, --set vcomh 0x20 = 0.77xVcc
0x8D, 0x14, // Set DC-DC enable
0xAF, // Display ON
};
class SSD1306Device: public Print {
public:
#define SSD1306_ADDR 0x3C // Slave address
#define SSD1306_PAGES 4
#define SSD1306_COMMAND 0x00
#define SSD1306_DATA 0x40
uint8_t oledX = 0, oledY = 0;
uint8_t renderingFrame = 0xB0;
bool wrap = false;
void begin(uint8_t cols, uint8_t rows, uint8_t charsize = 0){
Wire.begin();
Wire.beginTransmission(SSD1306_ADDR); Wire.write(SSD1306_COMMAND);
for (uint8_t i = 0; i < sizeof(ssd1306_init_sequence); i++) {
Wire.write(pgm_read_byte(&ssd1306_init_sequence[i]));
}
Wire.endTransmission();
delayMicroseconds(100);
}
void noCursor(){}
void cursor(){}
void _setCursor(uint8_t x, uint8_t y) { oledX = x; oledY = y;
Wire.beginTransmission(SSD1306_ADDR); Wire.write(SSD1306_COMMAND);
Wire.write(renderingFrame | (oledY & 0x07));
Wire.write(0x10 | ((oledX & 0xf0) >> 4));
Wire.write(oledX & 0x0f);
Wire.endTransmission();
}
void setCursor(uint8_t x, uint8_t y) { _setCursor(x * FONT_W, y * FONT_H); }
void newLine() {
oledY+=FONT_H;
if (oledY > SSD1306_PAGES - FONT_H) {
oledY = SSD1306_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;
}
uint16_t offset = ((uint16_t)c - ' ') * FONT_W * FONT_H;
uint8_t line = FONT_H;
do
{
if(FONT_STRETCH) offset = ((uint16_t)c - ' ') * FONT_W * FONT_H/2;
Wire.beginTransmission(SSD1306_ADDR); Wire.write(SSD1306_DATA);
for (uint8_t i = 0; i < FONT_W; i++) {
uint8_t b = pgm_read_byte(&(font[offset++]));
if(FONT_STRETCH){
uint8_t b2 = 0;
if(line > 1) for(int i = 0; i!=4; i++) b2 |=/* ! */(b & (1<<i)) ? (1<<(i*2)) | (1<<(i*2)+1): 0x00;
else for(int i = 0; i!=4; i++) b2 |=/* ! */(b & (1<<(i+4))) ? (1<<(i*2)) | (1<<(i*2)+1): 0x00;
Wire.write(b2);
} else Wire.write(b);
}
Wire.endTransmission();
if (FONT_H == 1) {
oledX+=FONT_W;
}
else {
if (line > 1) {
_setCursor(oledX, oledY + 1);
}
else {
_setCursor(oledX + FONT_W, oledY - (FONT_H - 1));
}
}
}
while (--line);
return 1;
}
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(SSD1306_ADDR); Wire.write(SSD1306_DATA);
for (uint8_t x = x0; x < x1; x++) {
Wire.write(pgm_read_byte(&bitmap[j++]));
}
Wire.endTransmission();
}
setCursor(0, 0);
}
};
#ifdef OLED
SSD1306Device lcd;
#else
#include <LiquidCrystal.h>
LiquidCrystal lcd(LCD_RS, LCD_EN, LCD_D4, LCD_D5, LCD_D6, LCD_D7);
#endif
volatile int8_t encoder_val = 0;
volatile int8_t encoder_step = 0;
static uint8_t last_state;
ISR(PCINT2_vect){ // Interrupt on rotary encoder turn
switch(last_state = (last_state << 4) | (digitalRead(ROT_B) << 1) | digitalRead(ROT_A)){ //transition (see: https://www.allaboutcircuits.com/projects/how-to-use-a-rotary-encoder-in-a-mcu-based-project/ )
case 0x31: case 0x10: case 0x02: case 0x23: if(encoder_step < 0) encoder_step = 0; encoder_step++; if(encoder_step > 3){ encoder_step = 0; encoder_val++; } break;
case 0x32: case 0x20: case 0x01: case 0x13: if(encoder_step > 0) encoder_step = 0; encoder_step--; if(encoder_step < -3){ encoder_step = 0; encoder_val--; } break;
}
}
void encoder_setup()
{
pinMode(ROT_A, INPUT_PULLUP);
pinMode(ROT_B, INPUT_PULLUP);
PCMSK2 |= (1 << PCINT22) | (1 << PCINT23); // interrupt-enable for ROT_A, ROT_B pin changes; see https://github.com/EnviroDIY/Arduino-SDI-12/wiki/2b.-Overview-of-Interrupts
PCICR |= (1 << PCIE2);
last_state = (digitalRead(ROT_B) << 1) | digitalRead(ROT_A);
interrupts();
}
class I2C {
public:
/*
#define DELAY() for(uint8_t i = 0; i != 8; i++) asm("nop"); // 4=731kb/s
#define I2C_SDA (1 << 4) // PC4
#define I2C_SCL (1 << 5) // PC5
#define I2C_INIT() I2C_SDA_HI(); I2C_SCL_HI(); DDRC |= (I2C_SDA | I2C_SCL);
#define I2C_SDA_HI() PORTC |= I2C_SDA; DELAY();
#define I2C_SDA_LO() PORTC &= ~I2C_SDA; DELAY();
#define I2C_SCL_HI() PORTC |= I2C_SCL; DELAY();
#define I2C_SCL_LO() PORTC &= ~I2C_SCL; DELAY();
#define I2C_START() I2C_SDA_LO(); I2C_SCL_LO(); I2C_SDA_HI();
#define I2C_STOP() I2C_SCL_HI(); I2C_SDA_HI();
#define I2C_SUSPEND() //I2C_SDA_LO(); // SDA_LO to allow re-use as output port
*/
#define DELAY() for(uint8_t i = 0; i != 4; i++) asm("nop"); // 4=731kb/s
#define I2C_SDA (1 << 4) // PC4
#define I2C_SCL (1 << 5) // PC5
#define I2C_INIT() PORTC &= ~(I2C_SDA | I2C_SCL);
#define I2C_SDA_HI() DDRC &= ~I2C_SDA;
#define I2C_SDA_LO() DDRC |= I2C_SDA;
#define I2C_SCL_HI() DDRC &= ~I2C_SCL; DELAY();
#define I2C_SCL_LO() DDRC |= I2C_SCL; DELAY();
#define I2C_START() I2C_SCL_LO(); I2C_SDA_HI();
#define I2C_STOP() I2C_SCL_HI(); I2C_SDA_HI();
#define I2C_SUSPEND() I2C_SDA_LO(); // SDA_LO to allow re-use as output port
#define SendBit(data, bit) \
if(data & 1 << bit){ \
I2C_SDA_HI(); \
} else { \
I2C_SDA_LO(); \
} \
I2C_SCL_HI(); \
I2C_SCL_LO();
inline void start(){ I2C_INIT(); I2C_START(); };
inline void stop() { I2C_STOP(); I2C_SUSPEND(); };
inline void SendByte(uint8_t data){
SendBit(data, 7);
SendBit(data, 6);
SendBit(data, 5);
SendBit(data, 4);
SendBit(data, 3);
SendBit(data, 2);
SendBit(data, 1);
SendBit(data, 0);
I2C_SDA_HI(); // recv ACK
DELAY();
I2C_SCL_HI();
I2C_SCL_LO();
}
void SendRegister(uint8_t addr, uint8_t reg, uint8_t* data, uint8_t n){
start();
SendByte(addr << 1);
SendByte(reg);
while(n--) SendByte(*data++);
stop();
}
void SendRegister(uint8_t addr, uint8_t reg, uint8_t val){ SendRegister(addr, reg, &val, 1); }
};
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 _fxtal;
volatile uint8_t pll_regs[8];
#define BB0(x) ((uint8_t)(x)) // Bash byte x of int32_t
#define BB1(x) ((uint8_t)((x)>>8))
#define BB2(x) ((uint8_t)((x)>>16))
#define SI5351_ADDR 0x60 // I2C address of Si5351 (typical)
inline void SendRegister(uint8_t reg, uint8_t val){ i2c.SendRegister(SI5351_ADDR, reg, &val, 1); }
inline void SendRegister(uint8_t reg, uint8_t* data, uint8_t n){ i2c.SendRegister(SI5351_ADDR, reg, data, n); }
inline void SendPLLBRegisterBulk(){
i2c.start();
i2c.SendByte(SI5351_ADDR << 1);
i2c.SendByte(26+1*8 + 3); // Write to PLLB
i2c.SendByte(pll_regs[3]);
i2c.SendByte(pll_regs[4]);
i2c.SendByte(pll_regs[5]);
i2c.SendByte(pll_regs[6]);
i2c.SendByte(pll_regs[7]);
i2c.stop();
}
uint32_t fxtal = 27004900; // Crystal freq in Hz, nominal frequency 27004300
int16_t iqmsa; // to detect a need for a PLL reset
#define FAST __attribute__((optimize("Ofast")))
inline void FAST freq_calc_fast(int16_t df){ // relies on cache: _msb128, _msa128min512, _div, _fout, fxtal
#define _MSC 0x80000
uint32_t msb128 = _msb128 + ((int64_t)(_div * (int32_t)df) * _MSC * 128) / fxtal;
uint32_t msp1 = _msa128min512 + msb128 / _MSC;
uint32_t msp2 = msb128 % _MSC; // = msb128 - msb128/_MSC * _MSC;
pll_regs[3] = BB1(msp1); // first 3 regs are constant hence skipped
pll_regs[4] = BB0(msp1);
pll_regs[5] = ((_MSC&0xF0000)>>(16-4))|BB2(msp2); // top nibble MUST be same as top nibble of _MSC !
pll_regs[6] = BB1(msp2);
pll_regs[7] = BB0(msp2);
}
void freq(uint32_t fout, uint8_t i, uint8_t q){ // Set a CLK0,1 to fout Hz with phase i, q
uint8_t msa; uint32_t msb, msc, msp1, msp2, msp3p2;
uint8_t rdiv = 0; // CLK pin sees fout/(2^rdiv)
if(fout < 500000){ rdiv = 7; fout *= 128; }; // Divide by 128 for fout 4..500kHz
uint16_t d = (16 * fxtal) / fout; // Integer part
if( (d * (fout - 5000) / fxtal) != (d * (fout + 5000) / fxtal) ) d--; // Test if multiplier remains same for freq deviation +/- 5kHz, if not use different divider to make same
uint32_t fvcoa = d * fout; // Variable PLLA VCO frequency at integer multiple of fout at around 27MHz*16 = 432MHz
msa = fvcoa / fxtal; // Integer part of vco/fxtal
msb = ((uint64_t)(fvcoa % fxtal)*_MSC) / fxtal; // Fractional part
msc = _MSC;
msp1 = 128*msa + 128*msb/msc - 512;
msp2 = 128*msb - 128*msb/msc * msc; // msp3 == msc
msp3p2 = (((msc & 0x0F0000) <<4) | msp2); // msp3 on top nibble
uint8_t pll_regs[8] = { BB1(msc), BB0(msc), BB2(msp1), BB1(msp1), BB0(msp1), BB2(msp3p2), BB1(msp2), BB0(msp2) };
SendRegister(26+0*8, pll_regs, 8); // Write to PLLA
SendRegister(26+1*8, pll_regs, 8); // Write to PLLB
msa = fvcoa / fout; // Integer part of vco/fout
msp1 = (128*msa - 512) | (((uint32_t)rdiv)<<20); // msp1 and msp2=0, msp3=1, not fractional
uint8_t ms_regs[8] = {0, 1, BB2(msp1), BB1(msp1), BB0(msp1), 0, 0, 0};
SendRegister(42+0*8, ms_regs, 8); // Write to MS0
SendRegister(42+1*8, ms_regs, 8); // Write to MS1
SendRegister(42+2*8, ms_regs, 8); // Write to MS2
SendRegister(16+0, 0x0C|3); // CLK0: PLLA local msynth; 3=8mA
SendRegister(16+1, 0x0C|3); // CLK1: PLLA local msynth; 3=8mA
SendRegister(16+2, 0x2C|3); // CLK2: PLLB local msynth; 3=8mA
SendRegister(165, i * msa / 90); // CLK0: I-phase (on change -> Reset PLL)
SendRegister(166, q * msa / 90); // CLK1: Q-phase (on change -> Reset PLL)
if(iqmsa != ((i-q)*msa/90)){ iqmsa = (i-q)*msa/90; SendRegister(177, 0xA0); } // 0x20 reset PLLA; 0x80 reset PLLB
SendRegister(3, 0b11111100); // Enable/disable clock
_fout = fout; // cache
_div = d;
_msa128min512 = fvcoa / fxtal * 128 - 512;
_msb128=((uint64_t)(fvcoa % fxtal)*_MSC*128) / fxtal;
}
};
static SI5351 si5351;
#define log2(n) (log(n) / log(2))
#pragma GCC push_options
#pragma GCC optimize ("Ofast") // compiler-optimization for speed
volatile int8_t volume = 10;
enum mode_t { LSB, USB, CW, AM, FM };
volatile int8_t mode = USB;
volatile bool agc = true;
volatile uint8_t nr =0;
volatile uint8_t att = 0;
volatile uint8_t att2 = 0;
volatile uint8_t _init;
static int16_t gain = 1024;
inline int16_t process_agc(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;
}
#define EA(y, x, one_over_alpha) (y) = (y) + ((x) - (y)) / (one_over_alpha); // exponental averaging [Lyons 13.33.1]
#define MLEA(y, x, L, M) (y) = (y) + ((((x) - (y)) >> (L)) - (((x) - (y)) >> (M))); // multiplierless exponental averaging [Lyons 13.33.1], with alpha=1/2^L - 1/2^M
inline int16_t process_nr(int16_t in)
{
static int16_t ea1;
ea1 = EA(ea1, in, 1 << (nr-1) );
return ea1;
}
#define N_FILT 7
volatile int8_t filt = 0;
inline int16_t filt_var(int16_t za0) //filters build with www.micromodeler.com
{
static int16_t za1,za2;
static int16_t zb0,zb1,zb2;
static int16_t zc0,zc1,zc2;
if(filt < 4)
{ // for SSB filters
// 1st Order (SR=8kHz) IIR in Direct Form I, 8x8:16
// M0PUB: There was a bug here, since za1 == zz1 at this point in the code, and the old algorithm for the 300Hz high-pass was:
// za0=(29*(za0-zz1)+50*za1)/64;
// zz2=zz1;
// zz1=za0;
// After correction, this filter still introduced almost 6dB attenuation, so I adjusted the coefficients
static int16_t zz1,zz2;
//za0=(29*(za0-zz1)+50*za1)/64; //300-Hz
zz2=zz1;
zz1=za0;
za0=(30*(za0-zz2)+25*za1)/32; //300-Hz
// 4th Order (SR=8kHz) IIR in Direct Form I, 8x8:16
switch(filt){
case 1: zb0=(za0+2*za1+za2)/2-(13*zb1+11*zb2)/16; break; // 0-2900Hz filter, first biquad section
case 2: zb0=(za0+2*za1+za2)/2-(2*zb1+8*zb2)/16; break; // 0-2400Hz filter, first biquad section
//case 3: zb0=(za0+2*za1+za2)/2-(4*zb1+2*zb2)/16; break; // 0-2400Hz filter, first biquad section
case 3: zb0=(za0+2*za1+za2)/2-(0*zb1+4*zb2)/16; break; //0-1800Hz elliptic
//case 3: zb0=(za0+7*za1+za2)/16-(-24*zb1+9*zb2)/16; break; //0-1700Hz elliptic with slope
}
switch(filt){
case 1: zc0=(zb0+2*zb1+zb2)/2-(18*zc1+11*zc2)/16; break; // 0-2900Hz filter, second biquad section
case 2: zc0=(zb0+2*zb1+zb2)/4-(4*zc1+8*zc2)/16; break; // 0-2400Hz filter, second biquad section
//case 3: zc0=(zb0+2*zb1+zb2)/4-(1*zc1+9*zc2)/16; break; // 0-2400Hz filter, second biquad section
case 3: zc0=(zb0+2*zb1+zb2)/4-(0*zc1+4*zc2)/16; break; //0-1800Hz elliptic
//case 3: zc0=(zb0+zb1+zb2)/16-(-22*zc1+47*zc2)/64; break; //0-1700Hz elliptic with slope
}
zc2=zc1;
zc1=zc0;
zb2=zb1;
zb1=zb0;
za2=za1;
za1=za0;
return zc0;
} else { // for CW filters
// (2nd Order (SR=4465Hz) IIR in Direct Form I, 8x8:16), adding 64x front-gain (to deal with later division)
{
switch(filt){
case 4: zb0=(0*za0+1*za1+0*za2)+(114L*zb1-57L*zb2)/64; break; //600Hz+-250Hz
case 5: zb0=(0*za0+1*za1+0*za2)+(113L*zb1-60L*zb2)/64; break; //600Hz+-100Hz
case 6: zb0=(0*za0+1*za1+0*za2)+(110L*zb1-62L*zb2)/64; break; //600Hz+-50Hz
case 7: zb0=(0*za0+1*za1+0*za2)+(110L*zb1-61L*zb2)/64; break; //600Hz+-18Hz
//case 8: zb0=(0*za0+1*za1+0*za2)+(110L*zb1-60L*zb2)/64; break; //591Hz+-12Hz
}
switch(filt){
case 4: zc0=(zb0-2*zb1+zb2)/1+(95L*zc1-52L*zc2)/64; break; //600Hz+-250Hz
case 5: zc0=(zb0-2*zb1+zb2)/4+(106L*zc1-59L*zc2)/64; break; //600Hz+-100Hz
case 6: zc0=(zb0-2*zb1+zb2)/16+(113L*zc1-62L*zc2)/64; break; //600Hz+-50Hz
case 7: zc0=(zb0-2*zb1+zb2)/32+(112L*zc1-62L*zc2)/64; break; //600Hz+-18Hz
//case 8: zc0=(zb0-2*zb1+zb2)/64+(113L*zc1-63L*zc2)/64; break; //591Hz+-12Hz
}
}
zc2=zc1;
zc1=zc0;
zb2=zb1;
zb1=zb0;
za2=za1;
za1=za0;
//return zc0 / 64; // compensate the 64x front-end gain
return zc0 / 8; // compensate the front-end gain
}
}
#define _UA 8000
inline int16_t arctan3(int16_t q, int16_t i) // error ~ 0.8 degree
{ // source: [1] http://www-labs.iro.umontreal.ca/~mignotte/IFT2425/Documents/EfficientApproximationArctgFunction.pdf
#define _atan2(z) (_UA/8 - _UA/22 * z + _UA/22) * z //derived from (5) [1]
//#define _atan2(z) (_UA/8 - _UA/24 * z + _UA/24) * z //derived from (7) [1]
int16_t r;
if(abs(q) > abs(i))
r = _UA / 4 - _atan2(abs(i) / abs(q)); // arctan(z) = 90-arctan(1/z)
else
r = (i == 0) ? 0 : _atan2(abs(q) / abs(i)); // arctan(z)
r = (i < 0) ? _UA / 2 - r : r; // arctan(-z) = -arctan(z)
return (q < 0) ? -r : r; // arctan(-z) = -arctan(z)
}
#define magn(i, q) (abs(i) > abs(q) ? abs(i) + abs(q) / 4 : abs(q) + abs(i) / 4) // approximation of: magnitude = sqrt(i*i + q*q); error 0.95dB
static uint32_t absavg256 = 0;
volatile uint32_t _absavg256 = 0;
volatile int16_t i, q;
inline int16_t slow_dsp(int16_t ac)
{
static uint8_t absavg256cnt;
if(!(absavg256cnt--)){ _absavg256 = absavg256; absavg256 = 0; } else absavg256 += abs(ac);
if(mode == AM) { // (12%CPU for the mode selection etc)
ac = magn(i, q); //(25%CPU)
{ static int16_t dc;
dc += (ac - dc) / 2;
ac = ac - dc; } // DC decoupling
} else if(mode == FM){
static int16_t z1;
int16_t z0 = arctan3(q, i);
ac = z0 - z1; // Differentiator
z1 = z0;
//ac = ac * (F_SAMP_RX/R) / _UA; // =ac*3.5 -> skip
} // needs: p.12 https://www.veron.nl/wp-content/uploads/2014/01/FmDemodulator.pdf
else { ; } // USB, LSB, CW
if(agc) ac = process_agc(ac);
ac = ac >> (16-volume);
if(nr) ac = process_nr(ac);
if(filt) ac = filt_var(ac);
if(mode == CW && filt) ac = ac << 2;
ac = min(max(ac, -512), 511);
return ac;
}
typedef void (*func_t)(void);
volatile func_t func_ptr;
#undef R // Decimating 2nd Order CIC filter
#define R 4 // Rate change from 62500/2 kSPS to 7812.5SPS, providing 12dB gain
volatile uint8_t admux[3];
volatile int16_t ocomb, qh, q_ac2;
volatile uint8_t rx_state = 0;
static uint8_t tc = 0;
void process(int16_t i_ac2, int16_t q_ac2)
{
static int16_t ac3;
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;
//#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
ozd2 = od1;
ozd1 = ac3;
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 ozi1, ozi2;
inline int16_t sdr_rx_common_q(){
ADMUX = admux[0]; ADCSRA |= (1 << ADSC); int16_t ac = ADC - 511;
ozi2 = ozi1 + ozi2; // Integrator section
ozi1 = ocomb + ozi1;
OCR1AL = min(max((ozi2>>5) + 128, 0), 255);
return ac;
}
inline int16_t sdr_rx_common_i()
{
ADMUX = admux[1]; ADCSRA |= (1 << ADSC); int16_t adc = ADC - 511;
if(_init){ ocomb=0; ozi1 = 0; ozi2 = 0; } // hack
static int16_t prev_adc;
int16_t ac = (prev_adc + adc) / 2; prev_adc = adc;
//ozi2 = ozi1 + ozi2; // Integrator section
//ozi1 = ocomb + ozi1;
//OCR1AL = min(max((ozi2>>5) + 128, 0), 255);
return ac;
}
ISR(TIMER2_COMPA_vect) // Timer2 COMPA interrupt
{
func_ptr();
}
#pragma GCC pop_options // end of DSP section
char blanks[] = " ";
#define lcd_blanks() lcd.print(blanks);
int analogSafeRead(uint8_t pin)
{ // performs classical analogRead with default Arduino sample-rate and analog reference setting; restores previous settings
noInterrupts();
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
ADMUX = (1 << REFS0); // restore reference voltage AREF (5V)
delay(1); // settle
int val = analogRead(pin);
ADCSRA = adcsra;
ADMUX = admux;
interrupts();
return val;
}
volatile bool change = true;
volatile int32_t freq = 7074000;
#define _admux(adcpin, ref1v1) (adcpin | ((ref1v1) ? (1 << REFS1) : 0) | (1 << REFS0))
void start_rx()
{
_init = 1;
rx_state = 0;
admux[0] = _admux(0, !(att == 1));
admux[1] = _admux(1, !(att == 1));
ADCSRA = 0; // clear ADCSRA register
ADCSRA |= ((uint8_t)log2((uint8_t)(20007000 / 13 / 192307))) & 0x07; // ADC Prescaler (for normal conversions non-auto-triggered): ADPS = log2(F_CPU / 13 / Fs) - 1; ADSP=0..7 resulting in resp. conversion rate of 1536, 768, 384, 192, 96, 48, 24, 12 kHz
ADCSRA |= (1 << ADEN); // enable ADC
TCCR1A = 0; // Timer 1: OC1A and OC1B in PWM mode
TCCR1B = 0;
TCCR1A |= (1 << COM1A1) | (1 << COM1B1) | (1 << WGM11); // Clear OC1A,OC1B on Compare Match when upcounting. Set OC1A,OC1B on Compare Match when downcounting.
TCCR1B |= (1 << CS10) | (1 << WGM13) | (1 << WGM12); // WGM13: Mode 14 - Fast PWM; CS10: clkI/O/1 (No prescaling)
ICR1H = 0x00;
ICR1L = min(255, (float)20007000 / (float)78125 - 0.5); // PWM value range (fs>78431): Fpwm = F_CPU / [Prescaler * (1 + TOP)]
OCR1AH = 0x00;
OCR1AL = 0x00; // OC1A (SIDETONE) PWM duty-cycle (span defined by ICR).
OCR1BH = 0x00;
OCR1BL = 0x00; // OC1B (KEY_OUT) PWM duty-cycle (span defined by ICR).
TCCR1A &= ~(1 << COM1B1); digitalWrite(KEY_OUT, LOW); // disable KEY_OUT PWM
func_ptr = sdr_rx_00;
//#define INTERRUPT_BASED 1
#ifdef INTERRUPT_BASED
// Timer 2: interrupt mode
ASSR &= ~(1 << AS2); // Timer 2 clocked from CLK I/O (like Timer 0 and 1)
TCCR2A = 0;
TCCR2B = 0;
TCNT2 = 0;
TCCR2A |= (1 << WGM21); // WGM21: Mode 2 - CTC (Clear Timer on Compare Match)
TCCR2B |= (1 << CS22); // Set C22 bits for 64 prescaler
TIMSK2 |= (1 << OCIE2A); // enable timer compare interrupt TIMER2_COMPA_vect
uint8_t ocr = (((float)20000000 / (float)64) / (float)/*78125*/62500 + 0.5) - 1; // OCRn = (F_CPU / pre-scaler / fs) - 1;
OCR2A = ocr;
#endif
}
int8_t prev_bandval = 2;
int8_t bandval = 2;
#define N_BANDS 11
uint32_t band[N_BANDS] = { /*472000, 1840000,*/ 3573000, 5357000, 7074000, 10136000, 14074000, 18100000, 21074000, 24915000, 28074000, 50313000, 70101000/*, 144125000*/ }; // { 3573000, 5357000, 7074000, 10136000, 14074000, 18100000, 21074000 };
enum step_t { STEP_10M, STEP_1M, STEP_500k, STEP_100k, STEP_10k, STEP_1k, STEP_500, STEP_100, STEP_10, STEP_1 };
int32_t stepsizes[10] = { 10000000, 1000000, 500000, 100000, 10000, 1000, 500, 100, 10, 1 };
volatile int8_t stepsize = STEP_1k;
void process_encoder_tuning_step(int8_t steps)
{
int32_t stepval = stepsizes[stepsize];
freq += steps * stepval;
change = true;
}
void stepsize_showcursor()
{
lcd.setCursor(stepsize+1, 1); // display stepsize with cursor
lcd.cursor();
}
void stepsize_change(int8_t val)
{
stepsize += val;
if(stepsize < STEP_1M) stepsize = STEP_10;
if(stepsize > STEP_10) stepsize = STEP_1M;
if(stepsize == STEP_10k || stepsize == STEP_500k) stepsize += val;
stepsize_showcursor();
}
const char* mode_label[5] = { "LSB", "USB", "CW ", "AM ", "FM " };
void display_vfo(uint32_t f){
lcd.setCursor(0, 1);
lcd.print('A'); // VFO-A/B
uint32_t scale=10e6; // VFO frequency
if(f/scale == 0){ lcd.print(' '); scale/=10; } // Initial space instead of zero
for(; scale!=1; f%=scale, scale/=10){
lcd.print(f/scale);
if(scale == (uint32_t)1e3 || scale == (uint32_t)1e6) lcd.print(','); // Thousands separator
}
lcd.print(" "); lcd.print(mode_label[mode]); lcd.print(" ");
lcd.setCursor(15, 1); lcd.print("R");
stepsize_showcursor();
}
// Support functions for parameter and menu handling
enum action_t { UPDATE, UPDATE_MENU, LOAD, SAVE, SKIP };
template<typename T> void paramAction(uint8_t action, T& value, const __FlashStringHelper* menuid, const __FlashStringHelper* label, const char* enumArray[], int32_t _min, int32_t _max, bool continuous){
switch(action){
case UPDATE:
case UPDATE_MENU:
if(((int32_t)value + encoder_val) < _min) value = (continuous) ? _max : _min;
else value += encoder_val;
encoder_val = 0;
if(continuous) value = (value % (_max+1));
value = max(_min, min((int32_t)value, _max));
if(action == UPDATE_MENU){
lcd.setCursor(0, 0);
lcd.print(menuid); lcd.print(' ');
lcd.print(label); lcd_blanks(); lcd_blanks();
lcd.setCursor(0, 1); // value on next line
} else { // UPDATE (not in menu)
lcd.setCursor(0, 1); lcd.print(label); lcd.print(F(": "));
}
if(enumArray == NULL){
if((_min < 0) && (value >= 0)) lcd.print('+');
lcd.print(value);
} else {
lcd.print(enumArray[value]);
}
lcd_blanks(); lcd_blanks();
break;
}
}
const char* offon_label[2] = {"OFF", "ON"};
const char* filt_label[N_FILT+1] = { "Full", "4000", "2500", "1700", "500", "200", "100", "50" };
const char* band_label[N_BANDS] = { "80m", "60m", "40m", "30m", "20m", "17m", "15m", "12m", "10m", "6m", "4m" };
const char* stepsize_label[10] = { "10M", "1M", "0.5M", "100k", "10k", "1k", "0.5k", "100", "10", "1" };
const char* att_label[] = { "0dB", "-13dB", "-20dB", "-33dB", "-40dB", "-53dB", "-60dB", "-73dB" };
const char* smode_label[4] = { "OFF", "dBm", "S", "S-bar" };
#define _N(a) sizeof(a)/sizeof(a[0])
#define N_PARAMS 10 // number of (visible) parameters
#define N_ALL_PARAMS (N_PARAMS) // number of parameters
enum params_t {ALL, VOLUME, MODE, FILTER, BAND, STEP, AGC, NR, ATT, ATT2};
void paramAction(uint8_t action, uint8_t id = ALL) // list of parameters
{
switch(id){
// Visible parameters
case VOLUME: paramAction(action, volume, F("1.1"), F("Volume"), NULL, -1, 16, false); break;
case MODE: paramAction(action, mode, F("1.2"), F("Mode"), mode_label, 0, _N(mode_label) - 1, true); break;
case FILTER: paramAction(action, filt, F("1.3"), F("Filter BW"), filt_label, 0, _N(filt_label) - 1, false); break;
case BAND: paramAction(action, bandval, F("1.4"), F("Band"), band_label, 0, _N(band_label) - 1, false); break;
case STEP: paramAction(action, stepsize, F("1.5"), F("Tune Rate"), stepsize_label, 0, _N(stepsize_label) - 1, false); break;
case AGC: paramAction(action, agc, F("1.6"), F("AGC"), offon_label, 0, 1, false); break;
case NR: paramAction(action, nr, F("1.7"), F("NR"), NULL, 0, 8, false); break;
case ATT: paramAction(action, att, F("1.8"), F("ATT"), att_label, 0, 7, false); break;
case ATT2: paramAction(action, att2, F("1.9"), F("ATT2"), NULL, 0, 16, false); break;
}
}
void handle_button()
{
uint8_t event;
if(digitalRead(BUTTONS)){ // Left-/Right-/Rotary-button (while not already pressed)
enum event_t { BL=0x10, BR=0x20, BE=0x30, SC=0x01, DC=0x02, PL=0x04, PT=0x0C }; // button-left, button-right and button-encoder; single-click, double-click, push-long, push-and-turn
if(!(event & PL)){ // hack: if there was long-push before, then fast forward
uint16_t v = analogSafeRead(BUTTONS);
event = SC;
int32_t t0 = millis();
for(; digitalRead(BUTTONS);){ // until released or long-press
if((millis() - t0) > 300){ event = PL; break; }
}
delay(10); //debounce
for(; (event != PL) && ((millis() - t0) < 500);){ // until 2nd press or timeout
if(digitalRead(BUTTONS)){ event = DC; break; }
}
for(; digitalRead(BUTTONS);){ // until released, or encoder is turned while longpress
if(encoder_val && event == PL){ event = PT; break; }
}
event |= (v < 862) ? BL : (v < 1023) ? BR : BE; // determine which button pressed based on threshold levels
} else { // hack: fast forward handling
event = (event&0xf0) | ((encoder_val) ? PT : PL); // only alternate bewteen push-long/turn when applicable
}
switch(event){
case BR|SC:
encoder_val = 1;
paramAction(UPDATE, MODE);
if(mode != CW) stepsize = STEP_1k; else stepsize = STEP_500;
//if(mode > FM) mode = LSB;
if(mode > CW) mode = LSB; // skip all other modes (only LSB, USB, CW)
if(mode == CW) filt = 4; else filt = 0;
change = true;
break;
case BR|DC:
filt++;
_init = true;
if(mode == CW && filt > N_FILT) filt = 4;
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);
delay(1500);
change = true; // refresh display
break;
case BR|PL:
nr++;
if(nr>2) nr = 0;
paramAction(UPDATE, NR);
delay(1500);
change = true; // refresh display
break;
case BE|DC:
delay(100);
bandval++;
if(bandval >= N_BANDS) bandval = 0;
stepsize = STEP_1k;
change = true;
break;
case BE|SC: stepsize_change(+1); break;
case BE|PL: stepsize_change(-1); break;
case BE|PT:
for(; digitalRead(BUTTONS);){ // process encoder changes until released
if(encoder_val){
paramAction(UPDATE, VOLUME);
}
}
change = true; // refresh display
break;
}
} else event = 0; // no button pressed: reset event
}
void setup()
{
digitalWrite(KEY_OUT, LOW);
digitalWrite(RX, HIGH);
digitalWrite(SIDETONE, LOW);
digitalWrite(SIG_OUT, LOW);
pinMode(KEY_OUT, OUTPUT);
pinMode(RX, OUTPUT);
pinMode(SIDETONE, OUTPUT);
pinMode(SIG_OUT, OUTPUT);
pinMode(BUTTONS, INPUT);
pinMode(DIT, INPUT_PULLUP);
pinMode(DAH, INPUT_PULLUP);
pinMode(AUDIO1, INPUT);
pinMode(AUDIO2, INPUT);
encoder_setup();
lcd.begin(16, 2); // Init LCD
lcd.setCursor(0, 0);
//lcd.print(F("QCX-SDR")); lcd_blanks();
lcd_blanks(); lcd_blanks();
change = true;
prev_bandval = bandval;
start_rx();
}
void loop()
{
#ifndef INTERRUPT_BASED
func_ptr();
#endif
handle_button();
if(encoder_val){ // process encoder tuning steps
process_encoder_tuning_step(encoder_val);
encoder_val = 0;
}
if(change){
change = false;
if(prev_bandval != bandval){ freq = band[bandval]; prev_bandval = bandval; }
display_vfo(freq);
if(mode == CW){
const int cw_offset = 600; // this is actual the center frequency of the CW-filters
si5351.freq(freq + cw_offset, 90, 0); // RX in CW-R (=LSB), correct for CW-tone offset
si5351.freq_calc_fast(-cw_offset); si5351.SendPLLBRegisterBulk(); // TX at freq
} else
if(mode == LSB)
si5351.freq(freq, 90, 0); // RX in LSB
else
si5351.freq(freq, 0, 90); // RX in USB, ...
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment