#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 m_ms = 0; public: 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 m_ms; } }; // 1000-sample averaging window fed by the conversion interrupt. class sampler { 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; public: using input = dev::adc<{.trigger = avr::adc::trigger::free_running, .on_conversion = [](std::uint16_t value) { 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>; // The bound input, whose start() is free-running's one kick. using thermistor = input::in; // The finished average (raw 10-bit), once per window. static bool take(std::uint16_t &value) { avr::irq::interrupt_guard lock; if (!m_ready) { return false; } value = m_window; m_ready = false; return true; } }; using fan = dev::pwm; // 115200 at 16 MHz lands +2.1 % off, past the receiver-tolerance table the // solver holds rates to. It is the rate the console on the other end of the // cable expects, so the override states that the miss is meant. using serial_t = dev::uart0<{ .baud = 115200_Bd, .rx_buffer = 32, .allow_baud_error = true, }>; inline constexpr serial_t serial{}; } // namespace app