usdx/QCX-SSB.ino

1109 wiersze
42 KiB
C++

// Arduino Sketch of the QCX-SSB: SSB with your QCX transceiver (modification)
//
// https://github.com/threeme3/QCX-SSB
#define VERSION "1.01d"
// QCX pin defintion
#define LCD_D4 0
#define LCD_D5 1
#define LCD_D6 2
#define LCD_D7 3
#define LCD_EN 4
#define ROT_A 6
#define ROT_B 7
#define RX 8
#define KEY_OUT 10
#define SIG_OUT 11
#define DAH 12
#define DIT 13
#define AUDIO1 14 //A0
#define AUDIO2 15 //A1
#define DVM 16 //A2
#define BUTTONS 17 //A3
#define LCD_RS 18
#define SDA 18 //shared with LCD_RS
#define SCL 19
#include <LiquidCrystal.h>
class QCXLiquidCrystal : 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.
QCXLiquidCrystal(uint8_t rs, uint8_t en, uint8_t d4, uint8_t d5, uint8_t d6, uint8_t d7) : LiquidCrystal(rs, en, d4, d5, d6, 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
};
};
QCXLiquidCrystal lcd(LCD_RS, LCD_EN, LCD_D4, LCD_D5, LCD_D6, LCD_D7);
#include <inttypes.h>
#include <avr/sleep.h>
#include <avr/wdt.h>
#define F_CPU 20008440 //20000000 // Actual crystal frequency of XTAL1
#define I2C_DELAY 4 // Determines I2C Speed (2=939kb/s (too fast!!); 3=822kb/s; 4=731kb/s; 5=658kb/s; 6=598kb/s). Increase this value when you get I2C tx errors (E05); decrease this value when you get a CPU overload (E01). An increment eats ~3.5% CPU load; minimum value is 3 on my QCX, resulting in 84.5% CPU load
#define I2C_DDR DDRC // Pins for the I2C bit banging
#define I2C_PIN PINC
#define I2C_PORT PORTC
#define I2C_SDA (1 << 4) // PC4
#define I2C_SCL (1 << 5) // PC5
#define DELAY(n) for(uint8_t i = 0; i != n; i++) asm("nop");
#define I2C_SDA_GET() I2C_PIN & I2C_SDA
#define I2C_SCL_GET() I2C_PIN & I2C_SCL
#define I2C_SDA_HI() I2C_DDR &= ~I2C_SDA;
#define I2C_SDA_LO() I2C_DDR |= I2C_SDA;
#define I2C_SCL_HI() I2C_DDR &= ~I2C_SCL; DELAY(I2C_DELAY);
#define I2C_SCL_LO() I2C_DDR |= I2C_SCL; DELAY(I2C_DELAY);
inline void i2c_start()
{
i2c_resume(); //prepare for I2C
I2C_SCL_LO();
I2C_SDA_HI();
}
inline void i2c_stop()
{
I2C_SCL_HI();
I2C_SDA_HI();
I2C_DDR &= ~(I2C_SDA | I2C_SCL); // prepare for a start: pull-up both SDA, SCL
i2c_suspend();
}
#define i2c_SendBit(data, mask) \
if(data & mask){ \
I2C_SDA_HI(); \
} else { \
I2C_SDA_LO(); \
} \
I2C_SCL_HI(); \
I2C_SCL_LO();
inline void i2c_SendByte(uint8_t data)
{
i2c_SendBit(data, 1 << 7);
i2c_SendBit(data, 1 << 6);
i2c_SendBit(data, 1 << 5);
i2c_SendBit(data, 1 << 4);
i2c_SendBit(data, 1 << 3);
i2c_SendBit(data, 1 << 2);
i2c_SendBit(data, 1 << 1);
i2c_SendBit(data, 1 << 0);
I2C_SDA_HI(); // recv ACK
DELAY(I2C_DELAY);
I2C_SCL_HI();
I2C_SCL_LO();
}
inline uint8_t i2c_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("E07 I2C timeout"); }
uint8_t data = I2C_SDA_GET();
I2C_SCL_LO();
return (data) ? mask : 0;
}
inline uint8_t i2c_RecvByte(uint8_t last)
{
uint8_t data = 0;
data |= i2c_RecvBit(1 << 7);
data |= i2c_RecvBit(1 << 6);
data |= i2c_RecvBit(1 << 5);
data |= i2c_RecvBit(1 << 4);
data |= i2c_RecvBit(1 << 3);
data |= i2c_RecvBit(1 << 2);
data |= i2c_RecvBit(1 << 1);
data |= i2c_RecvBit(1 << 0);
if(last){
I2C_SDA_HI(); // NACK
} else {
I2C_SDA_LO(); // ACK
}
DELAY(I2C_DELAY);
I2C_SCL_HI();
I2C_SDA_HI(); // restore SDA for read
I2C_SCL_LO();
return data;
}
void i2c_init()
{
I2C_PORT &= ~( I2C_SDA | I2C_SCL );
I2C_SCL_HI();
I2C_SDA_HI();
i2c_suspend();
}
void i2c_deinit()
{
I2C_PORT &= ~( I2C_SDA | I2C_SCL );
I2C_DDR &= ~( I2C_SDA | I2C_SCL );
}
inline void i2c_resume()
{
#ifdef LCD_RS_PORTIO
I2C_PORT &= ~I2C_SDA; // pin sharing SDA/LCD_RS mitigation
#endif
}
inline void i2c_suspend()
{
I2C_SDA_LO(); // pin sharing SDA/LCD_RS: pull-down LCD_RS; QCXLiquidCrystal require this for any operation
}
#define SI_I2C_ADDR 96 // SI5351A I2C address
#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
#define SI_XTAL_FREQ 27003847 //27003847 27004452 Measured crystal frequency of XTAL2 for CL = 10pF (default), calibrate your QCX 27MHz crystal frequency here
#define log2(n) (log(n) / log(2))
volatile uint8_t si5351_prev_divider;
volatile int32_t si5351_raw_freq;
volatile uint8_t si5351_divider; // note: because of int8 only freq > 3.6MHz can be covered for R_DIV=1
volatile uint8_t si5351_mult;
volatile uint8_t si5351_pll_data[8];
static int32_t si5351_prev_pll_freq = 0;
uint8_t si5351_RecvRegister(uint8_t reg)
{
// Data write to set the register address
i2c_start();
i2c_SendByte(SI_I2C_ADDR << 1);
i2c_SendByte(reg);
i2c_stop();
// Data read to retrieve the data from the set address
i2c_start();
i2c_SendByte((SI_I2C_ADDR << 1) | 1);
uint8_t data = i2c_RecvByte(true);
i2c_stop();
return data;
}
void si5351_SendRegister(uint8_t reg, uint8_t data)
{
i2c_start();
i2c_SendByte(SI_I2C_ADDR << 1);
i2c_SendByte(reg);
i2c_SendByte(data);
i2c_stop();
}
inline void si5351_SendPLLBRegisterBulk() // fast freq change of PLLB, takes about [ 2 + 7*(8+1) + 2 ] / 840000 = 80 uS
{
i2c_start();
i2c_SendByte(SI_I2C_ADDR << 1);
i2c_SendByte(SI_SYNTH_PLL_B + 3); // Skip the first three pll_data bytes (first two always 0xFF and third not often changing
i2c_SendByte(si5351_pll_data[3]);
i2c_SendByte(si5351_pll_data[4]);
i2c_SendByte(si5351_pll_data[5]);
i2c_SendByte(si5351_pll_data[6]);
i2c_SendByte(si5351_pll_data[7]);
i2c_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 si5351_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
P1 = (uint32_t)(128 * ((float)num / (float)denom));
P1 = (uint32_t)(128 * (uint32_t)(divider) + P1 - 512);
P2 = (uint32_t)(128 * ((float)num / (float)denom));
P2 = (uint32_t)(128 * num - denom * P2);
P3 = denom;
si5351_SendRegister(reg + 0, (P3 & 0x0000FF00) >> 8);
si5351_SendRegister(reg + 1, (P3 & 0x000000FF));
si5351_SendRegister(reg + 2, (P1 & 0x00030000) >> 16 | ((int)log2(r_div) << 4) );
si5351_SendRegister(reg + 3, (P1 & 0x0000FF00) >> 8);
si5351_SendRegister(reg + 4, (P1 & 0x000000FF));
si5351_SendRegister(reg + 5, ((P3 & 0x000F0000) >> 12) | ((P2 & 0x000F0000) >> 16));
si5351_SendRegister(reg + 6, (P2 & 0x0000FF00) >> 8);
si5351_SendRegister(reg + 7, (P2 & 0x000000FF));
}
// this function relies on cached (global) variables: si5351_divider, si5351_mult, si5351_raw_freq, si5351_pll_data
inline void si5351_freq_calc_fast(int16_t freq_offset)
{ // freq_offset is relative to freq set in si5351_freq(freq)
// uint32_t num128 = ((si5351_divider * (si5351_raw_freq + offset)) % SI_XTAL_FREQ) * (float)(0xFFFFF * 128) / SI_XTAL_FREQ;
// Above definition (for SI_XTAL_FREQ=27.00491M) can be optimized by pre-calculating factor (0xFFFFF*128)/SI_XTAL_FREQ (=4.97) as integer constant (5) and
// substracting the rest error factor (0.03). Note that the latter is shifted left (0.03<<6)=2, while the other term is shifted right (>>6)
register int32_t z = ((si5351_divider * (si5351_raw_freq + freq_offset)) % SI_XTAL_FREQ);
register int32_t z2 = -(z >> 5);
int32_t num128 = (z * 5) + z2;
// Set up specified PLL with si5351_mult, num and denom: si5351_mult is 15..90, num128 is 0..128*1,048,575 (128*0xFFFFF), denom is 0..1,048,575 (0xFFFFF)
uint32_t P1 = 128 * si5351_mult + (num128 / 0xFFFFF) - 512;
uint32_t P2 = num128 % 0xFFFFF;
//si5351_pll_data[0] = 0xFF;
//si5351_pll_data[1] = 0xFF;
//si5351_pll_data[2] = (P1 >> 14) & 0x0C;
si5351_pll_data[3] = P1 >> 8;
si5351_pll_data[4] = P1;
si5351_pll_data[5] = 0xF0 | (P2 >> 16);
si5351_pll_data[6] = P2 >> 8;
si5351_pll_data[7] = P2;
}
uint16_t si5351_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;
}
volatile int32_t pll_freq; // temporary
void si5351_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 > 4000000) ? 1 : (freq > 400000) ? 32 : 128; // helps si5351_divider to be in range
freq *= r_div; // take r_div into account, now freq is in the range 1MHz to 150MHz
si5351_raw_freq = freq; // cache frequency generated by PLL and MS stages (excluding R divider stage); used by si5351_freq_calc_fast()
si5351_divider = 900000000 / freq; // Calculate the division ratio. 900,000,000 is the maximum internal PLL freq (official range 600..900MHz but can be pushed to 300MHz..~1200Mhz)
if(si5351_divider % 2) si5351_divider--; // si5351_divider in range 8.. 900 (including 4,6 for integer mode), even numbers preferred. Note that uint8 datatype is used, so 254 is upper limit
if( (si5351_divider * (freq - 5000) / SI_XTAL_FREQ) != (si5351_divider * (freq + 5000) / SI_XTAL_FREQ) ) si5351_divider -= 2; // Test if si5351_multiplier remains same for freq deviation +/- 5kHz, if not use different si5351_divider to make same
/*int32_t*/ pll_freq = si5351_divider * freq; // Calculate the pll_freq: the si5351_divider * desired output freq
uint32_t num, denom;
si5351_mult = si5351_div(pll_freq, SI_XTAL_FREQ, &num, &denom); // Determine the mult to get to the required pll_freq (in the range 15..90)
// Set up specified PLL with mult, num and denom: mult is 15..90, num is 0..1,048,575 (0xFFFFF), denom is 0..1,048,575 (0xFFFFF)
// Set up PLL A and PLL B with the calculated multiplication ratio
si5351_SetupMultisynth(SI_SYNTH_PLL_A, si5351_mult, num, denom, 1);
si5351_SetupMultisynth(SI_SYNTH_PLL_B, si5351_mult, num, denom, 1);
//if(denom == 1) si5351_SendRegister(22, SI_MSx_INT); // FBA_INT: MSNA operates in integer mode
//if(denom == 1) si5351_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
si5351_SetupMultisynth(SI_SYNTH_MS_0, si5351_divider, 0, 1, r_div);
si5351_SetupMultisynth(SI_SYNTH_MS_1, si5351_divider, 0, 1, r_div);
si5351_SetupMultisynth(SI_SYNTH_MS_2, si5351_divider, 0, 1, r_div);
//if(si5351_prev_divider != si5351_divider){ lcd.setCursor(0, 0); lcd.print(si5351_divider); lcd.print(blanks); }
// Set I/Q phase
si5351_SendRegister(SI_CLK0_PHOFF, i * si5351_divider / 90); // one LSB equivalent to a time delay of Tvco/4 range 0..127
si5351_SendRegister(SI_CLK1_PHOFF, q * si5351_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 si5351_multiSynth0, si5351_multiSynth1 input (0x0F = SI_CLK_SRC_MS | SI_CLK_IDRV_8MA)
si5351_SendRegister(SI_CLK0_CONTROL, 0x0F | SI_MS_INT | SI_CLK_SRC_PLL_A);
si5351_SendRegister(SI_CLK1_CONTROL, 0x0F | SI_MS_INT | SI_CLK_SRC_PLL_A);
// Switch on the CLK2 output to be PLL B and set si5351_multiSynth2 input
si5351_SendRegister(SI_CLK2_CONTROL, 0x0F | SI_MS_INT | SI_CLK_SRC_PLL_B);
si5351_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 - si5351_prev_pll_freq) > 16000000L) || si5351_divider != si5351_prev_divider){
si5351_prev_pll_freq = pll_freq;
si5351_prev_divider = si5351_divider;
si5351_SendRegister(SI_PLL_RESET, 0xA0);
}
}
void si5351_alt_clk2(uint32_t freq)
{
uint32_t num, denom;
uint16_t mult = si5351_div(pll_freq, freq, &num, &denom);
si5351_SetupMultisynth(SI_SYNTH_MS_2, mult, num, denom, 1);
// Switch on the CLK2 output to be PLL A and set si5351_multiSynth2 input
si5351_SendRegister(SI_CLK2_CONTROL, 0x0F | SI_CLK_SRC_PLL_A);
si5351_SendRegister(SI_CLK_OE, 0b11111000); // Enable CLK2|CLK1|CLK0
//si5351_SendRegister(SI_CLK0_PHOFF, 0 * si5351_divider / 90); // one LSB equivalent to a time delay of Tvco/4 range 0..127
//si5351_SendRegister(SI_CLK1_PHOFF, 90 * si5351_divider / 90); // one LSB equivalent to a time delay of Tvco/4 range 0..127
//si5351_SendRegister(SI_CLK2_PHOFF, 45 * si5351_divider / 90); // one LSB equivalent to a time delay of Tvco/4 range 0..127
//si5351_SendRegister(SI_PLL_RESET, 0xA0);
}
volatile bool usb = true;
volatile bool change = true;
volatile int32_t freq = 7074000;
volatile uint8_t tx = 0;
volatile bool vox_enable = false;
inline void vox(bool trigger)
{
if(trigger){
if(!tx){
lcd.setCursor(15, 1); lcd.print("T");
si5351_SendRegister(SI_CLK_OE, 0b11111011); // CLK2_EN=1, CLK1_EN,CLK0_EN=0
digitalWrite(RX, LOW); // TX
}
tx = 255; // hangtime = 255 / 4402 = 58ms (the time that TX at least stays on when not triggered again)
} else {
if(tx){
tx--;
if(!tx){
digitalWrite(RX, HIGH); // RX
si5351_SendRegister(SI_CLK_OE, 0b11111100); // CLK2_EN=0, CLK1_EN,CLK0_EN=1
lcd.setCursor(15, 1); lcd.print("V");
}
}
}
}
volatile uint8_t drive = 4;
#define F_SAMP 4810 //4810 // ADC sample-rate; is best a multiple of _UA and fits exactly in OCR0A = ((F_CPU / 64) / F_SAMP) - 1 , should not exceed CPU utilization
#define _UA (F_SAMP) //360 // unit angle; integer representation of one full circle turn or 2pi radials or 360 degrees, should be a integer divider of F_SAMP and maximized to have higest precision
//#define MAX_DP (_UA/1) //(_UA/2) // the occupied SSB bandwidth can be further reduced by restricting the maximum phase change (set MAX_DP to _UA/2).
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)
}
uint8_t lut[256];
volatile uint8_t amp;
inline int16_t ssb(int16_t in)
{
static int16_t dc;
int16_t i, q;
uint8_t j;
static int16_t v[16];
for(j = 0; j != 15; j++) v[j] = v[j + 1];
dc += (in - dc) / 2;
v[15] = in - dc; // DC decoupling
i = v[7];
q = ((v[0] - v[14]) * 2 + (v[2] - v[12]) * 8 + (v[4] - v[10]) * 21 + (v[6] - v[8]) * 15) / 128 + (v[6] - v[8]) / 2; // Hilbert transform, 40dB side-band rejection in 400..1900Hz (@4kSPS) when used in image-rejection scenario; (Hilbert transform require 5 additional bits)
uint16_t _amp = abs(i) > abs(q) ? abs(i) + abs(q) / 4 : abs(q) + abs(i) / 4; // approximation of: amp = sqrt(i*i + q*q); error 0.95dB
#define VOX_THRESHOLD (1 << 1) // 1*6dB above ADC noise level
if(vox_enable) vox((_amp > VOX_THRESHOLD));
_amp = _amp << drive;
_amp = ((_amp > 255) || (drive == 8)) ? 255 : _amp; // clip or when drive=8 use max output
amp = (tx) ? lut[_amp] : 0;
static int16_t prev_phase;
int16_t phase = arctan3(q, i);
int16_t dp = phase - prev_phase; // phase difference and restriction
prev_phase = phase;
if(dp < 0) dp = dp + _UA; // make negative phase shifts positive: prevents negative frequencies and will reduce spurs on other sideband
#ifdef MAX_DP
if(dp > MAX_DP){ // dp should be less than half unit-angle in order to keep frequencies below F_SAMP/2
prev_phase = phase - (dp - MAX_DP); // substract restdp
dp = MAX_DP;
}
#endif
if(usb)
return dp * ( F_SAMP / _UA); // calculate frequency-difference based on phase-difference
else
return dp * (-F_SAMP / _UA);
}
volatile uint16_t numSamples = 0;
#define MIC_ATTEN 0 // 0*6dB attenuation (note that the LSB bits are quite noisy)
// 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.
ISR(ADC_vect) // ADC conversion interrupt
{ // jitter dependent things first
uint8_t low = ADCL; // ADC sample 10-bits analog input, first ADCL, then ADCH
uint8_t high = ADCH;
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_SendPLLBRegisterBulk(); // 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 = ((high << 8) | low) - 512;
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
numSamples++;
}
ISR(TIMER0_COMPB_vect) // Timer0 interrupt
{
ADCSRA |= (1 << ADSC); // start ADC conversion (triggers ADC interrupt)
}
uint8_t old_TCCR0A;
uint8_t old_TCCR0B;
uint8_t old_TCNT0;
uint8_t old_TIMSK0;
void adc_start(uint8_t adcpin, uint8_t ref1v1)
{
// Setup ADC
DIDR0 |= (1 << adcpin); // disable digital input
ADCSRA = 0; // clear ADCSRA register
ADCSRB = 0; // clear ADCSRB register
ADMUX = 0; // clear ADMUX register
ADMUX |= (adcpin & 0x0f); // set analog input pin
ADMUX |= ((ref1v1) ? (1 << REFS1) : 0) | (1 << REFS0); // set AREF=1.1V (Internal ref); otherwise AREF=AVCC=(5V)
//ADCSRA |= (1 << ADPS2) | (1 << ADPS1) | (1 << ADPS0); // 128 prescaler for 9.6kHz*1.25
//ADCSRA |= (1 << ADPS2) | (1 << ADPS1); // 64 prescaler for 19.2kHz*1.25
//ADCSRA |= (1 << ADPS2) | (1 << ADPS0); // 32 prescaler for 38.5 KHz*1.25
//ADCSRA |= (1 << ADPS2); // 16 prescaler for 76.9 KHz*1.25
ADCSRA |= (1 << ADPS1) | (1 << ADPS0); // ADPS=011: 8 prescaler for 153.8 KHz; sampling rate is [ADC clock] / [prescaler] / [conversion clock cycles] for Arduino Uno ADC clock is 20 MHz and a conversion takes 13 clock cycles: ADPS=011: 8 prescaler for 153.8 KHz, ADPS=100: 16 prescaler for 76.9 KHz; ADPS=101: 32 prescaler for 38.5 KHz; ADPS=110: 64 prescaler for 19.2kHz; // ADPS=111: 128 prescaler for 9.6kHz
ADCSRA |= (1 << ADIE); // enable interrupts when measurement complete
ADCSRA |= (1 << ADEN); // enable ADC
//ADCSRA |= (1 << ADSC); // start ADC measurements
// backup Timer 0, is used by delay(), micros(), etc.
old_TCCR0A = TCCR0A;
old_TCCR0B = TCCR0B;
old_TCNT0 = TCNT0;
old_TIMSK0 = TIMSK0;
// Timer 0: interrupt mode
TCCR0A = 0;
TCCR0B = 0;
TCNT0 = 0;
TCCR0A |= (1 << WGM01); // turn on CTC mode
TCCR0B |= (1 << CS01) | (1 << CS00); // Set CS01 and CS00 bits for 64 prescaler
TIMSK0 |= (1 << OCIE0B); // enable timer compare interrupt TIMER0_COMPB_vect
uint8_t ocr = (((float)F_CPU / (float)64) / (float)F_SAMP + 0.5) - 1; // ((F_CPU / 64) / F_SAMP) - 1;
OCR0A = ocr; // Set the value that you want to count to : OCRn = [clock_speed / (Prescaler_value * Fs)] - 1 : sampling frequency
// Timer 1: PWM mode
TCCR1A = 0;
TCCR1B = 0;
TCCR1A |= (1 << COM1B1); // Clear OC1A/OC1B on Compare Match when upcounting. Set OC1A/OC1B on Compare Match when downcounting.
TCCR1B |= ((1 << CS10) | (1 << WGM13)); // WGM13: Mode 8 - PWM, Phase and Frequency Correct; CS10: clkI/O/1 (No prescaling)
ICR1H = 0x00; // TOP. This sets the PWM frequency: PWM_FREQ=312.500kHz ICR=0x1freq bit_depth=5; PWM_FREQ=156.250kHz ICR=0x3freq bit_depth=6; PWM_FREQ=78.125kHz ICR=0x7freq bit_depth=7; PWM_FREQ=39.250kHz ICR=0xFF bit_depth=8
ICR1L = 0xFF; // Fpwm = F_CPU / (2 * Prescaler * TOP) : PWM_FREQ = 39.25kHz, bit-depth=8
OCR1BH = 0;
OCR1BL = 0; // PWM duty-cycle (span set by ICR).
amp = 0;
}
void adc_stop()
{
// restore Timer 0, is shared with delay(), micros(), etc.
TCCR0A = old_TCCR0A;
TCCR0B = old_TCCR0B;
TCNT0 = old_TCNT0;
TIMSK0 = old_TIMSK0;
// Stop Timer 0 interrupt
//TIMSK0 &= ~(1 << OCIE0B); // disable timer compare interrupt TIMER0_COMPB_vect
//TCCR0A |= (1 << WGM00); // for some reason WGM00 must be 1 in normal operation
OCR1BL = 0x00;
//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
ADMUX = 0; // clear ADMUX register
ADMUX |= (1 << REFS0); // restore reference voltage AREF (5V)
}
static uint8_t bandval = 2; //4
#define N_BANDS 7 //13
uint32_t band[] = { 3573000, 5357000, 7074000, 10136000, 14074000, 18100000, 21074000 }; //{ 472000, 1840000, 3573000, 5357000, 7074000, 10136000, 14074000, 18100000, 21074000, 24915000, 28074000, 50313000, 70101000 };
enum step_enum { STEP_10M, STEP_1M, STEP_500k, STEP_100k, STEP_10k, STEP_1k, STEP_500, STEP_100, STEP_10, STEP_1 };
volatile int8_t stepsize = STEP_1k;
void encoder_vect(int8_t sign)
{
int32_t stepval;
switch(stepsize){
case STEP_10M: stepval = 10000000; break;
case STEP_1M: stepval = 1000000; break;
case STEP_500k: stepval = 500000; break;
case STEP_100k: stepval = 100000; break;
case STEP_10k: stepval = 10000; break;
case STEP_1k: stepval = 1000; break;
case STEP_500: stepval = 500; break;
case STEP_100: stepval = 100; break;
case STEP_10: stepval = 10; break;
case STEP_1: stepval = 1; break;
}
if(stepval < STEP_100) freq %= 1000; // when tuned and stepsize > 100Hz then forget fine-tuning details
freq += sign * stepval;
freq = max(1, min(99999999, freq));
change = true;
}
void stepsize_showcursor()
{
lcd.setCursor(stepsize, 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();
}
ISR(PCINT2_vect){ // Interrupt on rotary encoder turn
static uint8_t last_state;
noInterrupts();
uint8_t curr_state = (digitalRead(ROT_B) << 1) | digitalRead(ROT_A);
switch((last_state << 2) | curr_state){ //transition
#ifdef ENCODER_ENHANCED_RESOLUTION // Option: enhance encoder from 24 to 96 steps/revolution, see: https://www.sdr-kits.net/documents/PA0KLT_Manual.pdf
case 0b1101: case 0b0100: case 0b0010: case 0b1011: encoder_vect(1); break;
case 0b1110: case 0b1000: case 0b0001: case 0b0111: encoder_vect(-1); break;
#else
case 0b1101: encoder_vect(1); break;
case 0b1110: encoder_vect(-1); break;
#endif
}
last_state = curr_state;
interrupts();
}
void encoder_setup()
{
pinMode(ROT_A, INPUT_PULLUP);
pinMode(ROT_B, INPUT_PULLUP);
PCICR |= (1 << PCIE2);
PCMSK2 |= (1 << PCINT22) | (1 << PCINT23);
interrupts();
}
void qcx_setup()
{
// initialize
digitalWrite(SIG_OUT, LOW);
digitalWrite(RX, HIGH);
digitalWrite(KEY_OUT, LOW);
// pins
pinMode(SIG_OUT, OUTPUT);
pinMode(RX, OUTPUT);
pinMode(KEY_OUT, OUTPUT);
pinMode(BUTTONS, INPUT); // L/R/rotary button
pinMode(DIT, INPUT_PULLUP);
pinMode(DAH, INPUT);
pinMode(AUDIO1, INPUT);
pinMode(AUDIO2, INPUT);
}
char blanks[] = " ";
byte font_run[] = {
0b01000,
0b00100,
0b01010,
0b00101,
0b01010,
0b00100,
0b01000,
0b00000
};
void customDelay(uint32_t _micros) //_micros=100000 is 132052us delay
{
uint32_t i; for(i = 0; i != _micros * 3; i++) wdt_reset();
}
uint32_t sample_amp(uint8_t pin, uint16_t osr)
{
uint16_t avg = 1024 / 2;
uint32_t rms = 0;
uint16_t i;
for(i = 0; i != 16; i++){
uint16_t adc = analogRead(pin);
avg = (avg + adc) / 2;
}
for(i = 0; i != osr; i++){ // 128 overampling is 42dB gain => with 10-bit ADC resulting in a total of 102dB DR
uint16_t adc = analogRead(pin);
avg = (avg + adc) / 2; // average
rms += ((adc > avg) ? 1 : -1) * (adc - avg); // rectify based
wdt_reset();
}
return rms;
}
float smeter(float ref = 0.0)
{
float rms = ((float)sample_amp(AUDIO1, 128)) * 5.0 / (1024.0 * 128.0 * 100.0 * 120.0 / 1.750); // rmsV = ADC value * AREF / [ADC DR * processing gain * receiver gain * audio gain]
float dbm = (10.0 * log10((rms * rms) / 50.0) + 30.0) - ref; //from rmsV to dBm at 50R
static float dbm_max;
dbm_max = max(dbm_max, dbm);
static uint8_t cnt;
cnt++;
if((cnt % 8) == 0){
//#define DBM_METER 1
#ifdef DBM_METER
lcd.setCursor(9, 0); lcd.print((int16_t)dbm_max); lcd.print((ref == 0.0) ? "dBm " : "dB ");
#else
uint8_t s = (dbm_max < -63) ? ((dbm_max - -127) / 6) : (uint8_t)(dbm_max - -63 + 10) % 10; // dBm to S
lcd.setCursor(14, 0); if(s < 10){ lcd.print("S"); } lcd.print(s);
#endif
dbm_max = -174.0 + 34.0;
}
return dbm;
}
float dbmeter(float ref = 0.0)
{
float rms = ((float)sample_amp(AUDIO1, 128)) * 5.0 / (1024.0 * 128.0 * 100.0); // rmsV = ADC value * AREF / [ADC DR * processing gain * receiver gain * audio gain]
float dbm = (10.0 * log10((rms * rms) / 50.0) + 30.0) - ref; //from rmsV to dBm at 50R
lcd.setCursor(9, 0); lcd.print(dbm); lcd.print("dB ");
delay(300);
return dbm;
}
void test_tx_amp()
{
lcd.setCursor(9, 0); lcd.print(blanks);
TCCR1A = 0; // Timer 1: PWM mode
TCCR1B = 0;
TCCR1A |= (1 << COM1B1); // Clear OC1A/OC1B on Compare Match when upcounting. Set OC1A/OC1B on Compare Match when downcounting.
TCCR1B |= ((1 << CS10) | (1 << WGM13)); // WGM13: Mode 8 - PWM, Phase and Frequency Correct; CS10: clkI/O/1 (No prescaling)
ICR1H = 0x00; // TOP. This sets the PWM frequency: PWM_FREQ=312.500kHz ICR=0x1freq bit_depth=5; PWM_FREQ=156.250kHz ICR=0x3freq bit_depth=6; PWM_FREQ=78.125kHz ICR=0x7freq bit_depth=7; PWM_FREQ=39.250kHz ICR=0xFF bit_depth=8
ICR1L = 0xFF; // Fpwm = F_CPU / (2 * Prescaler * TOP) : PWM_FREQ = 39.25kHz, bit-depth=8
OCR1BH = 0x00;
OCR1BL = 0x00; // PWM duty-cycle (span set by ICR).
si5351_prev_pll_freq = 0; // enforce PLL reset
si5351_freq(freq, 0, 90); // RX in USB
si5351_alt_clk2(freq);
si5351_SendRegister(SI_CLK_OE, 0b11111011); // CLK2_EN=1, CLK1_EN,CLK0_EN=0
digitalWrite(RX, LOW); // TX
uint16_t i;
for(i = 0; i != 256; i++)
{
OCR1BL = lut[i];
si5351_alt_clk2(freq + i * 10);
wdt_reset();
lcd.setCursor(0, 1); lcd.print("SWEEP("); lcd.print(i); lcd.print(")="); lcd.print(lut[i]); lcd.print(blanks);
delay(200);
}
OCR1BL = 0;
delay(500);
digitalWrite(RX, HIGH); // RX
si5351_SendRegister(SI_CLK_OE, 0b11111100); // CLK2_EN=0, CLK1_EN,CLK0_EN=1
change = true; //restore original frequency setting
}
void calibrate_predistortion()
{
lcd.setCursor(9, 0); lcd.print(blanks);
TCCR1A = 0; // Timer 1: PWM mode
TCCR1B = 0;
TCCR1A |= (1 << COM1B1); // Clear OC1A/OC1B on Compare Match when upcounting. Set OC1A/OC1B on Compare Match when downcounting.
TCCR1B |= ((1 << CS10) | (1 << WGM13)); // WGM13: Mode 8 - PWM, Phase and Frequency Correct; CS10: clkI/O/1 (No prescaling)
ICR1H = 0x00; // TOP. This sets the PWM frequency: PWM_FREQ=312.500kHz ICR=0x1freq bit_depth=5; PWM_FREQ=156.250kHz ICR=0x3freq bit_depth=6; PWM_FREQ=78.125kHz ICR=0x7freq bit_depth=7; PWM_FREQ=39.250kHz ICR=0xFF bit_depth=8
ICR1L = 0xFF; // Fpwm = F_CPU / (2 * Prescaler * TOP) : PWM_FREQ = 39.25kHz, bit-depth=8
OCR1BH = 0x00;
OCR1BL = 0x00; // PWM duty-cycle (span set by ICR).
si5351_prev_pll_freq = 0; //enforce PLL reset
si5351_freq(freq, 0, 90); // RX in USB
si5351_alt_clk2(freq + 1000); // si5351_freq_clk2(freq + 1000);
si5351_SendRegister(SI_CLK_OE, 0b11111000); // CLK2_EN=1, CLK1_EN,CLK0_EN=1
digitalWrite(RX, LOW); // TX
OCR1BL = 0xFF; // Max power to determine scale
delay(200);
float scale = sample_amp(AUDIO2, 128);
wdt_reset();
uint8_t amp[256];
int16_t i, j;
for(i = 127; i >= 0; i--) // determine amp for pwm value i
{
OCR1BL = i;
wdt_reset();
delay(100);
amp[i] = min(255, (float)sample_amp(AUDIO2, 128) * 255.0 / scale);
lcd.setCursor(0, 1); lcd.print("CALIB("); lcd.print(i); lcd.print(")="); lcd.print(amp[i]); lcd.print(blanks);
}
OCR1BL = 0xFF; // Max power to determine scale
delay(200);
for(j = 0; j != 256; j++){
for(i = 0; i != 255 && amp[i] < j; i++) ; //lookup pwm i for amp[i] >= j
if(j == 255) i = 255; // do not use PWM for peak RF
lut[j] = i;
}
OCR1BL = 0x00;
digitalWrite(RX, HIGH); // RX
si5351_SendRegister(SI_CLK_OE, 0b11111100); // CLK2_EN=0, CLK1_EN,CLK0_EN=1
change = true; //restore original frequency setting
}
// 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()
{
lcd.setCursor(9, 0); lcd.print(blanks);
digitalWrite(SIG_OUT, true); // loopback on
si5351_prev_pll_freq = 0; //enforce PLL reset
si5351_freq(freq, 0, 90); // RX in USB
float dbc;
si5351_alt_clk2(freq + 700);
dbc = dbmeter();
si5351_alt_clk2(freq - 700);
lcd.setCursor(0, 1); lcd.print("I-Q bal. (700 Hz)"); lcd.print(blanks);
for(; !digitalRead(BUTTONS);){ wdt_reset(); dbmeter(dbc); } for(; digitalRead(BUTTONS);) wdt_reset();
si5351_alt_clk2(freq + 600);
dbc = dbmeter();
si5351_alt_clk2(freq - 600);
lcd.setCursor(0, 1); lcd.print("Phase Lo (600 Hz)"); lcd.print(blanks);
for(; !digitalRead(BUTTONS);){ wdt_reset(); dbmeter(dbc); } for(; digitalRead(BUTTONS);) wdt_reset();
si5351_alt_clk2(freq + 800);
dbc = dbmeter();
si5351_alt_clk2(freq - 800);
lcd.setCursor(0, 1); lcd.print("Phase Hi (800 Hz)"); lcd.print(blanks);
for(; !digitalRead(BUTTONS);){ wdt_reset(); dbmeter(dbc); } for(; digitalRead(BUTTONS);) wdt_reset();
lcd.setCursor(9, 0); lcd.print(blanks); // cleanup dbmeter
digitalWrite(SIG_OUT, false); // loopback off
si5351_SendRegister(SI_CLK_OE, 0b11111100); // CLK2_EN=0, CLK1_EN,CLK0_EN=1
change = true; //restore original frequency setting
}
void powermeter()
{
lcd.setCursor(9, 0); lcd.print(blanks);
si5351_prev_pll_freq = 0; //enforce PLL reset
si5351_freq(freq, 0, 90); // RX in USB
si5351_alt_clk2(freq + 1000); // si5351_freq_clk2(freq + 1000);
si5351_SendRegister(SI_CLK_OE, 0b11111000); // CLK2_EN=1, CLK1_EN,CLK0_EN=1
digitalWrite(KEY_OUT, HIGH); //OCR1BL = 0xFF;
digitalWrite(RX, LOW); // TX
delay(100);
float rms = ((float)sample_amp(AUDIO2, 128)) * 5.0 / (1024.0 * 128.0 * 100.0 * 50.0 / 100000.0); // rmsV = ADC value * AREF / [ADC DR * processing gain * receiver gain * rx/tx switch attenuation]
float dbm = 10.0 * log10((rms * rms) / 50.0) + 30.0; //from rmsV to dBm at 50R
lcd.setCursor(9, 0); lcd.print(" +"); lcd.print((int16_t)dbm); lcd.print("dBm ");
digitalWrite(KEY_OUT, LOW); //OCR1BL = 0x00;
digitalWrite(RX, HIGH); // RX
si5351_SendRegister(SI_CLK_OE, 0b11111100); // CLK2_EN=0, CLK1_EN,CLK0_EN=1
change = true; //restore original frequency setting
delay(1000);
}
/*
volatile boolean WDTalarm = false;
ISR(WDT_vect)
{
wdt_disable(); // disable watchdog so the system does not restart
WDTalarm = true; // flag the event
}
float getTemp()
{
WDTalarm = false;
// Set the Watchdog timer from: https://www.gammon.com.au/power
byte interval = 0b000110; // 1s=0b000110, 2s=0b000111, 4s=0b100000, 8s=0b10000
//64ms= 0b000010, 128ms = 0b000011, 256ms= 0b000100, 512ms= 0b000101
noInterrupts ();
MCUSR = 0;
WDTCSR |= 0b00011000; // set WDCE, WDE
WDTCSR = 0b01000000 | interval; // set WDIE & delay interval
wdt_reset(); // pat the dog
interrupts ();
unsigned long startTime = micros();
while(!WDTalarm) { //sleep while waiting for the WDT
set_sleep_mode (SLEEP_MODE_IDLE);
noInterrupts (); sleep_enable(); interrupts (); sleep_cpu ();
sleep_disable(); //processor starts here when any interrupt occurs
}
unsigned long WDTmicrosTime = micros() - startTime; // this is your measurement!
return (float)WDTmicrosTime * 0.0026 - 3668.1; //calibrate here
}
*/
void setup()
{
// Benchmark ADC_vect() ISR (this needs to be done in beginning of setup() otherwise when VERSION containts 5 chars, mis-alignment impact performance by a few percent)
uint32_t t0, t1;
t0 = micros();
TIMER0_COMPB_vect();
ADC_vect();
t1 = micros();
float load = (t1 - t0) * F_SAMP * 100.0 / 1000000.0;
wdt_enable(WDTO_2S); // Enable watchdog, resolves QCX startup issue
lcd.begin(16, 2);
lcd.createChar(1, font_run);
lcd.setCursor(0, 0); lcd.print("QCX-SSB R"); lcd.print(VERSION); lcd.print(blanks);
delay(200);
//lcd.setCursor(0, 1); lcd.print("temp="); lcd.print(getTemp()); lcd.print("C");
//for(;!digitalRead(BUTTONS);) wdt_reset();
i2c_init();
encoder_setup();
qcx_setup();
//PCICR |= (1 << PCIE0);
//PCMSK0 |= (1 << PCINT5) | (1 << PCINT4) | (1 << PCINT3);
//interrupts();
// initialize LUT
//#define C31_IS_INSTALLED 1 // Uncomment this line when C31 is installed (shaping circuit will be driven with analog signal instead of being switched digitally with PWM signal)
#ifdef C31_IS_INSTALLED // In case of analog driven shaping circuit:
#define PWM_MIN 29 // The PWM value where the voltage over L4 is approaching its minimum (~0.6V)
#define PWM_MAX 96 // The PWM value where the voltage over L4 is approaching its maximum (~11V)
#else // In case of digital driven shaping circuit:
#define PWM_MIN 0 // The PWM value where the voltage over L4 is its minimum (0V)
#define PWM_MAX 255 // The PWM value where the voltage over L4 is its maximum (12V)
#endif
uint16_t i;
for(i = 0; i != 256; i++)
lut[i] = (float)i / ((float)255 / ((float)PWM_MAX - (float)PWM_MIN)) + PWM_MIN;
// Measure CPU loads
if(!(load < 100.0)){
lcd.setCursor(0, 1); lcd.print("E01 CPU overload");
delay(1500);
wdt_reset();
}
// Measure VDD (+5V); should be ~5V
digitalWrite(RX, LOW); // mute RX
digitalWrite(KEY_OUT, LOW);
si5351_SendRegister(SI_CLK_OE, 0b11111111); // Mute QSD: CLK2_EN=CLK1_EN,CLK0_EN=0
float vdd = 2.0 * (float)analogRead(AUDIO2) * 5.0 / 1024.0;
digitalWrite(RX, HIGH);
if(!(vdd > 4.5 && vdd < 5.2)){
lcd.setCursor(0, 1); lcd.print("E02 +5V not OK");
delay(1500);
wdt_reset();
}
// Measure VEE (+3.3V); should be ~3.3V
float vee = (float)analogRead(SCL) * 5.0 / 1024.0;
if(!(vee > 3.2 && vee < 3.8)){
lcd.setCursor(0, 1); lcd.print("E03 +3.3V not OK");
delay(1500);
wdt_reset();
}
// 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.1)){
lcd.setCursor(0, 1); lcd.print("E04 AVCC not OK");
delay(1500);
wdt_reset();
}
// Measure DVM bias; should be ~VAREF/2
float dvm = (float)analogRead(DVM) * 5.0 / 1024.0;
if(!(dvm > 1.8 && dvm < 3.2)){
lcd.setCursor(0, 1); lcd.print("E05 DVM bias err");
delay(1500);
wdt_reset();
}
// Measure I2C Bus speed for Bulk Transfers
t0 = micros();
for(i = 0; i != 1000; i++) si5351_SendPLLBRegisterBulk();
t1 = micros();
uint32_t i2c_speed = (1000000 * 8 * 7) / (t1 - t0); // speed in kbit/s
// Measure I2C Bit-Error Rate (BER); should be error free for a thousand random bulk PLLB writes
uint16_t i2c_error = 0; // number of I2C byte transfer errors
for(i = 0; i != 1000; i++){
for(int j = 3; j != 8; j++) si5351_pll_data[j] = rand();
si5351_SendPLLBRegisterBulk();
for(int j = 3; j != 8; j++) if(si5351_RecvRegister(SI_SYNTH_PLL_B + j) != si5351_pll_data[j]) i2c_error++;
}
if(i2c_error){
lcd.setCursor(0, 1); lcd.print("E06 I2C tx error");
delay(1500);
wdt_reset();
}
lcd.setCursor(0, 1); lcd.print("CPU_tx="); lcd.print(load); lcd.print("%"); lcd.print(blanks);
delay(800);
lcd.setCursor(7, 0); lcd.print("\001"); lcd.print(blanks); // Ready: display initialization complete normally
}
void loop()
{
delay(100);
smeter();
if(!digitalRead(DIT)){
lcd.setCursor(15, 1); lcd.print("T");
lcd.setCursor(9, 0); lcd.print(blanks);
si5351_SendRegister(SI_CLK_OE, 0b11111011); // CLK2_EN=1, CLK1_EN,CLK0_EN=0
adc_start(2, true);
customDelay(1000); //allow setup time
digitalWrite(RX, LOW); // TX
tx = 1;
for(; !digitalRead(DIT);){ //until released
wdt_reset();
}
digitalWrite(RX, HIGH); // RX
customDelay(1000); //allow setup time
tx = 0;
adc_stop();
si5351_SendRegister(SI_CLK_OE, 0b11111100); // CLK2_EN=0, CLK1_EN,CLK0_EN=1
lcd.setCursor(15, 1); lcd.print("R");
}
if(digitalRead(BUTTONS)){ // Left-/Right-/Rotary-button
uint16_t val = analogRead(BUTTONS);
bool longpress = false;
bool doubleclick = false;
int32_t t0 = millis();
for(; digitalRead(BUTTONS) && !longpress;){ // until released or long-press
longpress = ((millis() - t0) > 300);
wdt_reset();
}
delay(10); //debounce
for(; !longpress && ((millis() - t0) < 500) && !doubleclick;){ //until 2nd press or timeout
doubleclick = digitalRead(BUTTONS);
wdt_reset();
}
for(; digitalRead(BUTTONS);) wdt_reset(); // until released
if(val < 862){ // LEFT-button ADC=780
if(doubleclick){
calibrate_predistortion();
return;
}
if(longpress){
test_tx_amp();
//powermeter();
return;
} //single-click
calibrate_iq();
} else if(val < 1023){ // RIGHT-button ADC=943
if(doubleclick){
if(drive == 0) drive = 1;
else drive += 1;
if(drive > 8) drive = 0;
lcd.setCursor(0, 1); lcd.print("Drive "); lcd.print(drive); lcd.print(blanks);
return;
}
if(longpress){
lcd.setCursor(15, 1); lcd.print("V");
lcd.setCursor(9, 0); lcd.print(blanks);
vox_enable = true;
adc_start(2, true);
for(; !digitalRead(BUTTONS);){ // while in VOX mode
wdt_reset(); // until 2nd press
}
adc_stop();
vox_enable = false;
lcd.setCursor(15, 1); lcd.print("R");
for(; digitalRead(BUTTONS);) wdt_reset(); // until released
delay(100);
return;
} //single-click
usb = !usb;
si5351_prev_pll_freq = 0; // enforce PLL reset
change = true;
} else { // ROTARY-button ADC=1023
if(doubleclick){
delay(100);
bandval++;
if(bandval >= N_BANDS) bandval = 0;
freq = band[bandval];
stepsize = STEP_1k;
change = true;
return;
}
if(longpress){
stepsize_change(-1);
return;
} //single-click
stepsize_change(+1);
}
}
if(change){
change = false;
uint32_t n = freq / 1000000; // lcd.print(f) with commas
uint32_t n2 = freq % 1000000;
uint32_t scale = 1000000;
char buf[16];
sprintf(buf, "%2u", n); lcd.setCursor(0, 1); lcd.print(buf);
while(scale != 1){
scale /= 1000;
n = n2 / scale;
n2 = n2 % scale;
if(scale == 1) sprintf(buf, ",%02u", n / 10); else // leave last digit out
sprintf(buf, ",%03u", n);
lcd.print(buf);
}
lcd.print((usb) ? " USB" : " LSB");
lcd.print(" ");
lcd.setCursor(15, 1); lcd.print("R");
if(usb)
si5351_freq(freq, 0, 90); // RX in USB
else
si5351_freq(freq, 90, 0); // RX in LSB
}
wdt_reset();
stepsize_showcursor();
}