mirror of
https://github.com/tinygo-org/drivers.git
synced 2026-07-26 10:38:41 +00:00
si5351: add support for si5351 (#810)
* si5351: add support for si5351 Adds support for the si5351 I2C programmable clock generator using code from @chiefMarlin which used code from @conotto which somehow never got merged. Thank you everyone! Signed-off-by: deadprogram <ron@hybridgroup.com> * refactor: use regmap instead of legacy package to avoid heap allocations Signed-off-by: deadprogram <ron@hybridgroup.com> --------- Signed-off-by: deadprogram <ron@hybridgroup.com>
This commit is contained in:
@@ -0,0 +1,107 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"machine"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"tinygo.org/x/drivers/si5351"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Simple demo of the SI5351 clock generator.
|
||||||
|
// This is like the Arduino library example:
|
||||||
|
// https://github.com/adafruit/Adafruit_Si5351_Library/blob/master/examples/si5351/si5351.ino
|
||||||
|
// Which will configure the chip with:
|
||||||
|
// - PLL A at 900mhz
|
||||||
|
// - PLL B at 616.66667mhz
|
||||||
|
// - Clock 0 at 112.5mhz, using PLL A as a source divided by 8
|
||||||
|
// - Clock 1 at 13.5531mhz, using PLL B as a source divided by 45.5
|
||||||
|
// - Clock 2 at 10.76khz, using PLL B as a source divided by 900 and further divided with an R divider of 64.
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
time.Sleep(5 * time.Second)
|
||||||
|
|
||||||
|
println("Si5351 Clockgen Test")
|
||||||
|
println()
|
||||||
|
|
||||||
|
// Configure I2C bus
|
||||||
|
machine.I2C0.Configure(machine.I2CConfig{})
|
||||||
|
|
||||||
|
// Create driver instance
|
||||||
|
clockgen := si5351.New(machine.I2C0)
|
||||||
|
|
||||||
|
// Verify device wired properly
|
||||||
|
connected, err := clockgen.Connected()
|
||||||
|
if err != nil {
|
||||||
|
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
|
||||||
|
clockgen.Configure()
|
||||||
|
|
||||||
|
// Now configue the PLLs and clock outputs.
|
||||||
|
// The PLLs can be configured with a multiplier and division of the on-board
|
||||||
|
// 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")
|
||||||
|
|
||||||
|
// Next configure clock 1 to be driven by PLL B divided by 45.5 to get
|
||||||
|
// 13.5531mhz (616.6667mhz / 45.5). This uses fractional division and again
|
||||||
|
// notice the numerator and denominator are explicitly specified. This is less
|
||||||
|
// 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")
|
||||||
|
|
||||||
|
// Finally configure clock 2 to be driven by PLL B divided once by 900 to get
|
||||||
|
// down to 685.15 khz and then further divided by a special R divider that
|
||||||
|
// 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")
|
||||||
|
|
||||||
|
// After configuring PLLs and clocks, enable the outputs.
|
||||||
|
clockgen.EnableOutputs()
|
||||||
|
|
||||||
|
for {
|
||||||
|
time.Sleep(5 * time.Second)
|
||||||
|
println()
|
||||||
|
println("Clock 0: 112.5mhz")
|
||||||
|
println("Clock 1: 13.5531mhz")
|
||||||
|
println("Clock 2: 10.706khz")
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,64 @@
|
|||||||
|
package si5351
|
||||||
|
|
||||||
|
// The I2C address which this device listens to.
|
||||||
|
const AddressDefault = 0x60 // Assumes ADDR pin is low
|
||||||
|
const AddressAlternative = 0x61 // Assumes ADDR pin is high
|
||||||
|
|
||||||
|
const (
|
||||||
|
OUTPUT_ENABLE_CONTROL = 3
|
||||||
|
|
||||||
|
CLK0_CONTROL = 16
|
||||||
|
CLK1_CONTROL = 17
|
||||||
|
CLK2_CONTROL = 18
|
||||||
|
CLK3_CONTROL = 19
|
||||||
|
CLK4_CONTROL = 20
|
||||||
|
CLK5_CONTROL = 21
|
||||||
|
CLK6_CONTROL = 22
|
||||||
|
CLK7_CONTROL = 23
|
||||||
|
|
||||||
|
MULTISYNTH0_PARAMETERS_1 = 42
|
||||||
|
MULTISYNTH0_PARAMETERS_3 = 44
|
||||||
|
MULTISYNTH1_PARAMETERS_1 = 50
|
||||||
|
MULTISYNTH1_PARAMETERS_3 = 52
|
||||||
|
MULTISYNTH2_PARAMETERS_1 = 58
|
||||||
|
MULTISYNTH2_PARAMETERS_3 = 60
|
||||||
|
|
||||||
|
SPREAD_SPECTRUM_PARAMETERS = 149
|
||||||
|
|
||||||
|
PLL_RESET = 177
|
||||||
|
|
||||||
|
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
|
||||||
|
)
|
||||||
@@ -0,0 +1,448 @@
|
|||||||
|
package si5351
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/binary"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"math"
|
||||||
|
|
||||||
|
"tinygo.org/x/drivers"
|
||||||
|
"tinygo.org/x/drivers/internal/regmap"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Device wraps an I2C connection to a SI5351 device.
|
||||||
|
type Device struct {
|
||||||
|
bus drivers.I2C
|
||||||
|
Address uint8
|
||||||
|
|
||||||
|
rw regmap.Device8I2C
|
||||||
|
initialised bool
|
||||||
|
crystalFreq uint32
|
||||||
|
crystalLoad uint8
|
||||||
|
pllaConfigured bool
|
||||||
|
pllaFreq uint32
|
||||||
|
pllbConfigured bool
|
||||||
|
pllbFreq uint32
|
||||||
|
lastRdivValue [3]uint8
|
||||||
|
}
|
||||||
|
|
||||||
|
var ErrNotInitialised = errors.New("Si5351 not initialised")
|
||||||
|
var ErrInvalidParameter = errors.New("Si5351 invalid parameter")
|
||||||
|
|
||||||
|
// New creates a new SI5351 connection. The I2C bus must already be configured.
|
||||||
|
//
|
||||||
|
// This function only creates the Device object, it does not touch the device.
|
||||||
|
func New(bus drivers.I2C) Device {
|
||||||
|
rw := regmap.Device8I2C{}
|
||||||
|
rw.SetBus(bus, AddressDefault, binary.BigEndian)
|
||||||
|
|
||||||
|
return Device{
|
||||||
|
bus: bus,
|
||||||
|
rw: rw,
|
||||||
|
Address: AddressDefault,
|
||||||
|
crystalFreq: CRYSTAL_FREQ_25MHZ,
|
||||||
|
crystalLoad: CRYSTAL_LOAD_10PF,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Configure sets up the device for communication
|
||||||
|
// TODO error handling
|
||||||
|
func (d *Device) Configure() error {
|
||||||
|
// // Disable all outputs setting CLKx_DIS high
|
||||||
|
d.rw.Write8(OUTPUT_ENABLE_CONTROL, 0xFF)
|
||||||
|
|
||||||
|
// Set the load capacitance for the XTAL
|
||||||
|
d.rw.Write8(CRYSTAL_INTERNAL_LOAD_CAPACITANCE, d.crystalLoad)
|
||||||
|
|
||||||
|
// Power down all output drivers
|
||||||
|
buf := []byte{CLK0_CONTROL, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80}
|
||||||
|
d.bus.Tx(uint16(d.Address), buf, nil)
|
||||||
|
|
||||||
|
// Disable spread spectrum output.
|
||||||
|
if err := d.DisableSpreadSpectrum(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
d.initialised = true
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Connected returns whether a device at SI5351 address has been found.
|
||||||
|
func (d *Device) Connected() (bool, error) {
|
||||||
|
if err := d.bus.Tx(uint16(d.Address), []byte{}, []byte{0}); err != nil {
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
return true, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// EnableSpreadSpectrum enables spread spectrum modulation to reduce EMI.
|
||||||
|
func (d *Device) EnableSpreadSpectrum() error {
|
||||||
|
data, err := d.rw.Read8(SPREAD_SPECTRUM_PARAMETERS)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
data |= 0x80
|
||||||
|
return d.rw.Write8(SPREAD_SPECTRUM_PARAMETERS, data)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *Device) DisableSpreadSpectrum() error {
|
||||||
|
data, err := d.rw.Read8(SPREAD_SPECTRUM_PARAMETERS)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
data &^= 0x80
|
||||||
|
return d.rw.Write8(SPREAD_SPECTRUM_PARAMETERS, data)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *Device) OutputEnable(output uint8, enable bool) error {
|
||||||
|
if !d.initialised {
|
||||||
|
return ErrNotInitialised
|
||||||
|
}
|
||||||
|
|
||||||
|
// Read the current value of the OUTPUT_ENABLE_CONTROL register
|
||||||
|
regVal, err := d.rw.Read8(OUTPUT_ENABLE_CONTROL)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Modify regVal based on clk and enable
|
||||||
|
if enable {
|
||||||
|
regVal &= ^(1 << output)
|
||||||
|
} else {
|
||||||
|
regVal |= (1 << output)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Write the modified value back to the OUTPUT_ENABLE_CONTROL register
|
||||||
|
return d.rw.Write8(OUTPUT_ENABLE_CONTROL, regVal)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *Device) EnableOutputs() error {
|
||||||
|
if !d.initialised {
|
||||||
|
return ErrNotInitialised
|
||||||
|
}
|
||||||
|
|
||||||
|
return d.rw.Write8(OUTPUT_ENABLE_CONTROL, 0x00)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *Device) DisableOutputs() error {
|
||||||
|
if !d.initialised {
|
||||||
|
return ErrNotInitialised
|
||||||
|
}
|
||||||
|
return d.rw.Write8(OUTPUT_ENABLE_CONTROL, 0xFF)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ConfigurePLL sets the multiplier for the specified PLL
|
||||||
|
// pll The PLL to configure, which must be one of the following:
|
||||||
|
// - PLL_A
|
||||||
|
// - PLL_B
|
||||||
|
//
|
||||||
|
// mult The PLL integer multiplier (must be between 15 and 90)
|
||||||
|
//
|
||||||
|
// num The 20-bit numerator for fractional output (0..1,048,575).
|
||||||
|
// Set this to '0' for integer output.
|
||||||
|
//
|
||||||
|
// denom The 20-bit denominator for fractional output (1..1,048,575).
|
||||||
|
// Set this to '1' or higher to avoid divider by zero errors.
|
||||||
|
//
|
||||||
|
// PLL Configuration
|
||||||
|
// fVCO is the PLL output, and must be between 600..900MHz, where:
|
||||||
|
//
|
||||||
|
// fVCO = fXTAL * (a+(b/c))
|
||||||
|
//
|
||||||
|
// fXTAL = the crystal input frequency
|
||||||
|
// a = an integer between 15 and 90
|
||||||
|
// b = the fractional numerator (0..1,048,575)
|
||||||
|
// c = the fractional denominator (1..1,048,575)
|
||||||
|
//
|
||||||
|
// NOTE: Try to use integers whenever possible to avoid clock jitter
|
||||||
|
// (only use the a part, setting b to '0' and c to '1').
|
||||||
|
//
|
||||||
|
// See: http://www.silabs.com/Support%20Documents/TechnicalDocs/AN619.pdf
|
||||||
|
func (d *Device) ConfigurePLL(pll uint8, mult uint8, num uint32, denom uint32) error {
|
||||||
|
// Basic validation
|
||||||
|
if !d.initialised {
|
||||||
|
return ErrNotInitialised
|
||||||
|
}
|
||||||
|
// mult = 15..90
|
||||||
|
if !((mult > 14) && (mult < 91)) {
|
||||||
|
return ErrInvalidParameter
|
||||||
|
}
|
||||||
|
// Avoid divide by zero
|
||||||
|
if !(denom > 0) {
|
||||||
|
return ErrInvalidParameter
|
||||||
|
}
|
||||||
|
// 20-bit limit
|
||||||
|
if !(num <= 0xFFFFF) {
|
||||||
|
return ErrInvalidParameter
|
||||||
|
}
|
||||||
|
// 20-bit limit
|
||||||
|
if !(denom <= 0xFFFFF) {
|
||||||
|
return ErrInvalidParameter
|
||||||
|
}
|
||||||
|
|
||||||
|
// PLL Multiplier Equations
|
||||||
|
//
|
||||||
|
// P1 register is an 18-bit value using following formula:
|
||||||
|
//
|
||||||
|
// P1[17:0] = 128 * mult + floor(128*(num/denom)) - 512
|
||||||
|
//
|
||||||
|
// P2 register is a 20-bit value using the following formula:
|
||||||
|
//
|
||||||
|
// P2[19:0] = 128 * num - denom * floor(128*(num/denom))
|
||||||
|
//
|
||||||
|
// P3 register is a 20-bit value using the following formula:
|
||||||
|
//
|
||||||
|
// P3[19:0] = denom
|
||||||
|
//
|
||||||
|
|
||||||
|
// Set PLL config registers
|
||||||
|
var p1, p2, p3 uint32
|
||||||
|
if num == 0 {
|
||||||
|
// Integer mode
|
||||||
|
p1 = 128*uint32(mult) - 512
|
||||||
|
p2 = num
|
||||||
|
p3 = denom
|
||||||
|
} else {
|
||||||
|
// Fractional mode
|
||||||
|
p1 = uint32(128*float64(mult) + math.Floor(128*(float64(num)/float64(denom))) - 512)
|
||||||
|
p2 = uint32(128*float64(num) - float64(denom)*math.Floor(128*(float64(num)/float64(denom))))
|
||||||
|
p3 = denom
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get the appropriate starting point for the PLL registers
|
||||||
|
baseaddr := uint8(26)
|
||||||
|
if pll == PLL_B {
|
||||||
|
baseaddr = 34
|
||||||
|
}
|
||||||
|
|
||||||
|
// The datasheet is a nightmare of typos and inconsistencies here!
|
||||||
|
data := [8]byte{}
|
||||||
|
data[0] = uint8((p3 & 0x0000FF00) >> 8)
|
||||||
|
data[1] = uint8(p3 & 0x000000FF)
|
||||||
|
data[2] = uint8((p1 & 0x00030000) >> 16)
|
||||||
|
data[3] = uint8((p1 & 0x0000FF00) >> 8)
|
||||||
|
data[4] = uint8(p1 & 0x000000FF)
|
||||||
|
data[5] = uint8(((p3 & 0x000F0000) >> 12) | ((p2 & 0x000F0000) >> 16))
|
||||||
|
data[6] = uint8((p2 & 0x0000FF00) >> 8)
|
||||||
|
data[7] = uint8(p2 & 0x000000FF)
|
||||||
|
if err := d.bus.Tx(uint16(baseaddr), data[:], nil); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Reset both PLLs
|
||||||
|
if err := d.rw.Write8(PLL_RESET, (1<<7)|(1<<5)); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Store the frequency settings for use with the Multisynth helper
|
||||||
|
fvco := float64(d.crystalFreq) * (float64(mult) + (float64(num) / float64(denom)))
|
||||||
|
if pll == PLL_A {
|
||||||
|
d.pllaConfigured = true
|
||||||
|
d.pllaFreq = uint32(math.Floor(fvco))
|
||||||
|
} else {
|
||||||
|
d.pllbConfigured = true
|
||||||
|
d.pllbFreq = uint32(math.Floor(fvco))
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// ConfigureMultisynth divider, which determines the
|
||||||
|
// output clock frequency based on the specified PLL input.
|
||||||
|
//
|
||||||
|
// output The output channel to use (0..2)
|
||||||
|
//
|
||||||
|
// pll The PLL input source to use, which must be one of:
|
||||||
|
// - PLL_A
|
||||||
|
// - PLL_B
|
||||||
|
//
|
||||||
|
// div The integer divider for the Multisynth output.
|
||||||
|
//
|
||||||
|
// If pure integer values are used, this value must be one of:
|
||||||
|
// - MULTISYNTH_DIV_4
|
||||||
|
// - MULTISYNTH_DIV_6
|
||||||
|
// - MULTISYNTH_DIV_8
|
||||||
|
// If fractional output is used, this value must be between 8 and 900.
|
||||||
|
//
|
||||||
|
// num The 20-bit numerator for fractional output (0..1,048,575).
|
||||||
|
//
|
||||||
|
// Set this to '0' for integer output.
|
||||||
|
//
|
||||||
|
// denom The 20-bit denominator for fractional output (1..1,048,575).
|
||||||
|
//
|
||||||
|
// Set this to '1' or higher to avoid divide by zero errors.
|
||||||
|
//
|
||||||
|
// # Output Clock Configuration
|
||||||
|
//
|
||||||
|
// The multisynth dividers are applied to the specified PLL output,
|
||||||
|
// and are used to reduce the PLL output to a valid range (500kHz
|
||||||
|
// to 160MHz). The relationship can be seen in this formula, where
|
||||||
|
// fVCO is the PLL output frequency and MSx is the multisynth divider:
|
||||||
|
//
|
||||||
|
// fOUT = fVCO / MSx
|
||||||
|
//
|
||||||
|
// Valid multisynth dividers are 4, 6, or 8 when using integers,
|
||||||
|
// or any fractional values between 8 + 1/1,048,575 and 900 + 0/1
|
||||||
|
// The following formula is used for the fractional mode divider:
|
||||||
|
//
|
||||||
|
// a + b / c
|
||||||
|
//
|
||||||
|
// a = The integer value, which must be 4, 6 or 8 in integer mode (MSx_INT=1) or 8..900 in fractional mode (MSx_INT=0).
|
||||||
|
// b = The fractional numerator (0..1,048,575)
|
||||||
|
// c = The fractional denominator (1..1,048,575)
|
||||||
|
//
|
||||||
|
// NOTE: Try to use integers whenever possible to avoid clock jitter
|
||||||
|
// NOTE: For output frequencies > 150MHz, you must set the divider
|
||||||
|
//
|
||||||
|
// to 4 and adjust to PLL to generate the frequency (for example
|
||||||
|
// a PLL of 640 to generate a 160MHz output clock). This is not
|
||||||
|
// yet supported in the driver, which limits frequencies to 500kHz .. 150MHz.
|
||||||
|
//
|
||||||
|
// NOTE: For frequencies below 500kHz (down to 8kHz) Rx_DIV must be
|
||||||
|
//
|
||||||
|
// used, but this isn't currently implemented in the driver.
|
||||||
|
func (d *Device) ConfigureMultisynth(output uint8, pll uint8, div uint32, num uint32, denom uint32) error {
|
||||||
|
// Basic validation
|
||||||
|
if !d.initialised {
|
||||||
|
return ErrNotInitialised
|
||||||
|
}
|
||||||
|
// Channel range
|
||||||
|
if !(output < 3) {
|
||||||
|
return fmt.Errorf("output channel must be between 0 and 2")
|
||||||
|
}
|
||||||
|
// Divider integer value
|
||||||
|
if !((div > 3) && (div < 2049)) {
|
||||||
|
return ErrInvalidParameter
|
||||||
|
}
|
||||||
|
// Avoid divide by zero
|
||||||
|
if !(denom > 0) {
|
||||||
|
return ErrInvalidParameter
|
||||||
|
}
|
||||||
|
// 20-bit limit
|
||||||
|
if !(num <= 0xFFFFF) {
|
||||||
|
return ErrInvalidParameter
|
||||||
|
}
|
||||||
|
// 20-bit limit
|
||||||
|
if !(denom <= 0xFFFFF) {
|
||||||
|
return ErrInvalidParameter
|
||||||
|
}
|
||||||
|
|
||||||
|
// Make sure the requested PLL has been initialised
|
||||||
|
if pll == PLL_A && !d.pllaConfigured {
|
||||||
|
return ErrInvalidParameter
|
||||||
|
}
|
||||||
|
if pll == PLL_B && !d.pllbConfigured {
|
||||||
|
return ErrInvalidParameter
|
||||||
|
}
|
||||||
|
|
||||||
|
// Output Multisynth Divider Equations
|
||||||
|
//
|
||||||
|
// where: a = div, b = num and c = denom
|
||||||
|
//
|
||||||
|
// P1 register is an 18-bit value using following formula:
|
||||||
|
//
|
||||||
|
// P1[17:0] = 128 * a + floor(128*(b/c)) - 512
|
||||||
|
//
|
||||||
|
// P2 register is a 20-bit value using the following formula:
|
||||||
|
//
|
||||||
|
// P2[19:0] = 128 * b - c * floor(128*(b/c))
|
||||||
|
//
|
||||||
|
// P3 register is a 20-bit value using the following formula:
|
||||||
|
//
|
||||||
|
// P3[19:0] = c
|
||||||
|
//
|
||||||
|
|
||||||
|
// Set PLL config registers
|
||||||
|
var p1, p2, p3 uint32
|
||||||
|
if num == 0 {
|
||||||
|
// Integer mode
|
||||||
|
p1 = 128*div - 512
|
||||||
|
p2 = 0
|
||||||
|
p3 = denom
|
||||||
|
} else if denom == 1 {
|
||||||
|
// Fractional mode, simplified calculations
|
||||||
|
p1 = 128*div + 128*num - 512
|
||||||
|
p2 = 128*num - 128
|
||||||
|
p3 = 1
|
||||||
|
} else {
|
||||||
|
// Fractional mode
|
||||||
|
p1 = uint32(128*float64(div) + math.Floor(128*(float64(num)/float64(denom))) - 512)
|
||||||
|
p2 = uint32(128*float64(num) - float64(denom)*math.Floor(128*(float64(num)/float64(denom))))
|
||||||
|
p3 = denom
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get the appropriate starting point for the PLL registers
|
||||||
|
baseaddr := uint8(0)
|
||||||
|
switch output {
|
||||||
|
case 0:
|
||||||
|
baseaddr = MULTISYNTH0_PARAMETERS_1
|
||||||
|
case 1:
|
||||||
|
baseaddr = MULTISYNTH1_PARAMETERS_1
|
||||||
|
case 2:
|
||||||
|
baseaddr = MULTISYNTH2_PARAMETERS_1
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set the MSx config registers
|
||||||
|
data := [8]byte{}
|
||||||
|
data[0] = uint8((p3 & 0xFF00) >> 8)
|
||||||
|
data[1] = uint8(p3 & 0xFF)
|
||||||
|
data[2] = uint8(((p1 & 0x30000) >> 16)) | d.lastRdivValue[output]
|
||||||
|
data[3] = uint8((p1 & 0xFF00) >> 8)
|
||||||
|
data[4] = uint8(p1 & 0xFF)
|
||||||
|
data[5] = uint8(((p3 & 0xF0000) >> 12) | ((p2 & 0xF0000) >> 16))
|
||||||
|
data[6] = uint8((p2 & 0xFF00) >> 8)
|
||||||
|
data[7] = uint8(p2 & 0xFF)
|
||||||
|
if err := d.bus.Tx(uint16(baseaddr), data[:], nil); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Configure the clk control and enable the output
|
||||||
|
// TODO: Check if the clk control byte needs to be updated.
|
||||||
|
clkControlReg := uint8(0x0F) // 8mA drive strength, MS0 as CLK0 source, Clock not inverted, powered up
|
||||||
|
if pll == PLL_B {
|
||||||
|
clkControlReg |= (1 << 5) // Uses PLLB
|
||||||
|
}
|
||||||
|
if num == 0 {
|
||||||
|
clkControlReg |= (1 << 6) // Integer mode
|
||||||
|
}
|
||||||
|
|
||||||
|
var register uint8
|
||||||
|
switch output {
|
||||||
|
case 0:
|
||||||
|
register = CLK0_CONTROL
|
||||||
|
case 1:
|
||||||
|
register = CLK1_CONTROL
|
||||||
|
case 2:
|
||||||
|
register = CLK2_CONTROL
|
||||||
|
}
|
||||||
|
|
||||||
|
return d.rw.Write8(register, clkControlReg)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *Device) ConfigureRdiv(output uint8, div uint8) error {
|
||||||
|
// Channel range
|
||||||
|
if !(output < 3) {
|
||||||
|
return ErrInvalidParameter
|
||||||
|
}
|
||||||
|
|
||||||
|
var register uint8
|
||||||
|
switch output {
|
||||||
|
case 0:
|
||||||
|
register = MULTISYNTH0_PARAMETERS_3
|
||||||
|
case 1:
|
||||||
|
register = MULTISYNTH1_PARAMETERS_3
|
||||||
|
case 2:
|
||||||
|
register = MULTISYNTH2_PARAMETERS_3
|
||||||
|
}
|
||||||
|
|
||||||
|
data, err := d.rw.Read8(register)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
d.lastRdivValue[output] = (div & 0x07) << 4
|
||||||
|
data = (data & 0x0F) | d.lastRdivValue[output]
|
||||||
|
return d.rw.Write8(register, data)
|
||||||
|
}
|
||||||
@@ -144,6 +144,7 @@ tinygo build -size short -o ./build/test.hex -target=pico ./examples/tmc5160/mai
|
|||||||
tinygo build -size short -o ./build/test.uf2 -target=nicenano ./examples/sharpmem/main.go
|
tinygo build -size short -o ./build/test.uf2 -target=nicenano ./examples/sharpmem/main.go
|
||||||
tinygo build -size short -o ./build/test.hex -target=feather-nrf52840 ./examples/max6675/main.go
|
tinygo build -size short -o ./build/test.hex -target=feather-nrf52840 ./examples/max6675/main.go
|
||||||
tinygo build -size short -o ./build/test.hex -target=pico ./examples/ens160/main.go
|
tinygo build -size short -o ./build/test.hex -target=pico ./examples/ens160/main.go
|
||||||
|
tinygo build -size short -o ./build/test.hex -target=pico ./examples/si5351/main.go
|
||||||
# network examples (espat)
|
# network examples (espat)
|
||||||
tinygo build -size short -o ./build/test.hex -target=challenger-rp2040 ./examples/net/ntpclient/
|
tinygo build -size short -o ./build/test.hex -target=challenger-rp2040 ./examples/net/ntpclient/
|
||||||
# network examples (wifinina)
|
# network examples (wifinina)
|
||||||
|
|||||||
Reference in New Issue
Block a user