Last active
March 14, 2021 12:27
-
-
Save threeme3/dc6e02a3a1c2ba49e717ac622719e720 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// 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