主机单测:新增 ADC 输入层(L1)用例 70 项并登记 L1/L2 单测钩子
新增 App/algo/adc_input.c 的主机侧单测(L1),补齐 L1/L2 算法层
"每个函数都可离线验证"的最后一处空白。
- test/host/adc_input/{adc_input_test.c,run_unit_test.py,stub/}:
70 个用例,覆盖 6 个公开函数 + map_check + stm32_scan。
关键判据 = "通道号 <-> Rank 顺序 <-> 语义"三者映射:给每个 Rank
注入互不相同的码值,逐通道钉死映射(板端无信号源,无法验此项)。
另钉死 HAL 启动失败 / 轮询超时 / 参数错 / 无有效数据等分支。
- .pre-commit-config.yaml:登记 phasor-unit-test(7d)与
adc-input-unit-test(7e)两个主机侧单测钩子。
主机侧全套:adc_input 70/70、phasor 52/52,均 0 失败、无 /W4 告警。
This commit is contained in:
@@ -232,6 +232,34 @@ repos:
|
|||||||
files: ^(App/cfg/cfg_file\.(c|h)|App/cfg/cfg_file_store\.h|App/log/sys_log\.h|test/host/cfg_file/.*|test/host/common/unit_test_harness\.py)$
|
files: ^(App/cfg/cfg_file\.(c|h)|App/cfg/cfg_file_store\.h|App/log/sys_log\.h|test/host/cfg_file/.*|test/host/common/unit_test_harness\.py)$
|
||||||
pass_filenames: false
|
pass_filenames: false
|
||||||
|
|
||||||
|
# 7d) 相量层(L2)主机侧单元测试(2026-10-10 加)
|
||||||
|
# 背景:App/algo/ 为**纯算法**层(无寄存器、无 RTOS、无外设依赖),是本项目
|
||||||
|
# 首个可完全离线验证的算法层。板端**无信号源**,无法注入已知幅值/相位/频率的
|
||||||
|
# 标准波形,故"读数 = 设定值"这一核心判据在板上不可验;本钩子用 MSVC 离线编译
|
||||||
|
# 真实源文件(#include 纳入,无需桩)+ 合成任意波形,把系数表、幅值/相位、
|
||||||
|
# 直流与谐波抑制、频偏敏感度、滑动窗、双区交换一次性钉死。
|
||||||
|
# files 含被测的两个源与其头,以及 harness 本身。
|
||||||
|
- id: phasor-unit-test
|
||||||
|
name: 相量层单测(主机侧)
|
||||||
|
entry: python test/host/phasor/run_unit_test.py
|
||||||
|
language: system
|
||||||
|
files: ^(App/algo/(phasor|sample_ring)\.(c|h)|test/host/phasor/.*|test/host/common/unit_test_harness\.py)$
|
||||||
|
pass_filenames: false
|
||||||
|
|
||||||
|
# 7e) ADC 输入层(L1)主机侧单元测试(2026-10-10 加)
|
||||||
|
# 背景:adc_input.c 的核心是"通道号 <-> Rank 顺序 <-> 语义"三者映射的正确性;
|
||||||
|
# 板端无信号源,无法注入已知电压验证"读到的是哪只脚"(映射说明 §8)。本钩子用
|
||||||
|
# MSVC 离线编译真实源文件(#include 纳入)+ HAL/TPAFE 可注入桩:给每个 Rank
|
||||||
|
# 注入互不相同的码值以**逐通道钉死映射**,并一次钉死 HAL 启动失败 / 转换超时 /
|
||||||
|
# 参数错等真机难复现的分支。
|
||||||
|
# files 含被测源与其头、两个桩头所在目录,以及 harness 本身。
|
||||||
|
- id: adc-input-unit-test
|
||||||
|
name: ADC 输入层单测(主机侧)
|
||||||
|
entry: python test/host/adc_input/run_unit_test.py
|
||||||
|
language: system
|
||||||
|
files: ^(App/algo/adc_input\.(c|h)|test/host/adc_input/.*|test/host/common/unit_test_harness\.py)$
|
||||||
|
pass_filenames: false
|
||||||
|
|
||||||
# 8) 测试套件开关门禁(2026-10-01 加)
|
# 8) 测试套件开关门禁(2026-10-01 加)
|
||||||
# 背景:test_config.h 的 TEST_SUITE_* 决定固件里编入哪个测试套件,
|
# 背景:test_config.h 的 TEST_SUITE_* 决定固件里编入哪个测试套件,
|
||||||
# 全部注释 = 产品构建。但此前**无任何门禁检查**——实测已提交的 HEAD 里
|
# 全部注释 = 产品构建。但此前**无任何门禁检查**——实测已提交的 HEAD 里
|
||||||
|
|||||||
@@ -0,0 +1,463 @@
|
|||||||
|
/*
|
||||||
|
* 模块名称:ADC 输入层(L1)主机侧单元测试
|
||||||
|
* 模块功能:在 PC 上对 App/algo/adc_input.c 做离线验证(6 个公开函数 + 2 个静态函数)。
|
||||||
|
*
|
||||||
|
* 被测对象:真实源文件 ../../../App/algo/adc_input.c(以 #include 原样纳入,
|
||||||
|
* 保证测的是生产代码而非副本)。平台依赖由本文件提供的**可注入桩**实现:
|
||||||
|
* - adc.h -> HAL_ADC_Start / PollForConversion / GetValue / Stop
|
||||||
|
* (可注入 启动失败 / 第 N 次轮询超时 / 逐 Rank 返回值)
|
||||||
|
* - tpafe5160.h-> tpafe5160_snapshot(可注入 新帧/无新帧/参数错 + 帧内容)
|
||||||
|
*
|
||||||
|
* 为什么要主机侧单测(而不是只用板端套件):
|
||||||
|
* 1. 本层核心是"**通道号 <-> Rank 顺序 <-> 语义**"三者的映射正确性。板端**无信号源**
|
||||||
|
* (见 docs/硬件平台/ADC通道映射说明.md §8),无法注入已知电压去验证"读到的是
|
||||||
|
* 哪只脚";主机侧可直接给每个 Rank 注入互不相同的码值,从而**逐通道钉死映射**
|
||||||
|
* (这是唯一能在离线端证伪"通道宏写错 -> 读到另一只脚"的手段)。
|
||||||
|
* 2. HAL 启动失败 / 转换超时 / 参数错等分支在真机上极难稳定复现,主机侧一次钉死。
|
||||||
|
* 3. 不占 MCU 的 RAM,毫秒级完成。
|
||||||
|
*
|
||||||
|
* 覆盖点(对应 docs/算法/继电保护功能复现规划.md §2.1 的 L1-3 / L1-4):
|
||||||
|
* —— adc_input_init(TC-AI-001..006)——
|
||||||
|
* TC-AI-001 poll 失败后失败计数可见(前置状态构造)
|
||||||
|
* TC-AI-002 poll 成功后 TPAFE 侧可取数(前置状态构造)
|
||||||
|
* TC-AI-003 init 返回 0(映射表自检通过)
|
||||||
|
* TC-AI-004 init 后 STM32 状态码 = NOT_RUN
|
||||||
|
* TC-AI-005 init 后失败计数归零
|
||||||
|
* TC-AI-006 init 后 TPAFE / STM32 两侧均无有效数据(有效标志被清)
|
||||||
|
* —— adc_input_desc(TC-AI-010..01A)——
|
||||||
|
* TC-AI-010..018 九个语义通道 -> (来源, 下标) 与权威映射表逐项一致
|
||||||
|
* TC-AI-019/01A 越界(ADC_SEM_COUNT)与负数语义 -> NULL
|
||||||
|
* —— map_check(TC-AI-020)——
|
||||||
|
* TC-AI-020 真实映射表自洽(反向路径结构上不可达,见文件末说明)
|
||||||
|
* —— stm32_scan(TC-AI-021..03A)——
|
||||||
|
* TC-AI-021..025 全通道成功:返回 0 / 状态码 / 不计失败 / Start-Stop 各一次 / 轮询 9 次
|
||||||
|
* TC-AI-026..02B **Rank -> 通道号映射**:UB->CH14(rank9) / UC->CH3(rank4) /
|
||||||
|
* IB->CH6(rank7) / IC->CH1(rank2) / IN->CH2(rank3) / TEMP->CH5(rank6)
|
||||||
|
* TC-AI-02C stm32_scan 不置 TPAFE 有效标志(两来源独立)
|
||||||
|
* TC-AI-030..034 HAL_ADC_Start 失败:返回 -1 / 状态码 RC_START / 计数 +1 / 不调 Stop
|
||||||
|
* TC-AI-035..039 轮询超时:返回 -2 / 状态码 RC_TIMEOUT / 计数 +1 / 调 Stop 收尾 / 立即退出
|
||||||
|
* TC-AI-03A 失败计数跨次单调累计
|
||||||
|
* —— adc_input_raw(TC-AI-040..04B)——
|
||||||
|
* TC-AI-040..043 参数守卫:out=NULL / 越界 / 负数 -> -1,且不写 out
|
||||||
|
* TC-AI-044/045 无有效数据 -> -2(两来源分别)
|
||||||
|
* TC-AI-046..048 poll 快照契约:返回码透传 / 请求 8 通道 / 目的缓冲非空
|
||||||
|
* TC-AI-049..04B TPAFE 按**下标**取数(UA=6 / UN=5 / IA=2)且补码负值原样保留
|
||||||
|
* —— adc_input_poll(TC-AI-050..058)——
|
||||||
|
* TC-AI-050..052 无新帧(0):返回 0 / TPAFE 无效 / STM32 侧照常扫描
|
||||||
|
* TC-AI-053/054 参数错(-1):返回 -1 / TPAFE 无效
|
||||||
|
* TC-AI-055 已有有效帧后再"无新帧"**不清零**(保持上次有效值)
|
||||||
|
* TC-AI-056..058 STM32 失败**不阻断** TPAFE、**不影响** poll 返回值,另由状态码暴露
|
||||||
|
* —— adc_input_stm32_rc / _errs(TC-AI-060..066)——
|
||||||
|
* TC-AI-060/061 init 后 = NOT_RUN / 计数 0
|
||||||
|
* TC-AI-062 成功: rc=OK 且 errs 不变
|
||||||
|
* TC-AI-063/064 两次失败累计 = 2,rc = 最近一次(TIMEOUT)
|
||||||
|
* TC-AI-065/066 恢复成功 rc=OK,errs 单调不回退
|
||||||
|
*
|
||||||
|
* 通过:全部 TC PASS(main 返回 0)。
|
||||||
|
* 入口:int main(void),返回 0=PASS / 1=FAIL(由 run 脚本判读)。
|
||||||
|
*
|
||||||
|
* 创建日期:2026-10-10
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <stddef.h>
|
||||||
|
#include <stdint.h>
|
||||||
|
#include <stdio.h>
|
||||||
|
|
||||||
|
/* ===================== 平台桩(先纳入,覆盖真实头) ===================== */
|
||||||
|
#include "adc.h"
|
||||||
|
#include "tpafe5160.h"
|
||||||
|
|
||||||
|
/* ===================== HAL ADC1 桩 =====================
|
||||||
|
* 被测代码经 HAL 只做四件事:启动、逐 Rank 轮询、读 DR、停止。桩把它们变成可注入:
|
||||||
|
* s_start_rc HAL_ADC_Start 返回值(注入启动失败)
|
||||||
|
* s_poll_fail_at PollForConversion 第几次(0 基)失败;-1 = 永不失败(注入超时)
|
||||||
|
* s_poll_vals[i] 第 i 个 Rank 读到的码值(默认 100+i,用于钉死 Rank->通道映射)
|
||||||
|
* s_poll_calls / s_rank_idx 轮询次数 / 取数次数(观测:超时后是否立即退出)
|
||||||
|
*/
|
||||||
|
static HAL_StatusTypeDef s_start_rc = HAL_OK;
|
||||||
|
static int s_poll_fail_at = -1;
|
||||||
|
static uint32_t s_poll_vals[16];
|
||||||
|
static int s_poll_calls = 0;
|
||||||
|
static int s_rank_idx = 0;
|
||||||
|
static int s_start_calls = 0;
|
||||||
|
static int s_stop_calls = 0;
|
||||||
|
|
||||||
|
/* 被测代码把 &hadc1 透传给 HAL 桩;桩不读其字段 */
|
||||||
|
ADC_HandleTypeDef hadc1;
|
||||||
|
|
||||||
|
HAL_StatusTypeDef HAL_ADC_Start(ADC_HandleTypeDef *hadc) {
|
||||||
|
(void)hadc;
|
||||||
|
s_start_calls++;
|
||||||
|
s_poll_calls = 0;
|
||||||
|
s_rank_idx = 0;
|
||||||
|
return s_start_rc;
|
||||||
|
}
|
||||||
|
|
||||||
|
HAL_StatusTypeDef HAL_ADC_PollForConversion(ADC_HandleTypeDef *hadc, uint32_t Timeout) {
|
||||||
|
(void)hadc;
|
||||||
|
(void)Timeout;
|
||||||
|
{
|
||||||
|
int idx = s_poll_calls;
|
||||||
|
s_poll_calls++;
|
||||||
|
if (s_poll_fail_at >= 0 && idx == s_poll_fail_at) {
|
||||||
|
return HAL_ERROR;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return HAL_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint32_t HAL_ADC_GetValue(ADC_HandleTypeDef *hadc) {
|
||||||
|
uint32_t v;
|
||||||
|
(void)hadc;
|
||||||
|
v = (s_rank_idx < 16) ? s_poll_vals[s_rank_idx] : 0u;
|
||||||
|
s_rank_idx++;
|
||||||
|
return v;
|
||||||
|
}
|
||||||
|
|
||||||
|
HAL_StatusTypeDef HAL_ADC_Stop(ADC_HandleTypeDef *hadc) {
|
||||||
|
(void)hadc;
|
||||||
|
s_stop_calls++;
|
||||||
|
return HAL_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ===================== TPAFE5160 快照桩 ===================== */
|
||||||
|
static int16_t s_fake_tpafe[8];
|
||||||
|
static int s_snapshot_rc = 0;
|
||||||
|
static int s_snapshot_calls = 0;
|
||||||
|
static uint8_t s_snapshot_count = 0u;
|
||||||
|
static int16_t *s_snapshot_dst = NULL;
|
||||||
|
|
||||||
|
int tpafe5160_snapshot(int16_t *dst, uint8_t count) {
|
||||||
|
uint8_t i;
|
||||||
|
|
||||||
|
s_snapshot_calls++;
|
||||||
|
s_snapshot_count = count;
|
||||||
|
s_snapshot_dst = dst;
|
||||||
|
if (s_snapshot_rc == 1) {
|
||||||
|
for (i = 0u; (i < count) && (i < 8u); i++) {
|
||||||
|
dst[i] = s_fake_tpafe[i];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return s_snapshot_rc;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ===================== 断言 ===================== */
|
||||||
|
static int s_total = 0, s_passed = 0, s_failed = 0;
|
||||||
|
|
||||||
|
#define CHECK(cond, name) do { \
|
||||||
|
s_total++; \
|
||||||
|
if (cond) { s_passed++; printf(" [PASS] %s\n", name); } \
|
||||||
|
else { s_failed++; printf(" [FAIL] %s (line %d)\n", name, __LINE__); } \
|
||||||
|
} while (0)
|
||||||
|
|
||||||
|
/* ===================== 被测代码(原样纳入生产源) ===================== */
|
||||||
|
#include "adc_input.c"
|
||||||
|
|
||||||
|
/* ===================== 工具 ===================== */
|
||||||
|
|
||||||
|
/*
|
||||||
|
* 函数功能:把全部桩复位到"健康"初值
|
||||||
|
* 入口参数:无
|
||||||
|
* 返 回 值:无
|
||||||
|
* 限定条件:无
|
||||||
|
* 函数说明:默认 Start 成功、轮询永不超时、第 i 个 Rank 读到 100+i(互不相同,
|
||||||
|
* 以便逐通道钉死 Rank->通道号映射)、TPAFE 快照无新帧。
|
||||||
|
*/
|
||||||
|
static void fakes_reset(void) {
|
||||||
|
uint8_t i;
|
||||||
|
|
||||||
|
s_start_rc = HAL_OK;
|
||||||
|
s_poll_fail_at = -1;
|
||||||
|
s_poll_calls = 0;
|
||||||
|
s_rank_idx = 0;
|
||||||
|
s_start_calls = 0;
|
||||||
|
s_stop_calls = 0;
|
||||||
|
for (i = 0u; i < 16u; i++) {
|
||||||
|
s_poll_vals[i] = 100u + (uint32_t)i;
|
||||||
|
}
|
||||||
|
s_snapshot_rc = 0;
|
||||||
|
s_snapshot_calls = 0;
|
||||||
|
s_snapshot_count = 0u;
|
||||||
|
s_snapshot_dst = NULL;
|
||||||
|
for (i = 0u; i < 8u; i++) {
|
||||||
|
s_fake_tpafe[i] = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ===================== TC-AI-001..006:init ===================== */
|
||||||
|
|
||||||
|
static void tc_init(void) {
|
||||||
|
int32_t v = -999;
|
||||||
|
|
||||||
|
fakes_reset();
|
||||||
|
/* 前置:制造"STM32 失败一次 + TPAFE 有效一帧"的状态,再验 init 是否清干净 */
|
||||||
|
s_snapshot_rc = 1;
|
||||||
|
s_fake_tpafe[6] = 4321;
|
||||||
|
s_start_rc = HAL_ERROR;
|
||||||
|
(void)adc_input_poll();
|
||||||
|
|
||||||
|
CHECK(adc_input_stm32_errs() == 1u, "TC-AI-001 poll 失败后 STM32 失败计数 = 1");
|
||||||
|
CHECK((adc_input_raw(ADC_SEM_UA, &v) == 0) && (v == 4321),
|
||||||
|
"TC-AI-002 poll 后 UA 可取数且值正确");
|
||||||
|
|
||||||
|
CHECK(adc_input_init() == 0, "TC-AI-003 adc_input_init 返回 0(映射自检通过)");
|
||||||
|
CHECK(adc_input_stm32_rc() == ADC_INPUT_RC_NOT_RUN,
|
||||||
|
"TC-AI-004 init 后 STM32 状态码 = NOT_RUN");
|
||||||
|
CHECK(adc_input_stm32_errs() == 0u, "TC-AI-005 init 后 STM32 失败计数归零");
|
||||||
|
CHECK((adc_input_raw(ADC_SEM_UA, &v) == -2) && (adc_input_raw(ADC_SEM_UB, &v) == -2),
|
||||||
|
"TC-AI-006 init 后 TPAFE / STM32 两侧均无有效数据");
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ===================== TC-AI-010..01A:desc 映射表 ===================== */
|
||||||
|
|
||||||
|
static void tc_desc(void) {
|
||||||
|
const adc_channel_desc_t *d;
|
||||||
|
|
||||||
|
d = adc_input_desc(ADC_SEM_UA);
|
||||||
|
CHECK((d != NULL) && (d->source == ADC_SRC_TPAFE) && (d->index == 6u),
|
||||||
|
"TC-AI-010 UA -> TPAFE buf[6]");
|
||||||
|
d = adc_input_desc(ADC_SEM_UB);
|
||||||
|
CHECK((d != NULL) && (d->source == ADC_SRC_STM32) && (d->index == 14u),
|
||||||
|
"TC-AI-011 UB -> STM32 CH14");
|
||||||
|
d = adc_input_desc(ADC_SEM_UC);
|
||||||
|
CHECK((d != NULL) && (d->source == ADC_SRC_STM32) && (d->index == 3u),
|
||||||
|
"TC-AI-012 UC -> STM32 CH3");
|
||||||
|
d = adc_input_desc(ADC_SEM_UN);
|
||||||
|
CHECK((d != NULL) && (d->source == ADC_SRC_TPAFE) && (d->index == 5u),
|
||||||
|
"TC-AI-013 UN -> TPAFE buf[5]");
|
||||||
|
d = adc_input_desc(ADC_SEM_IA);
|
||||||
|
CHECK((d != NULL) && (d->source == ADC_SRC_TPAFE) && (d->index == 2u),
|
||||||
|
"TC-AI-014 IA -> TPAFE buf[2]");
|
||||||
|
d = adc_input_desc(ADC_SEM_IB);
|
||||||
|
CHECK((d != NULL) && (d->source == ADC_SRC_STM32) && (d->index == 6u),
|
||||||
|
"TC-AI-015 IB -> STM32 CH6");
|
||||||
|
d = adc_input_desc(ADC_SEM_IC);
|
||||||
|
CHECK((d != NULL) && (d->source == ADC_SRC_STM32) && (d->index == 1u),
|
||||||
|
"TC-AI-016 IC -> STM32 CH1");
|
||||||
|
d = adc_input_desc(ADC_SEM_IN);
|
||||||
|
CHECK((d != NULL) && (d->source == ADC_SRC_STM32) && (d->index == 2u),
|
||||||
|
"TC-AI-017 IN -> STM32 CH2");
|
||||||
|
d = adc_input_desc(ADC_SEM_TEMP);
|
||||||
|
CHECK((d != NULL) && (d->source == ADC_SRC_STM32) && (d->index == 5u),
|
||||||
|
"TC-AI-018 TEMP -> STM32 CH5");
|
||||||
|
|
||||||
|
CHECK(adc_input_desc(ADC_SEM_COUNT) == NULL, "TC-AI-019 越界(ADC_SEM_COUNT) -> NULL");
|
||||||
|
CHECK(adc_input_desc((adc_semantic_t)(-1)) == NULL, "TC-AI-01A 负数语义 -> NULL");
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ===================== TC-AI-020:map_check ===================== */
|
||||||
|
|
||||||
|
static void tc_map_check(void) {
|
||||||
|
/* 真实映射表自洽:TPAFE 下标 < 8 且每个 STM32 通道号都出现在扫描 Rank 内。
|
||||||
|
* 反向路径(表不自洽 -> -1)需在运行时改表,而 k_adc_map / k_stm32_rank_ch 均为
|
||||||
|
* 编译期 static const、无运行时输入可使其不自洽 -> 结构上不可达(见文件末说明)。 */
|
||||||
|
CHECK(map_check() == 0, "TC-AI-020 映射表自洽(TPAFE 下标与 STM32 Rank 均在范围内)");
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ===================== TC-AI-021..02C:stm32_scan 成功路径 ===================== */
|
||||||
|
|
||||||
|
static void tc_scan_ok(void) {
|
||||||
|
int32_t v = 0;
|
||||||
|
|
||||||
|
fakes_reset();
|
||||||
|
(void)adc_input_init();
|
||||||
|
|
||||||
|
CHECK(stm32_scan() == ADC_INPUT_RC_OK, "TC-AI-021 stm32_scan 全通道成功 -> 0");
|
||||||
|
CHECK(adc_input_stm32_rc() == ADC_INPUT_RC_OK, "TC-AI-022 状态码 = RC_OK");
|
||||||
|
CHECK(adc_input_stm32_errs() == 0u, "TC-AI-023 成功不计入失败");
|
||||||
|
CHECK((s_start_calls == 1) && (s_stop_calls == 1), "TC-AI-024 Start / Stop 各一次");
|
||||||
|
CHECK((s_poll_calls == (int)ADC_INPUT_STM32_CH_NUM) && (s_rank_idx == (int)ADC_INPUT_STM32_CH_NUM),
|
||||||
|
"TC-AI-025 轮询与取数各 9 次(Rank1..9)");
|
||||||
|
|
||||||
|
/* Rank -> 通道号映射:第 i 个 Rank 读到 100+i。
|
||||||
|
* k_stm32_rank_ch = [CH0..CH7, CH14],故 CH3->103 / CH5->105 / CH6->106 / CH14->108。 */
|
||||||
|
CHECK((adc_input_raw(ADC_SEM_UB, &v) == 0) && (v == 108), "TC-AI-026 UB 取到 CH14(rank9)=108");
|
||||||
|
CHECK((adc_input_raw(ADC_SEM_UC, &v) == 0) && (v == 103), "TC-AI-027 UC 取到 CH3(rank4)=103");
|
||||||
|
CHECK((adc_input_raw(ADC_SEM_IB, &v) == 0) && (v == 106), "TC-AI-028 IB 取到 CH6(rank7)=106");
|
||||||
|
CHECK((adc_input_raw(ADC_SEM_IC, &v) == 0) && (v == 101), "TC-AI-029 IC 取到 CH1(rank2)=101");
|
||||||
|
CHECK((adc_input_raw(ADC_SEM_IN, &v) == 0) && (v == 102), "TC-AI-02A IN 取到 CH2(rank3)=102");
|
||||||
|
CHECK((adc_input_raw(ADC_SEM_TEMP, &v) == 0) && (v == 105), "TC-AI-02B TEMP 取到 CH5(rank6)=105");
|
||||||
|
|
||||||
|
CHECK(adc_input_raw(ADC_SEM_UA, &v) == -2, "TC-AI-02C stm32_scan 不置 TPAFE 有效标志");
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ===================== TC-AI-030..034:stm32_scan 启动失败 ===================== */
|
||||||
|
|
||||||
|
static void tc_scan_start_fail(void) {
|
||||||
|
int32_t v = 0;
|
||||||
|
|
||||||
|
fakes_reset();
|
||||||
|
(void)adc_input_init();
|
||||||
|
s_start_rc = HAL_ERROR;
|
||||||
|
|
||||||
|
CHECK(stm32_scan() == ADC_INPUT_RC_START, "TC-AI-030 HAL_ADC_Start 失败 -> -1");
|
||||||
|
CHECK(adc_input_stm32_rc() == ADC_INPUT_RC_START, "TC-AI-031 状态码 = RC_START");
|
||||||
|
CHECK(adc_input_stm32_errs() == 1u, "TC-AI-032 失败计数 +1");
|
||||||
|
CHECK(s_stop_calls == 0, "TC-AI-033 启动失败不调用 Stop");
|
||||||
|
CHECK(adc_input_raw(ADC_SEM_UB, &v) == -2, "TC-AI-034 启动失败后 STM32 侧仍无有效数据");
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ===================== TC-AI-035..03A:stm32_scan 轮询超时 ===================== */
|
||||||
|
|
||||||
|
static void tc_scan_timeout(void) {
|
||||||
|
int32_t v = 0;
|
||||||
|
|
||||||
|
fakes_reset();
|
||||||
|
(void)adc_input_init();
|
||||||
|
s_poll_fail_at = 4; /* 第 5 个 Rank 轮询超时 */
|
||||||
|
|
||||||
|
CHECK(stm32_scan() == ADC_INPUT_RC_TIMEOUT, "TC-AI-035 轮询超时 -> -2");
|
||||||
|
CHECK(adc_input_stm32_rc() == ADC_INPUT_RC_TIMEOUT, "TC-AI-036 状态码 = RC_TIMEOUT");
|
||||||
|
CHECK(adc_input_stm32_errs() == 1u, "TC-AI-037 超时计入失败");
|
||||||
|
CHECK(s_stop_calls == 1, "TC-AI-038 超时前已 Start -> 调 Stop 收尾");
|
||||||
|
CHECK(s_poll_calls == 5, "TC-AI-039 超时后立即退出(只轮询 5 次)");
|
||||||
|
CHECK(adc_input_raw(ADC_SEM_UB, &v) == -2, "TC-AI-039b 超时后 STM32 侧无有效数据");
|
||||||
|
|
||||||
|
s_poll_fail_at = 0; /* 再失败一次 */
|
||||||
|
(void)stm32_scan();
|
||||||
|
CHECK(adc_input_stm32_errs() == 2u, "TC-AI-03A 失败计数跨次单调累计 = 2");
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ===================== TC-AI-040..045:raw 参数守卫与无数据 ===================== */
|
||||||
|
|
||||||
|
static void tc_raw_guards(void) {
|
||||||
|
int32_t v = 0;
|
||||||
|
|
||||||
|
fakes_reset();
|
||||||
|
(void)adc_input_init();
|
||||||
|
|
||||||
|
CHECK(adc_input_raw(ADC_SEM_UA, NULL) == -1, "TC-AI-040 out=NULL -> -1");
|
||||||
|
CHECK(adc_input_raw(ADC_SEM_COUNT, &v) == -1, "TC-AI-041 ch 越界 -> -1");
|
||||||
|
CHECK(adc_input_raw((adc_semantic_t)(-1), &v) == -1, "TC-AI-042 ch 负数 -> -1");
|
||||||
|
CHECK(v == 0, "TC-AI-043 参数错时不写 out");
|
||||||
|
CHECK(adc_input_raw(ADC_SEM_UA, &v) == -2, "TC-AI-044 TPAFE 无有效帧 -> -2");
|
||||||
|
CHECK(adc_input_raw(ADC_SEM_UB, &v) == -2, "TC-AI-045 STM32 无有效扫描 -> -2");
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ===================== TC-AI-046..04B:raw TPAFE 取数与补码 ===================== */
|
||||||
|
|
||||||
|
static void tc_raw_tpafe(void) {
|
||||||
|
int32_t v = 0;
|
||||||
|
|
||||||
|
fakes_reset();
|
||||||
|
(void)adc_input_init();
|
||||||
|
/* 注入 8 路 TPAFE 码值(互不相同),UA 应取 buf[6] / UN 取 buf[5] / IA 取 buf[2] */
|
||||||
|
s_fake_tpafe[0] = 1000;
|
||||||
|
s_fake_tpafe[1] = 1001;
|
||||||
|
s_fake_tpafe[2] = -1234;
|
||||||
|
s_fake_tpafe[3] = 1003;
|
||||||
|
s_fake_tpafe[4] = 1004;
|
||||||
|
s_fake_tpafe[5] = 2005;
|
||||||
|
s_fake_tpafe[6] = 3006;
|
||||||
|
s_fake_tpafe[7] = 1007;
|
||||||
|
s_snapshot_rc = 1;
|
||||||
|
|
||||||
|
CHECK(adc_input_poll() == 1, "TC-AI-046 poll 透传 TPAFE 快照码 1");
|
||||||
|
CHECK(s_snapshot_count == (uint8_t)ADC_INPUT_TPAFE_CH_NUM, "TC-AI-047 快照请求 8 通道");
|
||||||
|
CHECK(s_snapshot_dst != NULL, "TC-AI-048 快照目的缓冲非空");
|
||||||
|
CHECK((adc_input_raw(ADC_SEM_UA, &v) == 0) && (v == 3006), "TC-AI-049 UA -> buf[6]=3006");
|
||||||
|
CHECK((adc_input_raw(ADC_SEM_UN, &v) == 0) && (v == 2005), "TC-AI-04A UN -> buf[5]=2005");
|
||||||
|
CHECK((adc_input_raw(ADC_SEM_IA, &v) == 0) && (v == -1234),
|
||||||
|
"TC-AI-04B IA -> buf[2]=-1234(补码负值原样保留)");
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ===================== TC-AI-050..058:poll 契约 ===================== */
|
||||||
|
|
||||||
|
static void tc_poll(void) {
|
||||||
|
int32_t v = 0;
|
||||||
|
|
||||||
|
/* --- 无新帧(0):返回 0,TPAFE 无效,STM32 侧照常扫描 --- */
|
||||||
|
fakes_reset();
|
||||||
|
(void)adc_input_init();
|
||||||
|
s_snapshot_rc = 0;
|
||||||
|
CHECK(adc_input_poll() == 0, "TC-AI-050 快照无新帧 -> poll 返回 0");
|
||||||
|
CHECK(adc_input_raw(ADC_SEM_UA, &v) == -2, "TC-AI-051 无新帧时 TPAFE 侧无效");
|
||||||
|
CHECK(adc_input_stm32_rc() == ADC_INPUT_RC_OK, "TC-AI-052 同拍 STM32 扫描照常执行");
|
||||||
|
|
||||||
|
/* --- 参数错(-1):返回 -1,TPAFE 无效 --- */
|
||||||
|
fakes_reset();
|
||||||
|
(void)adc_input_init();
|
||||||
|
s_snapshot_rc = -1;
|
||||||
|
CHECK(adc_input_poll() == -1, "TC-AI-053 快照参数错 -> poll 返回 -1");
|
||||||
|
CHECK(adc_input_raw(ADC_SEM_UA, &v) == -2, "TC-AI-054 参数错时 TPAFE 侧无效");
|
||||||
|
|
||||||
|
/* --- 已有有效帧后再"无新帧":保持上次有效值(不清零) --- */
|
||||||
|
fakes_reset();
|
||||||
|
(void)adc_input_init();
|
||||||
|
s_fake_tpafe[6] = 777;
|
||||||
|
s_snapshot_rc = 1;
|
||||||
|
(void)adc_input_poll();
|
||||||
|
s_snapshot_rc = 0;
|
||||||
|
(void)adc_input_poll();
|
||||||
|
CHECK((adc_input_raw(ADC_SEM_UA, &v) == 0) && (v == 777),
|
||||||
|
"TC-AI-055 已有有效帧后无新帧不清零");
|
||||||
|
|
||||||
|
/* --- STM32 失败不阻断 TPAFE、不影响 poll 返回值 --- */
|
||||||
|
fakes_reset();
|
||||||
|
(void)adc_input_init();
|
||||||
|
s_fake_tpafe[2] = 55;
|
||||||
|
s_snapshot_rc = 1;
|
||||||
|
s_start_rc = HAL_ERROR;
|
||||||
|
CHECK(adc_input_poll() == 1, "TC-AI-056 STM32 失败不影响 poll 返回值(仍为 1)");
|
||||||
|
CHECK((adc_input_raw(ADC_SEM_IA, &v) == 0) && (v == 55),
|
||||||
|
"TC-AI-057 STM32 失败不影响 TPAFE 取数");
|
||||||
|
CHECK(adc_input_stm32_rc() == ADC_INPUT_RC_START, "TC-AI-058 STM32 失败另由状态码暴露");
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ===================== TC-AI-060..066:rc / errs 观测 ===================== */
|
||||||
|
|
||||||
|
static void tc_rc_errs(void) {
|
||||||
|
fakes_reset();
|
||||||
|
(void)adc_input_init();
|
||||||
|
|
||||||
|
CHECK(adc_input_stm32_rc() == ADC_INPUT_RC_NOT_RUN, "TC-AI-060 init 后未扫描 = NOT_RUN(-3)");
|
||||||
|
CHECK(adc_input_stm32_errs() == 0u, "TC-AI-061 init 后失败计数 = 0");
|
||||||
|
|
||||||
|
(void)stm32_scan(); /* 成功 */
|
||||||
|
CHECK((adc_input_stm32_rc() == ADC_INPUT_RC_OK) && (adc_input_stm32_errs() == 0u),
|
||||||
|
"TC-AI-062 成功: rc=OK 且 errs 不变");
|
||||||
|
|
||||||
|
s_start_rc = HAL_ERROR;
|
||||||
|
(void)stm32_scan(); /* 失败 1 */
|
||||||
|
s_start_rc = HAL_OK;
|
||||||
|
s_poll_fail_at = 0;
|
||||||
|
(void)stm32_scan(); /* 失败 2 */
|
||||||
|
CHECK(adc_input_stm32_errs() == 2u, "TC-AI-063 两次失败累计 = 2");
|
||||||
|
CHECK(adc_input_stm32_rc() == ADC_INPUT_RC_TIMEOUT, "TC-AI-064 rc = 最近一次(TIMEOUT)");
|
||||||
|
|
||||||
|
s_poll_fail_at = -1;
|
||||||
|
(void)stm32_scan(); /* 恢复成功 */
|
||||||
|
CHECK(adc_input_stm32_rc() == ADC_INPUT_RC_OK, "TC-AI-065 恢复成功后 rc=OK");
|
||||||
|
CHECK(adc_input_stm32_errs() == 2u, "TC-AI-066 errs 单调递增(不因成功回落)");
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ===================== main ===================== */
|
||||||
|
|
||||||
|
int main(void) {
|
||||||
|
printf("=== ADC Input Tests ===\n");
|
||||||
|
|
||||||
|
printf("-- init / reset --\n");
|
||||||
|
tc_init();
|
||||||
|
printf("-- semantic map desc --\n");
|
||||||
|
tc_desc();
|
||||||
|
printf("-- map self-check --\n");
|
||||||
|
tc_map_check();
|
||||||
|
printf("-- stm32 scan: success + rank/channel mapping --\n");
|
||||||
|
tc_scan_ok();
|
||||||
|
printf("-- stm32 scan: HAL_ADC_Start fail --\n");
|
||||||
|
tc_scan_start_fail();
|
||||||
|
printf("-- stm32 scan: poll timeout --\n");
|
||||||
|
tc_scan_timeout();
|
||||||
|
printf("-- raw: guards / no-data --\n");
|
||||||
|
tc_raw_guards();
|
||||||
|
printf("-- raw: tpafe index + two's complement --\n");
|
||||||
|
tc_raw_tpafe();
|
||||||
|
printf("-- poll contract --\n");
|
||||||
|
tc_poll();
|
||||||
|
printf("-- rc / errs observability --\n");
|
||||||
|
tc_rc_errs();
|
||||||
|
|
||||||
|
printf("\n=== ADC Input Tests: %d/%d PASSED (failed=%d) ===\n",
|
||||||
|
s_passed, s_total, s_failed);
|
||||||
|
return (s_failed == 0) ? 0 : 1;
|
||||||
|
}
|
||||||
@@ -0,0 +1,54 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
"""ADC 输入层(L1,adc_input)的主机侧单元测试运行器(L0.5,纯标准库)。
|
||||||
|
|
||||||
|
做什么:
|
||||||
|
用本机 MSVC 编译 test/host/adc_input/adc_input_test.c(它以 #include 纳入
|
||||||
|
**生产源文件** App/algo/adc_input.c),在 PC 上离线跑语义映射表 / STM32 ADC1
|
||||||
|
顺序扫描 / TPAFE 快照消费 / 取数守卫 / poll 契约 / 状态码统计,失败即非 0 退出。
|
||||||
|
零硬件、零烧录、毫秒级。
|
||||||
|
|
||||||
|
工具链探测 / INCLUDE-LIB 拼接 / 编译运行 / 退出码判定全部委托给通用 harness
|
||||||
|
`test/host/common/unit_test_harness.py`(本文件只做声明)。
|
||||||
|
|
||||||
|
为什么要主机侧单测(而不是只用板端套件):
|
||||||
|
本层核心是"**通道号 <-> Rank 顺序 <-> 语义**"三者映射的正确性。板端无信号源
|
||||||
|
(docs/硬件平台/ADC通道映射说明.md §8),无法注入已知电压验证"读到的是哪只脚";
|
||||||
|
主机侧给每个 Rank 注入互不相同的码值,即可**逐通道钉死映射**,并一次钉死
|
||||||
|
HAL 启动失败 / 转换超时 / 参数错等真机难复现的分支。
|
||||||
|
|
||||||
|
用法(仓库根目录):
|
||||||
|
python test/host/adc_input/run_unit_test.py
|
||||||
|
或统一用 .venv: & '.venv\\Scripts\\python.exe' test/host/adc_input/run_unit_test.py
|
||||||
|
|
||||||
|
退出码:0=PASS / 1=测试失败 / 2=环境错误(找不到编译器 / 编译失败 / 无汇总行)。
|
||||||
|
|
||||||
|
设计依据:docs/算法/继电保护功能复现规划.md §2.1(L1-3 / L1-4);
|
||||||
|
创建日期:2026-10-10
|
||||||
|
"""
|
||||||
|
import os
|
||||||
|
import sys
|
||||||
|
|
||||||
|
HERE = os.path.dirname(os.path.abspath(__file__))
|
||||||
|
ROOT = os.path.dirname(os.path.dirname(os.path.dirname(HERE)))
|
||||||
|
# harness 位于 test/host/common/
|
||||||
|
sys.path.insert(0, os.path.join(ROOT, "test", "host", "common"))
|
||||||
|
|
||||||
|
from unit_test_harness import TestBuild, run_host_unit_test # noqa: E402
|
||||||
|
|
||||||
|
# 顺序敏感:stub 目录必须**先于**生产目录,才能覆盖 adc.h / tpafe5160.h
|
||||||
|
# (真实头会经 main.h 拖入整套 HAL,主机侧无法编译)。adc_input.h 用真实头
|
||||||
|
# (接口契约原汁);HAL_ADC_* 与 tpafe5160_snapshot 由测试单元给强符号桩。
|
||||||
|
BUILD = TestBuild(
|
||||||
|
name="ADC Input Tests",
|
||||||
|
src=os.path.join(HERE, "adc_input_test.c"),
|
||||||
|
include_dirs=[
|
||||||
|
os.path.join(HERE, "stub"),
|
||||||
|
os.path.join(ROOT, "App", "algo"),
|
||||||
|
HERE,
|
||||||
|
],
|
||||||
|
summary_marker="=== ADC Input Tests:",
|
||||||
|
)
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
sys.exit(run_host_unit_test(BUILD))
|
||||||
@@ -0,0 +1,58 @@
|
|||||||
|
/*
|
||||||
|
* 主机侧单测桩:adc.h
|
||||||
|
* 模块功能:为 App/algo/adc_input.c 提供主机可编译的 STM32 ADC1 句柄与 HAL 接口
|
||||||
|
* 声明(真实 Inc/adc.h 会经 main.h 拖入整套 HAL,主机侧无法编译)。
|
||||||
|
*
|
||||||
|
* ⚠ 仅供 test/host/adc_input/ 使用,不参与 MCU 构建。
|
||||||
|
*
|
||||||
|
* 保真点:F4 HAL 的 ADC_CHANNEL_x 宏值**即通道号 x**(0..15),本桩按此定义,
|
||||||
|
* 故被测代码里 `(uint8_t)ADC_CHANNEL_14` 得到 14 —— 与真机一致。
|
||||||
|
* 另声明 4 个 HAL_ADC_* 接口,由测试单元给强符号桩(可注入失败/超时/返回值)。
|
||||||
|
*
|
||||||
|
* 创建日期:2026-10-10
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef ADC_H
|
||||||
|
#define ADC_H
|
||||||
|
|
||||||
|
#include <stdint.h>
|
||||||
|
|
||||||
|
/* HAL 状态码(与 stm32f4xx_hal_def.h 同值,仅取被测代码用到的子集) */
|
||||||
|
typedef enum {
|
||||||
|
HAL_OK = 0x00,
|
||||||
|
HAL_ERROR = 0x01,
|
||||||
|
HAL_BUSY = 0x02,
|
||||||
|
HAL_TIMEOUT = 0x03
|
||||||
|
} HAL_StatusTypeDef;
|
||||||
|
|
||||||
|
/* ADC 句柄:被测代码只把 &hadc1 透传给 HAL 接口,从不读其字段,故为不透明占位 */
|
||||||
|
typedef struct {
|
||||||
|
uint32_t reserved;
|
||||||
|
} ADC_HandleTypeDef;
|
||||||
|
|
||||||
|
extern ADC_HandleTypeDef hadc1;
|
||||||
|
|
||||||
|
/* ADC 通道宏(F4:宏值 = 通道号) */
|
||||||
|
#define ADC_CHANNEL_0 (0x00000000U)
|
||||||
|
#define ADC_CHANNEL_1 (0x00000001U)
|
||||||
|
#define ADC_CHANNEL_2 (0x00000002U)
|
||||||
|
#define ADC_CHANNEL_3 (0x00000003U)
|
||||||
|
#define ADC_CHANNEL_4 (0x00000004U)
|
||||||
|
#define ADC_CHANNEL_5 (0x00000005U)
|
||||||
|
#define ADC_CHANNEL_6 (0x00000006U)
|
||||||
|
#define ADC_CHANNEL_7 (0x00000007U)
|
||||||
|
#define ADC_CHANNEL_8 (0x00000008U)
|
||||||
|
#define ADC_CHANNEL_9 (0x00000009U)
|
||||||
|
#define ADC_CHANNEL_10 (0x0000000AU)
|
||||||
|
#define ADC_CHANNEL_11 (0x0000000BU)
|
||||||
|
#define ADC_CHANNEL_12 (0x0000000CU)
|
||||||
|
#define ADC_CHANNEL_13 (0x0000000DU)
|
||||||
|
#define ADC_CHANNEL_14 (0x0000000EU)
|
||||||
|
#define ADC_CHANNEL_15 (0x0000000FU)
|
||||||
|
|
||||||
|
HAL_StatusTypeDef HAL_ADC_Start(ADC_HandleTypeDef *hadc);
|
||||||
|
HAL_StatusTypeDef HAL_ADC_PollForConversion(ADC_HandleTypeDef *hadc, uint32_t Timeout);
|
||||||
|
uint32_t HAL_ADC_GetValue(ADC_HandleTypeDef *hadc);
|
||||||
|
HAL_StatusTypeDef HAL_ADC_Stop(ADC_HandleTypeDef *hadc);
|
||||||
|
|
||||||
|
#endif /* ADC_H */
|
||||||
@@ -0,0 +1,28 @@
|
|||||||
|
/*
|
||||||
|
* 主机侧单测桩:tpafe5160.h
|
||||||
|
* 模块功能:为 App/algo/adc_input.c 提供主机可编译的 TPAFE5160 快照接口声明
|
||||||
|
* (真实头会经 main.h 拖入 HAL 与 GPIO 宏,主机侧无法编译)。
|
||||||
|
*
|
||||||
|
* ⚠ 仅供 test/host/adc_input/ 使用,不参与 MCU 构建。
|
||||||
|
*
|
||||||
|
* 被测代码只消费 tpafe5160_snapshot()(E-58 原子快照),故桩只保留该声明;
|
||||||
|
* 其返回值语义(1=新帧 / 0=无新帧 / -1=参数错)与真实驱动一致,由测试单元
|
||||||
|
* 给强符号桩并按用例注入。
|
||||||
|
*
|
||||||
|
* 创建日期:2026-10-10
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef TPAFE5160_H
|
||||||
|
#define TPAFE5160_H
|
||||||
|
|
||||||
|
#include <stdint.h>
|
||||||
|
|
||||||
|
/*
|
||||||
|
* 函数功能:原子快照最新一帧(消费者取数的唯一正确姿势)
|
||||||
|
* 入口参数:dst - 输出缓冲区 int16_t* 不为 NULL
|
||||||
|
* count - 拷贝通道数 uint8_t 1 - 8
|
||||||
|
* 返 回 值:1 - 取到新帧,0 - 无新帧,-1 - 参数无效
|
||||||
|
*/
|
||||||
|
int tpafe5160_snapshot(int16_t *dst, uint8_t count);
|
||||||
|
|
||||||
|
#endif /* TPAFE5160_H */
|
||||||
Reference in New Issue
Block a user