Skip to content

v2.4.1: bug fixes (LOS, SSC, CLK6/7, PLL recalc, I2C errors) - #110

Open
NT7S wants to merge 22 commits into
masterfrom
v2.4.1-fixes
Open

NT7S wants to merge 22 commits into
masterfrom
v2.4.1-fixes

Conversation

@NT7S

@NT7S NT7S commented Oct 21, 2026

Copy link
Copy Markdown
Member

Si5351Arduino v2.4.1: bug fixes rebased onto v2.4.0.

Changes

See the v2.4.1 changelog entry in README.md. Highlights:

Version bumped to 2.4.1 in library.properties and library.json.

Verification

  • Test Lab regression harness: PASS (two rounds)
  • Independent fresh-reviewer code review of full diff: PASS, 0 blocking, 12 non-blocking (documented in changelog notes / follow-ups)
  • arduino-cli compile, si5351_example on Uno: OK, 12,976 B flash
  • Hardware tests on Si5351B/C boards: pending board arrival

Known follow-ups (non-blocking)

  • *_MAX_FREQ macros still say 225 MHz
  • set_freq can return success without programming a PLL (e.g. invalid ref)
  • set_freq_manual doesn't reject a non-working divide-by-4 case

Closes #109

NT7S added 22 commits October 20, 2026 15:33
AN619 only allows even integer MS6/MS7 ratios from 6 to 254. Several
CLK6/CLK7 paths could write something else:

- set_freq() below ~18.5 kHz clamped to 4 kHz (SI5351_CLKOUT_MIN_FREQ)
  instead of the CLK6/7 minimum, which ended with a ratio of 256 written
  to reg 90/91 as 0x00 and pllb_freq stored as 128 MHz (DEV-03). Clamp to
  600 MHz / 254 / 128 (rounded up) and pick the smallest R divider that
  keeps the ratio <= 254.
- set_freq_manual() on CLK6/CLK7 used the fractional MS0-5 math and wrote
  the low byte of P1 (87.4 MHz from 874 MHz wrote 0x00 instead of 10,
  DEV-15). It now writes the integer ratio, using R if needed, and
  returns 1 without writing anything if the chosen PLL has no valid even
  integer ratio for that frequency.
- With the peer clock set, set_freq() checked the ratio before applying
  R and then wrote an uninitialised byte to reg 90/91 when the divider
  calculation failed. The ratio/R search is now done first and the call
  returns 1 without writes if no even integer ratio 6..254 exists. Odd
  ratios after R are refused too.
- set_freq(0) on CLK6/CLK7 divided by zero with the peer set; it now
  returns 1 (there is no valid setting for 0 Hz).
set_freq() enables CLK0-CLK5 (reg 3) the first time each is set, but
the CLK6/CLK7 branch never called output_enable(), so after reset()
those outputs stayed disabled until the sketch enabled them (DEV-14).
Do the same first-set enable for CLK6/CLK7 once the frequency has been
accepted. No power-up write is needed: reset() already writes 0x0C to
reg 22/23, as it does for CLK0-CLK5.
set_freq(), set_freq_manual() and multisynth_calc() clamped CLK0-CLK5 at
SI5351_MULTISYNTH_MAX_FREQ / SI5351_CLKOUT_MAX_FREQ = 225 MHz, but the
datasheet (Table 7) and AN619 (sec. 2, 4.1.3: DIVBY4 for 150 < Fout <=
200 MHz) give 200 MHz as the maximum (DEV-01). Requests above 200 MHz
are now clamped to 200 MHz (DIVBY4, fVCO = 800 MHz) instead of up to
225 MHz (fVCO = 900 MHz). For example, set_freq(210 MHz) now gives
200 MHz.

The public 225 MHz #defines are left unchanged so existing sketches
that reference them still compile.
When pll_freq / freq is below 6 (for example set_freq_manual(140 MHz,
700 MHz)), multisynth_calc() clamps the frequency to pll_freq / 6 but
kept the old integer part, so it wrote a ratio of 5, which AN619 does
not allow (DEV-05). Set a = 6 together with the clamped frequency.

The other out-of-range manual ratios (fractional 6..8, integer 7) are
not changed here.
set_phase(CLK6/CLK7) wrote reg 171/172, which are reserved: only CLK0-5
have a phase offset register (165-170, AN619 sec. 6) (DEV-09).

set_int(CLK6/CLK7) toggled bit 6 of reg 22/23, which is FBA_INT/FBB_INT,
the PLLA/PLLB feedback integer-mode bit, not an MS6/7 bit. Setting it
on a fractional PLL forces the PLL into integer mode (DEV-11). MS6/MS7
are always integer dividers and have no MSx_INT bit, so there is
nothing for set_int() to do on those clocks.
set_pll_input(..., SI5351_PLL_INPUT_CLKIN) ORed clkin_div into reg 15
without clearing CLKIN_DIV[1:0] first (the clearing line was commented
out), so a /2 setting followed by /4 left 11b = /8 (DEV-12). Replace the
field when a PLL is switched to CLKIN. Switching a PLL to XO leaves the
field alone, because the other PLL may still be using CLKIN.
init(SI5351_CRYSTAL_LOAD_0PF, ...) wrote XTAL_CL[1:0] = 00b to reg 183,
which AN619 marks "Reserved. Do not select this option." (DEV-10). The
chip has no 0 pF setting. Use the smallest valid internal load, 6 pF
(01b), instead. The 6/8/10 pF settings are unchanged.
With the VCXO in use, AN619 (sec. 3.2, 7) requires the PLLB feedback
denominator c to stay at 10^6. set_vcxo() set that up, but did not
record that the VCXO was active or update pllb_freq. set_correction(),
set_pll_input() and set_freq_manual()/set_freq() on PLLB later re-ran
the normal PLL math with the stale pllb_freq (800 MHz). That dropped
c = 10^6 (c = 1 when b = 0) and moved PLLB away from the VCXO frequency
(DEV-08).

set_vcxo() now records the pull range (new private member vcxo_pull)
and pllb_freq. While it is set, set_pll(..., SI5351_PLLB) goes through
set_vcxo(), so PLLB keeps c = 10^6 and VCXO_Param is recalculated for
the new correction. reset() clears it, as it already cleared the VCXO
registers. Public API unchanged.
In DIVBY4 mode the output is fVCO / 4 whatever frequency is requested,
so set_freq_manual(225 MHz, 900 MHz) still gave 225 MHz after the 200 MHz
clamp. Limit pll_freq to 800 MHz for DIVBY4 outputs so the output stays
within the datasheet maximum.

Also refuse (return 1, no writes) set_freq_manual() requests on CLK0-5
whose MS ratio is not one AN619 allows (4 via DIVBY4, 6, or 8 and up),
e.g. 90 MHz from 600 MHz (6.667) or 100 MHz from 700 MHz (7). The check
runs before the PLL write.
0 pF is not a supported XTAL_CL setting. Leave the crystal load at the
chip's reset default (10 pF, 11b) rather than picking 6 pF, and document
SI5351_CRYSTAL_LOAD_0PF as unsupported.
AN619 limits the feedback ratio to 15..90 and the VCO to 600..900 MHz.
pll_calc() clamped the frequency for out-of-range ratios but kept the old
integer part, so a 5 MHz reference wrote feedback ratios of 120 or 160
(DEV-13). Recompute a when it is clamped. If the reference cannot reach
the VCO range with a legal ratio at all (below about 6.7 MHz or above
60 MHz after CLKIN_DIV), set_pll() and set_vcxo() now leave the PLL
registers untouched instead of writing an illegal block. In-spec
references (10-40 MHz) are not affected.

Also mark SI5351_CRYSTAL_LOAD_0PF as unsupported in the header.
When a PLL was reprogrammed to a new frequency, outputs already running
from it kept dividers computed for the old PLL frequency and drifted off
frequency. Only set_freq()'s >100 MHz / re-plan path recalculated them,
and only for CLK0-CLK5. Examples: CLK1 at 10 MHz on PLLB, then
set_freq() on CLK6 re-plans PLLB to 874 MHz -> CLK1 at 10.925 MHz;
set_pll(700 MHz) under a 10 MHz CLK0 -> 8.75 MHz; set_vcxo() with
another output on PLLB.

The existing recalc loop is moved into a private helper, pll_update(),
which every PLL write now goes through (set_pll(), set_vcxo(), and so
everything that calls set_pll(): set_freq re-plans, the >100 MHz path,
the low-clock move, CLK6/7 PLLB planning, set_freq_manual(), init(),
set_correction(), set_pll_input()). It stores the new PLL frequency and,
if that changed, rewrites the MS (R, DIVBY4, integer bits) of every other
output on that PLL whose frequency the library knows (clk_freq[] != 0),
including CLK6/CLK7 (even-integer MS6/7 plus R, as in set_freq) and
outputs set with set_freq_manual().

- Only recalculates when the stored PLL frequency changes, so steady-
  state tuning, set_correction() and set_pll_input() (same nominal VCO)
  add no I2C writes. set_freq()'s re-plan path forces the recalc as
  before.
- Outputs with no valid ratio on the new PLL (AN619: 4 via DIVBY4 at
  exactly PLL/4, 6 exact, 8+; MS6/7 even 6..254 with R) are left as they
  are.
- A PLL frequency outside the 600-900 MHz VCO range (pll_calc() clamps
  it, so the stored value is not what the chip runs at) does not trigger
  a recalc.
- Still one PLL reset per operation, after all PLL and MS writes:
  DIVBY4 exits during the recalc are deferred (34c486c semantics) and
  set_freq_manual() / the CLK6/7 planning path issue the owed reset
  after their own MS write.
- reset() clears clk_freq[] before reprogramming the PLLs, so it does
  not recalculate outputs it is about to turn off.
- Moving an output with set_ms_source() or writing set_ms() directly
  still does not recalculate (no PLL frequency change).

No public API change. New private member pll_update(); file-statics
reset_owed, recalc_skip, recalc_force.
@github-actions

Copy link
Copy Markdown

Memory usage change @ 1c7199f

Board flash % RAM for global variables %
arduino:avr:uno 🔺 +1120 - +3176 +3.47 - +9.85 🔺 +8 - +16 +0.39 - +0.78
esp32:esp32:esp32 🔺 +1132 - +1508 +0.09 - +0.12 🔺 +8 - +8 0.0 - 0.0
arduino:samd:mkrzero 🔺 +1108 - +1340 +0.42 - +0.51 🔺 0 - +16 0.0 - +0.05
rp2040:rp2040:rpipico 🔺 +1048 - +1288 +0.05 - +0.06 0 - 0 0.0 - 0.0
Click for full report table
Board examples/si5351_calibration
flash
% examples/si5351_calibration
RAM for global variables
% examples/si5351_clk67_example
flash
% examples/si5351_clk67_example
RAM for global variables
% examples/si5351_example
flash
% examples/si5351_example
RAM for global variables
% examples/si5351_ext_ref
flash
% examples/si5351_ext_ref
RAM for global variables
% examples/si5351_outputs
flash
% examples/si5351_outputs
RAM for global variables
% examples/si5351_phase
flash
% examples/si5351_phase
RAM for global variables
% examples/si5351_sweeper
flash
% examples/si5351_sweeper
RAM for global variables
% examples/si5351_vcxo
flash
% examples/si5351_vcxo
RAM for global variables
%
arduino:avr:uno 1808 5.61 8 0.39 1120 3.47 8 0.39 1848 5.73 8 0.39 1524 4.72 16 0.78 1804 5.59 10 0.49 2958 9.17 10 0.49 1762 5.46 8 0.39 3176 9.85 8 0.39
esp32:esp32:esp32 1272 0.1 8 0.0 1272 0.1 8 0.0 1508 0.12 8 0.0 1296 0.1 8 0.0 1272 0.1 8 0.0 1412 0.11 8 0.0 1256 0.1 8 0.0 1132 0.09 8 0.0
arduino:samd:mkrzero 1124 0.43 0 0.0 1140 0.43 16 0.05 1340 0.51 16 0.05 1164 0.44 16 0.05 1136 0.43 16 0.05 1284 0.49 16 0.05 1120 0.43 16 0.05 1108 0.42 16 0.05
rp2040:rp2040:rpipico 1080 0.05 0 0.0 1080 0.05 0 0.0 1288 0.06 0 0.0 1096 0.05 0 0.0 1072 0.05 0 0.0 1256 0.06 0 0.0 1056 0.05 0 0.0 1048 0.05 0 0.0
Click for full report CSV
Board,examples/si5351_calibration<br>flash,%,examples/si5351_calibration<br>RAM for global variables,%,examples/si5351_clk67_example<br>flash,%,examples/si5351_clk67_example<br>RAM for global variables,%,examples/si5351_example<br>flash,%,examples/si5351_example<br>RAM for global variables,%,examples/si5351_ext_ref<br>flash,%,examples/si5351_ext_ref<br>RAM for global variables,%,examples/si5351_outputs<br>flash,%,examples/si5351_outputs<br>RAM for global variables,%,examples/si5351_phase<br>flash,%,examples/si5351_phase<br>RAM for global variables,%,examples/si5351_sweeper<br>flash,%,examples/si5351_sweeper<br>RAM for global variables,%,examples/si5351_vcxo<br>flash,%,examples/si5351_vcxo<br>RAM for global variables,%
arduino:avr:uno,1808,5.61,8,0.39,1120,3.47,8,0.39,1848,5.73,8,0.39,1524,4.72,16,0.78,1804,5.59,10,0.49,2958,9.17,10,0.49,1762,5.46,8,0.39,3176,9.85,8,0.39
esp32:esp32:esp32,1272,0.1,8,0.0,1272,0.1,8,0.0,1508,0.12,8,0.0,1296,0.1,8,0.0,1272,0.1,8,0.0,1412,0.11,8,0.0,1256,0.1,8,0.0,1132,0.09,8,0.0
arduino:samd:mkrzero,1124,0.43,0,0.0,1140,0.43,16,0.05,1340,0.51,16,0.05,1164,0.44,16,0.05,1136,0.43,16,0.05,1284,0.49,16,0.05,1120,0.43,16,0.05,1108,0.42,16,0.05
rp2040:rp2040:rpipico,1080,0.05,0,0.0,1080,0.05,0,0.0,1288,0.06,0,0.0,1096,0.05,0,0.0,1072,0.05,0,0.0,1256,0.06,0,0.0,1056,0.05,0,0.0,1048,0.05,0,0.0

NT7S commented Oct 28, 2026

Copy link
Copy Markdown
Member Author

Hardware verification, Si5351C (2026-10-27): all 12 steps of the v2.4.1 hardware test sketch passed on the Si5351C breakout (head 1c7199f), including step 12, the 10 MHz external clock on CLKIN (J3 on 2-3). The Si5351B board run is pending a chip swap.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

SAMD -Wtype-limits warnings in set_clock_disable (si5351.cpp:972, 984)

1 participant