/* * 模块名称: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 #include #include /* ===================== 平台桩(先纳入,覆盖真实头) ===================== */ #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; }