mirror of
https://github.com/tinygo-org/drivers.git
synced 2026-08-14 03:43:42 +00:00
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:
+145
-66
@@ -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
File diff suppressed because it is too large
Load Diff
@@ -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)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user