Add NTC sample configuration and PID temperature control

- Add CONFIG_SAMPLE_NTC to CMakeLists.txt and Kconfig
- Implement PI temperature controller with PWM output in heating.hpp
- Convert NtcGroup from template to non-template class
- Add LED helper class with flash support
- Implement COM protocol handlers for device ID and running state
- Add SPI, sensor, and PWM configuration to prj.conf
- Remove indicator overlay and update main.cpp with temperature
  monitoring
- Add test script for serial protocol communication
This commit is contained in:
zhangyisong 2026-07-09 11:06:11 +08:00
parent 1afea0b932
commit eaa5d0c28b
13 changed files with 631 additions and 48 deletions

View File

@ -10,6 +10,10 @@ target_include_directories(app PRIVATE include)
if(CONFIG_SAMPLE_HELLOWORLD) if(CONFIG_SAMPLE_HELLOWORLD)
message("CONFIG_SAMPLE_HELLOWORLD is enabled") message("CONFIG_SAMPLE_HELLOWORLD is enabled")
target_sources(app PRIVATE src/sample_hello_world.cpp) target_sources(app PRIVATE src/sample_hello_world.cpp)
elseif(CONFIG_SAMPLE_NTC)
message("CONFIG_SAMPLE_NTC is enabled")
target_sources(app PRIVATE src/sample_ntc.cpp)
else() else()
message("build main app")
target_sources(app PRIVATE src/main.cpp) target_sources(app PRIVATE src/main.cpp)
endif() endif()

View File

@ -4,3 +4,5 @@ endmenu
config SAMPLE_HELLOWORLD config SAMPLE_HELLOWORLD
bool "Hello World sample" bool "Hello World sample"
config SAMPLE_NTC
bool "NTC sample"

View File

@ -1,18 +0,0 @@
&indicator {
led-strip = <&led_strip>;
compatible = "led-strip-indicator";
en-gpios = <&gpiob 13 GPIO_ACTIVE_HIGH>;
standby {
rgb = <0 0 255>; // Blue
};
running {
rgb = <0 255 0>; // Green
};
pause {
rgb = <254 254 0>;
// interval-ms = <500>;
};
error {
rgb = <255 0 0>; //Red
};
};

View File

@ -1,34 +1,59 @@
#ifndef __THER_COM_HPP__ #ifndef __THER_COM_HPP__
#define __THER_COM_HPP__ #define __THER_COM_HPP__
#include "led.hpp"
#include <led_strip_indicator/led_strip_indicator.hpp> #include <led_strip_indicator/led_strip_indicator.hpp>
#include <uart_com/simple_protocal.hpp> #include <uart_com/simple_protocal.hpp>
#include <zephyr/drivers/hwinfo.h>
#include <zephyr/drivers/sensor.h>
#include <zpp/driver.hpp> #include <zpp/driver.hpp>
#include <zpp/error.hpp> #include <zpp/error.hpp>
namespace ther { namespace ther {
template <device *kDev> class Com { class Com {
public: public:
enum Addr : uint8_t { TEMP = 0, GET_ID, RUNNING_STATE };
using CbTableValueType =
std::pair<const uint8_t, void (*)(uart_com::DataType)>;
static auto Init() -> zpp::error { static auto Init() -> zpp::error {
s_proto->SetRxCallbackTable({GET_ID, Cb::GetId}, s_proto->SetRxCallbackTable(kRxCallbackTable);
{RUNNING_STATE, Cb::RunningState});
return zpp::ok(); return zpp::ok();
} }
static auto Send(sensor_value val) -> void {
const uint8_t temp_data[] = {(uint8_t)val.val1, (uint8_t)val.val2};
s_proto->Send(TEMP, temp_data);
}
private: private:
enum HostStatus : uint8_t { STANDBY, RUNNING, PAUSE, ERROR };
enum Addr : uint8_t { TEMP = 0, GET_ID, RUNNING_STATE };
using CbTableValueType =
std::pair<const uint8_t, void (*)(uart_com::DataType)>;
struct Cb { struct Cb {
static auto GetId(uart_com::DataType data) -> void { static auto GetId(uart_com::DataType data) -> void {
// TODO 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 { static auto RunningState(uart_com::DataType data) -> void {
// TODO if (data.size() != 1) {
return;
}
if (data[0] == RUNNING) {
InfLed::On();
} else {
InfLed::Off();
}
s_led_strip_indicator->Status(data[0]);
} }
}; };
constexpr static uart_com::SimpleProtocal *s_proto = constexpr static std::pair<const uint8_t,
(uart_com::SimpleProtocal *)(kDev); uart_com::SimpleProtocal::CallbackType>
kRxCallbackTable[] = {{GET_ID, Cb::GetId},
{RUNNING_STATE, Cb::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)));
using InfLed = ther::Led<LED_DT_SPEC_GET(DT_NODELABEL(inf_led))>;
}; };
} // namespace ther } // namespace ther

View File

@ -1,11 +1,154 @@
#ifndef __THER_HEATING_HPP__ #ifndef __THER_HEATING_HPP__
#define __THER_HEATING_HPP__ #define __THER_HEATING_HPP__
#include <algorithm>
#include <zephyr/device.h> #include <zephyr/device.h>
#include <zephyr/drivers/pwm.h>
#include <zephyr/drivers/sensor.h>
#include <zephyr/kernel.h>
#include <zephyr/sys/printk.h>
namespace ther { namespace ther {
template <const device *kDev> class HeatingPad {
class HeatingPad {
public: public:
/// Start PID temperature control toward the given target.
static auto Start(sensor_value target_temp) -> void {
s_target = sensor_value_to_double(&target_temp);
s_integral = 0.0;
s_last_error = 0.0;
s_last_time = k_uptime_get();
printk("[heat] start target=%.1f°C\n", s_target);
// Check PWM device
if (!device_is_ready(s_pwm_spec.dev)) {
printk("[heat] ERR: PWM device not ready\n");
return;
}
printk("[heat] PWM dev=%s ch=%d period=%u ns\n", s_pwm_spec.dev->name,
s_pwm_spec.channel, s_pwm_spec.period);
// Check sensor
if (!device_is_ready(s_temp_sensor)) {
printk("[heat] ERR: temp sensor not ready\n");
return;
}
printk("[heat] temp sensor OK\n");
// Test: set 50% duty immediately to verify PWM works
const uint32_t pulse_50 = s_pwm_spec.period / 2;
int ret = pwm_set_pulse_dt(&s_pwm_spec, pulse_50);
printk("[heat] test 50%% duty pulse=%u ret=%d\n", pulse_50, ret);
k_timer_init(&s_timer, Tick, nullptr);
k_timer_start(&s_timer, K_NO_WAIT, K_MSEC(kUpdatePeriodMs));
s_active = true;
}
static auto Start() {
const uint32_t pulse_50 = s_pwm_spec.period / 2;
int ret = pwm_set_pulse_dt(&s_pwm_spec, pulse_50);
return ret;
}
/// Stop PID control and turn off heating.
static auto Stop() -> void {
printk("[heat] stop\n");
s_active = false;
k_timer_stop(&s_timer);
pwm_set_pulse_dt(&s_pwm_spec, 0);
}
/// Change the target temperature while keeping PID running.
static auto SetTarget(sensor_value target_temp) -> void {
s_target = sensor_value_to_double(&target_temp);
printk("[heat] new target=%.1f°C\n", s_target);
}
private:
static constexpr uint32_t kPeriodNs = PWM_KHZ(5);
static constexpr uint32_t kUpdatePeriodMs = 100;
static constexpr double kOutMin = 0.0;
static constexpr double kOutMax = 1.0;
// ── PI gains (tune these) ─────────────────────────
static constexpr double kKp = 2.0; // proportional
static constexpr double kKi = 0.02; // integral
static constexpr double kKd = 0.0; // derivative (not needed for heating-only)
inline static bool s_active{false};
inline static double s_target{0.0};
inline static double s_integral{0.0};
inline static double s_last_error{0.0};
inline static int64_t s_last_time{0};
inline static pwm_dt_spec s_pwm_spec = {
.dev = DEVICE_DT_GET(DT_NODELABEL(pwm1)),
.channel = 1,
.period = PWM_KHZ(5),
.flags = PWM_POLARITY_NORMAL,
};
inline static const device *s_temp_sensor =
DEVICE_DT_GET(DT_NODELABEL(heating_pad_ntc));
inline static k_timer s_timer;
/// Called periodically by the timer.
static auto Tick(k_timer * /*timer*/) -> void {
double current_temp;
if (ReadTemperature(current_temp) != 0) {
printk("[heat] ERR: read temp failed\n");
return;
}
printk("[heat] tick: cur=%.1f°C target=%.1f°C\n", current_temp, s_target);
// PI computation
const int64_t now = k_uptime_get();
const double dt = static_cast<double>(now - s_last_time) / 1000.0;
s_last_time = now;
const double error = s_target - current_temp;
// Proportional
const double p = kKp * error;
// Integral with anti-windup
s_integral += kKi * error * dt;
s_integral = std::clamp(s_integral, kOutMin, kOutMax);
s_last_error = error;
// Compute output, clamp to [0, 1]
double output = p + s_integral;
output = std::clamp(output, kOutMin, kOutMax);
// Apply PWM duty cycle
const uint32_t pulse = static_cast<uint32_t>(output * kPeriodNs);
printk("[heat] PID out=%.2f%% pulse=%u / %u\n", output * 100.0, pulse,
s_pwm_spec.period);
pwm_set_pulse_dt(&s_pwm_spec, pulse);
}
/// Read temperature from the NTC sensor into `out_temp` (in °C).
static auto ReadTemperature(double &out_temp) -> int {
if (0 != sensor_sample_fetch(s_temp_sensor)) {
printk("[heat] ERR: sensor fetch failed\n");
return -EIO;
}
sensor_value val{};
if (0 !=
sensor_channel_get(s_temp_sensor, SENSOR_CHAN_AMBIENT_TEMP, &val)) {
printk("[heat] ERR: channel_get failed\n");
return -EIO;
}
out_temp = sensor_value_to_double(&val);
return 0;
}
}; };
} // namespace ther } // namespace ther
#endif #endif

29
include/led.hpp Normal file
View File

@ -0,0 +1,29 @@
#ifndef __THER_LED_HPP__
#define __THER_LED_HPP__
#include <zephyr/drivers/led.h>
#include <zpp/work_queue.hpp>
namespace ther {
template <led_dt_spec kSpec> class Led {
public:
static auto On() { led_on_dt(&kSpec); }
static auto Off() { led_off_dt(&kSpec); }
template <typename TRep, typename TPeriod>
static auto Flash(std::chrono::duration<TRep, TPeriod> period) {
s_work.submit(period);
}
private:
inline static zpp::periodic_work<> s_work{[]() {
static bool is_on = false;
if (is_on) {
Off();
} else {
On();
}
is_on = !is_on;
}};
};
} // namespace ther
#endif

View File

@ -1,43 +1,55 @@
#ifndef THER_NTC_HPP #ifndef THER_NTC_HPP
#define THER_NTC_HPP #define THER_NTC_HPP
#include <cmath>
#include <zephyr/device.h> #include <zephyr/device.h>
#include <zephyr/drivers/sensor.h> #include <zephyr/drivers/sensor.h>
#include <zpp/result.hpp> #include <zpp/result.hpp>
#include <zpp/value.hpp>
namespace ther { namespace ther {
template <device *...kNtcDev> class Ntc { class NtcGroup {
public: public:
/// Read all NTC sensors, return the maximum temperature value. /// Read all NTC sensors, return the maximum temperature value.
/// Returns error if every sensor read fails. /// Returns error if every sensor read fails.
static auto GetSensorValue() static auto GetSensorValue() -> zpp::result<sensor_value> {
-> zpp::result<zpp::value<zpp::value_type::AMBIENT_TEMP>> {
bool found = false; bool found = false;
zpp::value<zpp::value_type::AMBIENT_TEMP> max_val; sensor_value max_val{};
for (const auto *dev : s_devices) { for (const auto *dev : s_devices) {
if (0 != sensor_sample_fetch(dev)) if (0 != sensor_sample_fetch(dev)) {
continue; continue;
}
zpp::value<zpp::value_type::AMBIENT_TEMP> val; sensor_value val{};
if (0 != sensor_channel_get(dev, SENSOR_CHAN_AMBIENT_TEMP, &val)) if (0 != sensor_channel_get(dev, SENSOR_CHAN_AMBIENT_TEMP, &val)) {
continue; continue;
}
if (!found || val.to_double() > max_val.to_double()) { if (!found || val.val1 > max_val.val1 ||
(val.val1 == max_val.val1 && val.val2 > max_val.val2)) {
max_val = val; max_val = val;
found = true; found = true;
} }
} }
if (found) if (found) {
return max_val; return max_val;
}
return zpp::error_code::k_io; return zpp::error_code::k_io;
} }
private: private:
static constexpr device *s_devices[] = {kNtcDev...}; inline static const device *s_devices[] = {
DEVICE_DT_GET(DT_NODELABEL(pole_ntc1)),
DEVICE_DT_GET(DT_NODELABEL(pole_ntc2)),
DEVICE_DT_GET(DT_NODELABEL(pole_ntc3)),
DEVICE_DT_GET(DT_NODELABEL(pole_ntc4)),
DEVICE_DT_GET(DT_NODELABEL(pole_ntc5)),
DEVICE_DT_GET(DT_NODELABEL(pole_ntc6)),
DEVICE_DT_GET(DT_NODELABEL(pole_ntc7)),
DEVICE_DT_GET(DT_NODELABEL(pole_ntc8)),
};
}; };
} // namespace ther } // namespace ther

View File

@ -9,5 +9,16 @@ CONFIG_UART_INTERRUPT_DRIVEN=y
CONFIG_HWINFO=y CONFIG_HWINFO=y
CONFIG_ADC=y CONFIG_ADC=y
CONFIG_SPI=y
CONFIG_SPI_STM32=y
CONFIG_SENSOR=y
CONFIG_ADC_MCP320X_ACQUISITION_THREAD_STACK_SIZE=2048
CONFIG_REBOOT=y CONFIG_REBOOT=y
CONFIG_WATCHDOG=y CONFIG_WATCHDOG=y
CONFIG_PWM=y
# # Debug logging
# CONFIG_LOG=y
# CONFIG_LOG_MODE_IMMEDIATE=y
# CONFIG_ADC_LOG_LEVEL_DBG=y
# CONFIG_SENSOR_LOG_LEVEL_DBG=y
# CONFIG_SPI_LOG_LEVEL_DBG=y

299
scripts/test.py Normal file
View File

@ -0,0 +1,299 @@
#!/usr/bin/env python3
"""
Test script for app_photomagnetic communication protocol.
Frame format (SimpleProtocal, per DTS config):
H0(0x7E) H1(0xE7) CMD(1B) LEN(1B) DATA[0..N] CRC_LO CRC_HI
CRC = CRC-16 Modbus over CMD+LEN+DATA, LSB-MSB order
Usage:
python test.py /dev/ttyUSB0 --get-id (request device ID)
python test.py /dev/ttyUSB0 --state 1 (set running state)
python test.py /dev/ttyUSB0 --monitor (listen for temp data)
python test.py /dev/ttyUSB0 --baud 115200 --loop (poll ID in a loop)
"""
import argparse
import struct
import sys
import time
try:
import serial
except ImportError:
print("Please install pyserial: pip install pyserial", file=sys.stderr)
sys.exit(1)
# ── Protocol constants ────────────────────────────────────────
HEADER = b"\x7e\xe7" # 2-byte header (DTS: header = <0x7EE7>)
HEADER_SIZE = len(HEADER)
CMD_TEMP = 0x00
CMD_GET_ID = 0x01
CMD_RUNNING_STATE = 0x02
STATE_NAMES = {
0x00: "Standby",
0x01: "Running",
0x02: "Pause",
0x03: "Fault",
0x04: "Upgrade",
0x05: "EStop",
0x06: "Exception",
}
# ── CRC-16 Modbus ─────────────────────────────────────────────
def crc16_modbus(data: bytes) -> int:
"""CRC-16 Modbus (polynomial 0xA001, init 0xFFFF)."""
crc = 0xFFFF
for byte in data:
crc ^= byte
for _ in range(8):
if crc & 1:
crc = (crc >> 1) ^ 0xA001
else:
crc >>= 1
return crc & 0xFFFF
# ── Frame helpers ─────────────────────────────────────────────
def build_frame(cmd: int, payload: bytes = b"") -> bytes:
"""Build a SimpleProtocal frame with LSB-MSB CRC.
Frame: H0 H1 CMD LEN DATA[0..N] CRC_LO CRC_HI"""
body = bytes([cmd, len(payload)]) + payload
crc = crc16_modbus(body)
crc_lo = crc & 0xFF
crc_hi = (crc >> 8) & 0xFF
return HEADER + body + bytes([crc_lo, crc_hi])
def parse_frame(frame: bytes) -> tuple[int, bytes] | None:
"""Parse a SimpleProtocal frame with 2-byte header + LSB-MSB CRC.
Returns (cmd, data) or None on error."""
min_len = HEADER_SIZE + 2 + 2 # HDR(2) + CMD(1) + LEN(1) + CRC(2) = 6
if len(frame) < min_len:
return None
if frame[:HEADER_SIZE] != HEADER:
return None
cmd = frame[HEADER_SIZE]
length = frame[HEADER_SIZE + 1]
expected = HEADER_SIZE + 2 + length + 2 # HDR + CMD+LEN + DATA + CRC
if len(frame) != expected:
return None
data = frame[HEADER_SIZE + 2 : HEADER_SIZE + 2 + length]
# CRC covers CMD + LEN + DATA (LSB-MSB order on wire)
crc_body = frame[HEADER_SIZE : HEADER_SIZE + 2 + length]
actual_crc = crc16_modbus(crc_body)
crc_lo = frame[HEADER_SIZE + 2 + length]
crc_hi = frame[HEADER_SIZE + 2 + length + 1]
wire_crc = (crc_hi << 8) | crc_lo
if actual_crc != wire_crc:
return None
return cmd, data
# ── Serial reader ─────────────────────────────────────────────
class ProtocolReader:
"""State-machine frame reader for 2-byte header protocol."""
def __init__(self, ser: serial.Serial):
self._ser = ser
self._buf = bytearray()
def read_frame(self, timeout: float = 1.0) -> bytes | None:
"""Read one complete frame. Returns raw frame bytes or None on timeout."""
min_frame = HEADER_SIZE + 2 + 2 # HDR + CMD + LEN + CRC = 6
deadline = time.time() + timeout
while time.time() < deadline:
# Look for header
if len(self._buf) >= 2:
idx = self._buf.find(HEADER)
if idx > 0:
del self._buf[:idx] # discard bytes before header
elif idx < 0 and len(self._buf) > 0:
self._buf.clear()
# Have a header → try to parse frame length
if len(self._buf) >= HEADER_SIZE:
if self._buf[:HEADER_SIZE] == HEADER:
if len(self._buf) >= min_frame:
length = self._buf[HEADER_SIZE + 1]
total = HEADER_SIZE + 2 + length + 2
if len(self._buf) >= total:
frame = bytes(self._buf[:total])
del self._buf[:total]
return frame
else:
# Wrong header, discard first byte and retry
del self._buf[0]
continue
# Read more bytes
try:
chunk = self._ser.read(self._ser.in_waiting or 1)
if chunk:
self._buf.extend(chunk)
except (serial.SerialTimeoutException, serial.SerialException):
pass
return None
# ── Command handlers ──────────────────────────────────────────
def cmd_get_id(ser: serial.Serial, timeout: float = 2.0):
"""Send GET_ID request and print response."""
print("→ Sending GET_ID...")
ser.write(build_frame(CMD_GET_ID))
reader = ProtocolReader(ser)
frame = reader.read_frame(timeout)
if frame is None:
print("✗ No response (timeout)")
return
result = parse_frame(frame)
if result is None:
print(f"✗ Invalid frame: {frame.hex()}")
return
cmd, data = result
if cmd != CMD_GET_ID:
print(f"✗ Unexpected cmd=0x{cmd:02X} (expected 0x{CMD_GET_ID:02X})")
return
print(f"✓ Device ID ({len(data)} bytes): {data.hex(' ')}")
def cmd_set_state(ser: serial.Serial, state: int):
"""Send RUNNING_STATE command."""
name = STATE_NAMES.get(state, "Unknown")
print(f"→ Setting state: {name} (0x{state:02X})")
ser.write(build_frame(CMD_RUNNING_STATE, bytes([state])))
print(f"✓ Sent")
def cmd_monitor(ser: serial.Serial):
"""Listen for incoming frames (TEMP data) and print them."""
print("Monitoring... (Ctrl+C to stop)")
reader = ProtocolReader(ser)
try:
while True:
frame = reader.read_frame(timeout=0.5)
if frame is None:
continue
result = parse_frame(frame)
if result is None:
print(f"⚠ Bad frame: {frame.hex()}")
continue
cmd, data = result
if cmd == CMD_TEMP:
if len(data) >= 2:
# sensor_value: val1 (int8), val2 (int8)
t = data[0]
frac = data[1] / 100.0
temp = t + frac
print(f"🌡 Temp: {temp:.2f} °C (raw: {data.hex(' ')})")
else:
print(f"⚠ TEMP with invalid data: {data.hex()}")
elif cmd == CMD_GET_ID:
print(f"🆔 Device ID response: {data.hex(' ')}")
elif cmd == CMD_RUNNING_STATE:
print(f"🔁 Running state echo: 0x{data.hex()}")
else:
print(f"📦 Unknown cmd=0x{cmd:02X} data={data.hex()}")
except KeyboardInterrupt:
print("\nDone.")
def cmd_loop(ser: serial.Serial, count: int = 0):
"""Send GET_ID in a loop."""
i = 0
print(f"Looping GET_ID... (Ctrl+C to stop)")
try:
while count == 0 or i < count:
cmd_get_id(ser, timeout=1.0)
i += 1
time.sleep(0.5)
except KeyboardInterrupt:
print("\nDone.")
# ── Main ──────────────────────────────────────────────────────
def main():
parser = argparse.ArgumentParser(
description="Test script for app_photomagnetic communication protocol"
)
parser.add_argument("port", help="Serial port (e.g. /dev/ttyUSB0, COM3)")
parser.add_argument(
"--baud", type=int, default=115200, help="Baud rate (default: 115200)"
)
parser.add_argument(
"--timeout", type=float, default=0.1, help="Serial timeout in seconds"
)
parser.add_argument(
"--get-id", action="store_true", help="Send GET_ID and print response"
)
parser.add_argument(
"--state",
type=lambda x: int(x, 0),
metavar="N",
help="Set running state (0=Standby, 1=Running, 2=Pause, ...)",
)
parser.add_argument(
"--monitor", action="store_true", help="Listen for incoming frames"
)
parser.add_argument(
"--loop",
type=int,
nargs="?",
const=0,
metavar="N",
help="Send GET_ID in a loop (N times, or infinite if omitted)",
)
parser.add_argument(
"--raw",
type=lambda x: bytes.fromhex(x),
metavar="HEX",
help="Send raw payload (CMD DATA..., CRC auto-appended)",
)
parser.add_argument("--debug", action="store_true", help="Show raw frame hex")
args = parser.parse_args()
ser = serial.Serial(args.port, args.baud, timeout=args.timeout)
print(f"Connected to {args.port} @ {args.baud} baud")
try:
if args.get_id:
cmd_get_id(ser)
elif args.state is not None:
cmd_set_state(ser, args.state)
elif args.monitor:
cmd_monitor(ser)
elif args.loop is not None:
cmd_loop(ser, args.loop)
elif args.raw is not None:
payload = args.raw
cmd, data = payload[0], payload[1:] if len(payload) > 1 else b""
print(f"→ Sending CMD=0x{cmd:02X} data={data.hex()}")
frame = build_frame(cmd, data)
print(f" Frame: {frame.hex()}")
ser.write(frame)
print(f"✓ Sent")
else:
print("No action. Use --get-id, --state N, --monitor, --loop, or --raw")
finally:
ser.close()
if __name__ == "__main__":
main()

View File

@ -1,16 +1,63 @@
#include <com.hpp> #include <com.hpp>
#include <heating.hpp> #include <heating.hpp>
#include <infrared.hpp> #include <infrared.hpp>
#include <led.hpp>
#include <ntc.hpp> #include <ntc.hpp>
#include <optional>
#include <watchdog.hpp> #include <watchdog.hpp>
namespace {} // namespace namespace {
using StatusLed = ther::Led<LED_DT_SPEC_GET(DT_NODELABEL(status_led))>;
bool is_infrared_ready = false;
auto infrared = DEVICE_DT_GET(DT_NODELABEL(godtek));
auto GetmaxTemp() -> std::optional<sensor_value> {
if (is_infrared_ready) {
sensor_value max_val{};
bool found = false;
auto main(void) -> int { // NTC 8 路最大值
auto wdt = app::WatchDogConfig{}; auto ntc_result = ther::NtcGroup::GetSensorValue();
if (ntc_result) {
max_val = *ntc_result;
found = true;
}
while (1) { // 红外数据就绪时参与比较
sensor_value ir_val{};
wdt.Feed(); if (0 == sensor_sample_fetch_chan(infrared, SENSOR_CHAN_AMBIENT_TEMP)) {
if (0 ==
sensor_channel_get(infrared, SENSOR_CHAN_AMBIENT_TEMP, &ir_val)) {
if (!found || ir_val.val1 > max_val.val1 ||
(ir_val.val1 == max_val.val1 && ir_val.val2 > max_val.val2)) {
max_val = ir_val;
}
is_infrared_ready = false;
}
}
return max_val;
} }
return std::nullopt;
}
} // namespace
auto main(void) -> int {
using namespace std::chrono_literals;
printk("app on board %s", CONFIG_BOARD);
// auto wdt = app::WatchDogConfig{};
StatusLed::Flash(1s);
ther::Com::Init();
// ther::HeatingPad::Start();
ther::HeatingPad::Start(sensor_value{42, 0});
sensor_trigger tri{.type = SENSOR_TRIG_DATA_READY,
.chan = SENSOR_CHAN_AMBIENT_TEMP};
sensor_trigger_set(infrared, &tri,
[](const device *dev, const sensor_trigger *trig) {
is_infrared_ready = true;
});
while (1) {
if (auto v = GetmaxTemp(); v.has_value()) {
ther::Com::Send(v.value());
}
}
// wdt.Feed();
k_sleep(K_MSEC(20));
return 0; return 0;
} }

View File

@ -1,4 +1,18 @@
#include <zephyr/device.h> #include <zephyr/device.h>
#include <zephyr/drivers/sensor.h>
#include <zephyr/kernel.h> #include <zephyr/kernel.h>
auto main() -> int { return 0; } #define NTC_INS(node) DEVICE_DT_GET(node),
const device *ntc = DEVICE_DT_GET(DT_NODELABEL(pole_ntc1));
auto main() -> int {
sensor_value v;
while (1) {
if (0 == sensor_sample_fetch(ntc)) {
sensor_channel_get(ntc, SENSOR_CHAN_AMBIENT_TEMP, &v);
printk("temp: %d.%d\n", v.val1, v.val2);
}
k_sleep(K_SECONDS(1));
}
return 0;
}

View File

@ -6,3 +6,6 @@ tests:
sample.helloworld: sample.helloworld:
extra_configs: extra_configs:
- CONFIG_SAMPLE_HELLOWORLD=y - CONFIG_SAMPLE_HELLOWORLD=y
sample.ntc:
extra_configs:
- CONFIG_SAMPLE_NTC=y

View File

@ -6,16 +6,18 @@ Usage:
python zbuild.py dr2501a_g070rb python zbuild.py dr2501a_g070rb
python zbuild.py dr2501a_g070rb -p auto python zbuild.py dr2501a_g070rb -p auto
python zbuild.py -p always (auto-detect board from west.yml) python zbuild.py -p always (auto-detect board from west.yml)
python zbuild.py dr2501a_g070rb -T sample.helloworld
Options: Options:
board Board name (optional, auto-detected from west.yml if omitted) board Board name (optional, auto-detected from west.yml if omitted)
-p PURSE Pristine option: auto / always / never -p PURSE Pristine option: auto / always / never
-t TARGET CMake target to run after build, e.g. test / flash -t TARGET CMake target to run after build, e.g. test / flash
-T TEST Test name/sample identifier (e.g. sample.helloworld)
Steps: Steps:
1. west topdir find Zephyr workspace root 1. west topdir find Zephyr workspace root
2. west config --local manifest.file <this_script_dir>/west.yml 2. west config --local manifest.file <this_script_dir>/west.yml
3. west build [-p PURSE] -b BOARD [-t TARGET] <this_script_dir> 3. west build [-p PURSE] -b BOARD [-t TARGET] [-T TEST] <this_script_dir>
""" """
import argparse import argparse
@ -78,6 +80,13 @@ def main():
default=None, default=None,
help="CMake target to run after build (e.g. test, flash)", help="CMake target to run after build (e.g. test, flash)",
) )
parser.add_argument(
"-T",
"--test",
default=None,
dest="test_id",
help="Test name / sample identifier (e.g. sample.helloworld)",
)
parser.add_argument( parser.add_argument(
"--update", "--update",
action="store_true", action="store_true",
@ -117,6 +126,7 @@ def main():
print(f" board : {args.board}") print(f" board : {args.board}")
print(f" pristine : {args.pristine or '(none)'}") print(f" pristine : {args.pristine or '(none)'}")
print(f" target : {args.target or '(none)'}") print(f" target : {args.target or '(none)'}")
print(f" test : {args.test_id or '(none)'}")
print(f" update : {'yes' if args.update else 'no'}") print(f" update : {'yes' if args.update else 'no'}")
# ── 1. Find workspace root ─────────────────────────────── # ── 1. Find workspace root ───────────────────────────────
@ -146,6 +156,8 @@ def main():
build_cmd += ["-b", args.board] build_cmd += ["-b", args.board]
if args.target: if args.target:
build_cmd += ["-t", args.target] build_cmd += ["-t", args.target]
if args.test_id:
build_cmd += ["-T", args.test_id]
build_cmd += [script_dir] build_cmd += [script_dir]
print(f" $ {' '.join(build_cmd)}") print(f" $ {' '.join(build_cmd)}")