si5351: complete refactor for more complete interface

This completely refactors the interface and implementation
for the si5351 clock generator. The interface based on the
Arduino implementation was both somewhat hard to work with
and also missing a number of important features that are
needed to use this chip for RF communication.

Instead this new implementation draws inspiration from the
efforts of the Traquino community mostly using the rp2040
processor.

The TinyGo implementation is based on the patterns and code
in the drivers repo for other i2c devices. It also includes
some basic unit tests which are not comprehensive but at
least provide some coverage.

Signed-off-by: deadprogram <ron@hybridgroup.com>
This commit is contained in:
deadprogram
2026-01-06 10:25:09 +01:00
committed by Ron Evans
parent a35786be70
commit c21cd39813
4 changed files with 1388 additions and 687 deletions
+145 -66
View File
@@ -5,75 +5,154 @@ const AddressDefault = 0x60 // Assumes ADDR pin is low
const AddressAlternative = 0x61 // Assumes ADDR pin is high
const (
OUTPUT_ENABLE_CONTROL = 3
XTAL_FREQ = 25000000
PLL_FIXED = 80000000000
FREQ_MULT = 100
DEFAULT_CLK = 1000000000
CLK0_CONTROL = 16
CLK1_CONTROL = 17
CLK2_CONTROL = 18
CLK3_CONTROL = 19
CLK4_CONTROL = 20
CLK5_CONTROL = 21
CLK6_CONTROL = 22
CLK7_CONTROL = 23
PLL_VCO_MIN = 600000000
PLL_VCO_MAX = 900000000
MULTISYNTH_MIN_FREQ = 500000
MULTISYNTH_DIVBY4_FREQ = 150000000
MULTISYNTH_MAX_FREQ = 225000000
MULTISYNTH_SHARE_MAX = 100000000
MULTISYNTH_SHARE_MIN = 1024000
MULTISYNTH67_MAX_FREQ = MULTISYNTH_DIVBY4_FREQ
CLKOUT_MIN_FREQ = 4000
CLKOUT_MAX_FREQ = MULTISYNTH_MAX_FREQ
CLKOUT67_MS_MIN = PLL_VCO_MIN / MULTISYNTH67_A_MAX
CLKOUT67_MIN_FREQ = CLKOUT67_MS_MIN / 128
CLKOUT67_MAX_FREQ = MULTISYNTH67_MAX_FREQ
MULTISYNTH0_PARAMETERS_1 = 42
MULTISYNTH0_PARAMETERS_3 = 44
MULTISYNTH1_PARAMETERS_1 = 50
MULTISYNTH1_PARAMETERS_3 = 52
MULTISYNTH2_PARAMETERS_1 = 58
MULTISYNTH2_PARAMETERS_3 = 60
PLL_A_MIN = 15
PLL_A_MAX = 90
PLL_B_MAX = PLL_C_MAX - 1
PLL_C_MAX = 1048575
MULTISYNTH_A_MIN = 6
MULTISYNTH_A_MAX = 1800
MULTISYNTH67_A_MAX = 254
MULTISYNTH_B_MAX = MULTISYNTH_C_MAX - 1
MULTISYNTH_C_MAX = 1048575
MULTISYNTH_P1_MAX = (1 << 18) - 1
MULTISYNTH_P2_MAX = (1 << 20) - 1
MULTISYNTH_P3_MAX = (1 << 20) - 1
VCXO_PULL_MIN = 30
VCXO_PULL_MAX = 240
VCXO_MARGIN = 103
SPREAD_SPECTRUM_PARAMETERS = 149
DEVICE_STATUS = 0
INTERRUPT_STATUS = 1
INTERRUPT_MASK = 2
STATUS_SYS_INIT = 1 << 7
STATUS_LOL_B = 1 << 6
STATUS_LOL_A = 1 << 5
STATUS_LOS = 1 << 4
OUTPUT_ENABLE_CTRL = 3
OEB_PIN_ENABLE_CTRL = 9
PLL_INPUT_SOURCE = 15
CLKIN_DIV_MASK = 3 << 6
CLKIN_DIV_1 = 0 << 6
CLKIN_DIV_2 = 1 << 6
CLKIN_DIV_4 = 2 << 6
CLKIN_DIV_8 = 3 << 6
PLLB_SOURCE = 1 << 3
PLLA_SOURCE = 1 << 2
PLL_RESET = 177
CLK0_CTRL = 16
CLK1_CTRL = 17
CLK2_CTRL = 18
CLK3_CTRL = 19
CLK4_CTRL = 20
CLK5_CTRL = 21
CLK6_CTRL = 22
CLK7_CTRL = 23
CLK_POWERDOWN = 1 << 7
CLK_INTEGER_MODE = 1 << 6
CLK_PLL_SELECT = 1 << 5
CLK_INVERT = 1 << 4
CLK_INPUT_MASK = 3 << 2
CLK_INPUT_XTAL = 0 << 2
CLK_INPUT_CLKIN = 1 << 2
CLK_INPUT_MULTISYNTH_0_4 = 2 << 2
CLK_INPUT_MULTISYNTH_N = 3 << 2
CLK_DRIVE_STRENGTH_MASK = 3 << 0
CLK_DRIVE_STRENGTH_2MA = 0 << 0
CLK_DRIVE_STRENGTH_4MA = 1 << 0
CLK_DRIVE_STRENGTH_6MA = 2 << 0
CLK_DRIVE_STRENGTH_8MA = 3 << 0
CRYSTAL_INTERNAL_LOAD_CAPACITANCE = 183
)
const (
CRYSTAL_LOAD_6PF = (1 << 6)
CRYSTAL_LOAD_8PF = (2 << 6)
CRYSTAL_LOAD_10PF = (3 << 6)
)
const (
CRYSTAL_FREQ_25MHZ = 25000000
CRYSTAL_FREQ_27MHZ = 27000000
)
const (
PLL_A = iota
PLL_B
)
const (
R_DIV_1 = iota
R_DIV_2
R_DIV_4
R_DIV_8
R_DIV_16
R_DIV_32
R_DIV_64
R_DIV_128
)
const (
MULTISYNTH_DIV_4 = 4
MULTISYNTH_DIV_6 = 6
MULTISYNTH_DIV_8 = 8
)
// Frequency constants (in Hz)
const (
CLKOUT_MIN_FREQ = 8000 // 8 kHz
CLKOUT_MAX_FREQ = 150000000 // 150 MHz
MULTISYNTH_MAX_FREQ = 150000000 // 150 MHz
MULTISYNTH_SHARE_MAX = 100000000 // 100 MHz
MULTISYNTH_DIVBY4_FREQ = 150000000 // 150 MHz
PLL_VCO_MIN = 600000000 // 600 MHz
PLL_VCO_MAX = 900000000 // 900 MHz
)
const (
SI5351_PLL_C_MAX = 1048575
CLK3_0_DISABLE_STATE = 24
CLK7_4_DISABLE_STATE = 25
CLK_DISABLE_STATE_MASK = 3
CLK_DISABLE_STATE_LOW = 0
CLK_DISABLE_STATE_HIGH = 1
CLK_DISABLE_STATE_FLOAT = 2
CLK_DISABLE_STATE_NEVER = 3
PARAMETERS_LENGTH = 8
PLLA_PARAMETERS = 26
PLLB_PARAMETERS = 34
CLK0_PARAMETERS = 42
CLK1_PARAMETERS = 50
CLK2_PARAMETERS = 58
CLK3_PARAMETERS = 66
CLK4_PARAMETERS = 74
CLK5_PARAMETERS = 82
CLK6_PARAMETERS = 90
CLK7_PARAMETERS = 91
CLK6_7_OUTPUT_DIVIDER = 92
OUTPUT_CLK_DIV_MASK = 7 << 4
OUTPUT_CLK6_DIV_MASK = 7 << 0
OUTPUT_CLK_DIV_SHIFT = 4
OUTPUT_CLK_DIV6_SHIFT = 0
OUTPUT_CLK_DIV_1 = 0
OUTPUT_CLK_DIV_2 = 1
OUTPUT_CLK_DIV_4 = 2
OUTPUT_CLK_DIV_8 = 3
OUTPUT_CLK_DIV_16 = 4
OUTPUT_CLK_DIV_32 = 5
OUTPUT_CLK_DIV_64 = 6
OUTPUT_CLK_DIV_128 = 7
OUTPUT_CLK_DIVBY4 = 3 << 2
SSC_PARAM0 = 149
SSC_PARAM1 = 150
SSC_PARAM2 = 151
SSC_PARAM3 = 152
SSC_PARAM4 = 153
SSC_PARAM5 = 154
SSC_PARAM6 = 155
SSC_PARAM7 = 156
SSC_PARAM8 = 157
SSC_PARAM9 = 158
SSC_PARAM10 = 159
SSC_PARAM11 = 160
SSC_PARAM12 = 161
VXCO_PARAMETERS_LOW = 162
VXCO_PARAMETERS_MID = 163
VXCO_PARAMETERS_HIGH = 164
CLK0_PHASE_OFFSET = 165
CLK1_PHASE_OFFSET = 166
CLK2_PHASE_OFFSET = 167
CLK3_PHASE_OFFSET = 168
CLK4_PHASE_OFFSET = 169
CLK5_PHASE_OFFSET = 170
PLL_RESET = 177
PLL_RESET_B = 1 << 7
PLL_RESET_A = 1 << 5
CRYSTAL_LOAD = 183
CRYSTAL_LOAD_MASK = 3 << 6
CRYSTAL_LOAD_0PF = 0 << 6
CRYSTAL_LOAD_6PF = 1 << 6
CRYSTAL_LOAD_8PF = 2 << 6
CRYSTAL_LOAD_10PF = 3 << 6
FANOUT_ENABLE = 187
CLKIN_ENABLE = 1 << 7
XTAL_ENABLE = 1 << 6
MULTISYNTH_ENABLE = 1 << 4
)
+996 -556
View File
File diff suppressed because it is too large Load Diff
+214
View File
@@ -0,0 +1,214 @@
package si5351
import (
"testing"
)
func TestSelectRDiv(t *testing.T) {
d := &Device{}
tests := []struct {
name string
freq Frequency
wantDiv uint8
wantFreq Frequency
}{
{"4kHz", 4000 * FREQ_MULT, OUTPUT_CLK_DIV_128, 4000 * FREQ_MULT * 128},
{"8kHz", 8000 * FREQ_MULT, OUTPUT_CLK_DIV_64, 8000 * FREQ_MULT * 64},
{"16kHz", 16000 * FREQ_MULT, OUTPUT_CLK_DIV_32, 16000 * FREQ_MULT * 32},
{"32kHz", 32000 * FREQ_MULT, OUTPUT_CLK_DIV_16, 32000 * FREQ_MULT * 16},
{"64kHz", 64000 * FREQ_MULT, OUTPUT_CLK_DIV_8, 64000 * FREQ_MULT * 8},
{"128kHz", 128000 * FREQ_MULT, OUTPUT_CLK_DIV_4, 128000 * FREQ_MULT * 4},
{"256kHz", 256000 * FREQ_MULT, OUTPUT_CLK_DIV_2, 256000 * FREQ_MULT * 2},
{"512kHz", 512000 * FREQ_MULT, OUTPUT_CLK_DIV_1, 512000 * FREQ_MULT},
{"1MHz", 1000000 * FREQ_MULT, OUTPUT_CLK_DIV_1, 1000000 * FREQ_MULT},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
freq := tt.freq
freq, gotDiv := d.selectRDiv(freq)
if gotDiv != tt.wantDiv {
t.Errorf("selectRDiv() div = %v, want %v", gotDiv, tt.wantDiv)
}
if freq != tt.wantFreq {
t.Errorf("selectRDiv() freq = %v, want %v", freq, tt.wantFreq)
}
})
}
}
func TestSelectRDivMS67(t *testing.T) {
d := &Device{}
tests := []struct {
name string
freq Frequency
wantDiv uint8
wantFreq Frequency
}{
{"4kHz", 4000 * FREQ_MULT, OUTPUT_CLK_DIV_128, 4000 * FREQ_MULT * 128},
{"8kHz", 8000 * FREQ_MULT, OUTPUT_CLK_DIV_64, 8000 * FREQ_MULT * 64},
{"16kHz", 16000 * FREQ_MULT, OUTPUT_CLK_DIV_32, 16000 * FREQ_MULT * 32},
{"64kHz", 64000 * FREQ_MULT, OUTPUT_CLK_DIV_8, 64000 * FREQ_MULT * 8},
{"256kHz", 256000 * FREQ_MULT, OUTPUT_CLK_DIV_2, 256000 * FREQ_MULT * 2},
{"512kHz", 512000 * FREQ_MULT, OUTPUT_CLK_DIV_1, 512000 * FREQ_MULT},
{"1MHz", 1000000 * FREQ_MULT, OUTPUT_CLK_DIV_1, 1000000 * FREQ_MULT},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
freq := tt.freq
freq, gotDiv := d.selectRDivMS67(freq)
if gotDiv != tt.wantDiv {
t.Errorf("selectRDivMS67() div = %v, want %v", gotDiv, tt.wantDiv)
}
if freq != tt.wantFreq {
t.Errorf("selectRDivMS67() freq = %v, want %v", freq, tt.wantFreq)
}
})
}
}
func TestCalculatePLL(t *testing.T) {
d := &Device{}
d.crystalFreq[0] = 25000000
tests := []struct {
name string
freq Frequency
wantMin Frequency
wantMax Frequency
}{
{"600MHz", 600000000 * FREQ_MULT, 599000000 * FREQ_MULT, 601000000 * FREQ_MULT},
{"750MHz", 750000000 * FREQ_MULT, 749000000 * FREQ_MULT, 751000000 * FREQ_MULT},
{"900MHz", 900000000 * FREQ_MULT, 899000000 * FREQ_MULT, 901000000 * FREQ_MULT},
{"BelowMin", 500000000 * FREQ_MULT, 600000000 * FREQ_MULT, 600000000 * FREQ_MULT},
{"AboveMax", 1000000000 * FREQ_MULT, 900000000 * FREQ_MULT, 900000000 * FREQ_MULT},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got, reg := d.CalculatePLL(PLL_A, tt.freq, 0, false)
if got < tt.wantMin || got > tt.wantMax {
t.Errorf("CalculatePLL() = %v, want between %v and %v", got, tt.wantMin, tt.wantMax)
}
if reg.p1 == 0 || reg.p3 == 0 {
t.Errorf("CalculatePLL() invalid register values: p1=%v, p2=%v, p3=%v", reg.p1, reg.p2, reg.p3)
}
})
}
}
func TestCalculateMultisynth(t *testing.T) {
d := &Device{}
tests := []struct {
name string
freq Frequency
pllFreq Frequency
wantDiv bool
}{
{"10MHz from 800MHz", 10000000 * FREQ_MULT, 800000000 * FREQ_MULT, false},
{"1MHz from 800MHz", 1000000 * FREQ_MULT, 800000000 * FREQ_MULT, false},
{"Auto PLL 10MHz", 10000000 * FREQ_MULT, 0, false},
{"150MHz DivBy4", 150000000 * FREQ_MULT, 600000000 * FREQ_MULT, true},
{"BelowMin", 100000 * FREQ_MULT, 800000000 * FREQ_MULT, false},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got, reg := d.CalculateMultisynth(tt.freq, tt.pllFreq)
if tt.pllFreq == 0 {
// Auto mode should return a valid PLL frequency
if got < PLL_VCO_MIN*FREQ_MULT || got > PLL_VCO_MAX*FREQ_MULT {
t.Errorf("CalculateMultisynth() returned invalid PLL freq %v", got)
}
}
if reg.p3 == 0 {
t.Errorf("CalculateMultisynth() p3 should not be 0")
}
})
}
}
func TestMultisynth67Calc(t *testing.T) {
d := &Device{}
tests := []struct {
name string
freq Frequency
pllFreq Frequency
wantErr bool
}{
{"10MHz Auto", 10000000 * FREQ_MULT, 0, false},
{"100MHz Auto", 100000000 * FREQ_MULT, 0, false},
{"100MHz from 800MHz", 100000000 * FREQ_MULT, 800000000 * FREQ_MULT, false},
{"Invalid Division", 10000000 * FREQ_MULT, 777000000 * FREQ_MULT, true},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got, reg := d.multisynth67Calc(tt.freq, tt.pllFreq)
if tt.pllFreq == 0 {
if got < PLL_VCO_MIN*FREQ_MULT || got > PLL_VCO_MAX*FREQ_MULT {
t.Errorf("multisynth67Calc() returned invalid PLL freq %v", got)
}
} else if tt.wantErr {
if got != 0 {
t.Errorf("multisynth67Calc() should return 0 for invalid division, got %v", got)
}
}
if reg.p1 == 0 && !tt.wantErr {
t.Errorf("multisynth67Calc() p1 should not be 0")
}
})
}
}
func TestSetCorrection(t *testing.T) {
// Skip this test as it requires a mock I2C bus
t.Skip("Requires mock I2C bus implementation")
}
func TestSetRefFreq(t *testing.T) {
d := &Device{}
tests := []struct {
name string
freq CrystalFrequency
wantFreq CrystalFrequency
wantDiv uint8
}{
{"25MHz", 25000000, 25000000, CLKIN_DIV_1},
{"50MHz", 50000000, 25000000, CLKIN_DIV_2},
{"100MHz", 100000000, 25000000, CLKIN_DIV_4},
{"30MHz", 30000000, 30000000, CLKIN_DIV_1},
{"60MHz", 60000000, 30000000, CLKIN_DIV_2},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
d.SetReferenceFrequency(PLLInputClockIn, tt.freq)
if d.crystalFreq[PLLInputClockIn] != tt.wantFreq {
t.Errorf("SetReferenceFrequency() freq = %v, want %v", d.crystalFreq[PLLInputClockIn], tt.wantFreq)
}
if d.clkinDiv != tt.wantDiv {
t.Errorf("SetReferenceFrequency() clkinDiv = %v, want %v", d.clkinDiv, tt.wantDiv)
}
})
}
}
func TestGetCorrection(t *testing.T) {
d := &Device{}
d.refCorrection[PLLInputXO] = 5000
d.refCorrection[PLLInputClockIn] = -3000
if got := d.GetCorrection(PLLInputXO); got != 5000 {
t.Errorf("GetCorrection(PLLInputXO) = %v, want 5000", got)
}
if got := d.GetCorrection(PLLInputClockIn); got != -3000 {
t.Errorf("GetCorrection(PLLInputClockIn) = %v, want -3000", got)
}
}