// Compile-only battery, built with the cross compiler so the target's 16-bit // int is exercised. Two of this firmware's three numeric surfaces are decided // entirely at compile time - the fan curve and the thermistor table - and the // third, quarter-degrees to whole ones, is the arithmetic between them. #include #include "curve.hpp" #include "terminal.hpp" #include "thermistor.hpp" namespace { using app::thermistor::whole_degrees; // Rounding to nearest, across zero. A negative quotient truncates toward zero // in C, so a `(q + 2) / 4` reads -3.00 C as -2 and every case below the tie // with it; the arithmetic shift floors, which is what these hold it to. static_assert(whole_degrees(0) == 0); static_assert(whole_degrees(1) == 0); // +0.25 static_assert(whole_degrees(2) == 1); // +0.50, the tie static_assert(whole_degrees(3) == 1); // +0.75 static_assert(whole_degrees(4) == 1); // +1.00 static_assert(whole_degrees(-1) == 0); // -0.25 static_assert(whole_degrees(-2) == 0); // -0.50, the tie, toward zero static_assert(whole_degrees(-3) == -1); static_assert(whole_degrees(-4) == -1); static_assert(whole_degrees(-12) == -3); static_assert(whole_degrees(-160) == -40); // the table's own floor static_assert(whole_degrees(500) == 125); // and its ceiling // The curve is held at zero below the temperature the fan starts at, and // saturates inside the table's window rather than at its edge. using app::curve::detail::duty_entry; static_assert(duty_entry(app::curve::start_celsius - 1) == 0); static_assert(duty_entry(app::curve::start_celsius) == 0); static_assert(duty_entry(30) == 7); static_assert(duty_entry(40) == 36); static_assert(duty_entry(49) == 93); static_assert(duty_entry(50) == 100); static_assert(duty_entry(99) == 100); // Monotone across the whole table: a warmer reading never asks for less air. consteval bool curve_rises() { for (std::int32_t t = 1; t < 100; ++t) { if (duty_entry(t) < duty_entry(t - 1)) { return false; } } return true; } static_assert(curve_rises()); // The thermistor table, anchored where the Beta equation fixes it rather than // against numbers this file computed the same way: the divider reads the // thermistor's nominal resistance at // adc = full_scale * nominal / (series + nominal), and the equation's own // definition puts that count at the nominal temperature. using app::thermistor::detail::quarters_entry; inline constexpr std::int32_t nominal_count = static_cast(app::thermistor::adc_full_scale * app::thermistor::nominal_resistance / (app::thermistor::series_resistor + app::thermistor::nominal_resistance)); static_assert(quarters_entry(nominal_count / 4) >= 100); // 25.00 C, in quarters static_assert(quarters_entry(nominal_count / 4 + 1) < 100); // and the step below it // Both clamps, at the ends the divider cannot leave. static_assert(quarters_entry(0) == 125 * 4); static_assert(quarters_entry(255) == -40 * 4); // An NTC on this divider falls with the count: more counts is more resistance // is a colder sensor, over every step of the table. consteval bool thermistor_falls() { for (std::int32_t i = 1; i < 256; ++i) { if (quarters_entry(i) > quarters_entry(i - 1)) { return false; } } return true; } static_assert(thermistor_falls()); // The console's one parsing rule, read out of the flash blob the names live // in. Every case here is a property of the *order* the names sit in, so this // is what an edit to that list has to answer to. using app::terminal; // Full names, and the ends of the list - a walk that miscounts a separator // lands on a neighbour rather than failing, so both ends are named. static_assert(terminal::lookup("help") == 0); static_assert(terminal::lookup("save") == 12); static_assert(terminal::lookup("bootloader") == 4); // Abbreviations resolve to the first entry they prefix. `s` prefixes show, // statistics, set and save, and show is first - which is the original's // behaviour and the reason the list is ordered rather than sorted. static_assert(terminal::lookup("s") == 1); static_assert(terminal::lookup("st") == 6); static_assert(terminal::lookup("se") == 9); static_assert(terminal::lookup("sa") == 12); static_assert(terminal::lookup("up") == 5); static_assert(terminal::lookup("b") == 4); // `reset` is the exception: it must be typed in full, so no abbreviation of it // resolves - and none of its prefixes names anything else either. static_assert(terminal::lookup("reset") == 8); static_assert(terminal::lookup("rese") == terminal::no_command); static_assert(terminal::lookup("res") == terminal::no_command); static_assert(terminal::lookup("r") == terminal::no_command); // Nothing, and nothing that matches. static_assert(terminal::lookup("") == terminal::no_command); static_assert(terminal::lookup("xyzzy") == terminal::no_command); // Longer than the name it prefixes is not a match: `helpful` is not `help`. static_assert(terminal::lookup("helpful") == terminal::no_command); } // namespace