test_led_strip/include/heating.hpp
zhangyisong eaa5d0c28b 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
2026-07-09 11:06:11 +08:00

155 lines
4.5 KiB
C++

#ifndef __THER_HEATING_HPP__
#define __THER_HEATING_HPP__
#include <algorithm>
#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 {
class HeatingPad {
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
#endif