From 0e5d15c8146639849e805b3dfcb61733368c4bbb Mon Sep 17 00:00:00 2001 From: zhangyisong Date: Mon, 3 Aug 2026 22:12:31 +0800 Subject: [PATCH] Refactor app to use static classes and event-driven callbacks Restructure Led as a template on the DT spec, add a generic signal/slot mechanism for host status, and cache Infrared sensor samples via trigger callbacks. Add new init modules for LED, temp, and watchdog, and build them into the main app. --- CMakeLists.txt | 8 ++- include/com.hpp | 106 ++++++++++++-------------------------- include/infrared.hpp | 118 +++++++++++++++++++++++++++++++++---------- include/led.hpp | 81 ++++------------------------- include/watchdog.hpp | 6 +-- prj.conf | 6 +++ src/led.cpp | 29 +++++++++++ src/main.cpp | 7 +-- src/temp.cpp | 69 +++++++++++++++++++++++++ src/watdog.cpp | 16 ++++++ 10 files changed, 266 insertions(+), 180 deletions(-) create mode 100644 src/led.cpp create mode 100644 src/temp.cpp create mode 100644 src/watdog.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index 616f81b..62ec2bd 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -15,5 +15,11 @@ elseif(CONFIG_SAMPLE_NTC) target_sources(app PRIVATE src/sample_ntc.cpp) else() message("build main app") - target_sources(app PRIVATE src/main.cpp) + + target_sources(app PRIVATE + src/main.cpp + src/led.cpp + src/temp.cpp + src/watdog.cpp + ) endif() diff --git a/include/com.hpp b/include/com.hpp index adc71b8..0037b0a 100644 --- a/include/com.hpp +++ b/include/com.hpp @@ -2,6 +2,8 @@ #define __THER_COM_HPP__ #include "led.hpp" +#include +#include #include #include #include @@ -12,96 +14,52 @@ namespace ther { class Com { public: + enum HostStatus : uint8_t { STANDBY, RUNNING, PAUSE, ERROR }; + using RunningStateSignal = etl::signal; static auto Init() -> zpp::error { s_proto->SetRxCallbackTable(kRxCallbackTable); + auto size = hwinfo_get_device_id(buff, sizeof(buff)); + s_id_buff = etl::span(buff, size); return zpp::ok(); } - static auto Send(sensor_value val) -> void { + static auto SendTemp(sensor_value val) -> void { const uint8_t temp_data[] = {(uint8_t)val.val1, (uint8_t)val.val2}; - s_proto->Send(TEMP, temp_data); + s_proto->Send(R_TEMP, temp_data); + } + static auto AddRunningState(RunningStateSignal::slot_type slot) -> bool { + return s_running_state_sig.connect(slot); } private: - enum HostStatus : uint8_t { STANDBY, RUNNING, PAUSE, ERROR }; enum Addr : uint8_t { - TEMP = 0, - GET_ID, - RUNNING_STATE, - W_HEATING_STATE, - R_HEATING_STATE + R_TEMP = 0, + R_GET_ID, + W_RUNNING_STATE, }; - enum DevAddr : uint8_t { - R_TEMP_POLE_NTC = 100, - R_TEMP_HETING_PAD_NTC = 101, - }; - using CbTableValueType = - std::pair; - struct Cb { - static auto GetId(uart_com::DataType data) -> void { - printk("handle get id"); - uint8_t buff[20]; - auto size = hwinfo_get_device_id(buff, sizeof(buff)); - s_proto->Send(GET_ID, uart_com::DataType(buff, size)); - } - static auto RunningState(uart_com::DataType data) -> void { - if (data.size() != 1) { - return; - } - if (data[0] == RUNNING) { - } else { - } - printk("set ledsrtip to %d", data[0]); - const uint8_t id = data[0]; - s_led_strip_indicator->Status(id).on_error([](zpp::error_code err) { - printk("err code: %s", zpp::error_str(err)); - }); - } - static auto WHeatingState(uart_com::DataType data) -> void { - // if (data.size() != 1) { - // return; - // } - // const bool state = (data[0] > 0); - // if (state) { - // HeatingPad::Start(sensor_value{data[0], 0}); - // } else { - // HeatingPad::Stop(); - // } - } - static auto RHeatingState(uart_com::DataType data) -> void { - // if (data.size() != 0) { - // return; - // } - // const sensor_value temp = HeatingPad::CurrentTemp(); - // const uint8_t state[] = {s_heating_state, (uint8_t)temp.val1, - // (uint8_t)temp.val2}; - // s_proto->Send(R_HEATING_STATE, state); - } - /// Read one pole NTC by index (0~7). - static auto ReadPoleNtcTemperature(uart_com::DataType data) -> void { - if (data.size() != 1) { - return; - } - } - /// Read heating pad NTC (onboard ADC). - static auto ReadHeatingPadNtcTemperature(uart_com::DataType data) -> void {} + inline static RunningStateSignal s_running_state_sig{}; + static auto RunningState(uart_com::DataType data) -> void { + if (data.size() != 1) { + return; + } + s_running_state_sig(HostStatus(data[0])); + } + static auto GetId(uart_com::DataType data) -> void { + printk("handle get id"); + s_proto->Send(R_GET_ID, uart_com::DataType(s_id_buff)); }; + constexpr static std::pair kRxCallbackTable[] = { - {GET_ID, Cb::GetId}, - {RUNNING_STATE, Cb::RunningState}, - {W_HEATING_STATE, Cb::WHeatingState}, - {R_HEATING_STATE, Cb::RHeatingState}, - // Pole NTC (MCP3208) - {R_TEMP_POLE_NTC, Cb::ReadPoleNtcTemperature}, - // Heating pad NTC (ADC1) - {R_TEMP_HETING_PAD_NTC, Cb::ReadHeatingPadNtcTemperature}, + {R_GET_ID, GetId}, + {W_RUNNING_STATE, RunningState}, }; - inline static auto s_led_strip_indicator = - ZPP_DRV_GET_P(ledstrip::Indicator, DT_NODELABEL(indicator)); - inline static auto s_proto = - (uart_com::SimpleProtocal *)(DEVICE_DT_GET(DT_NODELABEL(pm_protocal))); + inline static auto s_proto = (uart_com::SimpleProtocal *)DEVICE_DT_GET( + DT_COMPAT_GET_ANY_STATUS_OKAY(uart_com_simple_protocal)); + + inline static uint8_t buff[20]; inline static auto s_heating_state = false; + inline static etl::span s_id_buff; }; } // namespace ther diff --git a/include/infrared.hpp b/include/infrared.hpp index c8eeb31..b1614ef 100644 --- a/include/infrared.hpp +++ b/include/infrared.hpp @@ -1,8 +1,10 @@ #ifndef __THER_INFRARED_HPP__ #define __THER_INFRARED_HPP__ #include -#include +#include +#include #include +#include #include #include #include @@ -13,42 +15,34 @@ namespace ther { class Infrared { public: static auto Init() -> zpp::error { - static sensor_trigger tri{.type = SENSOR_TRIG_DATA_READY, - .chan = SENSOR_CHAN_AMBIENT_TEMP}; - for (auto dev : s_dev) { - if (auto r = sensor_trigger_set( - dev, &tri, - [](const device *dev, const sensor_trigger *trig) { - const size_t id = etl::distance( - s_dev.begin(), etl::find(s_dev.begin(), s_dev.end(), dev)); - s_dev_ready[id] = 1; - }); - r != 0) { - return -ENODEV; - } + return InitImpl(std::make_index_sequence{}); + } + + /* Register a callback invoked every scan period (20 ms) with the + * hottest sensor value. Runs in the system workqueue thread context - + * keep the callback lightweight (no blocking, no SPI). */ + static auto + AddCallbackWhenSensorValueReady(etl::delegate cb) + -> zpp::error { + if (s_cb.is_valid()) { + return zpp::error_code::k_busy; } + s_cb = cb; return zpp::ok(); } - /* One-shot fetch of ALL sensors, return the hottest one. */ + + /* Hottest sensor among the latest cached samples (no polling: the + * trigger callback keeps the cache fresh). */ static auto GetMaxSensorValue() -> zpp::result> { bool found = false; int max_id = 0; sensor_value max_val{}; for (size_t id = 0; id < s_dev.size(); ++id) { - /* -ENODATA just means no new sample for this channel; keep going. */ - if (0 != sensor_sample_fetch(s_dev[id])) { + if (!s_latest_valid[id]) { continue; } - - sensor_value val; - if (0 != sensor_channel_get(s_dev[id], SENSOR_CHAN_AMBIENT_TEMP, &val)) { - s_dev_ready[id] = 0; /* sample consumed, drop the ready flag */ - continue; - } - - s_dev_ready[id] = 0; - + const sensor_value val{s_latest_val1[id], s_latest_val2[id]}; if (!found || val.val1 > max_val.val1 || (val.val1 == max_val.val1 && val.val2 > max_val.val2)) { found = true; @@ -63,11 +57,81 @@ public: return std::make_pair(max_id, max_val); } + /* Snapshot of all cached sensor values (pure memory reads, safe in + * trigger context). Channels without a sample yet read as 0. + * auto return: the body is parsed in complete-class context where the + * trailing members (s_dev, caches) are visible. */ + static auto GetAllSensorValues() { + etl::array out{}; + for (size_t id = 0; id < s_dev.size(); ++id) { + if (s_latest_valid[id]) { + out[id] = sensor_value{s_latest_val1[id], s_latest_val2[id]}; + } + } + return out; + } + private: + /* Per-sensor trigger handler; the channel index is a compile-time + * constant. Runs in the UART trigger context (workqueue thread for + * CH9438 ports, ISR context for on-chip UARTs) - keep it lock-free. */ + template + static void OnSensorDataReady(const struct device *dev, + const struct sensor_trigger *trig) { + sensor_value val; + if (0 != sensor_channel_get(dev, SENSOR_CHAN_AMBIENT_TEMP, &val)) { + printk("[infra] ch%u channel_get failed\n", (unsigned)I); + return; + } + s_latest_val1[I] = val.val1; + s_latest_val2[I] = val.val2; + s_latest_valid[I] = true; + if (s_cb.is_valid()) { + s_cb(MaxCachedValue()); /* notify with the hottest sensor value */ + } + } + + /* Latest cached max value; pure memory reads, safe in trigger context. */ + static auto MaxCachedValue() -> sensor_value { + bool found = false; + sensor_value max_val{}; + for (size_t id = 0; id < s_dev.size(); ++id) { + if (!s_latest_valid[id]) { + continue; + } + const sensor_value val{s_latest_val1[id], s_latest_val2[id]}; + if (!found || val.val1 > max_val.val1 || + (val.val1 == max_val.val1 && val.val2 > max_val.val2)) { + found = true; + max_val = val; + } + } + return max_val; + } + + template + static auto InitImpl(std::index_sequence) -> zpp::error { + static sensor_trigger tri{.type = SENSOR_TRIG_DATA_READY, + .chan = SENSOR_CHAN_AMBIENT_TEMP}; + int ret = 0; + ((printk("[infra] ch%u ready=%d\n", (unsigned)Is, + device_is_ready(s_dev[Is])), + ret |= sensor_trigger_set(s_dev[Is], &tri, &OnSensorDataReady)), + ...); + return ret != 0 ? zpp::error{-ENODEV} : zpp::ok(); + } + #define INFRARED_DEV(node) DEVICE_DT_GET(node), inline static etl::array s_dev{ DT_FOREACH_STATUS_OKAY(godtek_temp_uart, INFRARED_DEV)}; - inline static etl::bitset s_dev_ready; + /* Field-level volatile caches: written by the trigger handlers, read by + * GetMaxSensorValue. sensor_value itself cannot be volatile (no volatile + * copy/assign), so the two 32-bit fields are cached separately. A torn + * cross-field read is possible but negligible for slowly changing temps. */ + inline static volatile int32_t s_latest_val1[s_dev.size()]{}; + inline static volatile int32_t s_latest_val2[s_dev.size()]{}; + inline static volatile bool s_latest_valid[s_dev.size()]{}; + inline static etl::delegate s_cb; }; } // namespace ther diff --git a/include/led.hpp b/include/led.hpp index 5d87554..eb38254 100644 --- a/include/led.hpp +++ b/include/led.hpp @@ -7,89 +7,26 @@ #include namespace ther { -/* Compile-time check: every LED name must be distinct so that - * Led::On/Off/Flash name lookup stays unambiguous. */ -template -consteval auto AreUniqueNames(const etl::array &names) -> bool { - for (std::size_t i = 0; i < N; ++i) { - for (std::size_t j = i + 1; j < N; ++j) { - if (names[i] == names[j]) { - return false; - } - } - } - return true; -} - -#define LED_SPEC(node) led_dt_spec LED_DT_SPEC_GET(node) -#define LED_DEV_CHILD_SPEC(node) DT_FOREACH_CHILD_SEP(node, LED_SPEC, (, )), -#define LED_NODE_NAME(node) DT_FOREACH_CHILD_SEP(node, DEVICE_DT_NAME, (, )) -class Led { - -private: - template struct Periodic { - static auto Flash(led_dt_spec *spec) -> void { - bool flag = false; - if (flag) { - led_on_dt(spec); - } else { - led_off_dt(spec); - } - flag = !flag; - } - static inline zpp::periodic_work work{Flash}; - }; - +template class Led { public: - template static auto On() -> zpp::error { - static_assert(IsDeviceName()); - const auto id = etl::find(s_led_node_name.begin(), s_led_node_name.end(), - std::string_view(kName)) - - s_led_node_name.begin(); - return led_on_dt(&s_led_dev[id]); - } - template static auto Off() -> zpp::error { - static_assert(IsDeviceName()); - const auto id = etl::find(s_led_node_name.begin(), s_led_node_name.end(), - std::string_view(kName)) - - s_led_node_name.begin(); - return led_off_dt(&s_led_dev[id]); - } - template + static auto On() -> zpp::error { return led_on_dt(&kSpec); } + static auto Off() -> zpp::error { return led_off_dt(&kSpec); } + template static auto Flash(std::chrono::duration period) { - static_assert(IsDeviceName()); - const auto id = etl::find(s_led_node_name.begin(), s_led_node_name.end(), - std::string_view(kName)) - - s_led_node_name.begin(); - Periodic::work.submit(period); + s_work.submit(period); } private: - template static consteval auto IsDeviceName() { - for (size_t i = 0; i < s_led_node_name.size(); ++i) { - if (s_led_node_name[i] == std::string_view(kName)) { - return true; - } - } - return false; - } - - static auto Flash(led_dt_spec *spec) -> void { + static auto Flash() -> void { static bool flag = false; if (flag) { - led_off_dt(spec); + led_off_dt(&kSpec); } else { - led_on_dt(spec); + led_on_dt(&kSpec); } flag = !flag; } - inline static constexpr etl::array s_led_dev{ - DT_FOREACH_STATUS_OKAY(gpio_leds, LED_DEV_CHILD_SPEC)}; - inline static constexpr auto s_led_node_name = - etl::make_array( - DT_FOREACH_STATUS_OKAY(gpio_leds, LED_NODE_NAME)); - static_assert(AreUniqueNames(s_led_node_name), - "LED node names must be unique"); + static inline zpp::periodic_work<> s_work{Flash}; }; } // namespace ther diff --git a/include/watchdog.hpp b/include/watchdog.hpp index de1f4f3..ddb75fd 100644 --- a/include/watchdog.hpp +++ b/include/watchdog.hpp @@ -3,7 +3,7 @@ #include #include #include -namespace app { +namespace ther { class WatchDogConfig { public: WatchDogConfig() { @@ -28,6 +28,6 @@ private: int chan_id; }; -} // namespace app +} // namespace ther -#endif \ No newline at end of file +#endif diff --git a/prj.conf b/prj.conf index a7da3d6..9dcc75b 100644 --- a/prj.conf +++ b/prj.conf @@ -8,9 +8,15 @@ CONFIG_SERIAL=y CONFIG_UART_INTERRUPT_DRIVEN=y CONFIG_HWINFO=y + +CONFIG_LED=y + +# CONFIG_PRINTK=n CONFIG_ADC=y CONFIG_SPI=y CONFIG_SPI_STM32=y +# SPI 中断模式:传输带 1s 超时,多路高频 SPI 下不会永久挂死(与 ch9438 测试对齐) +CONFIG_SPI_STM32_INTERRUPT=y CONFIG_SENSOR=y CONFIG_ADC_MCP320X_ACQUISITION_THREAD_STACK_SIZE=2048 CONFIG_REBOOT=y diff --git a/src/led.cpp b/src/led.cpp new file mode 100644 index 0000000..2b9180c --- /dev/null +++ b/src/led.cpp @@ -0,0 +1,29 @@ +#include +#include +#include +#include +#include +using namespace ther; +namespace { +using McuState = Led; +using Inf = Led; +auto indicator = ZPP_DRV_GET_P(ledstrip::Indicator, DT_NODELABEL(indicator)); +constexpr const char *MODULE = "app_led"; +} // namespace + +static auto Init() -> int { + Com::AddRunningState([](Com::HostStatus state) { + indicator->Status(static_cast(state)); + if (state == Com::HostStatus::RUNNING) { + Inf::On(); + } else { + Inf::Off(); + } + }); + using namespace std::chrono_literals; + McuState::Flash(1s); + printk("[%s] Init: MCU state LED flashing at 1s interval\n", MODULE); + return 0; +} + +SYS_INIT(Init, APPLICATION, 50); diff --git a/src/main.cpp b/src/main.cpp index c33a442..fd9b4cb 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -1,10 +1,11 @@ +#include #include - auto main(void) -> int { + ther::Com::Init(); + printk("[main] init on %s\n", CONFIG_BOARD); while (1) { - - k_sleep(K_MSEC(20)); + k_sleep(K_SECONDS(1)); } return 0; } diff --git a/src/temp.cpp b/src/temp.cpp new file mode 100644 index 0000000..511e8a3 --- /dev/null +++ b/src/temp.cpp @@ -0,0 +1,69 @@ +#include +#include +#include +#include +#include + +using namespace ther; + +/* 调试打印开关: + * 1 = 每次触发打印全部 9 路 + 最大值 + * 0 / 注释 = 只打印最大值(生产模式) + */ +#ifndef APP_TEMP_PRINT_ALL_CHANNELS +#define APP_TEMP_PRINT_ALL_CHANNELS 0 +#endif + +namespace { +using namespace std::string_literals; +constexpr const char *MODULE = "app_temp"; +constexpr auto kPeriodSend = std::chrono::milliseconds(20); + +/* 触发回调:只做调试打印(采集/缓存由 Infrared 内部完成), + * 发送由 20ms 周期的 OnSendTick 负责。 */ +constexpr auto OnSensorValueReady = + etl::delegate::create(+[](sensor_value val) { +#if APP_TEMP_PRINT_ALL_CHANNELS + const auto all = Infrared::GetAllSensorValues(); + for (size_t i = 0; i < all.size(); ++i) { + printk("[%s]ch%u: %d.%d\n", MODULE, (unsigned)i, all[i].val1, + all[i].val2); + } + printk("[%s]max: %d.%d (%lld ms)\n", MODULE, val.val1, val.val2, + (long long)k_uptime_get()); +#endif + }); + +static void OnSendTick(struct k_work *work); +/* 每 kPeriodSend(20ms) 发送一次最新缓存的最大值。 */ +static K_WORK_DELAYABLE_DEFINE(s_send_work, OnSendTick); + +static void OnSendTick(struct k_work *work) { + if (auto r = Infrared::GetMaxSensorValue(); r.ok()) { + Com::SendTemp(r.value().second); + } + k_work_schedule(&s_send_work, K_MSEC(kPeriodSend.count())); +} +} // namespace + +static auto Init() -> int { + printk("[%s]temp init\n", MODULE); + if (auto r = Infrared::Init(); r.code_id() != zpp::error_code::k_ok) { + printk("[%s] Infrared::Init failed: %d\n", MODULE, + static_cast(r.code_id())); + return 0; + } + if (auto r = Infrared::AddCallbackWhenSensorValueReady(OnSensorValueReady); + r.code_id() != zpp::error_code::k_ok) { + printk("[%s] AddCallback failed: %d\n", MODULE, + static_cast(r.code_id())); + } + /* 启动 20ms 周期发送 */ + k_work_schedule(&s_send_work, K_MSEC(kPeriodSend.count())); + return 0; +} + +/* Register AFTER the godtek/ch9438 drivers (POST_KERNEL): registering the + * sensor trigger earlier gets wiped by the sensor driver's own Init, + * which resets data_ready_handler to nullptr. */ +SYS_INIT(Init, APPLICATION, 50); diff --git a/src/watdog.cpp b/src/watdog.cpp new file mode 100644 index 0000000..35598ea --- /dev/null +++ b/src/watdog.cpp @@ -0,0 +1,16 @@ +#include +#include +using namespace ther; +namespace { +WatchDogConfig wdt; +} // namespace + +static auto Init() -> int { + printk("[watchdog] init\n"); + using namespace std::chrono_literals; + static zpp::periodic_work<> work{[]() { wdt.Feed(); }}; + work.submit(50ms); + return 0; +} + +SYS_INIT(Init, POST_KERNEL, 50);