diff --git a/README.md b/README.md index 9663e02..312f939 100644 --- a/README.md +++ b/README.md @@ -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 +``` + +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. @@ -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() ``` diff --git a/src/si5351.cpp b/src/si5351.cpp index 6e748ac..e28214d 100644 --- a/src/si5351.cpp +++ b/src/si5351.cpp @@ -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])) @@ -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); } @@ -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 +// 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) @@ -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) * @@ -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; diff --git a/src/si5351.h b/src/si5351.h index d809350..fa6e61b 100644 --- a/src/si5351.h +++ b/src/si5351.h @@ -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); @@ -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); @@ -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 + * 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_ */ diff --git a/tests/regression/README.md b/tests/regression/README.md index 5dd4298..7bef536 100644 --- a/tests/regression/README.md +++ b/tests/regression/README.md @@ -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. diff --git a/tests/regression/flash/budgets.txt b/tests/regression/flash/budgets.txt index 4a41930..5941a18 100644 --- a/tests/regression/flash/budgets.txt +++ b/tests/regression/flash/budgets.txt @@ -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 diff --git a/tests/regression/flash/check_flash_budget.sh b/tests/regression/flash/check_flash_budget.sh index 5a21509..d5aadf5 100755 --- a/tests/regression/flash/check_flash_budget.sh +++ b/tests/regression/flash/check_flash_budget.sh @@ -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 @@ -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 diff --git a/tests/regression/flash/sketches/min_runtime_sketch_no_clk67_no_vcxo.ino.txt b/tests/regression/flash/sketches/min_runtime_sketch_no_clk67_no_vcxo.ino.txt new file mode 100644 index 0000000..c181b14 --- /dev/null +++ b/tests/regression/flash/sketches/min_runtime_sketch_no_clk67_no_vcxo.ino.txt @@ -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() {} diff --git a/tests/regression/flash/sketches/min_runtime_sketch_no_vcxo.ino.txt b/tests/regression/flash/sketches/min_runtime_sketch_no_vcxo.ino.txt new file mode 100644 index 0000000..1b6aefc --- /dev/null +++ b/tests/regression/flash/sketches/min_runtime_sketch_no_vcxo.ino.txt @@ -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() {}