Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

support the quadrature LO generation by Si5351A without 74LCV74APW #13

Open
wants to merge 2 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions main.c
Original file line number Diff line number Diff line change
Expand Up @@ -197,7 +197,11 @@ void
set_tune(int hz)
{
center_frequency = hz - mode_freq_offset;
#ifdef SI5351_GEN_QUADRATURE_LO
si5351_set_frequency(center_frequency);
#else
si5351_set_frequency(center_frequency * 4);
#endif
}

static int current_fs = 48;
Expand Down
9 changes: 9 additions & 0 deletions nanosdr.h
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,15 @@
* Boston, MA 02110-1301, USA.
*/

/*
If you want to generate the quadrature LO directly from Si5351A without U2 (74LVC74APW),
enable the below SI5351_GEN_QUADRATURE_LO macro switch.
In that case you need to remove the U2 then populate the R5 and R6 with 0ohm resistor (jumper).
Si5351A only be able to generate the quadrature LO down to 3.5MHz, so below that frequency
no phase shift between I and Q LO, meaning the CW, USB and LSB mode will not work properly.
*/
//#define SI5351_GEN_QUADRATURE_LO 1

/*
* main.c
*/
Expand Down
120 changes: 119 additions & 1 deletion si5351.c
Original file line number Diff line number Diff line change
@@ -1,8 +1,14 @@
#include "hal.h"
#include "si5351.h"
#include "nanosdr.h"

#define SI5351_I2C_ADDR (0x60<<1)

#ifdef SI5351_GEN_QUADRATURE_LO
#define SI5351_SUPPORTED_QUADRATURE_FREQ_MIN 3500000
#define DELAY_RESET_PLL 5000
#endif

static void
si5351_write(uint8_t reg, uint8_t dat)
{
Expand Down Expand Up @@ -40,8 +46,15 @@ void si5351_enable_output(void)

void si5351_reset_pll(void)
{
#ifdef SI5351_GEN_QUADRATURE_LO
// Writing a 1<<5 will reset PLLA, 1<<7 reset PLLB, this is a self clearing bits.
// !!! Need delay before reset PLL for apply PLL freq changes before
chThdSleepMicroseconds(DELAY_RESET_PLL);
si5351_write(SI5351_REG_177_PLL_RESET, SI5351_PLL_RESET_B);
#else
//si5351_write(SI5351_REG_177_PLL_RESET, SI5351_PLL_RESET_A | SI5351_PLL_RESET_B);
si5351_write(SI5351_REG_177_PLL_RESET, 0xAC);
#endif
}

void si5351_setupPLL(uint8_t pll, /* SI5351_PLL_A or SI5351_PLL_B */
Expand Down Expand Up @@ -173,6 +186,29 @@ si5351_setupMultisynth(uint8_t output,
if (num == 0)
dat |= SI5351_CLK_INTEGER_MODE;
si5351_write(clkctrl[output], dat);

#ifdef SI5351_GEN_QUADRATURE_LO
#define SI5351_REG_165_CLK0_PHOFF 165
#define SI5351_REG_166_CLK1_PHOFF 166
#define SI5351_REG_167_CLK2_PHOFF 167

uint8_t phoff = 0;
if (div < 128 && rdiv == 0) {
phoff = (uint8_t)div;
if (num >= (denom / 2) && phoff <= 126) {
phoff++;
}
}

if (output == 0) {
// I/cosine
si5351_write(SI5351_REG_165_CLK0_PHOFF, phoff);
}
else if (output == 1) {
// Q/sine
si5351_write(SI5351_REG_166_CLK1_PHOFF, 0);
}
#endif
}

static uint32_t
Expand Down Expand Up @@ -209,6 +245,74 @@ si5351_set_frequency_fixedpll(int channel, int pll, int pllfreq, int freq,
si5351_setupMultisynth(channel, pll, div, num, denom, rdiv, drive_strength);
}

#ifdef SI5351_GEN_QUADRATURE_LO
static void
si5351_set_frequency_quadrature(int pll, int freq,
uint32_t rdiv, uint8_t drive_strength)
{
#define PLL_MAX_FREQ 900000000
#define PLL_MID_FREQ 750000000
#define PLL_MIN_FREQ 600000000
#define MULTISYNTH_DIV_MIN 4
#define MULTISYNTH_DIV_MAX 1800
#define PLL_DENOM_MAX 1048575
#define MULTISYNTH_PHOFF_MAX 126

static uint32_t s_prev_ms_div = 1;
static uint32_t s_prev_rdiv = SI5351_R_DIV_1;
static int s_prev_freq;

uint32_t pll_freq = freq * s_prev_ms_div;
uint32_t ms_div;
if (pll_freq <= PLL_MAX_FREQ &&
(pll_freq >= PLL_MIN_FREQ ||
(s_prev_ms_div == MULTISYNTH_PHOFF_MAX &&
rdiv == SI5351_R_DIV_1 &&
freq >= SI5351_SUPPORTED_QUADRATURE_FREQ_MIN)) &&
abs(freq - s_prev_freq) < (((PLL_MAX_FREQ - PLL_MIN_FREQ) / 2) / s_prev_ms_div)
) {
// keep using current ms_div
ms_div = s_prev_ms_div;
}
else {
ms_div = (PLL_MID_FREQ + freq) / freq;
ms_div &= 0xFFFFFFFE; // make it even number
}

// multisynth parameter
if (ms_div > MULTISYNTH_PHOFF_MAX && rdiv == SI5351_R_DIV_1 && freq >= SI5351_SUPPORTED_QUADRATURE_FREQ_MIN) {
ms_div = MULTISYNTH_PHOFF_MAX; // this will cause the pll_freq to be below PLL_MIN_FREQ
}
if (ms_div >= MULTISYNTH_DIV_MAX) {
ms_div = MULTISYNTH_DIV_MAX;
}
if (ms_div < MULTISYNTH_DIV_MIN) {
ms_div = MULTISYNTH_DIV_MIN;
}

pll_freq = freq * ms_div;

// PLL parameter
uint32_t pll_mult = pll_freq / XTALFREQ;
uint32_t pll_remain = pll_freq % XTALFREQ;
uint32_t pll_num = ((uint64_t)pll_remain * PLL_DENOM_MAX + XTALFREQ / 2) / XTALFREQ;
if (pll_num == PLL_DENOM_MAX) {
pll_mult++;
pll_num = 0;
}
si5351_setupPLL(pll, pll_mult, pll_num, PLL_DENOM_MAX);

if (ms_div != s_prev_ms_div || rdiv != s_prev_rdiv) {
s_prev_ms_div = ms_div;
s_prev_rdiv = rdiv;
s_prev_freq = freq;
si5351_setupMultisynth(0, pll, ms_div, 0, 1, rdiv, drive_strength);
si5351_setupMultisynth(1, pll, ms_div, 0, 1, rdiv, drive_strength);
si5351_reset_pll();
}
}
#endif

void
si5351_set_frequency_fixeddiv(int channel, int pll, int freq, int div,
uint8_t drive_strength)
Expand Down Expand Up @@ -246,7 +350,11 @@ si5351_set_frequency(int freq)
}
if (freq <= 500000) {
rdiv = SI5351_R_DIV_64;
} else if (freq <= 4000000) {
#ifdef SI5351_GEN_QUADRATURE_LO
} else if (freq < SI5351_SUPPORTED_QUADRATURE_FREQ_MIN) { // <= 3.5MHz
#else
} else if (freq <= 4000000) { // <= 4MHz
#endif
rdiv = SI5351_R_DIV_8;
}

Expand All @@ -255,6 +363,15 @@ si5351_set_frequency(int freq)
si5351_disable_output();
#endif

#ifdef SI5351_GEN_QUADRATURE_LO
if (rdiv == SI5351_R_DIV_8) {
freq *= 8;
} else if (rdiv == SI5351_R_DIV_64) {
freq *= 64;
}
si5351_set_frequency_quadrature(SI5351_PLL_B, freq,
rdiv, drive_strength);
#else
switch (band) {
case 0:
if (rdiv == SI5351_R_DIV_8) {
Expand Down Expand Up @@ -289,6 +406,7 @@ si5351_set_frequency(int freq)
}

current_band = band;
#endif
}