固件 A16:风扇闭环接进任务体 + NTC / TACH 真实采样(已上板)
- ntc.{h,c}(宿主机可测):分压端 mV → Ω → NCP18XH103F03RB 的 R-T 表插值;
开路 / 短路 / 表外一律「读不到」(t_thermal 因此满速)
- thermal_esp.{h,c}(仅 IDF):ADC1 oneshot + 曲线校准读两路 NTC;两个 PCNT 单元数 TACH 边沿
- app_main:task_thermal 任务体、t_thermal_init 放在上电恢复之前、status 带热管理快照
- test_ntc.c 4 例;全工程 429 例全绿;版本 0.1.0-a16
- 板 B:两路 NTC 38.2 / 34.0 °C;风扇两路 rpm 0 报停转(拉满电流不涨,疑未插风扇,待确认)
Co-Authored-By: Claude Opus 5.5 <[email protected]>
这个提交包含在:
@@ -1,5 +1,5 @@
|
||||
# ESP-IDF 侧的 main 组件。
|
||||
# app_main.c / ulink_esp.c / mlink_esp.c 是 ESP 专用;其余 .c 必须同时在宿主机上编得过
|
||||
# app_main.c / ulink_esp.c / mlink_esp.c / thermal_esp.c 是 ESP 专用;其余 .c 必须同时在宿主机上编得过
|
||||
# (新增文件时两处都要加:这里 + host/CMakeLists.txt 的 PHONERAIL_CORE_SRCS)。
|
||||
idf_component_register(
|
||||
SRCS "app_main.c"
|
||||
@@ -25,7 +25,10 @@ idf_component_register(
|
||||
"netcfg.c"
|
||||
"mlink.c"
|
||||
"bringup.c"
|
||||
"ntc.c"
|
||||
"thermal_esp.c"
|
||||
INCLUDE_DIRS "."
|
||||
EMBED_TXTFILES "certs/evse_ca.pem"
|
||||
REQUIRES phonerail_hal drv esp_driver_usb_serial_jtag
|
||||
esp_wifi esp_netif esp_event esp_timer nvs_flash mqtt)
|
||||
esp_wifi esp_netif esp_event esp_timer nvs_flash mqtt
|
||||
esp_adc esp_driver_pcnt)
|
||||
|
||||
+34
-6
@@ -25,7 +25,9 @@
|
||||
#include "slot.h"
|
||||
#include "t_actuator.h"
|
||||
#include "t_safety.h"
|
||||
#include "t_thermal.h"
|
||||
#include "tasks.h"
|
||||
#include "thermal_esp.h"
|
||||
#include "mlink_esp.h"
|
||||
#include "nvs.h"
|
||||
#include "nvs_flash.h"
|
||||
@@ -136,16 +138,30 @@ static void task_safety(void *arg)
|
||||
/* 下面四个是骨架:周期与优先级已经按 §10.3 定好,任务体等各自的包。
|
||||
现在就把它们建起来,是为了让 A9~A16 只需要往里填,不用再动启动流程。 */
|
||||
|
||||
/* 热管理快照(A16)。写者只有本任务;`status` 命令跨任务只读(cmd.h:遥测无害)。 */
|
||||
static thermal_snap_t s_therm;
|
||||
|
||||
/* A16:两路 NTC → 风扇 PWM 闭环 + TACH 采样窗口(TR-6)。判据全在 `t_thermal_step()`,
|
||||
温度 / TACH 的来源在 thermal_esp.c。`t_thermal_init()` 在 app_main 里做(风扇要先于上电转起来)。 */
|
||||
static void task_thermal(void *arg)
|
||||
{
|
||||
(void)arg;
|
||||
sched_periodic_t tick;
|
||||
sched_periodic_init(&tick, task_desc(TASK_THERMAL)->period_ms);
|
||||
for (;;) {
|
||||
/* A16:两路 NTC(IO4/IO5)→ 风扇 PWM 闭环(TR-6 的 TACH 采样窗口)。
|
||||
⚠️ R2 没有 LM75 了(载板整个不存在,计划 §5.10)。
|
||||
真正的任务体在 `main/t_thermal.c` 里,由 A16 那一包填;
|
||||
**这里不 include 它**,免得两个人同时改到同一处。 */
|
||||
(void)t_thermal_step(&s_therm); /* 写 PWM 失败只计数(pwm_errors),不在这里处理 */
|
||||
|
||||
/* 每 10 s 一行:温度 / 占空比 / 两路转速。上板定阈值(t_thermal.h 那四个「待实测」)就看它。 */
|
||||
if ((s_therm.cycles % 10u) == 1u) {
|
||||
ESP_LOGI(TAG, "t_thermal: %d/%d m°C (%d/%d mV) duty=%u%s rpm=%u/%u%s%s",
|
||||
s_therm.temp_ok[0] ? (int)s_therm.temp_mc[0] : -1,
|
||||
s_therm.temp_ok[1] ? (int)s_therm.temp_mc[1] : -1,
|
||||
(int)thermal_esp_last_mv(0), (int)thermal_esp_last_mv(1),
|
||||
(unsigned)s_therm.duty, s_therm.temp_valid ? "" : "(读不到温度→满速)",
|
||||
(unsigned)s_therm.fan[0].rpm, (unsigned)s_therm.fan[1].rpm,
|
||||
(s_therm.fan[0].stalled || s_therm.fan[1].stalled) ? " 停转!" : "",
|
||||
s_therm.derate ? " 过温降载!" : "");
|
||||
}
|
||||
sched_periodic_wait(&tick);
|
||||
}
|
||||
}
|
||||
@@ -368,15 +384,27 @@ void app_main(void)
|
||||
}
|
||||
log_report(&boot);
|
||||
|
||||
/* §10.9 第 7 步的前半:风扇闭环。**放在上电之前**:t_thermal_init 先满速、再拉 FAN_EN,
|
||||
第一圈之前风扇就在转(t_thermal.h)。ADC / PCNT 起不来不 abort ——
|
||||
温度读不到 t_thermal 会一直满速(§10.10),TACH 读不到就不判停转。 */
|
||||
err = thermal_esp_init();
|
||||
if (err != HAL_OK) {
|
||||
ESP_LOGE(TAG, "温度 / 转速采样不完整:%s(温度读不到 ⇒ 风扇满速)", hal_err_str(err));
|
||||
}
|
||||
err = t_thermal_init(&s_therm, thermal_esp_temp, thermal_esp_tach, NULL);
|
||||
if (err != HAL_OK) {
|
||||
ESP_LOGE(TAG, "风扇起不来:%s —— 没有主动散热", hal_err_str(err));
|
||||
}
|
||||
|
||||
/* 执行器:PG 从巡检的快照里取,本任务自己不碰 I²C(t_actuator.h 开头那一段)。
|
||||
⚠️ 必须在 `spawn(TASK_SAFETY, ...)` 之前:判据要关路时会先 `t_actuator_abort()`,
|
||||
而没 init 的执行器只会直接返回,那时序列还在跑,电关掉几秒又会被抬回来。 */
|
||||
t_actuator_init(act_pg_from_safety, NULL);
|
||||
|
||||
/* 命令处理器(A12)。status 读巡检快照;热管理快照等 A16 把任务体接上再给。
|
||||
/* 命令处理器(A12)。status 读巡检与热管理两份快照。
|
||||
`power` 命令的意图经 `want_save` 存 NVS(A15)。
|
||||
队列要在执行器任务起来之前建好 —— 那个任务第一件事就是等它。 */
|
||||
const cmd_env_t cmd_env = { .safety = &s_snap, .thermal = NULL, .intent = want_save };
|
||||
const cmd_env_t cmd_env = { .safety = &s_snap, .thermal = &s_therm, .intent = want_save };
|
||||
cmd_init(&cmd_env);
|
||||
s_cmd_q = xQueueCreate(CMD_QUEUE_DEPTH, sizeof(cmd_msg_t));
|
||||
if (s_cmd_q == NULL) {
|
||||
|
||||
@@ -0,0 +1,53 @@
|
||||
/* NTC 换算。电路与出处见 ntc.h。 */
|
||||
#include "ntc.h"
|
||||
|
||||
#include <stddef.h>
|
||||
|
||||
/* NCP18XH103F03RB 的 R-T 表(Murata 规格书,−40 ~ 125 °C,5 °C 一格),单位 Ω。
|
||||
⚠️ 单调**递减**是这张表唯一不能破的性质 —— `test_ntc.c` 逐格查。
|
||||
抽查:25 °C = 10000(标称);50 °C = 4161 与 B3380 公式算出的 4160 吻合。 */
|
||||
#define NTC_T0_MC (-40000)
|
||||
#define NTC_STEP_MC 5000
|
||||
static const uint32_t k_ohm[] = {
|
||||
195652, 148171, 113347, 87559, 68237, 53650, 42506, 33892, /* −40 … −5 */
|
||||
27219, 22021, 17926, 14674, 12081, 10000, 8315, 6948, /* 0 … 35 */
|
||||
5834, 4917, 4161, 3535, 3014, 2586, 2228, 1925, /* 40 … 75 */
|
||||
1669, 1452, 1268, 1110, 974, 858, 758, 672, /* 80 … 115 */
|
||||
596, 531, /* 120, 125 */
|
||||
};
|
||||
#define NTC_N ((int)(sizeof(k_ohm) / sizeof(k_ohm[0])))
|
||||
_Static_assert(NTC_N == 34, "表应当覆盖 −40 ~ 125 °C,5 °C 一格");
|
||||
|
||||
bool ntc_mc_from_ohm(uint32_t ohm, int32_t *milli_c)
|
||||
{
|
||||
if (milli_c == NULL || ohm > k_ohm[0] || ohm < k_ohm[NTC_N - 1]) {
|
||||
return false;
|
||||
}
|
||||
for (int i = 0; i < NTC_N - 1; i++) {
|
||||
const uint32_t hi = k_ohm[i]; /* 较冷的一端,电阻大 */
|
||||
const uint32_t lo = k_ohm[i + 1]; /* 较热的一端 */
|
||||
if (ohm <= hi && ohm >= lo) {
|
||||
/* 格内按电阻线性插值。分子最大 190000 × 5000 ≈ 9.5e8,int64 稳妥。 */
|
||||
const int64_t frac = (int64_t)(hi - ohm) * NTC_STEP_MC / (int64_t)(hi - lo);
|
||||
*milli_c = NTC_T0_MC + i * NTC_STEP_MC + (int32_t)frac;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false; /* 走不到:上面的范围判断已经把表外挡掉了 */
|
||||
}
|
||||
|
||||
bool ntc_mc_from_mv(int32_t mv, int32_t vref_mv, int32_t *milli_c)
|
||||
{
|
||||
if (milli_c == NULL || vref_mv <= 2 * NTC_RAIL_MARGIN_MV) {
|
||||
return false;
|
||||
}
|
||||
if (mv < NTC_RAIL_MARGIN_MV || mv > vref_mv - NTC_RAIL_MARGIN_MV) {
|
||||
return false; /* 贴地 = 短路,贴 +3V3 = 开路:都不是温度 */
|
||||
}
|
||||
/* 下臂分压:mv / vref = R / (Rpu + R) ⇒ R = Rpu × mv / (vref − mv)。 */
|
||||
const uint64_t ohm = (uint64_t)NTC_PULLUP_OHM * (uint64_t)mv / (uint64_t)(vref_mv - mv);
|
||||
if (ohm > 0xFFFFFFFFull) {
|
||||
return false;
|
||||
}
|
||||
return ntc_mc_from_ohm((uint32_t)ohm, milli_c);
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
/* 两路板载 NTC 的换算:ADC 读回的毫伏 → 毫摄氏度(A16,需求 §10.3 `t_thermal`)。
|
||||
|
||||
---- 电路(已按 `.claude/r2/_base_pcb_nets.json` 逐脚核实)----------------
|
||||
|
||||
+3V3 ── R660 10k ──┬── NTC1(IO4, ADC1_CH3) ── C736 ── GND
|
||||
└── RT601 ── GND
|
||||
(NTC2 同形:R661 / RT602 / C739 → IO5, ADC1_CH4)
|
||||
|
||||
**NTC 在下臂**:越热电阻越小、电压越低。写反成上臂的现象是「越热读数越低」——
|
||||
风扇闭环照样算、照样有数,只是方向反了,要等机器满载才暴露。
|
||||
|
||||
RT601/RT602 = Murata **NCP18XH103F03RB**(10 kΩ @25 °C,B25/50 = 3380,立创 C13564;
|
||||
设计文档 BOM 那一行)。换算走厂家 R-T 表(5 °C 一格、格内线性插值),
|
||||
不用 B 值公式:B 公式在 25/50 °C 以外偏差 1~2 °C,而且要浮点和 log。
|
||||
|
||||
---- 这一层不做的事 ------------------------------------------------------
|
||||
· 不读 ADC(那是 `thermal_esp.c`,只进 IDF)。本文件是纯整数换算,宿主机可测;
|
||||
· 不做滤波:C736/C739 是硬件 RC,t_thermal 1 s 才读一次,再滤就是加延迟。 */
|
||||
#ifndef NTC_H
|
||||
#define NTC_H
|
||||
|
||||
#include <stdbool.h>
|
||||
#include <stdint.h>
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
#define NTC_PULLUP_OHM 10000 /* R660 / R661 */
|
||||
|
||||
/* 分压端离两条轨多近就判「读不到」(mV)。
|
||||
开路(NTC 没焊 / 断线)⇒ 电压贴 +3V3;短路 ⇒ 贴地。两者都不是温度。
|
||||
⚠️ 这不是精度门限,是「这还是不是一颗 NTC」的门限:
|
||||
表的两端(−40 °C / +125 °C)分别对应约 3.13 V / 0.16 V,离两条轨都还远。 */
|
||||
#define NTC_RAIL_MARGIN_MV 30
|
||||
|
||||
/* ADC 读回的分压端电压 + 当时的上臂电源电压 → 毫摄氏度。
|
||||
`vref_mv` 传 +3V3 的实际值(不知道就传 3300);比例式测量,电源偏 1 % 在 25 °C 附近带来 ~0.6 °C(分压中点处电阻误差是电源误差的 2 倍,NTC 每度约 4 %)。
|
||||
返回 false = 开路 / 短路 / 超出表的范围(−40~125 °C),`*milli_c` 不动。 */
|
||||
bool ntc_mc_from_mv(int32_t mv, int32_t vref_mv, int32_t *milli_c);
|
||||
|
||||
/* 电阻 → 毫摄氏度(查表 + 插值)。超出表的范围返回 false。给测试用,也给标定用。 */
|
||||
bool ntc_mc_from_ohm(uint32_t ohm, int32_t *milli_c);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif /* NTC_H */
|
||||
@@ -3,7 +3,7 @@
|
||||
#define PHONERAIL_H
|
||||
|
||||
/* 固件版本。规则:<主>.<次>.<修订>-<最后完成的任务包> */
|
||||
#define PHONERAIL_FW_VERSION "0.1.0-a15"
|
||||
#define PHONERAIL_FW_VERSION "0.1.0-a16"
|
||||
|
||||
/* 通道数。五路直供,见设计文档 §2。 */
|
||||
#define PHONERAIL_RAIL_COUNT 5
|
||||
|
||||
@@ -0,0 +1,170 @@
|
||||
/* `t_thermal` 回调的 ESP 实现。契约见 thermal_esp.h;判据在 t_thermal.c(宿主机测过),
|
||||
换算在 ntc.c(同上)。**这里只放驱动胶水,不许出现判据。** */
|
||||
#include "thermal_esp.h"
|
||||
|
||||
#include "board_pins.h"
|
||||
#include "driver/pulse_cnt.h"
|
||||
#include "esp_adc/adc_cali.h"
|
||||
#include "esp_adc/adc_cali_scheme.h"
|
||||
#include "esp_adc/adc_oneshot.h"
|
||||
#include "esp_log.h"
|
||||
#include "ntc.h"
|
||||
#include "t_thermal.h"
|
||||
|
||||
static const char *TAG = "thermal";
|
||||
|
||||
/* 上臂电源的名义值。比例式测量,偏 1 % 只带来 ~0.6 °C(ntc.h),对风扇闭环无所谓,不值得多占一路 ADC 去量它。 */
|
||||
#define THERM_VREF_MV 3300
|
||||
|
||||
/* 一次读几次取平均。ADC 单次噪声 ±10 mV 量级 ≈ ±0.3 °C;8 次之后可以忽略。1 s 一回,开销无所谓。 */
|
||||
#define THERM_AVG 8
|
||||
|
||||
/* PCNT 计数器是 16 位有符号的;开 `accum_count` + 在上限放观察点之后,驱动会把溢出
|
||||
累进 `pcnt_unit_get_count()` 的返回值里(int,回绕后按 uint32 用即可)。 */
|
||||
#define TACH_LIMIT 30000
|
||||
|
||||
/* 毛刺滤波:TACH 最快 ~6000 RPM × 2 PPR = 200 Hz,周期 5 ms;1 µs 的滤波远在它下面,
|
||||
但能把 Q6 开关 25 kHz PWM 串进来的尖刺挡掉。 */
|
||||
#define TACH_GLITCH_NS 1000
|
||||
|
||||
static const int k_ntc_pin[T_THERMAL_NTC_COUNT] = { BOARD_PIN_NTC1, BOARD_PIN_NTC2 };
|
||||
static const int k_tach_pin[T_THERMAL_FAN_COUNT] = {
|
||||
[THERM_FAN_IN] = BOARD_PIN_FAN_TACH_IN,
|
||||
[THERM_FAN_OUT] = BOARD_PIN_FAN_TACH_OUT,
|
||||
};
|
||||
|
||||
static adc_oneshot_unit_handle_t s_adc;
|
||||
static adc_channel_t s_ch[T_THERMAL_NTC_COUNT];
|
||||
static adc_cali_handle_t s_cali[T_THERMAL_NTC_COUNT];
|
||||
static bool s_adc_ok[T_THERMAL_NTC_COUNT];
|
||||
static int32_t s_last_mv[T_THERMAL_NTC_COUNT] = { -1, -1 };
|
||||
|
||||
static pcnt_unit_handle_t s_pcnt[T_THERMAL_FAN_COUNT];
|
||||
static bool s_pcnt_ok[T_THERMAL_FAN_COUNT];
|
||||
|
||||
static hal_err_t adc_up(void)
|
||||
{
|
||||
const adc_oneshot_unit_init_cfg_t ucfg = { .unit_id = ADC_UNIT_1 };
|
||||
if (adc_oneshot_new_unit(&ucfg, &s_adc) != ESP_OK) {
|
||||
return HAL_ERR_STATE;
|
||||
}
|
||||
hal_err_t first = HAL_OK;
|
||||
for (int i = 0; i < T_THERMAL_NTC_COUNT; i++) {
|
||||
adc_unit_t unit;
|
||||
/* 由引脚反查通道,不写死「CH3/CH4」:board_pins.h 是引脚的唯一出处,
|
||||
而且这一步顺带证明了它确实落在 ADC1 上(ADC2 在 WiFi 开着时读不了)。 */
|
||||
if (adc_oneshot_io_to_channel(k_ntc_pin[i], &unit, &s_ch[i]) != ESP_OK ||
|
||||
unit != ADC_UNIT_1) {
|
||||
ESP_LOGE(TAG, "IO%d 不在 ADC1 上", k_ntc_pin[i]);
|
||||
first = (first == HAL_OK) ? HAL_ERR_ARG : first;
|
||||
continue;
|
||||
}
|
||||
/* 12 dB 衰减:量程到 ~3.1 V,覆盖整张 R-T 表(−40 °C 时 3.14 V 会略微饱和,
|
||||
那一端本来就不关心 —— 低温不需要风扇)。 */
|
||||
const adc_oneshot_chan_cfg_t ccfg = { .atten = ADC_ATTEN_DB_12,
|
||||
.bitwidth = ADC_BITWIDTH_DEFAULT };
|
||||
if (adc_oneshot_config_channel(s_adc, s_ch[i], &ccfg) != ESP_OK) {
|
||||
first = (first == HAL_OK) ? HAL_ERR_STATE : first;
|
||||
continue;
|
||||
}
|
||||
const adc_cali_curve_fitting_config_t kcfg = {
|
||||
.unit_id = ADC_UNIT_1,
|
||||
.chan = s_ch[i],
|
||||
.atten = ADC_ATTEN_DB_12,
|
||||
.bitwidth = ADC_BITWIDTH_DEFAULT,
|
||||
};
|
||||
if (adc_cali_create_scheme_curve_fitting(&kcfg, &s_cali[i]) != ESP_OK) {
|
||||
/* eFuse 里没有校准值(不该发生在 WROOM-1 上):没校准的读数误差可达
|
||||
±100 mV ≈ ±3 °C,宁可报「读不到」让风扇满速,也不给一个看着正常的错数。 */
|
||||
ESP_LOGE(TAG, "NTC%d 的 ADC 校准不可用", i + 1);
|
||||
first = (first == HAL_OK) ? HAL_ERR_STATE : first;
|
||||
continue;
|
||||
}
|
||||
s_adc_ok[i] = true;
|
||||
}
|
||||
return first;
|
||||
}
|
||||
|
||||
static hal_err_t pcnt_up(int idx)
|
||||
{
|
||||
const pcnt_unit_config_t ucfg = {
|
||||
.low_limit = -TACH_LIMIT,
|
||||
.high_limit = TACH_LIMIT,
|
||||
.flags.accum_count = 1,
|
||||
};
|
||||
pcnt_unit_handle_t u = NULL;
|
||||
if (pcnt_new_unit(&ucfg, &u) != ESP_OK) {
|
||||
return HAL_ERR_STATE;
|
||||
}
|
||||
const pcnt_glitch_filter_config_t fcfg = { .max_glitch_ns = TACH_GLITCH_NS };
|
||||
(void)pcnt_unit_set_glitch_filter(u, &fcfg);
|
||||
|
||||
/* ⚠️ 只数边沿、不看电平:Q9/Q10 反相(t_thermal.h 硬件第 3 条),
|
||||
数上升沿还是下降沿都一样 —— 每个 TACH 脉冲正好一个。板上 R753/R757 上拉,不开内部上拉。 */
|
||||
const pcnt_chan_config_t ccfg = { .edge_gpio_num = k_tach_pin[idx], .level_gpio_num = -1 };
|
||||
pcnt_channel_handle_t c = NULL;
|
||||
if (pcnt_new_channel(u, &ccfg, &c) != ESP_OK ||
|
||||
pcnt_channel_set_edge_action(c, PCNT_CHANNEL_EDGE_ACTION_INCREASE,
|
||||
PCNT_CHANNEL_EDGE_ACTION_HOLD) != ESP_OK ||
|
||||
pcnt_unit_add_watch_point(u, TACH_LIMIT) != ESP_OK ||
|
||||
pcnt_unit_enable(u) != ESP_OK || pcnt_unit_clear_count(u) != ESP_OK ||
|
||||
pcnt_unit_start(u) != ESP_OK) {
|
||||
return HAL_ERR_STATE;
|
||||
}
|
||||
s_pcnt[idx] = u;
|
||||
s_pcnt_ok[idx] = true;
|
||||
return HAL_OK;
|
||||
}
|
||||
|
||||
hal_err_t thermal_esp_init(void)
|
||||
{
|
||||
hal_err_t first = adc_up();
|
||||
for (int i = 0; i < T_THERMAL_FAN_COUNT; i++) {
|
||||
const hal_err_t e = pcnt_up(i);
|
||||
if (e != HAL_OK) {
|
||||
ESP_LOGE(TAG, "TACH%d(IO%d)的 PCNT 起不来", i, k_tach_pin[i]);
|
||||
if (first == HAL_OK) {
|
||||
first = e;
|
||||
}
|
||||
}
|
||||
}
|
||||
return first;
|
||||
}
|
||||
|
||||
bool thermal_esp_temp(void *ctx, int idx, int32_t *milli_c)
|
||||
{
|
||||
(void)ctx;
|
||||
if (idx < 0 || idx >= T_THERMAL_NTC_COUNT || !s_adc_ok[idx]) {
|
||||
return false;
|
||||
}
|
||||
int32_t sum = 0;
|
||||
for (int n = 0; n < THERM_AVG; n++) {
|
||||
int mv = 0;
|
||||
if (adc_oneshot_get_calibrated_result(s_adc, s_cali[idx], s_ch[idx], &mv) != ESP_OK) {
|
||||
s_last_mv[idx] = -1;
|
||||
return false;
|
||||
}
|
||||
sum += mv;
|
||||
}
|
||||
s_last_mv[idx] = sum / THERM_AVG;
|
||||
return ntc_mc_from_mv(s_last_mv[idx], THERM_VREF_MV, milli_c);
|
||||
}
|
||||
|
||||
bool thermal_esp_tach(void *ctx, int idx, uint32_t *edges)
|
||||
{
|
||||
(void)ctx;
|
||||
if (idx < 0 || idx >= T_THERMAL_FAN_COUNT || !s_pcnt_ok[idx]) {
|
||||
return false;
|
||||
}
|
||||
int v = 0;
|
||||
if (pcnt_unit_get_count(s_pcnt[idx], &v) != ESP_OK) {
|
||||
return false;
|
||||
}
|
||||
*edges = (uint32_t)v; /* 累计值;t_thermal 用 uint32 相减,回绕安全 */
|
||||
return true;
|
||||
}
|
||||
|
||||
int32_t thermal_esp_last_mv(int idx)
|
||||
{
|
||||
return (idx >= 0 && idx < T_THERMAL_NTC_COUNT) ? s_last_mv[idx] : -1;
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
/* `t_thermal` 的两个回调在 ESP32-S3 上的实现(A16)。**只进 IDF 目标。**
|
||||
温度:ADC1 oneshot + 曲线拟合校准 → mV → `ntc_mc_from_mv()`;
|
||||
TACH:两个 PCNT 单元数上升沿(累计计数,uint32 回绕安全)。 */
|
||||
#ifndef THERMAL_ESP_H
|
||||
#define THERMAL_ESP_H
|
||||
|
||||
#include <stdbool.h>
|
||||
#include <stdint.h>
|
||||
|
||||
#include "hal_err.h"
|
||||
|
||||
/* 起 ADC 与 PCNT。失败不 abort:温度读不到 t_thermal 会满速(§10.10),
|
||||
TACH 读不到就不判停转 —— 两者都是降级,不是固件写错了。
|
||||
返回第一个错误码;已经起来的那一半照常可用。 */
|
||||
hal_err_t thermal_esp_init(void);
|
||||
|
||||
bool thermal_esp_temp(void *ctx, int idx, int32_t *milli_c); /* therm_temp_fn */
|
||||
bool thermal_esp_tach(void *ctx, int idx, uint32_t *edges); /* therm_tach_fn */
|
||||
|
||||
/* 最近一次读到的两路原始毫伏(调试 / 标定用;读失败是 −1)。 */
|
||||
int32_t thermal_esp_last_mv(int idx);
|
||||
|
||||
#endif /* THERMAL_ESP_H */
|
||||
在新工单中引用
屏蔽一个用户