#pragma once #include // The board composition: every peripheral of the fan controller in one // place. ATmega328P at 16 MHz — thermistor divider on ADC0 (PC0), fan on // OC0B (PD5) at 50 kHz, console on the hardware UART. namespace app { using namespace avr::literals; 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; public: using ticker = dev::timer2<{.frequency = 1_kHz, .on_compare = [] { ms = ms + 1; }}>; static std::uint64_t millis() { avr::irq::atomic_guard lock; return 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 constexpr std::uint16_t samples = 1000; public: using input = dev::adc<{.input = avr::adc::input_pin(0), .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; } }}>; // The finished average (raw 10-bit), once per window. static bool take(std::uint16_t &value) { avr::irq::atomic_guard lock; if (!ready) return false; value = window; ready = false; return true; } }; using fan = dev::pwm; using serial_t = dev::uart0<{.baud = 115200_Bd, .rx_buffer = 32, .max_baud_error = 2.5_pct}>; inline constexpr serial_t serial{}; } // namespace app