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
+33 -65
View File
@@ -29,92 +29,60 @@ func main() {
// Create driver instance // Create driver instance
clockgen := si5351.New(machine.I2C0) clockgen := si5351.New(machine.I2C0)
// Verify device wired properly // Initialize device
connected, err := clockgen.Connected() cnf := si5351.Config{
if err != nil { Capacitance: si5351.CrystalLoad10PF,
println("Unable to read device status")
time.Sleep(time.Second)
}
if !connected {
for {
println("Unable to detect si5351 device")
time.Sleep(time.Second)
}
} }
// Initialise device if err := clockgen.Configure(cnf); err != nil {
clockgen.Configure() println("Failed to configure Si5351:", err.Error())
return
}
println("Si5351 configured")
// Now configue the PLLs and clock outputs. // Now configure the clock outputs.
// The PLLs can be configured with a multiplier and division of the on-board clockgen.SetFrequency(si5351.Clock0, 112_500_000)
// 25mhz reference crystal. For example configure PLL A to 900mhz by multiplying
// by 36. This uses an integer multiplier which is more accurate over time
// but allows less of a range of frequencies compared to a fractional
// multiplier shown next.
clockgen.ConfigurePLL(si5351.PLL_A, 36, 0, 1) // Multiply 25mhz by 36
println("PLL A frequency: 900mhz")
// And next configure PLL B to 616.6667mhz by multiplying 25mhz by 24.667 using
// the fractional multiplier configuration. Notice you specify the integer
// multiplier and then a numerator and denominator as separate values, i.e.
// numerator 2 and denominator 3 means 2/3 or 0.667. This fractional
// configuration is susceptible to some jitter over time but can set a larger
// range of frequencies.
clockgen.ConfigurePLL(si5351.PLL_B, 24, 2, 3) // Multiply 25mhz by 24.667 (24 2/3)
println("PLL B frequency: 616.6667mhz")
// Now configure the clock outputs. Each is driven by a PLL frequency as input
// and then further divides that down to a specific frequency.
// Configure clock 0 output to be driven by PLL A divided by 8, so an output
// of 112.5mhz (900mhz / 8). Again this uses the most precise integer division
// but can't set as wide a range of values.
clockgen.ConfigureMultisynth(0, si5351.PLL_A, 8, 0, 1) // Divide by 8 (8 0/1)
println("Clock 0: 112.5mhz") println("Clock 0: 112.5mhz")
// Next configure clock 1 to be driven by PLL B divided by 45.5 to get // Next configure clock 1 for 13.5531mhz (616.6667mhz / 45.5).
// 13.5531mhz (616.6667mhz / 45.5). This uses fractional division and again // This uses fractional division.
// notice the numerator and denominator are explicitly specified. This is less clockgen.SetFrequency(si5351.Clock1, 13_553_125)
// precise but allows a large range of frequencies.
clockgen.ConfigureMultisynth(1, si5351.PLL_B, 45, 1, 2) // Divide by 45.5 (45 1/2)
println("Clock 1: 13.5531mhz") println("Clock 1: 13.5531mhz")
// Finally configure clock 2 to be driven by PLL B divided once by 900 to get // Finally configure clock 2 to output of 10.706khz.
// down to 685.15 khz and then further divided by a special R divider that clockgen.SetFrequency(si5351.Clock2, 10_706)
// divides 685.15 khz by 64 to get a final output of 10.706khz.
clockgen.ConfigureMultisynth(2, si5351.PLL_B, 900, 0, 1) // Divide by 900 (900 0/1)
// Set the R divider, this can be a value of:
// - R_DIV_1: divider of 1
// - R_DIV_2: divider of 2
// - R_DIV_4: divider of 4
// - R_DIV_8: divider of 8
// - R_DIV_16: divider of 16
// - R_DIV_32: divider of 32
// - R_DIV_64: divider of 64
// - R_DIV_128: divider of 128
clockgen.ConfigureRdiv(2, si5351.R_DIV_64)
println("Clock 2: 10.706khz") println("Clock 2: 10.706khz")
// After configuring PLLs and clocks, enable the outputs. // After configuring the clocks enable the outputs.
clockgen.EnableOutputs() clockgen.EnableOutput(si5351.Clock0, true)
clockgen.EnableOutput(si5351.Clock1, true)
clockgen.EnableOutput(si5351.Clock2, true)
println("All outputs enabled")
time.Sleep(time.Second) time.Sleep(time.Second)
clockgen.DisableOutputs() clockgen.EnableOutput(si5351.Clock0, false)
clockgen.EnableOutput(si5351.Clock1, false)
clockgen.EnableOutput(si5351.Clock2, false)
println("All outputs disabled for 5 seconds") println("All outputs disabled for 5 seconds")
time.Sleep(5 * time.Second) time.Sleep(5 * time.Second)
// Now use SetFrequency to re-set the frequencies of the outputs // Now turn clock outputs on and off repeatedly
on := false on := false
for { for {
if on { if on {
println("Setting Clock 0 output off") println("Setting clock outputs off")
clockgen.OutputEnable(0, false) clockgen.EnableOutput(si5351.Clock0, false)
clockgen.EnableOutput(si5351.Clock1, false)
clockgen.EnableOutput(si5351.Clock2, false)
on = false on = false
} else { } else {
println("Setting Clock 0 output to 100mhz") println("Setting clock outputs on")
clockgen.SetFrequency(100*machine.MHz, 0, si5351.PLL_A) clockgen.EnableOutput(si5351.Clock0, true)
clockgen.EnableOutput(si5351.Clock1, true)
clockgen.EnableOutput(si5351.Clock2, true)
on = true on = true
} }
time.Sleep(5 * time.Second) time.Sleep(1 * time.Second)
} }
} }
+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 AddressAlternative = 0x61 // Assumes ADDR pin is high
const ( const (
OUTPUT_ENABLE_CONTROL = 3 XTAL_FREQ = 25000000
PLL_FIXED = 80000000000
FREQ_MULT = 100
DEFAULT_CLK = 1000000000
CLK0_CONTROL = 16 PLL_VCO_MIN = 600000000
CLK1_CONTROL = 17 PLL_VCO_MAX = 900000000
CLK2_CONTROL = 18 MULTISYNTH_MIN_FREQ = 500000
CLK3_CONTROL = 19 MULTISYNTH_DIVBY4_FREQ = 150000000
CLK4_CONTROL = 20 MULTISYNTH_MAX_FREQ = 225000000
CLK5_CONTROL = 21 MULTISYNTH_SHARE_MAX = 100000000
CLK6_CONTROL = 22 MULTISYNTH_SHARE_MIN = 1024000
CLK7_CONTROL = 23 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 PLL_A_MIN = 15
MULTISYNTH0_PARAMETERS_3 = 44 PLL_A_MAX = 90
MULTISYNTH1_PARAMETERS_1 = 50 PLL_B_MAX = PLL_C_MAX - 1
MULTISYNTH1_PARAMETERS_3 = 52 PLL_C_MAX = 1048575
MULTISYNTH2_PARAMETERS_1 = 58 MULTISYNTH_A_MIN = 6
MULTISYNTH2_PARAMETERS_3 = 60 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 CLK3_0_DISABLE_STATE = 24
) CLK7_4_DISABLE_STATE = 25
CLK_DISABLE_STATE_MASK = 3
const ( CLK_DISABLE_STATE_LOW = 0
CRYSTAL_LOAD_6PF = (1 << 6) CLK_DISABLE_STATE_HIGH = 1
CRYSTAL_LOAD_8PF = (2 << 6) CLK_DISABLE_STATE_FLOAT = 2
CRYSTAL_LOAD_10PF = (3 << 6) CLK_DISABLE_STATE_NEVER = 3
)
PARAMETERS_LENGTH = 8
const ( PLLA_PARAMETERS = 26
CRYSTAL_FREQ_25MHZ = 25000000 PLLB_PARAMETERS = 34
CRYSTAL_FREQ_27MHZ = 27000000 CLK0_PARAMETERS = 42
) CLK1_PARAMETERS = 50
CLK2_PARAMETERS = 58
const ( CLK3_PARAMETERS = 66
PLL_A = iota CLK4_PARAMETERS = 74
PLL_B CLK5_PARAMETERS = 82
) CLK6_PARAMETERS = 90
CLK7_PARAMETERS = 91
const ( CLK6_7_OUTPUT_DIVIDER = 92
R_DIV_1 = iota OUTPUT_CLK_DIV_MASK = 7 << 4
R_DIV_2 OUTPUT_CLK6_DIV_MASK = 7 << 0
R_DIV_4 OUTPUT_CLK_DIV_SHIFT = 4
R_DIV_8 OUTPUT_CLK_DIV6_SHIFT = 0
R_DIV_16 OUTPUT_CLK_DIV_1 = 0
R_DIV_32 OUTPUT_CLK_DIV_2 = 1
R_DIV_64 OUTPUT_CLK_DIV_4 = 2
R_DIV_128 OUTPUT_CLK_DIV_8 = 3
) OUTPUT_CLK_DIV_16 = 4
OUTPUT_CLK_DIV_32 = 5
const ( OUTPUT_CLK_DIV_64 = 6
MULTISYNTH_DIV_4 = 4 OUTPUT_CLK_DIV_128 = 7
MULTISYNTH_DIV_6 = 6 OUTPUT_CLK_DIVBY4 = 3 << 2
MULTISYNTH_DIV_8 = 8
) SSC_PARAM0 = 149
SSC_PARAM1 = 150
// Frequency constants (in Hz) SSC_PARAM2 = 151
const ( SSC_PARAM3 = 152
CLKOUT_MIN_FREQ = 8000 // 8 kHz SSC_PARAM4 = 153
CLKOUT_MAX_FREQ = 150000000 // 150 MHz SSC_PARAM5 = 154
MULTISYNTH_MAX_FREQ = 150000000 // 150 MHz SSC_PARAM6 = 155
MULTISYNTH_SHARE_MAX = 100000000 // 100 MHz SSC_PARAM7 = 156
MULTISYNTH_DIVBY4_FREQ = 150000000 // 150 MHz SSC_PARAM8 = 157
PLL_VCO_MIN = 600000000 // 600 MHz SSC_PARAM9 = 158
PLL_VCO_MAX = 900000000 // 900 MHz SSC_PARAM10 = 159
) SSC_PARAM11 = 160
SSC_PARAM12 = 161
const (
SI5351_PLL_C_MAX = 1048575 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)
}
}