From e74fa5c80488ce6c5077411313590af392f0fa59 Mon Sep 17 00:00:00 2001 From: zhangyisong Date: Tue, 7 Jul 2026 19:47:30 +0800 Subject: [PATCH] feat: Add Kconfig and conditional build for hello world sample --- CMakeLists.txt | 10 ++- Kconfig | 6 ++ include/com.hpp | 35 ++++++++ include/heating.hpp | 11 +++ include/infrared.hpp | 49 ++++++++++++ include/ntc.hpp | 45 +++++++++++ prj.conf | 6 +- src/main.cpp | 89 ++++++++++++++++----- src/sample_hello_world.cpp | 18 +++++ src/sample_ntc.cpp | 1 + testcase.yaml | 8 ++ west.yml | 47 +++++++++++ zbuild.py | 160 +++++++++++++++++++++++++++++++++++++ 13 files changed, 458 insertions(+), 27 deletions(-) create mode 100644 Kconfig create mode 100644 include/com.hpp create mode 100644 include/heating.hpp create mode 100644 include/infrared.hpp create mode 100644 include/ntc.hpp create mode 100644 src/sample_hello_world.cpp create mode 100644 src/sample_ntc.cpp create mode 100644 west.yml create mode 100755 zbuild.py diff --git a/CMakeLists.txt b/CMakeLists.txt index 55b8376..27b7228 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -3,7 +3,13 @@ cmake_minimum_required(VERSION 3.20.0) find_package(Zephyr REQUIRED HINTS $ENV{ZEPHYR_BASE}) project(app_photomagnetic) + + target_include_directories(app PRIVATE include) -target_sources(app PRIVATE src/main.cpp) - +if(CONFIG_SAMPLE_HELLOWORLD) + message("CONFIG_SAMPLE_HELLOWORLD is enabled") + target_sources(app PRIVATE src/sample_hello_world.cpp) +else() + target_sources(app PRIVATE src/main.cpp) +endif() diff --git a/Kconfig b/Kconfig new file mode 100644 index 0000000..592fff9 --- /dev/null +++ b/Kconfig @@ -0,0 +1,6 @@ +menu "app thermotherapy" +source "Kconfig.zephyr" +endmenu + +config SAMPLE_HELLOWORLD + bool "Hello World sample" diff --git a/include/com.hpp b/include/com.hpp new file mode 100644 index 0000000..99581f1 --- /dev/null +++ b/include/com.hpp @@ -0,0 +1,35 @@ +#ifndef __THER_COM_HPP__ +#define __THER_COM_HPP__ + +#include +#include +#include +#include +namespace ther { + +template class Com { +public: + enum Addr : uint8_t { TEMP = 0, GET_ID, RUNNING_STATE }; + using CbTableValueType = + std::pair; + static auto Init() -> zpp::error { + s_proto->SetRxCallbackTable({GET_ID, Cb::GetId}, + {RUNNING_STATE, Cb::RunningState}); + return zpp::ok(); + } + +private: + struct Cb { + static auto GetId(uart_com::DataType data) -> void { + // TODO + } + static auto RunningState(uart_com::DataType data) -> void { + // TODO + } + }; + constexpr static uart_com::SimpleProtocal *s_proto = + (uart_com::SimpleProtocal *)(kDev); +}; +} // namespace ther + +#endif diff --git a/include/heating.hpp b/include/heating.hpp new file mode 100644 index 0000000..eafd951 --- /dev/null +++ b/include/heating.hpp @@ -0,0 +1,11 @@ +#ifndef __THER_HEATING_HPP__ +#define __THER_HEATING_HPP__ +#include + +namespace ther { +template class HeatingPad { +public: +}; + +} // namespace ther +#endif diff --git a/include/infrared.hpp b/include/infrared.hpp new file mode 100644 index 0000000..419cab8 --- /dev/null +++ b/include/infrared.hpp @@ -0,0 +1,49 @@ +#ifndef __THER_INFRARED_HPP__ +#define __THER_INFRARED_HPP__ +#include +#include +#include +#include +#include +namespace ther { + +template class Infrared { +public: + static auto Init() -> zpp::error { + static sensor_trigger tri{.type = SENSOR_TRIG_DATA_READY, + .chan = SENSOR_CHAN_AMBIENT_TEMP}; + if (auto r = sensor_trigger_set( + kDev, &tri, + [](const device *dev, const sensor_trigger *trig) { + s_is_data_ready = true; + }); + r != 0) { + return -ENODEV; + }; + } + static auto GetSensorValue() + -> zpp::result> { + if (!s_is_data_ready) { + return zpp::error_code::k_nodata; + } + + if (0 != sensor_sample_fetch(kDev)) { + return zpp::error_code::k_io; + } + + zpp::value val; + if (0 != sensor_channel_get(kDev, SENSOR_CHAN_AMBIENT_TEMP, &val)) { + return zpp::error_code::k_io; + } + + s_is_data_ready = false; + return val; + } + +private: + inline static bool s_is_data_ready{false}; +}; + +} // namespace ther + +#endif diff --git a/include/ntc.hpp b/include/ntc.hpp new file mode 100644 index 0000000..0fbcaeb --- /dev/null +++ b/include/ntc.hpp @@ -0,0 +1,45 @@ +#ifndef THER_NTC_HPP +#define THER_NTC_HPP +#include +#include +#include +#include + +namespace ther { + +template class Ntc { +public: + /// Read all NTC sensors, return the maximum temperature value. + /// Returns error if every sensor read fails. + static auto GetSensorValue() + -> zpp::result> { + bool found = false; + zpp::value max_val; + + for (const auto *dev : s_devices) { + if (0 != sensor_sample_fetch(dev)) + continue; + + zpp::value val; + if (0 != sensor_channel_get(dev, SENSOR_CHAN_AMBIENT_TEMP, &val)) + continue; + + if (!found || val.to_double() > max_val.to_double()) { + max_val = val; + found = true; + } + } + + if (found) + return max_val; + + return zpp::error_code::k_io; + } + +private: + static constexpr device *s_devices[] = {kNtcDev...}; +}; + +} // namespace ther + +#endif // THER_NTC_HPP diff --git a/prj.conf b/prj.conf index 3da563d..6d0b439 100644 --- a/prj.conf +++ b/prj.conf @@ -1,14 +1,14 @@ CONFIG_STDOUT_CONSOLE=y CONFIG_CBPRINTF_FP_SUPPORT=y -CONFIG_STD_CPP20=y +CONFIG_STD_CPP23=y CONFIG_CPP=y CONFIG_REQUIRES_FULL_LIBCPP=y CONFIG_CONSOLE=y CONFIG_SERIAL=y CONFIG_UART_INTERRUPT_DRIVEN=y -CONFIG_PMC_COM=y CONFIG_HWINFO=y +CONFIG_ADC=y CONFIG_REBOOT=y -CONFIG_WATCHDOG=y \ No newline at end of file +CONFIG_WATCHDOG=y diff --git a/src/main.cpp b/src/main.cpp index ccc0a35..64e2905 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -3,47 +3,85 @@ #include #include -#include -#include -#include - #include "watchdog.hpp" #include "zephyr/drivers/gpio.h" #include "zephyr/kernel.h" #include "zpp/fmt.hpp" #include "zpp/timer.hpp" +#include +#include +#include +#include +#include namespace { +constexpr uart_com::SimpleProtocal::CallbackType kCbTable[] = {}; + auto sensor = DEVICE_DT_GET(DT_NODELABEL(godtek)); -auto &pmc = ZPP_DRV_GET(uart_com::Protocal, DT_NODELABEL(pm_protocal)); +auto &pmc = + *(uart_com::SimpleProtocal *)(DEVICE_DT_GET(DT_NODELABEL(pm_protocal))); + auto &led_strip = ZPP_DRV_GET(ledstrip::Indicator, DT_NODELABEL(indicator)); enum Command { kTemp = 0x00, kGetId, kRunningState }; enum RunningState {}; constexpr auto kDeviceIdSize = 20; -enum StatusId : uint8_t { kStandby, kRunning, kPause, kError}; -volatile bool flag{false}; +enum StatusId : uint8_t { kStandby, kRunning, kPause, kError }; gpio_dt_spec led_g = GPIO_DT_SPEC_GET(DT_NODELABEL(led_g), gpios); k_timer led_timer; -} // namespace -auto main(void) -> int { - gpio_pin_configure_dt(&led_g, GPIO_OUTPUT_ACTIVE); - k_timer_init(&led_timer, [](k_timer* tim) { - gpio_pin_toggle_dt(&led_g); - }, [](k_timer* tim) {}); - k_timer_start(&led_timer, K_NO_WAIT, K_SECONDS(1)); - pmc.SetRxCallback(kGetId, [](uart_com::DataType data) -> void { +#define GET_NTC_DRV(node) DEVICE_DT_GET(node), +const device *ntc_sensor[] = { + DT_FOREACH_CHILD(DT_NODELABEL(mcp3208), GET_NTC_DRV)}; + +struct Cb { + static auto GetId(uart_com::DataType data) -> void { uint8_t buffer[kDeviceIdSize]; auto size = hwinfo_get_device_id(buffer, sizeof(buffer)); pmc.Send(kGetId, uart_com::DataType(buffer, size)); - }); - - pmc.SetRxCallback(kRunningState, [](uart_com::DataType data) -> void { + } + static auto RunningState(uart_com::DataType data) -> void { auto state = static_cast(data[0]); pmc.Send( kRunningState, uart_com::DataType(reinterpret_cast(&state), sizeof(state))); led_strip.Status(state).on_error([](zpp::error_code code) {}); - }); + } + static constexpr std::pair + kCbTable[] = { + {kGetId, GetId}, + {kRunningState, RunningState}, + }; +}; +enum SensorType { + NTC0 = 0, + NTC1, + NTC2, + NTC3, + INFRARED, +}; +std::bitset<5> bs; +auto SensorTriggerCallback(const device *dev, const sensor_trigger *trig) + -> void { + if (dev == sensor) { + bs.set(NTC0); + } +} +auto InitSensors() { + static const sensor_trigger tri{.chan = SENSOR_CHAN_AMBIENT_TEMP, + .type = SENSOR_TRIG_DATA_READY}; + sensor_trigger_set(sensor, &tri, SensorTriggerCallback); +} + +} // namespace + +auto main(void) -> int { + gpio_pin_configure_dt(&led_g, GPIO_OUTPUT_ACTIVE); + k_timer_init( + &led_timer, [](k_timer *tim) { gpio_pin_toggle_dt(&led_g); }, + [](k_timer *tim) {}); + k_timer_start(&led_timer, K_NO_WAIT, K_SECONDS(1)); + pmc.SetRxCallbackTable(Cb::kCbTable); + auto wdt = app::WatchDogConfig{}; sensor_trigger tri{.type = SENSOR_TRIG_DATA_READY, .chan = SENSOR_CHAN_AMBIENT_TEMP}; @@ -61,12 +99,19 @@ auto main(void) -> int { sensor_channel_get(sensor, SENSOR_CHAN_AMBIENT_TEMP, &val); const uint8_t data[2] = {static_cast(val.val1), static_cast(val.val2)}; - pmc.Send("temp", data); + pmc.Send(kTemp, data); + } + for (auto ntc: ntc_sensor) { + if (sensor_sample_fetch_chan(ntc, SENSOR_CHAN_AMBIENT_TEMP) == 0) { + sensor_channel_get(ntc, SENSOR_CHAN_AMBIENT_TEMP, &val); + const uint8_t data[2] = {static_cast(val.val1), + static_cast(val.val2)}; + pmc.Send(kTemp, data); + } } - flag = false; } wdt.Feed(); } return 0; -} \ No newline at end of file +} diff --git a/src/sample_hello_world.cpp b/src/sample_hello_world.cpp new file mode 100644 index 0000000..98e3169 --- /dev/null +++ b/src/sample_hello_world.cpp @@ -0,0 +1,18 @@ +#include +#include + +led_dt_spec status = LED_DT_SPEC_GET(DT_NODELABEL(status_led)); +auto main() -> int { + printk("hello world %s\n", CONFIG_BOARD); + printk("hello world %s\n", CONFIG_BOARD); + printk("hello world %s\n", CONFIG_BOARD); + printk("hello world %s\n", CONFIG_BOARD); + while (true) { + printk("hello world\n"); + led_on_dt(&status); + k_sleep(K_MSEC(1000)); + led_off_dt(&status); + k_sleep(K_MSEC(1000)); + } + return 0; +} diff --git a/src/sample_ntc.cpp b/src/sample_ntc.cpp new file mode 100644 index 0000000..742d5b3 --- /dev/null +++ b/src/sample_ntc.cpp @@ -0,0 +1 @@ +auto main() -> int {} diff --git a/testcase.yaml b/testcase.yaml index e69de29..e8aad92 100644 --- a/testcase.yaml +++ b/testcase.yaml @@ -0,0 +1,8 @@ +common: + platform: + - native_sim/native/64 + - nucleo_f429zi +tests: + sample.helloworld: + extra_configs: + - CONFIG_SAMPLE_HELLOWORLD=y diff --git a/west.yml b/west.yml new file mode 100644 index 0000000..7e687ad --- /dev/null +++ b/west.yml @@ -0,0 +1,47 @@ +manifest: + projects: + - import: + name-allowlist: + - cmsis_6 + - hal_stm32 + - hal_ti + path-prefix: extern + name: zephyr + path: zephyr + remote: zephyr + revision: v4.4.0 + - name: etl + path: modules/etl + remote: robotstorm + revision: master + - name: uart_com + path: modules/uart_com + remote: robotstorm + revision: main + - name: zpp + path: modules/zpp + remote: robotstorm + revision: dev + - name: dr2501a_g070rb + path: boards/dr2501a_g070rb + remote: robotstorm + revision: main + - name: led_strip_indicator + path: modules/led_strip_indicator + remote: robotstorm + revision: main + - name: godtek_temp + path: modules/godtek_temp + remote: robotstorm + revision: main + - name: heading_pad + path: modules/heading_pad + revision: main + url: undefined + remotes: + - name: zephyr + url-base: https://gitcode.com/gh_mirrors/ze + - name: robotstorm + url-base: https://git.robotstorm.tech/EmbeddedTeam + self: + west-commands: scripts/west-commands.yml diff --git a/zbuild.py b/zbuild.py new file mode 100755 index 0000000..c96b28c --- /dev/null +++ b/zbuild.py @@ -0,0 +1,160 @@ +#!/usr/bin/env python3 +""" +Zephyr build helper for app_i_core_link. + +Usage: + python zbuild.py dr2501a_g070rb + python zbuild.py dr2501a_g070rb -p auto + python zbuild.py -p always (auto-detect board from west.yml) + +Options: + board Board name (optional, auto-detected from west.yml if omitted) + -p PURSE Pristine option: auto / always / never + -t TARGET CMake target to run after build, e.g. test / flash + +Steps: + 1. west topdir → find Zephyr workspace root + 2. west config --local manifest.file /west.yml + 3. west build [-p PURSE] -b BOARD [-t TARGET] +""" + +import argparse +import os +import re +import subprocess +import sys + + +def get_script_dir() -> str: + """Absolute path to the directory containing this script.""" + return os.path.dirname(os.path.abspath(__file__)) + + +def run(cmd: list[str], cwd: str | None = None) -> str: + """Run a command and return stdout. Exit on failure.""" + proc = subprocess.run(cmd, capture_output=True, text=True, cwd=cwd) + if proc.returncode != 0: + print(f" ✗ {' '.join(cmd)}", file=sys.stderr) + print(f" {proc.stderr.strip()}", file=sys.stderr) + sys.exit(proc.returncode) + return proc.stdout.strip() + + +def discover_boards(manifest_path: str) -> list[str]: + """Parse west.yml and return board names whose path starts with 'boards/'.""" + if not os.path.exists(manifest_path): + return [] + + with open(manifest_path) as f: + content = f.read() + + # Match entries like: + # - name: + # path: boards/ + boards = re.findall(r"name:\s*(\S+)\s*\n\s+path:\s*boards/\S+", content) + return boards + + +def main(): + parser = argparse.ArgumentParser( + description="Zephyr build helper for app_i_core_link" + ) + parser.add_argument( + "board", + nargs="?", + default=None, + help="Board name (optional, auto-detected from west.yml if omitted)", + ) + parser.add_argument( + "-p", + "--pristine", + default=None, + choices=["auto", "always", "never"], + help="Pristine build option", + ) + parser.add_argument( + "-t", + "--target", + default=None, + help="CMake target to run after build (e.g. test, flash)", + ) + parser.add_argument( + "--update", + action="store_true", + help="Run west update after setting manifest", + ) + args = parser.parse_args() + + # ── 0. Auto-detect board from west.yml when not specified ─── + if args.board is None: + script_dir = get_script_dir() + manifest = os.path.join(script_dir, "west.yml") + boards = discover_boards(manifest) + + if len(boards) == 0: + print( + " ✗ No board specified and no board found in west.yml " + "(no entry with path: boards/...)", + file=sys.stderr, + ) + sys.exit(1) + elif len(boards) == 1: + args.board = boards[0] + print(f" → Auto-detected board: {args.board}") + else: + print( + " ✗ No board specified. Multiple boards found in west.yml:", + ", ".join(boards), + "\n Please specify one explicitly.", + file=sys.stderr, + ) + sys.exit(1) + + script_dir = get_script_dir() + manifest = os.path.join(script_dir, "west.yml") + + print(f" script dir : {script_dir}") + print(f" board : {args.board}") + print(f" pristine : {args.pristine or '(none)'}") + print(f" target : {args.target or '(none)'}") + print(f" update : {'yes' if args.update else 'no'}") + + # ── 1. Find workspace root ─────────────────────────────── + print("\n [1/3] Finding west workspace root...") + topdir = run(["west", "topdir"]) + print(f" → {topdir}") + + # ── 2. Set local manifest ──────────────────────────────── + print("\n [2/3] Setting local manifest...") + if not os.path.exists(manifest): + print(f" ✗ manifest not found: {manifest}", file=sys.stderr) + sys.exit(1) + run(["west", "config", "--local", "manifest.file", manifest], cwd=topdir) + print(f" → manifest.file = {manifest}") + + # ── 2.5. Run west update (optional) ─────────────────────── + if args.update: + print("\n [2.5/3] Running west update...") + run(["west", "update", "--fetch", "smart"], cwd=topdir) + print(" → update done") + + # ── 3. Run west build ───────────────────────────────────── + print("\n [3/3] Running west build...") + build_cmd = ["west", "build"] + if args.pristine: + build_cmd += ["-p", args.pristine] + build_cmd += ["-b", args.board] + if args.target: + build_cmd += ["-t", args.target] + build_cmd += [script_dir] + + print(f" $ {' '.join(build_cmd)}") + proc = subprocess.run(build_cmd, cwd=topdir) + if proc.returncode != 0: + print(f"\n ✗ Build failed (exit code {proc.returncode})") + sys.exit(proc.returncode) + print("\n ✓ Build succeeded") + + +if __name__ == "__main__": + main()