固件 0.1.17~0.1.18-c5:CH224A 的 PD 档位实时进遥测(pdmon)
原来只在启动时读一次(bringup.c),而多口充电器重新分配功率时 20 V 档的电流上限当场会变。
- 新模块 pdmon:每秒读 0x09(协议状态)+ 0x50(档位电流),只读;先试上次应答的支路(主干 / mux 通道 5)。
档位变了(连续两拍一致、非 0)记 LOG_EV_PD_LIMIT,arg 2 = 运行中换档。只报不动,降载仍没人做。
- status 新增 "pd":{"st":..,"ma":..},这一拍没读到是 null(满配 1164 / 1536 字节,余量断言改成至少留 256)。
- 0.1.17 放在热管理任务里读,板 B 实测 safety.skipped 每秒涨 1(持锁撞上巡检的下一拍);
0.1.18 挪进巡检任务(t_safety_step 之后、每 50 圈一次),skipped 不再涨。
- 测试:新增 test_pdmon(6 例),test_cmd 加 pd 断言;28 ctest 全绿。
板 B 已 OTA 到 0.1.18-c5(两次都是热重启,五路没断电),读到 st 0x88 / 3000 mA。
Co-Authored-By: Claude Opus 5.5 <[email protected]>
这个提交包含在:
@@ -14,6 +14,7 @@ idf_component_register(
|
||||
"tasks.c"
|
||||
"chgmode.c"
|
||||
"hubctl.c"
|
||||
"pdmon.c"
|
||||
"t_safety.c"
|
||||
"t_actuator.c"
|
||||
"t_thermal.c"
|
||||
|
||||
@@ -33,6 +33,7 @@
|
||||
#include "nvs.h"
|
||||
#include "nvs_flash.h"
|
||||
#include "ota.h"
|
||||
#include "pdmon.h"
|
||||
#include "ota_esp.h"
|
||||
#include "warm_esp.h"
|
||||
#include "ulink_esp.h"
|
||||
@@ -151,6 +152,12 @@ static void task_safety(void *arg)
|
||||
for (;;) {
|
||||
(void)t_safety_step(snapp); /* 器件读失败不在这里处理:§10.2 失联 ≠ 断电 */
|
||||
|
||||
/* 0.1.17:CH224A 的档位进遥测,1 s 一拍(pdmon.h「线程」那一段:为什么非得在这里读)。
|
||||
错开 250 的倍数,不和下面那行日志挤在同一圈。 */
|
||||
if ((snapp->cycles % 50u) == 25u) {
|
||||
pdmon_poll();
|
||||
}
|
||||
|
||||
/* 每 5 s 报一次总线占用。§10.3 手算是「约 1.5 ms / 约 8 %」,
|
||||
首板上电时要拿逻辑分析仪复核这个数(§10.3 末尾那句)。 */
|
||||
if ((snapp->cycles % 250u) == 0u) {
|
||||
@@ -462,6 +469,7 @@ void app_main(void)
|
||||
abort();
|
||||
}
|
||||
log_report(&boot);
|
||||
pdmon_init(boot.pd_ma);
|
||||
if (adopt != 0) {
|
||||
evlog_put(0, LOG_EV_OTA, OTA_EV_WARM, (int32_t)boot.adopted, (int32_t)boot.adopt_dropped);
|
||||
ESP_LOGI(TAG, "接管:一直开着 0x%02x,关掉 0x%02x(刀片不在 / mux 失联)",
|
||||
|
||||
@@ -15,6 +15,7 @@
|
||||
#include "hubctl.h"
|
||||
#include "ota.h"
|
||||
#include "pca9555.h"
|
||||
#include "pdmon.h"
|
||||
#include "phonerail.h"
|
||||
#include "rail.h"
|
||||
#include "rail_cfg.h"
|
||||
@@ -114,6 +115,19 @@ void cmd_report_hub(cj_writer_t *w)
|
||||
(unsigned long)hubctl_hold_left_s(now), (unsigned long)hubctl_idle_left_s(now));
|
||||
}
|
||||
|
||||
/* 0.1.17:`"pd":{"st":8,"ma":3000}` —— CH224A 此刻的协议状态(0x09 原值)与 20 V 档的最大电流(pdmon.h)。
|
||||
上一拍没读到(或还没读过第一拍)写 null,**不报旧值**:上位机拿它当「现在」。 */
|
||||
static void pd_obj(cj_writer_t *w)
|
||||
{
|
||||
pdmon_snap_t pd;
|
||||
pdmon_get(&pd);
|
||||
if (pd.ok) {
|
||||
cj_w(w, ",\"pd\":{\"st\":%u,\"ma\":%lu}", (unsigned)pd.status, (unsigned long)pd.ma);
|
||||
} else {
|
||||
cj_w(w, ",\"pd\":null");
|
||||
}
|
||||
}
|
||||
|
||||
void cmd_report_status(cj_writer_t *w, const cmd_env_t *env)
|
||||
{
|
||||
const safety_snap_t *s = env->safety;
|
||||
@@ -127,6 +141,7 @@ void cmd_report_status(cj_writer_t *w, const cmd_env_t *env)
|
||||
cj_w(w, ",\"host\":null");
|
||||
}
|
||||
cmd_report_hub(w);
|
||||
pd_obj(w);
|
||||
if (s != NULL) {
|
||||
if (s->input_ok) {
|
||||
cj_w(w, ",\"in\":{\"mv\":%ld,\"ua\":%ld}", (long)s->input.bus_mv,
|
||||
|
||||
@@ -0,0 +1,72 @@
|
||||
/* CH224A 运行期轮询。契约见 pdmon.h。 */
|
||||
#include "pdmon.h"
|
||||
|
||||
#include <stddef.h>
|
||||
|
||||
#include "ch224a.h"
|
||||
#include "evlog.h"
|
||||
#include "i2cbus.h"
|
||||
#include "slot.h"
|
||||
#include "tca9548a.h"
|
||||
|
||||
typedef struct {
|
||||
uint8_t status;
|
||||
uint32_t ma;
|
||||
} pd_read_t;
|
||||
|
||||
static pdmon_snap_t s_snap;
|
||||
static int s_ch = I2CBUS_CH_TRUNK; /* 上次应答的支路 */
|
||||
static uint32_t s_logged_ma; /* 最近一次进了日志的档位 */
|
||||
static uint32_t s_prev_ma; /* 上一拍读到的(去抖用) */
|
||||
|
||||
static hal_err_t read_fn(void *arg)
|
||||
{
|
||||
pd_read_t *r = (pd_read_t *)arg;
|
||||
const hal_err_t err = ch224a_read_status(CH224A_ADDR_DEFAULT, &r->status);
|
||||
return (err == HAL_OK) ? ch224a_read_max_ma(CH224A_ADDR_DEFAULT, &r->ma) : err;
|
||||
}
|
||||
|
||||
void pdmon_init(uint32_t boot_ma)
|
||||
{
|
||||
s_snap = (pdmon_snap_t){ 0 };
|
||||
s_ch = I2CBUS_CH_TRUNK;
|
||||
s_logged_ma = boot_ma;
|
||||
s_prev_ma = boot_ma;
|
||||
}
|
||||
|
||||
void pdmon_poll(void)
|
||||
{
|
||||
pd_read_t r = { 0 };
|
||||
hal_err_t err = i2cbus_xact(s_ch, SLOT_LOCK_TIMEOUT_MS, read_fn, &r);
|
||||
if (err != HAL_OK) {
|
||||
const int other = (s_ch == I2CBUS_CH_TRUNK) ? TCA9548A_CH_PD : I2CBUS_CH_TRUNK;
|
||||
err = i2cbus_xact(other, SLOT_LOCK_TIMEOUT_MS, read_fn, &r);
|
||||
if (err == HAL_OK) {
|
||||
s_ch = other;
|
||||
}
|
||||
}
|
||||
s_snap.polls++;
|
||||
if (err != HAL_OK) {
|
||||
s_snap.ok = false; /* 值留着:旧的档位比一个 0 可信(ch224a.h 同一条) */
|
||||
s_snap.errors++;
|
||||
return;
|
||||
}
|
||||
s_snap.status = r.status;
|
||||
s_snap.ma = r.ma;
|
||||
s_snap.ok = true;
|
||||
|
||||
/* 换档:连续两拍读到同一个非零值、且和日志里最后那条不一样。0 = 协商中途 / 没协商上,不记。 */
|
||||
if (r.ma != 0 && r.ma == s_prev_ma && r.ma != s_logged_ma) {
|
||||
s_logged_ma = r.ma;
|
||||
evlog_put(0, LOG_EV_PD_LIMIT, PDMON_EV_ARG_CHANGED,
|
||||
(int32_t)(ch224a_power_limit_mw(r.ma) / 100u), (int32_t)r.ma);
|
||||
}
|
||||
s_prev_ma = r.ma;
|
||||
}
|
||||
|
||||
void pdmon_get(pdmon_snap_t *out)
|
||||
{
|
||||
if (out != NULL) {
|
||||
*out = s_snap;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
/* CH224A 运行期轮询(0.1.17):把充电器**此刻**给的 PD 档位放进遥测(status 的 `pd`)。
|
||||
|
||||
为什么不够用启动那一次(bringup.c 的 `pd_up()`):多口充电器在别的口插拔时会重新分配功率、
|
||||
重发 Source_Capabilities,20 V 档的电流上限当场就变(板 B 实测过 5 A → 3.25 A → 3 A),
|
||||
而 20 V 不一定掉、板子不一定重启 —— 只在启动读一次,面板上看到的就是旧档位。
|
||||
|
||||
做的事只有读:`0x09`(协议状态)和 `0x50`(档位电流)。**不写任何寄存器**(ch224a.h 那个框)。
|
||||
读到的档位和上一次记进日志的不一样(连续两拍一致才算数,免得协商中途的瞬时值刷日志)
|
||||
⇒ 记一条 `LOG_EV_PD_LIMIT`,arg = 2(运行中换档)。
|
||||
⚠️ 和 bringup 一样只**报**不**动**:逼近上限的降载(§8.3)还没人做(HANDOFF)。
|
||||
|
||||
---- 线程 ----------------------------------------------------------------
|
||||
`pdmon_poll()` 只在**巡检任务**里调,排在 `t_safety_step()` 之后、每 50 圈(1 s)一次,
|
||||
一次 ≈ 选主干 + 两个单字节读。status 跨任务读快照,遥测无害(cmd.h 同一条)。
|
||||
⚠️ 别挪到别的任务去:2026-10-06 板 B 实测放在热管理任务里时,它持锁的那一下正好撞上
|
||||
巡检的下一拍,`skipped` 每秒整整涨 1(每秒有一圈五路都没读)。巡检自己读就没有人跟它抢。 */
|
||||
#ifndef PDMON_H
|
||||
#define PDMON_H
|
||||
|
||||
#include <stdbool.h>
|
||||
#include <stdint.h>
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
/* `LOG_EV_PD_LIMIT` 的 arg:0 = 启动时读到,1 = 启动时读不到 / 读到 0、按保守值(bringup.c),
|
||||
2 = 运行中档位变了。⚠️ 冻结(上位机 protocol.py)。 */
|
||||
#define PDMON_EV_ARG_CHANGED 2u
|
||||
|
||||
typedef struct {
|
||||
bool ok; /* 上一拍读到了;false 时下面两个是最后一次读到的值 */
|
||||
uint8_t status; /* 0x09 原值 */
|
||||
uint32_t ma; /* 0x50 换算成 mA(没协商上 PD 时是 0) */
|
||||
uint32_t polls;
|
||||
uint32_t errors;
|
||||
} pdmon_snap_t;
|
||||
|
||||
/* `boot_ma` = 启动时读到的档位(`bringup_report_t.pd_ma`,读不到是 0):换档事件拿它当基线。 */
|
||||
void pdmon_init(uint32_t boot_ma);
|
||||
|
||||
/* 读一拍。先试上次应答的那条支路,不应答再试另一条(主干 / mux 通道 5,bringup.c `pd_up()` 的注释)。 */
|
||||
void pdmon_poll(void);
|
||||
|
||||
void pdmon_get(pdmon_snap_t *out);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif /* PDMON_H */
|
||||
@@ -3,7 +3,7 @@
|
||||
#define PHONERAIL_H
|
||||
|
||||
/* 固件版本。规则:<主>.<次>.<修订>-<最后完成的任务包> */
|
||||
#define PHONERAIL_FW_VERSION "0.1.16-c5"
|
||||
#define PHONERAIL_FW_VERSION "0.1.18-c5"
|
||||
|
||||
/* 通道数。五路直供,见设计文档 §2。 */
|
||||
#define PHONERAIL_RAIL_COUNT 5
|
||||
|
||||
在新工单中引用
屏蔽一个用户