Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
33 changes: 32 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -321,6 +321,35 @@ Measured flash on an Arduino Uno (arduino-cli 1.5.1, arduino:avr 1.8.6), in byte
| `init()` + one `set_freq()`, runtime clock index | 10,868 | 9,870 | 9,836 |
| `init()` + one `set_freq()` on CLK0 | 10,140 | 9,778 | 9,740 |

Leaving out the VCXO
--------------------
If your sketch never uses the Si5351B VCXO, you can leave that code out and save about 440 bytes of flash on an AVR board (472 bytes if you pass the macro as a compiler flag). In the Arduino IDE, put the define above the include in your sketch's .ino file:

```cpp
#define SI5351_NO_VCXO
#include <si5351.h>
```

Put it in the .ino file only. If more than one file in the sketch defines it before including `si5351.h`, the link fails with a duplicate definition. The define has to come before the `#include`. It can sit next to `SI5351_NO_CLK67`.

With the opt-out, `set_vcxo()` returns 1 and writes nothing. It does not store a pull range, and later PLL updates use the non-VCXO denominator (`c` is 1 when the numerator is 0, otherwise 10^6). `reset()` still clears the VCXO parameter registers. Without the define, register programming is unchanged. `set_vcxo()` returns 0 after it writes the PLL and VCXO registers, and 1 when the reference is not usable (it writes nothing in that case, as before).

How it works: the VCXO functions in `si5351.cpp` are weak symbols. The sketch define makes `si5351.h` add small stubs that replace them, and the linker drops the originals. This needs a GCC or Clang toolchain, which every common Arduino core uses.

You can also pass the macro as a compiler definition. This leaves the code out when `si5351.cpp` is compiled:

* Arduino CLI: `--build-property compiler.cpp.extra_flags=-DSI5351_NO_VCXO`
* PlatformIO: `build_flags = -DSI5351_NO_VCXO`

Measured flash on an Arduino Uno (arduino-cli 1.5.1, arduino:avr 1.8.6), in bytes:

| Sketch | Default | Sketch `#define` | Compiler flag |
|---|---|---|---|
| `init()` + one `set_freq()`, runtime clock index | 10,868 | 10,428 | 10,396 |
| `init()` + one `set_freq()` on CLK0 | 10,140 | 9,700 | 9,668 |

Defining both `SI5351_NO_CLK67` and `SI5351_NO_VCXO` in that runtime sketch is 9,390 bytes (9,362 with both compiler flags). The same pair on the CLK0 sketch is 9,298 bytes (9,266 with both flags).

Frequency Limits
----------------
Numbers below are what v2.4.1 programs, checked against `src/si5351.cpp`, `src/si5351.h`, AN619 rev. 0.8, and the Si5351A/B/C-B datasheet rev. 1.3. Frequencies passed to `set_freq()` are in units of 0.01 Hz.
Expand Down Expand Up @@ -709,8 +738,10 @@ void Si5351::set_pll_input(enum si5351_pll pll, enum si5351_pll_input input)
* ppm - VCXO pull limit in ppm
*
* Set the parameters for the VCXO on the Si5351B.
* Returns 0 when the PLL and VCXO registers are written, 1 when nothing
* is written (reference not usable, or SI5351_NO_VCXO).
*/
void Si5351::set_vcxo(uint64_t pll_freq, uint8_t ppm)
uint8_t Si5351::set_vcxo(uint64_t pll_freq, uint8_t ppm)
```
### set_ref_freq()
```
Expand Down
60 changes: 46 additions & 14 deletions src/si5351.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -716,7 +716,14 @@ void Si5351::set_pll(uint64_t pll_freq, enum si5351_pll target_pll)
// PLLB keeps c = 10^6 while the VCXO is in use (AN619 sec. 3.2, 7).
// set_vcxo() stores the pull range and calls back here, so this path
// is the only copy of pll_calc / pack / write / pll_update.
uint8_t vcxo = (target_pll == SI5351_PLLB && vcxo_pull) ? 1 : 0;
// vcxo_active() and write_vcxo() are weak so SI5351_NO_VCXO can
// replace them (see si5351.h). With that macro, vcxo stays 0 and this
// function programs the PLL exactly as a non-VCXO update.
#ifndef SI5351_NO_VCXO
uint8_t vcxo = vcxo_active(target_pll);
#else
uint8_t vcxo = 0;
#endif
enum si5351_pll_input ref = (target_pll == SI5351_PLLA) ? plla_ref_osc : pllb_ref_osc;

if(!ref_ok(xtal_freq[(uint8_t)ref]))
Expand All @@ -733,20 +740,14 @@ void Si5351::set_pll(uint64_t pll_freq, enum si5351_pll target_pll)
si5351_write_bulk((target_pll == SI5351_PLLA) ? SI5351_PLLA_PARAMETERS : SI5351_PLLB_PARAMETERS,
SI5351_PARAMETERS_LENGTH, params);

#ifndef SI5351_NO_VCXO
if(vcxo)
{
uint8_t temp;
uint8_t ppm = vcxo_pull;

vcxo_param = ((vcxo_param * ppm * SI5351_VCXO_MARGIN) / 100ULL) / 1000000ULL;

temp = (uint8_t)(vcxo_param & 0xFF);
si5351_write(SI5351_VXCO_PARAMETERS_LOW, temp);
temp = (uint8_t)((vcxo_param >> 8) & 0xFF);
si5351_write(SI5351_VXCO_PARAMETERS_MID, temp);
temp = (uint8_t)((vcxo_param >> 16) & 0x3F);
si5351_write(SI5351_VXCO_PARAMETERS_HIGH, temp);
write_vcxo(vcxo_param);
}
#else
(void)vcxo_param;
#endif

pll_update(target_pll, pll_freq);
}
Expand Down Expand Up @@ -1424,8 +1425,13 @@ void Si5351::set_pll_input(enum si5351_pll pll, enum si5351_pll_input input)
* ppm - VCXO pull limit in ppm
*
* Set the parameters for the VCXO on the Si5351B.
* Returns 0 when the PLL and VCXO registers are written, 1 when nothing
* is written (reference not usable, or SI5351_NO_VCXO).
*/
void Si5351::set_vcxo(uint64_t pll_freq, uint8_t ppm)
#ifndef SI5351_NO_VCXO
// Weak: a sketch that defines SI5351_NO_VCXO before #include <si5351.h>
// replaces this with a stub that returns 1 and writes nothing.
SI5351_VCXO_WEAK uint8_t Si5351::set_vcxo(uint64_t pll_freq, uint8_t ppm)
{
// Bounds check
if(ppm < SI5351_VCXO_PULL_MIN)
Expand All @@ -1440,15 +1446,39 @@ void Si5351::set_vcxo(uint64_t pll_freq, uint8_t ppm)

if(!ref_ok(xtal_freq[(uint8_t)pllb_ref_osc]))
{
return;
return 1;
}

// Remember the VCXO setting so later PLLB updates keep c = 10^6.
// set_pll() writes the PLL and the VCXO parameter registers.
vcxo_pull = ppm;
set_pll(pll_freq, SI5351_PLLB);
return 0;
}

// 1 when target_pll is PLLB and set_vcxo() has stored a pull range.
SI5351_VCXO_WEAK uint8_t Si5351::vcxo_active(enum si5351_pll target_pll)
{
return (target_pll == SI5351_PLLB && vcxo_pull) ? 1 : 0;
}

// Registers 162..164 from the pll_calc() VCXO word and vcxo_pull.
SI5351_VCXO_WEAK void Si5351::write_vcxo(uint64_t vcxo_param)
{
uint8_t temp;
uint8_t ppm = vcxo_pull;

vcxo_param = ((vcxo_param * ppm * SI5351_VCXO_MARGIN) / 100ULL) / 1000000ULL;

temp = (uint8_t)(vcxo_param & 0xFF);
si5351_write(SI5351_VXCO_PARAMETERS_LOW, temp);
temp = (uint8_t)((vcxo_param >> 8) & 0xFF);
si5351_write(SI5351_VXCO_PARAMETERS_MID, temp);
temp = (uint8_t)((vcxo_param >> 16) & 0x3F);
si5351_write(SI5351_VXCO_PARAMETERS_HIGH, temp);
}
#endif

/*
* set_ref_freq(uint32_t ref_freq, enum si5351_pll_input ref_osc)
*
Expand Down Expand Up @@ -1630,10 +1660,12 @@ uint64_t Si5351::pll_calc(enum si5351_pll pll, uint64_t freq, struct Si5351RegSe
// Calculate parameters
frac_to_reg(a, b, c, reg);

#ifndef SI5351_NO_VCXO
if(vcxo)
{
return (uint64_t)(128 * a * RFRAC_DENOM + b);
}
#endif

// Recalculate frequency as fIN * (a + b/c)
lltmp = ref_freq;
Expand Down
22 changes: 21 additions & 1 deletion src/si5351.h
Original file line number Diff line number Diff line change
Expand Up @@ -332,7 +332,8 @@ class Si5351
void set_clock_disable(enum si5351_clock, enum si5351_clock_disable);
void set_clock_fanout(enum si5351_clock_fanout, uint8_t);
void set_pll_input(enum si5351_pll, enum si5351_pll_input);
void set_vcxo(uint64_t, uint8_t);
/* 0 = PLL and VCXO registers written, 1 = nothing written. */
uint8_t set_vcxo(uint64_t, uint8_t);
void set_ref_freq(uint32_t, enum si5351_pll_input);
uint8_t si5351_write_bulk(uint8_t, uint8_t, uint8_t *);
uint8_t si5351_write(uint8_t, uint8_t);
Expand Down Expand Up @@ -360,6 +361,9 @@ class Si5351
uint8_t set_freq67(uint64_t, enum si5351_clock);
static uint8_t ms67_div(uint64_t, uint64_t, uint8_t *);
void set_ms67(enum si5351_clock, uint8_t, uint8_t);
// VCXO code. See SI5351_NO_VCXO at the end of this file.
uint8_t vcxo_active(enum si5351_pll);
void write_vcxo(uint64_t);
void update_sys_status(struct Si5351Status *);
void update_int_status(struct Si5351IntStatus *);
void ms_div(enum si5351_clock, uint8_t, uint8_t);
Expand Down Expand Up @@ -389,4 +393,20 @@ uint8_t Si5351::ms67_div(uint64_t, uint64_t, uint8_t *) { return 0; }
void Si5351::set_ms67(enum si5351_clock, uint8_t, uint8_t) {}
#endif

/* Leaving out the VCXO code. Put this at the top of the sketch, in one
* file only (the .ino), on its own or with SI5351_NO_CLK67:
* #define SI5351_NO_VCXO
* #include <si5351.h>
* or pass -DSI5351_NO_VCXO to the compiler. set_vcxo() then returns 1 and
* writes nothing. Later PLL updates take the non-VCXO path. reset() still
* clears the VCXO parameter registers.
* The library's VCXO functions are weak; the stubs below replace them,
* and the linker drops the originals. */
#define SI5351_VCXO_WEAK __attribute__((weak))
#if defined(SI5351_NO_VCXO) && !defined(SI5351_LIBRARY_SOURCE)
uint8_t Si5351::set_vcxo(uint64_t, uint8_t) { return 1; }
uint8_t Si5351::vcxo_active(enum si5351_pll) { return 0; }
void Si5351::write_vcxo(uint64_t) {}
#endif

#endif /* SI5351_H_ */
2 changes: 1 addition & 1 deletion tests/regression/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,6 @@ Exit code: 0 = every check PASS/XFAIL/SKIP/INFO; 1 = at least one unexpected FAI
## Layout
`v231/` acceptance runner, scenarios, oracle, locks, and reports · `stubs/` Arduino/Wire host stubs · `issue106/` t106 and its multi-bus stubs · `reset_freq/` `reset_freq()` regression ·
`equiv/` harness, goldens snapshot, compare.py · `reference/v242_f885fca/` pinned v2.4.2 source (the hard gate) · `reference/master_1ddd43e/` historical source, not the gate ·
`ctrl/` `plan/` branch locks · `flash/check_flash_budget.sh` Uno byte ceilings, including the `SI5351_NO_CLK67` compiler-flag and sketch-#define builds (separate CI workflow) · `python/` golden/spec suites and refs ·
`ctrl/` `plan/` branch locks · `flash/check_flash_budget.sh` Uno byte ceilings, including the `SI5351_NO_CLK67` compiler-flag and sketch-#define builds and the `SI5351_NO_VCXO` sketch-#define build (separate CI workflow) · `python/` golden/spec suites and refs ·
`sketches/` Ctor106 · `boards.txt` board set and Boards Manager URLs · `tools/summarize.py`.
`v231/Makefile` is the original manual build, kept for reference only; the script does not use it.
5 changes: 5 additions & 0 deletions tests/regression/flash/budgets.txt
Original file line number Diff line number Diff line change
Expand Up @@ -25,3 +25,8 @@ min_runtime_no_clk67 9836
# min_runtime_sketch_no_clk67: same program with #define SI5351_NO_CLK67 in the
# sketch (no compiler flag). A library that ignores the define builds 10930.
min_runtime_sketch_no_clk67 9870
# min_runtime_sketch_no_vcxo: same program with #define SI5351_NO_VCXO in the
# sketch (no compiler flag). A library that ignores the define builds 10868.
min_runtime_sketch_no_vcxo 10428
# Both sketch defines. A library that honors only SI5351_NO_CLK67 builds 9870.
min_runtime_sketch_no_clk67_no_vcxo 9390
4 changes: 3 additions & 1 deletion tests/regression/flash/check_flash_budget.sh
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
#!/usr/bin/env bash
# Fail-closed Uno flash budget for the minimal sketches, including the
# SI5351_NO_CLK67 build of the runtime-clock sketch.
# SI5351_NO_CLK67 and SI5351_NO_VCXO builds of the runtime-clock sketch.
# Missing CLI, core, compiler, map, or a "Sketch uses" line is a failure.
# This measures program bytes. It is not a timing benchmark.
set -euo pipefail
Expand Down Expand Up @@ -112,6 +112,8 @@ build_one min_runtime
build_one min_runtime_no_clk67 "compiler.cpp.extra_flags=-DSI5351_NO_CLK67"
# Sketch-side opt-out: the #define is in the .ino, no compiler flag.
build_one min_runtime_sketch_no_clk67
build_one min_runtime_sketch_no_vcxo
build_one min_runtime_sketch_no_clk67_no_vcxo

# Negative control: the same parser must reject an over-budget line and a
# real log scored against a 1-byte ceiling. Success of this script requires
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
/* min_runtime.ino.txt with both sketch-side opt-outs. The defines are here,
* before the include, and are not passed to the compiler. */
#define SI5351_NO_CLK67
#define SI5351_NO_VCXO
#include "si5351.h"
#include "Wire.h"

Si5351 si5351;

/* New flash fixture, not a reconstruction of the audit runtime sketch.
* That sketch was not saved (REPORT only). This file is the budget's program:
* a global volatile uint8_t index and a constant 14 MHz. The gate ceiling is
* the size of this file on the pinned Uno toolchain.
*/
volatile uint8_t clk_index = 0;

void setup()
{
si5351.init(SI5351_CRYSTAL_LOAD_8PF, 0, 0);
si5351.set_freq(1400000000ULL, (si5351_clock)clk_index);
}

void loop() {}
23 changes: 23 additions & 0 deletions tests/regression/flash/sketches/min_runtime_sketch_no_vcxo.ino.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
/* min_runtime.ino.txt with the sketch-side VCXO opt-out: SI5351_NO_VCXO is
* defined here, before the include, and NOT passed to the compiler. The
* weak VCXO functions are replaced by the header's stubs. */
#define SI5351_NO_VCXO
#include "si5351.h"
#include "Wire.h"

Si5351 si5351;

/* New flash fixture, not a reconstruction of the audit runtime sketch.
* That sketch was not saved (REPORT only). This file is the budget's program:
* a global volatile uint8_t index and a constant 14 MHz. The gate ceiling is
* the size of this file on the pinned Uno toolchain.
*/
volatile uint8_t clk_index = 0;

void setup()
{
si5351.init(SI5351_CRYSTAL_LOAD_8PF, 0, 0);
si5351.set_freq(1400000000ULL, (si5351_clock)clk_index);
}

void loop() {}
Loading