固件 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]>
这个提交包含在:
+12
-1
@@ -1,5 +1,16 @@
|
|||||||
# 交接
|
# 交接
|
||||||
更新:2026-09-30 固件 **0.1.16-c5**(开机键统一 5 s);板 B 经 OTA 推上;rack 已部署
|
更新:2026-10-06 固件 **0.1.18-c5**(CH224A 档位进遥测);板 B 经 OTA 推上;rack 已部署
|
||||||
|
|
||||||
|
## 0.1.17 / 0.1.18:CH224A 档位实时进遥测(2026-10-06)
|
||||||
|
- 起因:rack 要显示 PD 供电信息,而档位只在 `bringup.c` 启动时读一次;板 B 历史上读到过 5 A → 3.25 A → 3 A。
|
||||||
|
- 新模块 `pdmon.{h,c}`:每秒读 `0x09`(协议状态)+ `0x50`(档位电流),**只读**。先试上次应答的支路(主干 / mux 通道 5)。
|
||||||
|
档位变了(连续两拍一致、非 0、和日志里最后那条不同)记 `LOG_EV_PD_LIMIT`,**arg 2 = 运行中换档**(0 / 1 仍是启动那条)。
|
||||||
|
只报不动:`pd_limit_mw` 仍是启动时定的,降载(§8.3)照旧没人做。
|
||||||
|
- status 新增 `"pd":{"st":136,"ma":3000}`,这一拍没读到是 `null`(满配 1164 / 1536 字节,`test_cmd` 的余量断言改成「至少留 256」)。
|
||||||
|
- ⚠️ **0.1.17 放在热管理任务里读,板 B 实测 `safety.skipped` 每秒涨 1**(持锁那一下正好撞上巡检的下一拍,那一圈五路都没读)。
|
||||||
|
0.1.18 挪进巡检任务(`t_safety_step()` 之后、每 50 圈一次),`skipped` 停在启动时的 1 不再涨。别再挪出去。
|
||||||
|
- 板 B 实测:`st` = 0x88(bit3 = PD 握上;bit7 手册没写,rack 只认已知位)、`ma` = 3000。两次 OTA 都是热重启,五路 en 全程保持。
|
||||||
|
- 测试:新增 `test_pdmon`(6 例),`test_cmd` 加 `pd` 两条断言;**28 ctest 全绿**。rack 侧见 `rack/frontend/src/pages/Board.vue` 的「PD 供电」格。
|
||||||
|
|
||||||
## 0.1.15 / 0.1.16:开机长按 3 s 试验失败,所有开机操作统一 5 s(2026-09-30)
|
## 0.1.15 / 0.1.16:开机长按 3 s 试验失败,所有开机操作统一 5 s(2026-09-30)
|
||||||
- 0.1.15 把 `ACT_KEY_MS_ON` 5000 → 3000:板 B 第 2 路 `power_cycle` 三轮全是「按住 330~390 mA、松手掉回 0」⇒ `no-boot`(事件 19);
|
- 0.1.15 把 `ACT_KEY_MS_ON` 5000 → 3000:板 B 第 2 路 `power_cycle` 三轮全是「按住 330~390 mA、松手掉回 0」⇒ `no-boot`(事件 19);
|
||||||
|
|||||||
@@ -30,6 +30,7 @@ set(PHONERAIL_CORE_SRCS
|
|||||||
${FW_ROOT}/main/tasks.c
|
${FW_ROOT}/main/tasks.c
|
||||||
${FW_ROOT}/main/chgmode.c
|
${FW_ROOT}/main/chgmode.c
|
||||||
${FW_ROOT}/main/hubctl.c
|
${FW_ROOT}/main/hubctl.c
|
||||||
|
${FW_ROOT}/main/pdmon.c
|
||||||
${FW_ROOT}/main/t_safety.c
|
${FW_ROOT}/main/t_safety.c
|
||||||
${FW_ROOT}/main/t_actuator.c
|
${FW_ROOT}/main/t_actuator.c
|
||||||
${FW_ROOT}/main/t_thermal.c
|
${FW_ROOT}/main/t_thermal.c
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ idf_component_register(
|
|||||||
"tasks.c"
|
"tasks.c"
|
||||||
"chgmode.c"
|
"chgmode.c"
|
||||||
"hubctl.c"
|
"hubctl.c"
|
||||||
|
"pdmon.c"
|
||||||
"t_safety.c"
|
"t_safety.c"
|
||||||
"t_actuator.c"
|
"t_actuator.c"
|
||||||
"t_thermal.c"
|
"t_thermal.c"
|
||||||
|
|||||||
@@ -33,6 +33,7 @@
|
|||||||
#include "nvs.h"
|
#include "nvs.h"
|
||||||
#include "nvs_flash.h"
|
#include "nvs_flash.h"
|
||||||
#include "ota.h"
|
#include "ota.h"
|
||||||
|
#include "pdmon.h"
|
||||||
#include "ota_esp.h"
|
#include "ota_esp.h"
|
||||||
#include "warm_esp.h"
|
#include "warm_esp.h"
|
||||||
#include "ulink_esp.h"
|
#include "ulink_esp.h"
|
||||||
@@ -151,6 +152,12 @@ static void task_safety(void *arg)
|
|||||||
for (;;) {
|
for (;;) {
|
||||||
(void)t_safety_step(snapp); /* 器件读失败不在这里处理:§10.2 失联 ≠ 断电 */
|
(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 %」,
|
/* 每 5 s 报一次总线占用。§10.3 手算是「约 1.5 ms / 约 8 %」,
|
||||||
首板上电时要拿逻辑分析仪复核这个数(§10.3 末尾那句)。 */
|
首板上电时要拿逻辑分析仪复核这个数(§10.3 末尾那句)。 */
|
||||||
if ((snapp->cycles % 250u) == 0u) {
|
if ((snapp->cycles % 250u) == 0u) {
|
||||||
@@ -462,6 +469,7 @@ void app_main(void)
|
|||||||
abort();
|
abort();
|
||||||
}
|
}
|
||||||
log_report(&boot);
|
log_report(&boot);
|
||||||
|
pdmon_init(boot.pd_ma);
|
||||||
if (adopt != 0) {
|
if (adopt != 0) {
|
||||||
evlog_put(0, LOG_EV_OTA, OTA_EV_WARM, (int32_t)boot.adopted, (int32_t)boot.adopt_dropped);
|
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 失联)",
|
ESP_LOGI(TAG, "接管:一直开着 0x%02x,关掉 0x%02x(刀片不在 / mux 失联)",
|
||||||
|
|||||||
@@ -15,6 +15,7 @@
|
|||||||
#include "hubctl.h"
|
#include "hubctl.h"
|
||||||
#include "ota.h"
|
#include "ota.h"
|
||||||
#include "pca9555.h"
|
#include "pca9555.h"
|
||||||
|
#include "pdmon.h"
|
||||||
#include "phonerail.h"
|
#include "phonerail.h"
|
||||||
#include "rail.h"
|
#include "rail.h"
|
||||||
#include "rail_cfg.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));
|
(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)
|
void cmd_report_status(cj_writer_t *w, const cmd_env_t *env)
|
||||||
{
|
{
|
||||||
const safety_snap_t *s = env->safety;
|
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");
|
cj_w(w, ",\"host\":null");
|
||||||
}
|
}
|
||||||
cmd_report_hub(w);
|
cmd_report_hub(w);
|
||||||
|
pd_obj(w);
|
||||||
if (s != NULL) {
|
if (s != NULL) {
|
||||||
if (s->input_ok) {
|
if (s->input_ok) {
|
||||||
cj_w(w, ",\"in\":{\"mv\":%ld,\"ua\":%ld}", (long)s->input.bus_mv,
|
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_H
|
||||||
|
|
||||||
/* 固件版本。规则:<主>.<次>.<修订>-<最后完成的任务包> */
|
/* 固件版本。规则:<主>.<次>.<修订>-<最后完成的任务包> */
|
||||||
#define PHONERAIL_FW_VERSION "0.1.16-c5"
|
#define PHONERAIL_FW_VERSION "0.1.18-c5"
|
||||||
|
|
||||||
/* 通道数。五路直供,见设计文档 §2。 */
|
/* 通道数。五路直供,见设计文档 §2。 */
|
||||||
#define PHONERAIL_RAIL_COUNT 5
|
#define PHONERAIL_RAIL_COUNT 5
|
||||||
|
|||||||
+13
-1
@@ -11,6 +11,7 @@
|
|||||||
#include <string.h>
|
#include <string.h>
|
||||||
|
|
||||||
#include "board_pins.h"
|
#include "board_pins.h"
|
||||||
|
#include "ch224a.h"
|
||||||
#include "chgmode.h"
|
#include "chgmode.h"
|
||||||
#include "cmd.h"
|
#include "cmd.h"
|
||||||
#include "dac63204.h"
|
#include "dac63204.h"
|
||||||
@@ -21,6 +22,7 @@
|
|||||||
#include "hal_time.h"
|
#include "hal_time.h"
|
||||||
#include "i2cbus.h"
|
#include "i2cbus.h"
|
||||||
#include "pca9555.h"
|
#include "pca9555.h"
|
||||||
|
#include "pdmon.h"
|
||||||
#include "phonerail.h"
|
#include "phonerail.h"
|
||||||
#include "rail.h"
|
#include "rail.h"
|
||||||
#include "rail_cfg.h"
|
#include "rail_cfg.h"
|
||||||
@@ -689,9 +691,18 @@ static void test_status_fits_and_reports_unknowns_as_null(void)
|
|||||||
ASSERT_HAS(send("{\"cmd\":\"status\"}"), "\"host\":false");
|
ASSERT_HAS(send("{\"cmd\":\"status\"}"), "\"host\":false");
|
||||||
sim_gpio_drive(BOARD_PIN_UP_VBUS_DET, 1); /* 留着高电平:下面量满配长度 */
|
sim_gpio_drive(BOARD_PIN_UP_VBUS_DET, 1); /* 留着高电平:下面量满配长度 */
|
||||||
|
|
||||||
|
/* 0.1.17 CH224A:没读到过 ⇒ null;读到了报原值。 */
|
||||||
|
pdmon_init(0);
|
||||||
|
ASSERT_HAS(send("{\"cmd\":\"status\"}"), "\"pd\":null");
|
||||||
|
sim_i2c_add(CH224A_ADDR_DEFAULT, 1);
|
||||||
|
sim_i2c_set_reg(CH224A_ADDR_DEFAULT, CH224A_REG_STATUS, 0x08);
|
||||||
|
sim_i2c_set_reg(CH224A_ADDR_DEFAULT, CH224A_REG_CURRENT, 5000 / 50);
|
||||||
|
pdmon_poll();
|
||||||
|
|
||||||
const char *r = send("{\"seq\":4000000000,\"cmd\":\"status\"}");
|
const char *r = send("{\"seq\":4000000000,\"cmd\":\"status\"}");
|
||||||
ASSERT_HAS(r, "\"host\":true");
|
ASSERT_HAS(r, "\"host\":true");
|
||||||
ASSERT_HAS(r, "{\"seq\":4000000000,\"ok\":true,\"fw\":\"" PHONERAIL_FW_VERSION "\"");
|
ASSERT_HAS(r, "{\"seq\":4000000000,\"ok\":true,\"fw\":\"" PHONERAIL_FW_VERSION "\"");
|
||||||
|
ASSERT_HAS(r, "\"pd\":{\"st\":8,\"ma\":5000}");
|
||||||
ASSERT_HAS(r, "\"in\":{\"mv\":12030,\"ua\":1234567}");
|
ASSERT_HAS(r, "\"in\":{\"mv\":12030,\"ua\":1234567}");
|
||||||
ASSERT_HAS(r, "{\"r\":2,\"pr\":true,\"en\":false,\"vb\":false,\"set\":3900,\"mv\":null,\"ua\":null");
|
ASSERT_HAS(r, "{\"r\":2,\"pr\":true,\"en\":false,\"vb\":false,\"set\":3900,\"mv\":null,\"ua\":null");
|
||||||
ASSERT_HAS(r, "\"r\":4,");
|
ASSERT_HAS(r, "\"r\":4,");
|
||||||
@@ -701,7 +712,8 @@ static void test_status_fits_and_reports_unknowns_as_null(void)
|
|||||||
ASSERT_HAS(r, "\"st\":\"idle\"");
|
ASSERT_HAS(r, "\"st\":\"idle\"");
|
||||||
TEST_ASSERT_EQUAL_CHAR('}', r[strlen(r) - 1]);
|
TEST_ASSERT_EQUAL_CHAR('}', r[strlen(r) - 1]);
|
||||||
printf("status 满配 %u 字节(上限 %u)\n", (unsigned)strlen(r), (unsigned)CMD_OUT_MAX);
|
printf("status 满配 %u 字节(上限 %u)\n", (unsigned)strlen(r), (unsigned)CMD_OUT_MAX);
|
||||||
TEST_ASSERT_TRUE(strlen(r) < CMD_OUT_MAX * 3 / 4); /* 留四分之一给以后加字段 */
|
/* 原来留的是四分之一;0.1.17 加 `pd` 用掉了一点(1164 字节)。再加字段先看这个数,不够就该涨 CMD_OUT_MAX 了。 */
|
||||||
|
TEST_ASSERT_TRUE(strlen(r) < CMD_OUT_MAX - 256);
|
||||||
|
|
||||||
/* 不给快照:只报影子,照样是一行合法回应。 */
|
/* 不给快照:只报影子,照样是一行合法回应。 */
|
||||||
cmd_init(NULL);
|
cmd_init(NULL);
|
||||||
|
|||||||
@@ -0,0 +1,178 @@
|
|||||||
|
/* 0.1.17:CH224A 运行期轮询(pdmon.h)。
|
||||||
|
|
||||||
|
守四件事:
|
||||||
|
1. 读到的就是芯片此刻的值,换了档下一拍就跟着变(这个模块存在的理由);
|
||||||
|
2. 读不到时 `ok` 落下、旧值留着,恢复后自己回来;
|
||||||
|
3. 换档事件:连续两拍一致才记、同一个档位只记一次、和启动时那条不重复;
|
||||||
|
4. 主干不应答就去 mux 通道 5 找(设计与实物不一致,bringup.c `pd_up()`)。 */
|
||||||
|
#include "pdmon.h"
|
||||||
|
|
||||||
|
#include "board_pins.h"
|
||||||
|
#include "ch224a.h"
|
||||||
|
#include "evlog.h"
|
||||||
|
#include "hal_i2c.h"
|
||||||
|
#include "hal_lock.h"
|
||||||
|
#include "i2cbus.h"
|
||||||
|
#include "sim_hal.h"
|
||||||
|
#include "tca9548a.h"
|
||||||
|
#include "unity.h"
|
||||||
|
|
||||||
|
static int count_pd_ev(evlog_rec_t *last)
|
||||||
|
{
|
||||||
|
(void)evlog_flush(0);
|
||||||
|
evlog_stats_t st;
|
||||||
|
evlog_stats(&st);
|
||||||
|
int n = 0;
|
||||||
|
evlog_rec_t rec;
|
||||||
|
for (uint32_t seq = st.oldest_seq; seq < st.next_seq; seq++) {
|
||||||
|
if (evlog_read(seq, &rec, 1) == 1 && rec.type == (uint8_t)LOG_EV_PD_LIMIT) {
|
||||||
|
n++;
|
||||||
|
if (last != NULL) {
|
||||||
|
*last = rec;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return n;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void charger(uint32_t ma)
|
||||||
|
{
|
||||||
|
sim_i2c_set_reg(CH224A_ADDR_DEFAULT, CH224A_REG_STATUS, 0x08);
|
||||||
|
sim_i2c_set_reg(CH224A_ADDR_DEFAULT, CH224A_REG_CURRENT, (uint16_t)(ma / 50u));
|
||||||
|
}
|
||||||
|
|
||||||
|
void setUp(void)
|
||||||
|
{
|
||||||
|
sim_hal_reset();
|
||||||
|
TEST_ASSERT_EQUAL_INT(HAL_OK, hal_i2c_init(NULL));
|
||||||
|
TEST_ASSERT_EQUAL_INT(HAL_OK, hal_lock_init());
|
||||||
|
sim_i2c_add_mux(TCA9548A_ADDR, BOARD_PIN_MUX_RST_N);
|
||||||
|
TEST_ASSERT_EQUAL_INT(HAL_OK, i2cbus_init());
|
||||||
|
TEST_ASSERT_EQUAL_INT(HAL_OK, evlog_init_ex(0));
|
||||||
|
}
|
||||||
|
|
||||||
|
void tearDown(void) {}
|
||||||
|
|
||||||
|
static void test_reports_what_the_chip_says_now(void)
|
||||||
|
{
|
||||||
|
sim_i2c_add(CH224A_ADDR_DEFAULT, 1);
|
||||||
|
charger(3000);
|
||||||
|
pdmon_init(3000);
|
||||||
|
|
||||||
|
pdmon_snap_t s;
|
||||||
|
pdmon_get(&s);
|
||||||
|
TEST_ASSERT_FALSE(s.ok); /* 还没读过第一拍 */
|
||||||
|
|
||||||
|
pdmon_poll();
|
||||||
|
pdmon_get(&s);
|
||||||
|
TEST_ASSERT_TRUE(s.ok);
|
||||||
|
TEST_ASSERT_EQUAL_UINT8(0x08, s.status);
|
||||||
|
TEST_ASSERT_EQUAL_UINT32(3000, s.ma);
|
||||||
|
|
||||||
|
charger(5000);
|
||||||
|
pdmon_poll();
|
||||||
|
pdmon_get(&s);
|
||||||
|
TEST_ASSERT_EQUAL_UINT32(5000, s.ma); /* 下一拍就是新值,不等去抖 */
|
||||||
|
}
|
||||||
|
|
||||||
|
static void test_read_failure_keeps_the_old_value_and_recovers(void)
|
||||||
|
{
|
||||||
|
sim_i2c_add(CH224A_ADDR_DEFAULT, 1);
|
||||||
|
charger(3250);
|
||||||
|
pdmon_init(3250);
|
||||||
|
pdmon_poll();
|
||||||
|
|
||||||
|
sim_i2c_set_present(CH224A_ADDR_DEFAULT, false);
|
||||||
|
pdmon_poll();
|
||||||
|
pdmon_snap_t s;
|
||||||
|
pdmon_get(&s);
|
||||||
|
TEST_ASSERT_FALSE(s.ok);
|
||||||
|
TEST_ASSERT_EQUAL_UINT32(3250, s.ma);
|
||||||
|
TEST_ASSERT_EQUAL_UINT32(1, s.errors);
|
||||||
|
|
||||||
|
sim_i2c_set_present(CH224A_ADDR_DEFAULT, true);
|
||||||
|
pdmon_poll();
|
||||||
|
pdmon_get(&s);
|
||||||
|
TEST_ASSERT_TRUE(s.ok);
|
||||||
|
TEST_ASSERT_EQUAL_INT(0, count_pd_ev(NULL)); /* 掉线再回来、档位没变:不记 */
|
||||||
|
}
|
||||||
|
|
||||||
|
static void test_change_is_logged_once_after_two_matching_reads(void)
|
||||||
|
{
|
||||||
|
sim_i2c_add(CH224A_ADDR_DEFAULT, 1);
|
||||||
|
charger(5000);
|
||||||
|
pdmon_init(5000); /* 启动时 bringup 已经记过 5 A 那条 */
|
||||||
|
pdmon_poll();
|
||||||
|
pdmon_poll();
|
||||||
|
TEST_ASSERT_EQUAL_INT(0, count_pd_ev(NULL));
|
||||||
|
|
||||||
|
charger(0); /* 重新协商的中途 */
|
||||||
|
pdmon_poll();
|
||||||
|
pdmon_poll();
|
||||||
|
TEST_ASSERT_EQUAL_INT(0, count_pd_ev(NULL));
|
||||||
|
|
||||||
|
charger(3000);
|
||||||
|
pdmon_poll();
|
||||||
|
TEST_ASSERT_EQUAL_INT(0, count_pd_ev(NULL)); /* 只见过一拍 */
|
||||||
|
pdmon_poll();
|
||||||
|
evlog_rec_t rec;
|
||||||
|
TEST_ASSERT_EQUAL_INT(1, count_pd_ev(&rec));
|
||||||
|
TEST_ASSERT_EQUAL_UINT8(PDMON_EV_ARG_CHANGED, rec.arg);
|
||||||
|
TEST_ASSERT_EQUAL_INT32(600, rec.bus_mv); /* 60 W,单位 mW/100 */
|
||||||
|
TEST_ASSERT_EQUAL_INT32(3000, rec.current_ua);
|
||||||
|
|
||||||
|
for (int i = 0; i < 5; i++) {
|
||||||
|
pdmon_poll();
|
||||||
|
}
|
||||||
|
TEST_ASSERT_EQUAL_INT(1, count_pd_ev(NULL));
|
||||||
|
}
|
||||||
|
|
||||||
|
static void test_boot_missed_it_but_runtime_reads_it(void)
|
||||||
|
{
|
||||||
|
sim_i2c_add(CH224A_ADDR_DEFAULT, 1);
|
||||||
|
charger(3000);
|
||||||
|
pdmon_init(0); /* 启动时没读到,日志里是「按保守值」那条 */
|
||||||
|
pdmon_poll();
|
||||||
|
pdmon_poll();
|
||||||
|
evlog_rec_t rec;
|
||||||
|
TEST_ASSERT_EQUAL_INT(1, count_pd_ev(&rec));
|
||||||
|
TEST_ASSERT_EQUAL_INT32(3000, rec.current_ua);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void test_found_behind_channel_5(void)
|
||||||
|
{
|
||||||
|
sim_i2c_add_ch(TCA9548A_CH_PD, CH224A_ADDR_DEFAULT, 1);
|
||||||
|
sim_i2c_set_reg_ch(TCA9548A_CH_PD, CH224A_ADDR_DEFAULT, CH224A_REG_STATUS, 0x08);
|
||||||
|
sim_i2c_set_reg_ch(TCA9548A_CH_PD, CH224A_ADDR_DEFAULT, CH224A_REG_CURRENT, 5000 / 50);
|
||||||
|
pdmon_init(5000);
|
||||||
|
pdmon_poll();
|
||||||
|
pdmon_snap_t s;
|
||||||
|
pdmon_get(&s);
|
||||||
|
TEST_ASSERT_TRUE(s.ok);
|
||||||
|
TEST_ASSERT_EQUAL_UINT32(5000, s.ma);
|
||||||
|
TEST_ASSERT_EQUAL_UINT32(0, s.errors);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void test_absent_chip_is_just_not_ok(void)
|
||||||
|
{
|
||||||
|
pdmon_init(0);
|
||||||
|
pdmon_poll();
|
||||||
|
pdmon_snap_t s;
|
||||||
|
pdmon_get(&s);
|
||||||
|
TEST_ASSERT_FALSE(s.ok);
|
||||||
|
TEST_ASSERT_EQUAL_UINT32(1, s.polls);
|
||||||
|
TEST_ASSERT_EQUAL_UINT32(1, s.errors);
|
||||||
|
TEST_ASSERT_EQUAL_INT(0, count_pd_ev(NULL));
|
||||||
|
}
|
||||||
|
|
||||||
|
int main(void)
|
||||||
|
{
|
||||||
|
UNITY_BEGIN();
|
||||||
|
RUN_TEST(test_reports_what_the_chip_says_now);
|
||||||
|
RUN_TEST(test_read_failure_keeps_the_old_value_and_recovers);
|
||||||
|
RUN_TEST(test_change_is_logged_once_after_two_matching_reads);
|
||||||
|
RUN_TEST(test_boot_missed_it_but_runtime_reads_it);
|
||||||
|
RUN_TEST(test_found_behind_channel_5);
|
||||||
|
RUN_TEST(test_absent_chip_is_just_not_ok);
|
||||||
|
return UNITY_END();
|
||||||
|
}
|
||||||
在新工单中引用
屏蔽一个用户