diff --git a/dsp.h b/dsp.h index 99d4912..abaf9c2 100644 --- a/dsp.h +++ b/dsp.h @@ -7,10 +7,15 @@ * Author: Arjan te Marvelde * * See dsp.c for more information + * + * HERE THE SELECTION BETWEEN TIME OR FREQUENCY DOMAIN PROCESSING IS MADE + * DO THIS BY SETTING THE #define DSP_FFT TO 0 OR TO 1 RESPECTIVELY + * */ -// 1 for FFT, 0 for time domain processing -#define DSP_FFT 0 +#define DSP_FFT 1 + + /* diff --git a/hmi.c b/hmi.c index 88b8efe..eda5a46 100644 --- a/hmi.c +++ b/hmi.c @@ -115,11 +115,12 @@ char hmi_o_vox [HMI_NVOX][8] = {"NoVOX","VOX-L","VOX-M","VOX-H"}; // Indexed b char hmi_o_bpf [HMI_NBPF][8] = {"<2.5","2-6","5-12","10-24","20-40"}; // Indexed by // Map option to setting -int hmi_mode[4] = {MODE_USB, MODE_LSB, MODE_AM, MODE_CW}; -int hmi_agc[3] = {AGC_NONE, AGC_SLOW, AGC_FAST}; -int hmi_pre[5] = {REL_ATT_30, REL_ATT_20, REL_ATT_10, REL_ATT_00, REL_PRE_10}; -int hmi_vox[4] = {VOX_OFF, VOX_LOW, VOX_MEDIUM, VOX_HIGH}; -int hmi_bpf[5] = {REL_LPF2, REL_BPF6, REL_BPF12, REL_BPF24, REL_BPF40}; +int hmi_mode[HMI_NMODE] = {MODE_USB, MODE_LSB, MODE_AM, MODE_CW}; +int hmi_agc[HMI_NAGC] = {AGC_NONE, AGC_SLOW, AGC_FAST}; +int hmi_pre[HMI_NPRE] = {REL_ATT_30, REL_ATT_20, REL_ATT_10, REL_ATT_00, REL_PRE_10}; +int hmi_vox[HMI_NVOX] = {VOX_OFF, VOX_LOW, VOX_MEDIUM, VOX_HIGH}; +int hmi_bpf[HMI_NBPF] = {REL_LPF2, REL_BPF6, REL_BPF12, REL_BPF24, REL_BPF40}; + int hmi_state, hmi_option; // Current state and menu option selection int hmi_sub[HMI_NSTATES] = {4,0,0,3,0,2}; // Stored option selection per state @@ -159,7 +160,7 @@ void hmi_handler(uint8_t event) switch (event) { case HMI_E_ENTER: // Commit current value - SI_SETFREQ(0, HMI_MULFREQ*(hmi_freq-FC_OFFSET)); // Commit frequency + si_setfreq(0, HMI_MULFREQ*(hmi_freq-FC_OFFSET)); // Commit frequency break; case HMI_E_ESCAPE: // Enter submenus hmi_sub[hmi_state] = hmi_option; // Store selection (i.e. digit) @@ -291,8 +292,8 @@ void hmi_init(void) hmi_option = 4; // Active kHz digit hmi_freq = 7074000UL; // Initial frequency - SI_SETFREQ(0, HMI_MULFREQ*(hmi_freq-FC_OFFSET)); // Set freq to 7074 kHz (depends on mixer type) - SI_SETPHASE(0, 1); // Set phase to 90deg (depends on mixer type) + si_setfreq(0, HMI_MULFREQ*(hmi_freq-FC_OFFSET)); // Set freq to 7074 kHz (depends on mixer type) + si_setphase(0, 1); // Set phase to 90deg (depends on mixer type) ptt_state = PTT_DEBOUNCE; ptt_active = false; @@ -311,6 +312,7 @@ void hmi_init(void) */ void hmi_evaluate(void) { + int band; char s[32]; // Print top line of display @@ -373,11 +375,28 @@ void hmi_evaluate(void) if (ptt_state == 0) // Set PTT when debounced level low ptt_active = true; - + /* Set parameters corresponding to latest entered option value */ - SI_SETFREQ(0, HMI_MULFREQ*(hmi_freq-FC_OFFSET)); // Always set frequency - if (hmi_update) // Others only when indicated - { + + // Frequency might have been changed in hmi_handler, so set anyway + si_setfreq(0, HMI_MULFREQ*(hmi_freq-FC_OFFSET)); + + // Check bandfilter setting (thanks Alex) + if (hmi_freq < 2500000UL) band = REL_LPF2; + else if (hmi_freq < 6000000UL) band = REL_BPF6; + else if (hmi_freq < 12000000UL) band = REL_BPF12; + else if (hmi_freq < 24000000UL) band = REL_BPF24; + else band = REL_BPF40; + if (band != hmi_bpf[hmi_sub[HMI_S_BPF]]) // Force update when changed + { + hmi_bpf[hmi_sub[HMI_S_BPF]] = band; + hmi_update = true; + } + + // Update peripherals according to menu setting + // For frequency si5351 is set directly, HMI top line follows + if (hmi_update) + { dsp_setmode(hmi_sub[HMI_S_MODE]); dsp_setvox(hmi_sub[HMI_S_VOX]); dsp_setagc(hmi_sub[HMI_S_AGC]); diff --git a/relay.c b/relay.c index 55f48e8..4703780 100644 --- a/relay.c +++ b/relay.c @@ -6,15 +6,18 @@ * * Two PCF8574 expanders are on the I2C bus, one on the RX and one on the BPF board. * The RX (0x42) bit assignments: - * 0: Enable -20dB attenuator - * 1: Enable -10dB attenuator - * 2: Enable +10dB pre-amplifier + * 0x03: Enable -20dB and -10dB attenuators + * 0x01: Enable -20dB attenuator + * 0x02: Enable -10dB attenuator + * 0x04: Enable +10dB pre-amplifier + * 0x00: No attenuator or pre-amp + * * The BPF (0x40) bit assignments: - * 0: Enable LPF 2.5 MHz - * 1: Enable BPF 2.0 - 6.0 MHz - * 2: Enable BPF 5.0 -12.0 MHz - * 3: Enable BPF 10.0 -24.0 MHz - * 4: Enable BPF 20.0 -40.0 MHz + * 0x01: Enable LPF 2.5 MHz + * 0x02: Enable BPF 2.0 - 6.0 MHz + * 0x04: Enable BPF 5.0 -12.0 MHz + * 0x08: Enable BPF 10.0 -24.0 MHz + * 0x10: Enable BPF 20.0 -40.0 MHz * */ #include diff --git a/si5351.c b/si5351.c index 2c7b447..7248f1a 100644 --- a/si5351.c +++ b/si5351.c @@ -9,13 +9,13 @@ Si5351 principle of operation: ============================== -Crystal frequency Fxtal (usually 25MHz) is multiplied in a PLL by MSN to obtain Fvco. -PLL A and B have independent MSN values, the Fout channel i can be derived from either. -Fvco is between 600MHz and 900MHz, but the spec in reality is more relaxed. -Fvco is divided by MSi and Ri to obtain the output frequency Fout. -MSi and Ri are selected to be in the ballpark of desired frequency range. -MSN is then used for tuning. -Only certain values of MSi and Ri are allowed when quadrature output is needed. +Crystal frequency (usually 25MHz) is multiplied in a PLL by to obtain . +PLL A and B have independent MSN values, the on channel can be derived from either. + must be between 600MHz and 900MHz, but the spec is more relaxed in reality. + is divided by and to obtain the output frequency . + and are selected to be in the ballpark of desired frequency range. + is then used for tuning . +Only certain values of and are allowed when quadrature output is required. +-------+ +-------+ +------+ - Fxtal --> | * MSN | -- Fvco --> | / MSi | --> | / Ri | -- Fout --> @@ -27,28 +27,24 @@ Details: MSN determines: Fvco = Fxtal * (MSN) , where MSN = a + b/c MSi and Ri determine: Fout = Fvco / (Ri*MSi) , where MSi = a + b/c (different a, b and c) - ---Derivation of register values, for MSN and MSi--- + ---Derivation of the register values, that determine MSN and MSi--- P1 = 128*a + Floor(128*b/c) - 512 P2 = 128*b - c*Floor(128*b/c) (P2 = 0 for MSi integer mode, or calculated for MSN tuning) P3 = c (P3 = 1 for MSi integer mode, or P3 = 1000000 for MSN tuning) - This VFO implementation assumes PLLA is used for clk0 and clk1, PLLB is used for clk2 + This VFO implementation assumes PLLA is used for VFO0 (clk0 and clk1), and PLLB is used for VFO1 (clk2) - Algorithm to get from frequency to synthesizer settings: - (this assumes that the current settings are consistent, i.e. must be initialized at startup) - | calculate new MSN from the desired Fout, based on current Ri and MSi + The algorithm to get from required Fout to synthesizer settings: + | calculate new from the desired , based on the current and | if MSN is still inside [600/Fxtal, 900/Fxtal] | then - | just write the MSN parameter registers + | just update the MSN related registers | else - | re-calculate MSi, Ri and MSN from desired Fout - | write the MSi and Ri parameter registers, including phase offset (MSi equals phase offset for 90 deg, use INV to shift 180deg more) - | write the MSN parameter registers + | re-calculate , and from desired + | write the and parameter registers, including phase offset (MSi equals phase offset for 90 deg, use INV to shift 180deg more) + | write the parameter registers | reset PLL - -Ri=128 for Fout <1 MHz -Ri= 32 for Fout 1-6 MHz -Ri= 1 for Fout >6 MHz + (this all assumes that the current settings are consistent, i.e. must be initialized at startup) Some boundary values: Ri MSi Lo MHz Hi MHz @@ -70,7 +66,10 @@ Quadrature Phase offsets (i.e. delay): - Offset for MS0 (reg 165) must be 0 (cosine), - Offset for MS1 (reg 166) must be equal to divider MS1 for 90 deg (sine), - Set INV bit (reg 17) to add 180 deg. -NOTE: Phase offsets only work when Ri = 1, this means minimum Fout is 4.762MHz at Fvco = 600MHz. Additional flip/flop dividers are needed to get 80m band frequencies, or Fvco must be tuned below spec. + +NOTE: Phase offsets only work when Ri = 1, +This implies that minimum Fout is 4.762MHz at Fvco = 600MHz. +Additional flip/flop dividers are needed to get down to 80m band frequencies, or Fvco must be tuned below spec. Control Si5351 (see AN619): @@ -159,10 +158,12 @@ Control Si5351 (see AN619): #define SI_PLL_RESET 177 #define SI_XTAL_LOAD 183 -// CLK_OE register 3 values +// CLK_OE register 3 masks #define SI_CLK0_ENABLE 0b00000001 // Enable clock 0 output #define SI_CLK1_ENABLE 0b00000010 // Enable clock 1 output #define SI_CLK2_ENABLE 0b00000100 // Enable clock 2 output +#define SI_VFO0_DISABLE 0b00000011 // Set bits to disable +#define SI_VFO1_DISABLE 0b00000100 // Set bits to disable // CLKi_CTL register 16, 17, 18 values // Normally 0x4f for clk 0 and 1, 0x6f for clk 2 @@ -184,8 +185,59 @@ Control Si5351 (see AN619): #define SI_PLL_C 1000000UL // Parameter c for PLL-A and -B setting +typedef struct +{ + uint32_t freq; // type can hold up to 4GHz + uint8_t flag; // flag != 0 when update needed + uint8_t phase; // in quarter waves (0, 1, 2, 3) + uint8_t ri; // Ri (1 .. 128) + uint8_t msi; // MSi parameter a (4, 6, 8 .. 126) + double msn; // MSN (24.0 .. 35.9999) +} vfo_t; +vfo_t vfo[2]; // 0: clk0 / clk1 1: clk2 -vfo_t vfo[2]; // 0: clk0 & clk1 1: clk2 + +void si_setfreq(int i, uint32_t f) +{ + if ((i<0)||(i>1)) return; // Check VFO range + if (f>150000000) return; // Check frequency range + if (vfo[i].freq == f) return; // Anything to set at all? + + vfo[i].freq = f; // Entry checks pass, so do the actual setting + vfo[i].flag = 1; +} + +void si_setphase(int i, uint8_t p) +{ + if (i!=0) return; // Check VFO range + if (p>3) return; // Check phase range + if (vfo[i].phase == p) return; // Anything to set at all? + + vfo[i].phase = p; // Entry checks pass, so do the actual setting + vfo[i].flag = 1; +} + +void si_enable(int i, bool en) +{ + uint8_t data[2]; + + if ((i<0)||(i>1)) return; // Check VFO range + + data[0] = SI_CLK_OE; // Read OE register + i2c_write_blocking(i2c0, I2C_VFO, &data[0], 1, true); + i2c_read_blocking(i2c0, I2C_VFO, &data[1], 1, false); + + data[0] = SI_CLK_OE; + if (i==0) + { + data[1] = en ? data[1]&~SI_VFO0_DISABLE : data[1]|SI_VFO0_DISABLE; + } + else + { + data[1] = en ? data[1]&~SI_VFO1_DISABLE : data[1]|SI_VFO1_DISABLE; + } + i2c_write_blocking(i2c0, I2C_VFO, &data[0], 2, false); +} /* * read contents of SI5351 registers, from reg to reg+len-1, output in data array @@ -207,23 +259,26 @@ int si_getreg(uint8_t *data, uint8_t reg, uint8_t len) * Optimize for speed, this may be called with short intervals * See also SiLabs AN619 section 3.2 */ -void si_setmsn(uint8_t i) +void si_setmsn(int i) { uint8_t data[16]; // I2C trx buffer uint32_t P1, P2; // MSN parameters uint32_t A; uint32_t B; - i=(i>0?1:0); + if ((i<0)||(i>1)) return; // Check VFO range A = (uint32_t)(floor(vfo[i].msn)); // A is integer part of MSN - B = (uint32_t)((vfo[i].msn - (float)A) * SI_PLL_C); // B is C * fraction part of MSN (C is a constant) - P2 = (uint32_t)(floor((float)(128 * B) / (float)SI_PLL_C)); + B = (uint32_t)((vfo[i].msn - (double)A) * SI_PLL_C); // B is C * fraction part of MSN (C is a constant) + P2 = (uint32_t)(floor((double)(128 * B) / (double)SI_PLL_C)); P1 = (uint32_t)(128 * A + P2 - 512); P2 = (uint32_t)(128 * B - SI_PLL_C * P2); - // transfer registers - data[0] = (i==0?SI_SYNTH_PLLA:SI_SYNTH_PLLB); + // transfer PLL A or PLL B registers + if (i==0) + data[0] = SI_SYNTH_PLLA; + else + data[0] = SI_SYNTH_PLLB; data[1] = (SI_PLL_C & 0x0000FF00) >> 8; data[2] = (SI_PLL_C & 0x000000FF); data[3] = (P1 & 0x00030000) >> 16; @@ -252,7 +307,10 @@ void si_setmsi(uint8_t i) R = vfo[i].ri; R = (R&0xf0) ? ((R&0xc0)?((R&0x80)?7:6):(R&0x20)?5:4) : ((R&0x0c)?((R&0x08)?3:2):(R&0x02)?1:0); // quick log2(r) - data[0] = (i==0?SI_SYNTH_MS0:SI_SYNTH_MS2); + if (i==0) + data[0] = SI_SYNTH_MS0; + else + data[0] = SI_SYNTH_MS2; data[1] = 0x00; data[2] = 0x01; data[3] = ((P1 & 0x00030000) >> 16) | (R << 4 ); @@ -272,18 +330,18 @@ void si_setmsi(uint8_t i) if (vfo[0].phase&1) // Phase is either 90 or 270 deg? { data[0] = SI_CLK1_PHOFF; - data[1] = vfo[0].msi; + data[1] = vfo[0].msi; // offset == MSi for 90deg i2c_write_blocking(i2c0, I2C_VFO, data, 2, false); } else // Phase is 0 or 180 deg { data[0] = SI_CLK1_PHOFF; - data[1] = 0; + data[1] = 0; // offset == 0 for 0deg i2c_write_blocking(i2c0, I2C_VFO, data, 2, false); } if (vfo[0].phase&2) // Phase is 180 or 270 deg? { - data[0] = SI_CLK1_CTL; + data[0] = SI_CLK1_CTL; // Then set the invert flag data[1] = 0x5d; // CLK1: INT, PLLA, INV, MS, 8mA i2c_write_blocking(i2c0, I2C_VFO, data, 2, false); } @@ -304,13 +362,13 @@ void si_setmsi(uint8_t i) */ void si_evaluate(void) { - float msn; + double msn; if (vfo[0].flag) { - msn = (float)(vfo[0].msi); // Re-calculate MSN - msn = msn * (float)(vfo[0].ri); - msn = msn * (float)(vfo[0].freq) / SI_XTAL_FREQ; + msn = (double)(vfo[0].msi); // Re-calculate MSN + msn = msn * (double)(vfo[0].ri); + msn = msn * (double)(vfo[0].freq) / SI_XTAL_FREQ; if ((msn>=SI_MSN_LO)&&(msn= 3000000)&&(vfo[0].freq < 6000000)) // Low end of Ri=1 range - vfo[0].msi = (uint8_t)126; // Maximum MSi on Fvco=(4x126)MHz + // Pre-scale Ri, stretch down Ri=1 range + if (vfo[0].freq<1000000) + vfo[0].ri = 128; + else if (vfo[0].freq<3000000) + vfo[0].ri = 32; else - vfo[0].msi = (uint8_t)(750000000UL / (vfo[0].freq * vfo[0].ri)) & 0xfe; // Calculate MSi on Fvco=750MHz - msn = (float)(vfo[0].msi); // Re-calculate MSN - msn = msn * (float)(vfo[0].ri); - msn = msn * (float)(vfo[0].freq) / SI_XTAL_FREQ; + vfo[0].ri = 1; + + // Set MSi + if ((vfo[0].freq >= 3000000)&&(vfo[0].freq < 6000000)) // Handle Low end of Ri=1 range + vfo[0].msi = (uint8_t)126; // Maximum MSi on Fvco=(4x126)MHz + else // Or calculate MSi on Fvco=750MHz + vfo[0].msi = (uint8_t)(750000000UL / (vfo[0].freq * vfo[0].ri)) & 0x000000fe; + + msn = (double)(vfo[0].msi); // Re-calculate MSN + msn = msn * (double)(vfo[0].ri); + msn = msn * (double)(vfo[0].freq) / SI_XTAL_FREQ; vfo[0].msn = msn; + si_setmsn(0); si_setmsi(0); } @@ -424,9 +492,9 @@ void si_init(void) data[1] = 0xa0; i2c_write_blocking(i2c0, I2C_VFO, data, 2, false); - // Enable all outputs + // Enable only VFO0 outputs data[0] = SI_CLK_OE; - data[1] = 0x00; + data[1] = ~SI_VFO0_DISABLE; i2c_write_blocking(i2c0, I2C_VFO, data, 2, false); } diff --git a/si5351.h b/si5351.h index 2778418..a557451 100644 --- a/si5351.h +++ b/si5351.h @@ -6,31 +6,28 @@ * Created: March 2021 * Author: Arjan * + * Driver for Si5351A chip. + * VFO 0 is a quadrature clock on outputs 0 and 1, + * VFO 1 is a regular clock on output 2. + * + * The quadrature clock allows to set shared frequency and phase offsets of 0, 90, 180 and 270 deg + * The regular clock just allows to set frequency, phase is ignored + * * See si5351.c for more information * */ - - -typedef struct -{ - uint32_t freq; // type can hold up to 4GHz - uint8_t flag; // flag != 0 when update needed - uint8_t phase; // in quarter waves (0, 1, 2, 3) - uint8_t ri; // Ri (1 .. 128) - uint8_t msi; // MSi parameter a (4, 6, 8 .. 126) - float msn; // MSN (24.0 .. 35.9999) -} vfo_t; -extern vfo_t vfo[2]; // Table contains all control data for three clk outputs, but 0 and 1 are coupled in vfo[0] +#define PH000 0 +#define PH090 1 +#define PH180 2 +#define PH270 3 int si_getreg(uint8_t *data, uint8_t reg, uint8_t len); void si_init(void); void si_evaluate(void); +void si_setfreq(int i, uint32_t f); +void si_setphase(int i, uint8_t p); +void si_enable(int i, bool en); -#define SI_GETFREQ(i) ((((i)>=0)&&((i)<2))?vfo[(i)].freq:0) -#define SI_INCFREQ(i, d) if ((((i)>=0)&&((i)<2))&&((vfo[(i)].freq)<(150000000-(d)))) { vfo[(i)].freq += (d); vfo[(i)].flag = 1;} -#define SI_DECFREQ(i, d) if ((((i)>=0)&&((i)<2))&&((vfo[(i)].freq)>(d))) { (vfo[(i)].freq) -= (d); vfo[(i)].flag = 1;} -#define SI_SETFREQ(i, f) if ((((i)>=0)&&((i)<2))&&((f)<150000000)) { vfo[(i)].freq = (f); vfo[(i)].flag = 1;} -#define SI_SETPHASE(i, p) if (((i)>=0)&&((i)<2)) {vfo[(i)].phase = ((uint8_t)p)&3; vfo[(i)].flag = 1;} #endif /* _SI5351_H */ \ No newline at end of file