Skip to content

Commit d2eb60c

Browse files
committed
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 <[email protected]>
1 parent a35786b commit d2eb60c

File tree

4 files changed

+1280
-587
lines changed

4 files changed

+1280
-587
lines changed

examples/si5351/main.go

Lines changed: 34 additions & 65 deletions
Original file line numberDiff line numberDiff line change
@@ -29,92 +29,61 @@ func main() {
2929
// Create driver instance
3030
clockgen := si5351.New(machine.I2C0)
3131

32-
// Verify device wired properly
33-
connected, err := clockgen.Connected()
34-
if err != nil {
35-
println("Unable to read device status")
36-
time.Sleep(time.Second)
32+
// Initialize device
33+
cnf := si5351.Config{
34+
XtalLoadC: si5351.CRYSTAL_LOAD_10PF,
35+
XOFreq: si5351.XTAL_FREQ,
36+
Correction: 0,
3737
}
38-
if !connected {
39-
for {
40-
println("Unable to detect si5351 device")
41-
time.Sleep(time.Second)
42-
}
38+
if err := clockgen.Configure(cnf); err != nil {
39+
println("Failed to configure Si5351:", err.Error())
40+
return
4341
}
42+
println("Si5351 configured")
4443

45-
// Initialise device
46-
clockgen.Configure()
47-
48-
// Now configue the PLLs and clock outputs.
49-
// The PLLs can be configured with a multiplier and division of the on-board
50-
// 25mhz reference crystal. For example configure PLL A to 900mhz by multiplying
51-
// by 36. This uses an integer multiplier which is more accurate over time
52-
// but allows less of a range of frequencies compared to a fractional
53-
// multiplier shown next.
54-
clockgen.ConfigurePLL(si5351.PLL_A, 36, 0, 1) // Multiply 25mhz by 36
55-
println("PLL A frequency: 900mhz")
56-
57-
// And next configure PLL B to 616.6667mhz by multiplying 25mhz by 24.667 using
58-
// the fractional multiplier configuration. Notice you specify the integer
59-
// multiplier and then a numerator and denominator as separate values, i.e.
60-
// numerator 2 and denominator 3 means 2/3 or 0.667. This fractional
61-
// configuration is susceptible to some jitter over time but can set a larger
62-
// range of frequencies.
63-
clockgen.ConfigurePLL(si5351.PLL_B, 24, 2, 3) // Multiply 25mhz by 24.667 (24 2/3)
64-
println("PLL B frequency: 616.6667mhz")
65-
66-
// Now configure the clock outputs. Each is driven by a PLL frequency as input
67-
// and then further divides that down to a specific frequency.
68-
// Configure clock 0 output to be driven by PLL A divided by 8, so an output
69-
// of 112.5mhz (900mhz / 8). Again this uses the most precise integer division
70-
// but can't set as wide a range of values.
71-
clockgen.ConfigureMultisynth(0, si5351.PLL_A, 8, 0, 1) // Divide by 8 (8 0/1)
44+
// Now configure the clock outputs.
45+
clockgen.SetFrequency(si5351.Clock0, 112_500_000)
7246
println("Clock 0: 112.5mhz")
7347

74-
// Next configure clock 1 to be driven by PLL B divided by 45.5 to get
75-
// 13.5531mhz (616.6667mhz / 45.5). This uses fractional division and again
76-
// notice the numerator and denominator are explicitly specified. This is less
77-
// precise but allows a large range of frequencies.
78-
clockgen.ConfigureMultisynth(1, si5351.PLL_B, 45, 1, 2) // Divide by 45.5 (45 1/2)
48+
// Next configure clock 1 for 13.5531mhz (616.6667mhz / 45.5).
49+
// This uses fractional division.
50+
clockgen.SetFrequency(si5351.Clock1, 13_553_125)
7951
println("Clock 1: 13.5531mhz")
8052

81-
// Finally configure clock 2 to be driven by PLL B divided once by 900 to get
82-
// down to 685.15 khz and then further divided by a special R divider that
83-
// divides 685.15 khz by 64 to get a final output of 10.706khz.
84-
clockgen.ConfigureMultisynth(2, si5351.PLL_B, 900, 0, 1) // Divide by 900 (900 0/1)
85-
// Set the R divider, this can be a value of:
86-
// - R_DIV_1: divider of 1
87-
// - R_DIV_2: divider of 2
88-
// - R_DIV_4: divider of 4
89-
// - R_DIV_8: divider of 8
90-
// - R_DIV_16: divider of 16
91-
// - R_DIV_32: divider of 32
92-
// - R_DIV_64: divider of 64
93-
// - R_DIV_128: divider of 128
94-
clockgen.ConfigureRdiv(2, si5351.R_DIV_64)
53+
// Finally configure clock 2 to output of 10.706khz.
54+
clockgen.SetFrequency(si5351.Clock2, 10_706)
9555
println("Clock 2: 10.706khz")
9656

97-
// After configuring PLLs and clocks, enable the outputs.
98-
clockgen.EnableOutputs()
57+
// After configuring the clocks enable the outputs.
58+
clockgen.EnableOutput(si5351.Clock0, true)
59+
clockgen.EnableOutput(si5351.Clock1, true)
60+
clockgen.EnableOutput(si5351.Clock2, true)
61+
println("All outputs enabled")
9962

10063
time.Sleep(time.Second)
10164

102-
clockgen.DisableOutputs()
65+
clockgen.EnableOutput(si5351.Clock0, false)
66+
clockgen.EnableOutput(si5351.Clock1, false)
67+
clockgen.EnableOutput(si5351.Clock2, false)
10368
println("All outputs disabled for 5 seconds")
10469
time.Sleep(5 * time.Second)
10570

106-
// Now use SetFrequency to re-set the frequencies of the outputs
71+
// Now turn clock outputs on and off repeatedly
10772
on := false
10873
for {
10974
if on {
110-
println("Setting Clock 0 output off")
111-
clockgen.OutputEnable(0, false)
75+
println("Setting clock outputs off")
76+
clockgen.EnableOutput(si5351.Clock0, false)
77+
clockgen.EnableOutput(si5351.Clock1, false)
78+
clockgen.EnableOutput(si5351.Clock2, false)
11279
on = false
11380
} else {
114-
println("Setting Clock 0 output to 100mhz")
115-
clockgen.SetFrequency(100*machine.MHz, 0, si5351.PLL_A)
81+
println("Setting clock outputs on")
82+
clockgen.EnableOutput(si5351.Clock0, true)
83+
clockgen.EnableOutput(si5351.Clock1, true)
84+
clockgen.EnableOutput(si5351.Clock2, true)
11685
on = true
11786
}
118-
time.Sleep(5 * time.Second)
87+
time.Sleep(1 * time.Second)
11988
}
12089
}

si5351/registers.go

Lines changed: 138 additions & 59 deletions
Original file line numberDiff line numberDiff line change
@@ -5,75 +5,154 @@ const AddressDefault = 0x60 // Assumes ADDR pin is low
55
const AddressAlternative = 0x61 // Assumes ADDR pin is high
66

77
const (
8-
OUTPUT_ENABLE_CONTROL = 3
8+
XTAL_FREQ = 25000000
9+
PLL_FIXED = 80000000000
10+
FREQ_MULT = 100
11+
DEFAULT_CLK = 1000000000
912

10-
CLK0_CONTROL = 16
11-
CLK1_CONTROL = 17
12-
CLK2_CONTROL = 18
13-
CLK3_CONTROL = 19
14-
CLK4_CONTROL = 20
15-
CLK5_CONTROL = 21
16-
CLK6_CONTROL = 22
17-
CLK7_CONTROL = 23
13+
PLL_VCO_MIN = 600000000
14+
PLL_VCO_MAX = 900000000
15+
MULTISYNTH_MIN_FREQ = 500000
16+
MULTISYNTH_DIVBY4_FREQ = 150000000
17+
MULTISYNTH_MAX_FREQ = 225000000
18+
MULTISYNTH_SHARE_MAX = 100000000
19+
MULTISYNTH_SHARE_MIN = 1024000
20+
MULTISYNTH67_MAX_FREQ = MULTISYNTH_DIVBY4_FREQ
21+
CLKOUT_MIN_FREQ = 4000
22+
CLKOUT_MAX_FREQ = MULTISYNTH_MAX_FREQ
23+
CLKOUT67_MS_MIN = PLL_VCO_MIN / MULTISYNTH67_A_MAX
24+
CLKOUT67_MIN_FREQ = CLKOUT67_MS_MIN / 128
25+
CLKOUT67_MAX_FREQ = MULTISYNTH67_MAX_FREQ
1826

19-
MULTISYNTH0_PARAMETERS_1 = 42
20-
MULTISYNTH0_PARAMETERS_3 = 44
21-
MULTISYNTH1_PARAMETERS_1 = 50
22-
MULTISYNTH1_PARAMETERS_3 = 52
23-
MULTISYNTH2_PARAMETERS_1 = 58
24-
MULTISYNTH2_PARAMETERS_3 = 60
27+
PLL_A_MIN = 15
28+
PLL_A_MAX = 90
29+
PLL_B_MAX = PLL_C_MAX - 1
30+
PLL_C_MAX = 1048575
31+
MULTISYNTH_A_MIN = 6
32+
MULTISYNTH_A_MAX = 1800
33+
MULTISYNTH67_A_MAX = 254
34+
MULTISYNTH_B_MAX = MULTISYNTH_C_MAX - 1
35+
MULTISYNTH_C_MAX = 1048575
36+
MULTISYNTH_P1_MAX = (1 << 18) - 1
37+
MULTISYNTH_P2_MAX = (1 << 20) - 1
38+
MULTISYNTH_P3_MAX = (1 << 20) - 1
39+
VCXO_PULL_MIN = 30
40+
VCXO_PULL_MAX = 240
41+
VCXO_MARGIN = 103
2542

26-
SPREAD_SPECTRUM_PARAMETERS = 149
43+
DEVICE_STATUS = 0
44+
INTERRUPT_STATUS = 1
45+
INTERRUPT_MASK = 2
46+
STATUS_SYS_INIT = 1 << 7
47+
STATUS_LOL_B = 1 << 6
48+
STATUS_LOL_A = 1 << 5
49+
STATUS_LOS = 1 << 4
50+
OUTPUT_ENABLE_CTRL = 3
51+
OEB_PIN_ENABLE_CTRL = 9
52+
PLL_INPUT_SOURCE = 15
53+
CLKIN_DIV_MASK = 3 << 6
54+
CLKIN_DIV_1 = 0 << 6
55+
CLKIN_DIV_2 = 1 << 6
56+
CLKIN_DIV_4 = 2 << 6
57+
CLKIN_DIV_8 = 3 << 6
58+
PLLB_SOURCE = 1 << 3
59+
PLLA_SOURCE = 1 << 2
2760

28-
PLL_RESET = 177
61+
CLK0_CTRL = 16
62+
CLK1_CTRL = 17
63+
CLK2_CTRL = 18
64+
CLK3_CTRL = 19
65+
CLK4_CTRL = 20
66+
CLK5_CTRL = 21
67+
CLK6_CTRL = 22
68+
CLK7_CTRL = 23
69+
CLK_POWERDOWN = 1 << 7
70+
CLK_INTEGER_MODE = 1 << 6
71+
CLK_PLL_SELECT = 1 << 5
72+
CLK_INVERT = 1 << 4
73+
CLK_INPUT_MASK = 3 << 2
74+
CLK_INPUT_XTAL = 0 << 2
75+
CLK_INPUT_CLKIN = 1 << 2
76+
CLK_INPUT_MULTISYNTH_0_4 = 2 << 2
77+
CLK_INPUT_MULTISYNTH_N = 3 << 2
78+
CLK_DRIVE_STRENGTH_MASK = 3 << 0
79+
CLK_DRIVE_STRENGTH_2MA = 0 << 0
80+
CLK_DRIVE_STRENGTH_4MA = 1 << 0
81+
CLK_DRIVE_STRENGTH_6MA = 2 << 0
82+
CLK_DRIVE_STRENGTH_8MA = 3 << 0
2983

30-
CRYSTAL_INTERNAL_LOAD_CAPACITANCE = 183
31-
)
84+
CLK3_0_DISABLE_STATE = 24
85+
CLK7_4_DISABLE_STATE = 25
86+
CLK_DISABLE_STATE_MASK = 3
87+
CLK_DISABLE_STATE_LOW = 0
88+
CLK_DISABLE_STATE_HIGH = 1
89+
CLK_DISABLE_STATE_FLOAT = 2
90+
CLK_DISABLE_STATE_NEVER = 3
3291

33-
const (
34-
CRYSTAL_LOAD_6PF = (1 << 6)
35-
CRYSTAL_LOAD_8PF = (2 << 6)
36-
CRYSTAL_LOAD_10PF = (3 << 6)
37-
)
92+
PARAMETERS_LENGTH = 8
93+
PLLA_PARAMETERS = 26
94+
PLLB_PARAMETERS = 34
95+
CLK0_PARAMETERS = 42
96+
CLK1_PARAMETERS = 50
97+
CLK2_PARAMETERS = 58
98+
CLK3_PARAMETERS = 66
99+
CLK4_PARAMETERS = 74
100+
CLK5_PARAMETERS = 82
101+
CLK6_PARAMETERS = 90
102+
CLK7_PARAMETERS = 91
103+
CLK6_7_OUTPUT_DIVIDER = 92
104+
OUTPUT_CLK_DIV_MASK = 7 << 4
105+
OUTPUT_CLK6_DIV_MASK = 7 << 0
106+
OUTPUT_CLK_DIV_SHIFT = 4
107+
OUTPUT_CLK_DIV6_SHIFT = 0
108+
OUTPUT_CLK_DIV_1 = 0
109+
OUTPUT_CLK_DIV_2 = 1
110+
OUTPUT_CLK_DIV_4 = 2
111+
OUTPUT_CLK_DIV_8 = 3
112+
OUTPUT_CLK_DIV_16 = 4
113+
OUTPUT_CLK_DIV_32 = 5
114+
OUTPUT_CLK_DIV_64 = 6
115+
OUTPUT_CLK_DIV_128 = 7
116+
OUTPUT_CLK_DIVBY4 = 3 << 2
38117

39-
const (
40-
CRYSTAL_FREQ_25MHZ = 25000000
41-
CRYSTAL_FREQ_27MHZ = 27000000
42-
)
118+
SSC_PARAM0 = 149
119+
SSC_PARAM1 = 150
120+
SSC_PARAM2 = 151
121+
SSC_PARAM3 = 152
122+
SSC_PARAM4 = 153
123+
SSC_PARAM5 = 154
124+
SSC_PARAM6 = 155
125+
SSC_PARAM7 = 156
126+
SSC_PARAM8 = 157
127+
SSC_PARAM9 = 158
128+
SSC_PARAM10 = 159
129+
SSC_PARAM11 = 160
130+
SSC_PARAM12 = 161
43131

44-
const (
45-
PLL_A = iota
46-
PLL_B
47-
)
132+
VXCO_PARAMETERS_LOW = 162
133+
VXCO_PARAMETERS_MID = 163
134+
VXCO_PARAMETERS_HIGH = 164
48135

49-
const (
50-
R_DIV_1 = iota
51-
R_DIV_2
52-
R_DIV_4
53-
R_DIV_8
54-
R_DIV_16
55-
R_DIV_32
56-
R_DIV_64
57-
R_DIV_128
58-
)
136+
CLK0_PHASE_OFFSET = 165
137+
CLK1_PHASE_OFFSET = 166
138+
CLK2_PHASE_OFFSET = 167
139+
CLK3_PHASE_OFFSET = 168
140+
CLK4_PHASE_OFFSET = 169
141+
CLK5_PHASE_OFFSET = 170
59142

60-
const (
61-
MULTISYNTH_DIV_4 = 4
62-
MULTISYNTH_DIV_6 = 6
63-
MULTISYNTH_DIV_8 = 8
64-
)
143+
PLL_RESET = 177
144+
PLL_RESET_B = 1 << 7
145+
PLL_RESET_A = 1 << 5
65146

66-
// Frequency constants (in Hz)
67-
const (
68-
CLKOUT_MIN_FREQ = 8000 // 8 kHz
69-
CLKOUT_MAX_FREQ = 150000000 // 150 MHz
70-
MULTISYNTH_MAX_FREQ = 150000000 // 150 MHz
71-
MULTISYNTH_SHARE_MAX = 100000000 // 100 MHz
72-
MULTISYNTH_DIVBY4_FREQ = 150000000 // 150 MHz
73-
PLL_VCO_MIN = 600000000 // 600 MHz
74-
PLL_VCO_MAX = 900000000 // 900 MHz
75-
)
147+
CRYSTAL_LOAD = 183
148+
CRYSTAL_LOAD_MASK = 3 << 6
149+
CRYSTAL_LOAD_0PF = 0 << 6
150+
CRYSTAL_LOAD_6PF = 1 << 6
151+
CRYSTAL_LOAD_8PF = 2 << 6
152+
CRYSTAL_LOAD_10PF = 3 << 6
76153

77-
const (
78-
SI5351_PLL_C_MAX = 1048575
154+
FANOUT_ENABLE = 187
155+
CLKIN_ENABLE = 1 << 7
156+
XTAL_ENABLE = 1 << 6
157+
MULTISYNTH_ENABLE = 1 << 4
79158
)

0 commit comments

Comments
 (0)