From 837b832bc7bb6b72c6435913f23241cb9603aa8f Mon Sep 17 00:00:00 2001 From: BlackMark Date: Wed, 12 Aug 2026 14:40:33 +0200 Subject: [PATCH] build: the libavr pin advances past the audit sweep, and the numbers get names The pin crosses libavr's phase-6 close and the guideline sweep behind it; the image is byte-identical in both modes at 8206 bytes. The port's own sweep, against the same rules. Every mutable `static inline` takes `m_` - uptime's counter, the sampler's window, the controller's five, the statistics histogram and the terminal's line state (rule 46; a private `static constexpr` is a constant rather than state and keeps its bare name). The command table is `std::to_array` and the serial config breaks one member per line (rules 36, 40). And three numbers get the name they already had somewhere: duty goes through `percent_t::of()` rather than a hand-built basis-point count, the ADC's top count is `thermistor::adc_full_scale` instead of 1023 in four places, and the two `0xffffffff` are `open_circuit` - which was already declared five lines away - and `never_written`, which replaces a comment explaining the literal (rules 5, 6, 41). Measured, not assumed: rendering `adc_full_scale` into the `show` line instead of leaving it in the message string cost 6 bytes, so the display text stays text. Co-Authored-By: Claude Opus 5 --- libavr | 2 +- src/board.hpp | 40 +++++++++++++++++--------------- src/controller.hpp | 42 +++++++++++++++++----------------- src/statistics.hpp | 44 +++++++++++++++++++---------------- src/terminal.hpp | 56 ++++++++++++++++++++++----------------------- src/thermistor.hpp | 6 ++++- test/CMakeLists.txt | 2 +- 7 files changed, 102 insertions(+), 90 deletions(-) diff --git a/libavr b/libavr index 07a0c40..4c7d4d6 160000 --- a/libavr +++ b/libavr @@ -1 +1 @@ -Subproject commit 07a0c4023564eb4bd72e33ff84399105390716e9 +Subproject commit 4c7d4d6ff3af35dc1593a7311a91a1410578e978 diff --git a/src/board.hpp b/src/board.hpp index b9eb65a..df0bd55 100644 --- a/src/board.hpp +++ b/src/board.hpp @@ -13,24 +13,24 @@ using dev = avr::device<{.clock = 16_MHz}>; // Millisecond uptime from timer2 CTC (the fan owns timer0). class uptime { - static inline volatile std::uint64_t ms = 0; + static inline volatile std::uint64_t m_ms = 0; public: - using ticker = dev::timer2<{.frequency = 1_kHz, .on_compare = [] { ms = ms + 1; }}>; + using ticker = dev::timer2<{.frequency = 1_kHz, .on_compare = [] { m_ms = m_ms + 1; }}>; static std::uint64_t millis() { avr::irq::interrupt_guard lock; - return ms; + return m_ms; } }; // 1000-sample averaging window fed by the conversion interrupt. class sampler { - static inline volatile std::uint32_t sum = 0; - static inline volatile std::uint16_t count = 0; - static inline volatile std::uint16_t window = 0; - static inline volatile bool ready = false; + static inline volatile std::uint32_t m_sum = 0; + static inline volatile std::uint16_t m_count = 0; + static inline volatile std::uint16_t m_window = 0; + static inline volatile bool m_ready = false; static constexpr std::uint16_t samples = 1000; @@ -38,13 +38,13 @@ class sampler { using input = dev::adc<{.trigger = avr::adc::trigger::free_running, .on_conversion = [](std::uint16_t value) { - sum = sum + value; - count = count + 1; - if (count >= samples) { - window = static_cast(sum / samples); - sum = 0; - count = 0; - ready = true; + m_sum = m_sum + value; + m_count = m_count + 1; + if (m_count >= samples) { + m_window = static_cast(m_sum / samples); + m_sum = 0; + m_count = 0; + m_ready = true; } }}, avr::adc::input>; @@ -55,11 +55,11 @@ class sampler { static bool take(std::uint16_t &value) { avr::irq::interrupt_guard lock; - if (!ready) { + if (!m_ready) { return false; } - value = window; - ready = false; + value = m_window; + m_ready = false; return true; } }; @@ -69,7 +69,11 @@ using fan = dev::pwm; // 115200 at 16 MHz lands +2.1 % off, past the receiver-tolerance table the // solver holds rates to - the rate this board has always spoken, so the // override states that it is meant. -using serial_t = dev::uart0<{.baud = 115200_Bd, .rx_buffer = 32, .allow_baud_error = true}>; +using serial_t = dev::uart0<{ + .baud = 115200_Bd, + .rx_buffer = 32, + .allow_baud_error = true, +}>; inline constexpr serial_t serial{}; } // namespace app diff --git a/src/controller.hpp b/src/controller.hpp index db616a3..d950cee 100644 --- a/src/controller.hpp +++ b/src/controller.hpp @@ -11,16 +11,16 @@ namespace app { class controller { - static inline std::uint16_t adc_average = 0; - static inline std::int16_t temp_quarters = 0; - static inline std::uint8_t percent = 100; - static inline bool auto_mode = true; - static inline bool have_data = false; + static inline std::uint16_t m_adc_average = 0; + static inline std::int16_t m_temp_quarters = 0; + static inline std::uint8_t m_percent = 100; + static inline bool m_auto_mode = true; + static inline bool m_have_data = false; public: static void init() { - fan::set_duty(avr::percent_t{10000}); // full blast until the first reading + fan::set_duty(100_pct); // full blast until the first reading } static void poll() @@ -29,50 +29,50 @@ class controller { if (!sampler::take(sample)) { return; } - adc_average = sample; - temp_quarters = thermistor::quarters(sample); - have_data = true; - if (auto_mode) { - percent = curve::duty(static_cast((temp_quarters + 2) / 4)); + m_adc_average = sample; + m_temp_quarters = thermistor::quarters(sample); + m_have_data = true; + if (m_auto_mode) { + m_percent = curve::duty(static_cast((m_temp_quarters + 2) / 4)); } - fan::set_duty(avr::percent_t{static_cast(percent * 100)}); + fan::set_duty(avr::percent_t::of(m_percent)); } static void set_manual(std::uint8_t p) { - auto_mode = false; - percent = p; - fan::set_duty(avr::percent_t{static_cast(p * 100)}); + m_auto_mode = false; + m_percent = p; + fan::set_duty(avr::percent_t::of(p)); } static void set_automatic() { - auto_mode = true; + m_auto_mode = true; } static bool automatic() { - return auto_mode; + return m_auto_mode; } static bool data_available() { - return have_data; + return m_have_data; } static std::int16_t temperature_quarters() { - return temp_quarters; + return m_temp_quarters; } static std::uint16_t last_adc() { - return adc_average; + return m_adc_average; } static std::uint8_t fan_percent() { - return percent; + return m_percent; } }; diff --git a/src/statistics.hpp b/src/statistics.hpp index 9763d5b..4b22734 100644 --- a/src/statistics.hpp +++ b/src/statistics.hpp @@ -9,7 +9,7 @@ // Temperature histogram: one uint32 bucket per C 0..99, sampled once a // second, written back to EEPROM every 30 minutes (update() only touches -// changed bytes). Erased EEPROM reads back as 0xffffffff - treated as 0. +// changed bytes). namespace app { class statistics { @@ -17,11 +17,15 @@ class statistics { static constexpr std::uint32_t sample_delay_ms = 1'000; static constexpr std::uint32_t writeback_delay_ms = 1'800'000; + // What an erased cell reads back as, so a bucket nobody has written yet + // counts as no samples rather than four billion. + static constexpr std::uint32_t never_written = ~std::uint32_t{0}; + using stored = avr::eeprom::var, 0>; - static inline std::array histogram{}; - static inline std::uint64_t last_sample = 0; - static inline std::uint64_t last_writeback = 0; + static inline std::array m_histogram{}; + static inline std::uint64_t m_last_sample = 0; + static inline std::uint64_t m_last_writeback = 0; static constexpr std::uint8_t clamp(std::int8_t t) { @@ -33,9 +37,9 @@ class statistics { static void init() { - histogram = stored::read(); - for (auto &bucket : histogram) { - if (bucket == 0xffffffff) { + m_histogram = stored::read(); + for (auto &bucket : m_histogram) { + if (bucket == never_written) { bucket = 0; } } @@ -44,31 +48,31 @@ class statistics { static void record(std::int8_t celsius) { auto now = uptime::millis(); - if (now >= last_sample + sample_delay_ms) { - ++histogram[clamp(celsius)]; - last_sample = now; + if (now >= m_last_sample + sample_delay_ms) { + ++m_histogram[clamp(celsius)]; + m_last_sample = now; } - if (now >= last_writeback + writeback_delay_ms) { + if (now >= m_last_writeback + writeback_delay_ms) { save(); - last_writeback = now; + m_last_writeback = now; } } static void save() { - stored::update(histogram); + stored::update(m_histogram); } static void reset() { - histogram = {}; - stored::update(histogram); + m_histogram = {}; + stored::update(m_histogram); } static std::uint8_t min_temperature() { for (std::uint8_t i = 0; i < range; ++i) { - if (histogram[i]) { + if (m_histogram[i]) { return i; } } @@ -78,7 +82,7 @@ class statistics { static std::uint8_t max_temperature() { for (std::uint8_t i = range; i > 0; --i) { - if (histogram[i - 1]) { + if (m_histogram[i - 1]) { return i - 1; } } @@ -88,7 +92,7 @@ class statistics { static std::uint64_t total_samples() { std::uint64_t total = 0; - for (auto bucket : histogram) { + for (auto bucket : m_histogram) { total += bucket; } return total; @@ -97,7 +101,7 @@ class statistics { static std::uint32_t highest_bucket() { std::uint32_t highest = 0; - for (auto bucket : histogram) { + for (auto bucket : m_histogram) { if (bucket > highest) { highest = bucket; } @@ -107,7 +111,7 @@ class statistics { static std::uint32_t bucket(std::uint8_t celsius) { - return histogram[clamp(static_cast(celsius))]; + return m_histogram[clamp(static_cast(celsius))]; } }; diff --git a/src/terminal.hpp b/src/terminal.hpp index e64412b..7d17cb2 100644 --- a/src/terminal.hpp +++ b/src/terminal.hpp @@ -22,11 +22,11 @@ class terminal { static constexpr char ctrl_c = 0x03; static constexpr char backspace = 0x08; static constexpr char del = 0x7f; - static inline std::array line{}; - static inline std::uint8_t at = 0; - static inline bool overflowed = false; - static inline bool monitoring = false; - static inline std::uint64_t last_monitor = 0; + static inline std::array m_line{}; + static inline std::uint8_t m_at = 0; + static inline bool m_overflowed = false; + static inline bool m_monitoring = false; + static inline std::uint64_t m_last_monitor = 0; // Commands, in the order they are matched - which is the order the original // firmware matched them in, and that order is load-bearing. An abbreviation @@ -38,7 +38,7 @@ class terminal { bool exact; // reset only: an abbreviation must not be able to wipe data }; - static constexpr std::array commands{{ + static constexpr auto commands = std::to_array({ {"help", false}, {"show", false}, {"curve", false}, @@ -52,7 +52,7 @@ class terminal { {"auto", false}, {"version", false}, {"save", false}, - }}; + }); // Column the descriptions' colons line up in, counted from the start of the // name. The longest name is `bootloader` at 10, so 12 leaves it a space and @@ -146,10 +146,10 @@ class terminal { static std::uint32_t resistance() { auto adc = controller::last_adc(); - if (adc >= 1023) { - return 0xffffffff; // open circuit: the divider has no solution + if (adc >= thermistor::adc_full_scale) { + return open_circuit; } - return static_cast(thermistor::series_resistor) * adc / (1023u - adc); + return static_cast(thermistor::series_resistor) * adc / (thermistor::adc_full_scale - adc); } // One value per line behind a dotted label, as the original had it. A single @@ -282,9 +282,9 @@ class terminal { { // A line that overflowed the buffer is not a command - it is the tail of // one. Acting on it is how a truncated `reset` becomes a surprise. - if (overflowed) { + if (m_overflowed) { serial << "input too long, ignored\r\n"_P; - overflowed = false; + m_overflowed = false; return; } @@ -318,7 +318,7 @@ class terminal { print_curve(); return; case 3: - monitoring = true; + m_monitoring = true; return; case 4: serial << "entering bootloader\r\n"_P; @@ -398,17 +398,17 @@ class terminal { static void poll() { - if (monitoring) { - if (uptime::millis() >= last_monitor + 1000) { + if (m_monitoring) { + if (uptime::millis() >= m_last_monitor + 1000) { show(); - last_monitor = uptime::millis(); + m_last_monitor = uptime::millis(); } // Ctrl+C only, as the original had it. Stopping on *any* byte reads // well until a host sends a line ending: `monitor\r\n` then stops // itself on the `\n` it arrived with, one reading in. if (auto in = serial_t::read(); in && *in == ctrl_c) { serial << "^C\r\n"_P; - monitoring = false; + m_monitoring = false; prompt(); } return; @@ -419,18 +419,18 @@ class terminal { // Abandon whatever was typed and start a fresh line, which is // what Ctrl+C means at every other prompt in the world. serial << "^C\r\n"_P; - at = 0; - overflowed = false; + m_at = 0; + m_overflowed = false; prompt(); } else if (c == '\r' || c == '\n') { serial << "\r\n"_P; - if (at == 0 && !overflowed) { + if (m_at == 0 && !m_overflowed) { prompt(); // a bare Enter just reprompts, no gap needed continue; } - dispatch(std::string_view{line.data(), at}); - at = 0; - if (!monitoring) { + dispatch(std::string_view{m_line.data(), m_at}); + m_at = 0; + if (!m_monitoring) { // A blank line between a command's output and the next // prompt: without it the answer and the thing you type // next run together and a screen of them is unreadable. @@ -438,16 +438,16 @@ class terminal { prompt(); } } else if (c == del || c == backspace) { - if (at) { - --at; + if (m_at) { + --m_at; serial << "\b \b"_P; } } else if (c >= ' ') { - if (at < line.size()) { - line[at++] = c; + if (m_at < m_line.size()) { + m_line[m_at++] = c; serial << c; // echo } else { - overflowed = true; // reported when the line is submitted + m_overflowed = true; // reported when the line is submitted } } } diff --git a/src/thermistor.hpp b/src/thermistor.hpp index fa3c18b..341307b 100644 --- a/src/thermistor.hpp +++ b/src/thermistor.hpp @@ -11,6 +11,10 @@ // counts map to quarter- C with linear interpolation between table steps. namespace app::thermistor { +// The converter's top count: this board reads the divider at the ADC's +// full 10 bits, so a reading and the resistance it implies both scale by it. +inline constexpr std::uint16_t adc_full_scale = (1u << 10) - 1; + inline constexpr double series_resistor = 9951; inline constexpr double nominal_resistance = 9270; inline constexpr double beta = 3212; @@ -20,7 +24,7 @@ namespace detail { consteval double temperature_of(double adc) { - double resistance = series_resistor * adc / (1023.0 - adc); + double resistance = series_resistor * adc / (adc_full_scale - adc); // __builtin_log constant-folds on the AVR backend, so the table is // built at compile time with no runtime libm. double steinhart = __builtin_log(resistance / nominal_resistance) / beta + 1.0 / (nominal_temperature + 273.15); diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index 367b73e..335c3c9 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -1,6 +1,6 @@ # The board has no reset line and no programming header, so the loader-entry # route in the emitted image is the only thing standing between a firmware change -# and an unreflashable board. It has been wrong before - see the script. +# and an unreflashable board. find_package(Python3 COMPONENTS Interpreter) if(Python3_FOUND) add_test(NAME fantemp.reachability