Use libavr flash_table and __builtin_log
The thermistor Beta curve and the fan cubic move onto avr::flash_table instead of hand-rolled [[gnu::progmem]] arrays with raw pgm_read, and the compile-time logarithm uses __builtin_log (which constant-folds on the AVR backend) instead of a hand-rolled series. Same 11284 B, still byte-identical across libavr modes. Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -3,11 +3,11 @@
|
|||||||
#include <array>
|
#include <array>
|
||||||
#include <cstdint>
|
#include <cstdint>
|
||||||
|
|
||||||
#include <avr/pgmspace.h>
|
#include <libavr/flash.hpp>
|
||||||
|
|
||||||
// The auto-mode fan curve, tabulated at compile time: the legacy cubic
|
// The auto-mode fan curve, tabulated at compile time: the legacy cubic
|
||||||
// (0.002246·x³ − 0.09·x² + 0.91·x, zero below 20 °C) becomes a flash
|
// (0.002246·x³ − 0.09·x² + 0.91·x, zero below 20 °C) becomes a libavr
|
||||||
// lookup of duty percent per °C.
|
// flash_table of duty percent per °C.
|
||||||
namespace app::curve {
|
namespace app::curve {
|
||||||
|
|
||||||
namespace detail {
|
namespace detail {
|
||||||
@@ -25,14 +25,13 @@ consteval std::uint8_t duty_entry(int celsius)
|
|||||||
return static_cast<std::uint8_t>(duty + 0.5);
|
return static_cast<std::uint8_t>(duty + 0.5);
|
||||||
}
|
}
|
||||||
|
|
||||||
struct table {
|
inline constexpr avr::flash_table<[] {
|
||||||
[[gnu::progmem]] static constexpr std::array<std::uint8_t, 100> data = [] {
|
|
||||||
std::array<std::uint8_t, 100> out{};
|
std::array<std::uint8_t, 100> out{};
|
||||||
for (int t = 0; t < 100; ++t)
|
for (int t = 0; t < 100; ++t)
|
||||||
out[static_cast<std::size_t>(t)] = duty_entry(t);
|
out[static_cast<std::size_t>(t)] = duty_entry(t);
|
||||||
return out;
|
return out;
|
||||||
}();
|
}()>
|
||||||
};
|
table;
|
||||||
|
|
||||||
} // namespace detail
|
} // namespace detail
|
||||||
|
|
||||||
@@ -43,7 +42,7 @@ inline std::uint8_t duty(std::int8_t celsius)
|
|||||||
celsius = 0;
|
celsius = 0;
|
||||||
if (celsius > 99)
|
if (celsius > 99)
|
||||||
celsius = 99;
|
celsius = 99;
|
||||||
return pgm_read_byte(&detail::table::data[static_cast<std::uint8_t>(celsius)]);
|
return detail::table[static_cast<std::uint8_t>(celsius)];
|
||||||
}
|
}
|
||||||
|
|
||||||
} // namespace app::curve
|
} // namespace app::curve
|
||||||
|
|||||||
@@ -3,12 +3,12 @@
|
|||||||
#include <array>
|
#include <array>
|
||||||
#include <cstdint>
|
#include <cstdint>
|
||||||
|
|
||||||
#include <avr/pgmspace.h>
|
#include <libavr/flash.hpp>
|
||||||
|
|
||||||
// NTC thermistor on a series divider, solved entirely at compile time:
|
// NTC thermistor on a series divider, solved entirely at compile time:
|
||||||
// the Beta equation (with its logarithm) runs consteval into a flash
|
// the Beta equation (logarithm and all) runs consteval into a libavr
|
||||||
// table — the firmware never does floating point. Raw 10-bit ADC counts
|
// flash_table — the firmware never does floating point. Raw 10-bit ADC
|
||||||
// map to quarter-°C with linear interpolation between table steps.
|
// counts map to quarter-°C with linear interpolation between table steps.
|
||||||
namespace app::thermistor {
|
namespace app::thermistor {
|
||||||
|
|
||||||
inline constexpr double series_resistor = 9951;
|
inline constexpr double series_resistor = 9951;
|
||||||
@@ -18,40 +18,18 @@ inline constexpr double nominal_temperature = 25;
|
|||||||
|
|
||||||
namespace detail {
|
namespace detail {
|
||||||
|
|
||||||
// Natural log for the consteval evaluator (no freestanding <cmath>):
|
|
||||||
// range-reduce by powers of two, then the atanh series around 1.
|
|
||||||
consteval double ln(double x)
|
|
||||||
{
|
|
||||||
constexpr double ln2 = 0.6931471805599453;
|
|
||||||
int k = 0;
|
|
||||||
while (x > 1.5) {
|
|
||||||
x /= 2;
|
|
||||||
++k;
|
|
||||||
}
|
|
||||||
while (x < 0.75) {
|
|
||||||
x *= 2;
|
|
||||||
--k;
|
|
||||||
}
|
|
||||||
double z = (x - 1) / (x + 1);
|
|
||||||
double z2 = z * z;
|
|
||||||
double term = z;
|
|
||||||
double sum = 0;
|
|
||||||
for (int n = 1; n < 30; n += 2) {
|
|
||||||
sum += term / n;
|
|
||||||
term *= z2;
|
|
||||||
}
|
|
||||||
return 2 * sum + k * ln2;
|
|
||||||
}
|
|
||||||
|
|
||||||
consteval double temperature_of(double adc)
|
consteval double temperature_of(double adc)
|
||||||
{
|
{
|
||||||
double resistance = series_resistor * adc / (1023.0 - adc);
|
double resistance = series_resistor * adc / (1023.0 - adc);
|
||||||
double steinhart = ln(resistance / nominal_resistance) / beta + 1.0 / (nominal_temperature + 273.15);
|
// __builtin_log constant-folds on the AVR backend, so no runtime libm
|
||||||
|
// and no hand-rolled series is needed for the compile-time table.
|
||||||
|
double steinhart = __builtin_log(resistance / nominal_resistance) / beta + 1.0 / (nominal_temperature + 273.15);
|
||||||
return 1.0 / steinhart - 273.15;
|
return 1.0 / steinhart - 273.15;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 256 entries over the 10-bit range (steps of 4 counts), quarter-°C,
|
// 256 entries over the 10-bit range (steps of 4 counts), quarter-°C,
|
||||||
// clamped to a sane sensor window.
|
// clamped to a sane sensor window; entry 256 mirrors 255 so interpolation
|
||||||
|
// at full scale has a right neighbour.
|
||||||
consteval std::int16_t quarters_entry(int index)
|
consteval std::int16_t quarters_entry(int index)
|
||||||
{
|
{
|
||||||
double adc = index * 4.0;
|
double adc = index * 4.0;
|
||||||
@@ -67,19 +45,13 @@ consteval std::int16_t quarters_entry(int index)
|
|||||||
return static_cast<std::int16_t>(t < 0 ? t - 0.5 : t + 0.5);
|
return static_cast<std::int16_t>(t < 0 ? t - 0.5 : t + 0.5);
|
||||||
}
|
}
|
||||||
|
|
||||||
struct table {
|
inline constexpr avr::flash_table<[] {
|
||||||
[[gnu::progmem]] static constexpr std::array<std::int16_t, 257> data = [] {
|
|
||||||
std::array<std::int16_t, 257> out{};
|
std::array<std::int16_t, 257> out{};
|
||||||
for (int i = 0; i < 257; ++i)
|
for (int i = 0; i < 257; ++i)
|
||||||
out[static_cast<std::size_t>(i)] = quarters_entry(i < 256 ? i : 255);
|
out[static_cast<std::size_t>(i)] = quarters_entry(i < 256 ? i : 255);
|
||||||
return out;
|
return out;
|
||||||
}();
|
}()>
|
||||||
};
|
table;
|
||||||
|
|
||||||
inline std::int16_t read_entry(std::uint16_t index)
|
|
||||||
{
|
|
||||||
return static_cast<std::int16_t>(pgm_read_word(&table::data[index]));
|
|
||||||
}
|
|
||||||
|
|
||||||
} // namespace detail
|
} // namespace detail
|
||||||
|
|
||||||
@@ -88,8 +60,8 @@ inline std::int16_t quarters(std::uint16_t adc)
|
|||||||
{
|
{
|
||||||
std::uint16_t index = adc >> 2; // the 257th entry backs index+1 at full scale
|
std::uint16_t index = adc >> 2; // the 257th entry backs index+1 at full scale
|
||||||
std::uint8_t frac = adc & 3;
|
std::uint8_t frac = adc & 3;
|
||||||
auto a = detail::read_entry(index);
|
auto a = detail::table[index];
|
||||||
auto b = detail::read_entry(static_cast<std::uint16_t>(index + 1));
|
auto b = detail::table[static_cast<std::uint16_t>(index + 1)];
|
||||||
return static_cast<std::int16_t>(a + ((b - a) * frac) / 4);
|
return static_cast<std::int16_t>(a + ((b - a) * frac) / 4);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user