EMS 知识库
首页 / knowledge/代码解析/midware/debug

board_test.c

源文件路径:midware/debug/board_test.c 所属模块:midware/debug(设备中间件 - 板级调试/产线测试) 代码行数:638

1. 文件职责

本文件实现 EMS 主控板的单板自检(Single Board Test):把 LED、RTC、SPI Flash/EEPROM、SDRAM、RS485、USART、W5500 以太网、CAN、干接点 DI/DO、DRM 共 10 类外设依次测一遍,把每一项的通过/失败结果按位写进 32 位联合体 u_single_board_test_result,再由 Modbus 从机寄存器对外暴露给产线工装读取。

它处在"产线测试"这条旁路数据流上,不参与正常运行时的能量调度。流程由 Modbus 写寄存器 REG_SINGLE_BOARD_TEST_ENABLE 触发(midware/interface/modbus/modbus.c:6936-6938 把 g_u8_single_board_test_enable 置 TRUE),然后由 task_system_aux(midware/auxiliary/system_aux.c:2083)每 400 ms 轮询一次 board_test();board_test() 自身在入口检查使能标志,测完立刻把标志清回 FALSE,属于"一次性执行"的同步阻塞函数。因为整轮测试内含大量忙等延时,执行期间会长时间占用 task_system_aux,并通过反复调用 IWDG_Feed() 防止看门狗复位。

2. 关键数据结构与宏

名称类型说明
BOARD_TEST_U联合体(定义在 board_test.h:17-65)测试结果容器。u32 u32_all 与 struct { ... } bit 共用同一 4 字节空间,每一位对应一项外设(0=通过,1=失败),初值 0xFFFFFFFF 即全部判失败
BOARD_TEST_U.bit.u8_eeprom位域 / bit0EEPROM(I2C3)测试结果,实际由 flash_test() 与 Flash 一起判定
BOARD_TEST_U.bit.u8_rtc位域 / bit1PCF8563 RTC 测试结果
BOARD_TEST_U.bit.u8_flash位域 / bit2SPI Flash(SPI4)测试结果
BOARD_TEST_U.bit.u8_sdram位域 / bit3外部 SDRAM 读写测试结果
BOARD_TEST_U.bit.u8_rs485位域 / bit4RS485 双向测试(USART1 ↔ UART4)结果
BOARD_TEST_U.bit.u8_usart2位域 / bit5USART2 自发自收测试结果
BOARD_TEST_U.bit.u8_usart3位域 / bit6未测试,board_test() 中直接置 0(通过)
BOARD_TEST_U.bit.u8_usart6位域 / bit7实际由 USART5 自发自收测试写入,见风险点
BOARD_TEST_U.bit.u8_w5500位域 / bit8W5500 以太网 MAC 回读一致性结果
BOARD_TEST_U.bit.u8_can1位域 / bit9CAN0 ↔ CAN1 互通测试结果(can_test() 写入)
BOARD_TEST_U.bit.u8_can2位域 / bit10CAN1 ↔ CAN0 互通测试结果
BOARD_TEST_U.bit.u8_can3位域 / bit11未测试,直接置 0(通过)
BOARD_TEST_U.bit.u8_drm位域 / bit12DRM 4 路信号与 AD 采样测试结果
BOARD_TEST_U.bit.u8_dido1位域 / bit13干接点 DO1→DI1 回环测试结果
BOARD_TEST_U.bit.u8_dido2位域 / bit14干接点 DO2→DI2 回环测试结果
bit15 ~ bit31位域保留位
g_u8_usart_buf[16]全局 u8 数组UART 自发自收测试报文 "USART text"(board_test.c:33)
g_u8_rs485_buf[16]全局 u8 数组RS485 自发自收测试报文 "RS485 text"(board_test.c:34)
g_u8_single_board_test_enable全局 u8单板测试使能标志。Modbus 置 1,board_test() 测完清 0;同时被 ate.c 用来禁止串口透传(ate.c:138/172)
u_single_board_test_result全局 BOARD_TEST_U测试结果,初值 .u32_all = 0xFFFFFFFF(board_test.c:37)
FLASH_TEST__LEN宏(board_test.h:15,值 32)定义为 32,但本文件中从未被引用
SDRAM_SDRAM_TEST_ADDR宏(hardware/sdram/sdram.h:44)SDRAM 测试起始地址(SDRAM_BOOT_FILE_ADDR + SDRAM_16K),容量 1 KB
ADDR_MAX_SDRAM宏(hardware/sdram/sdram.h:20)SDRAM 地址上界 0xC0000000 + CAPACITY_SDRAM - 1,用于越界保护
DRY_NODE1_ON/OFF、DRY_NODE2_ON/OFF宏(dry_node.h:20-26)干接点输出控制,展开为 do0_output_on() / do1_output_off() 等
DI_1_ACCESS_POINT、DI_2_ACCESS_POINT宏(dry_node.h:24/30)干接点回读,展开为 di0_input_bit_get() / di1_input_bit_get()

结果位与外设对照(按代码实际写入)

测试函数写入的位头文件里的 bit 编号注释
led_test()不写任何位调用点注释称 "led bit10"
rtc_test()u8_rtcbit1 ✓
flash_test()u8_flash、u8_eepromflash bit2 ✓
sdram_test()u8_sdrambit3 ✓
rs485_test()u8_rs485bit4 ✓
usart_test()u8_usart2、u8_usart6usart bit5 ✓
w5500_test()u8_w5500调用点注释称 "w5500 bit6"
can_test()u8_can1、u8_can2调用点注释称 "can1 bit7, can2 bit8"
dry_node_test()u8_dido1、u8_dido2无注释
drm_test()u8_drm无注释

3. 函数清单

函数名作用域说明
w5500_teststatic(board_test.c:49)用 ctlnetwork(CN_GET_NETINFO) 回读 W5500 内部 MAC,与软件保存的 IP_Address.u8_mac_addr 比较,一致则 u8_w5500 = 0
led_teststatic(board_test.c:72)依次点亮/熄灭 4 个状态 LED 与 8 个 led_db0~led_db7 面板灯,各保持 1 s,纯目检,不写结果位
can_teststatic(board_test.c:139)构造 8 字节 CAN 数据帧(ID 0xA0),CAN0/CAN1 相互收发各 4 次,比对回读数据判定 u8_can1、u8_can2
usart_teststatic(board_test.c:212)USART2 与 USART5 分别自发自收 16 字节 "USART text",strncmp 比对后写 u8_usart2 和 u8_usart6
rs485_teststatic(board_test.c:287)两段测试:① USART1 发 → UART4 收;② UART4 发 → USART1 收。都成功才 u8_rs485 = 0,第一段失败直接返回
rtc_teststatic(board_test.c:373)最多尝试 5 次 PCF8563_GetDateTime() + PCF8563_Check_Time_Is_Valid(),任一次有效即 u8_rtc = 0
flash_teststatic(board_test.c:397)读 SPI Flash ID,命中 C2 20 18/19/1A 则 u8_flash、u8_eeprom 同时置 0,否则同时置 1
sdram_teststatic(board_test.c:422)从 SDRAM_SDRAM_TEST_ADDR 起逐字节写 0x65 再读回,1024 字节全部一致才 u8_sdram = 0,带 ADDR_MAX_SDRAM 越界保护
drm_teststatic(board_test.c:458)读 DRM0 的 AD 中间值与 DRM1~4 的 GPIO 标志,要求 AD 落在 2000~2100 且 4 路标志全为 TRUE;EMS3.6-2 板型直接判通过
dry_node_teststatic(board_test.c:502)DO1 拉高/拉低各等 100 ms 回读 DI1 验证 u8_dido1;EMS3.6-1/2/3/4 板型再对 DO2/DI2 做同样验证写 u8_dido2
board_test全局(board_test.c:613)单板测试总入口:检查使能 → 结果清 0xFFFFFFFF → 依次调用 10 个测试子函数 → 无条件补 u8_can3=0、u8_usart3=0 → 清使能标志

4. 核心逻辑解析

4.1 总调度与状态机

board_test() 是一个两级触发、单次执行的简单状态机,没有内部状态变量:

Modbus 写 REG_SINGLE_BOARD_TEST_ENABLE (modbus.c:6936)
        ↓
g_u8_single_board_test_enable = TRUE
        ↓
task_system_aux 周期 400ms → board_test()  (system_aux.c:2083)
        ↓
if (g_u8_single_board_test_enable == FALSE) return;   ← 未使能则立即返回
        ↓
u_single_board_test_result.u32_all = 0xFFFFFFFF;      ← 全部预设为"失败"
        ↓
  led_test → rtc_test → flash_test → sdram_test → rs485_test
  → usart_test → w5500_test → can_test → dry_node_test → drm_test
        ↓
u8_can3 = 0; u8_usart3 = 0;   ← 无对应测试,直接判通过
        ↓
g_u8_single_board_test_enable = FALSE;   ← 回到空闲,测试只跑一次

结果位在进入时被整体置 1(board_test.c:620),各子测试只在通过时清 0,所以"任何异常路径(提前 return / 未覆盖)都表现为失败",这是安全侧设计。

Modbus 侧读寄存器时还有一个互锁(midware/interface/modbus/modbus.c:4212/4223):只有在 g_u8_single_board_test_enable == FALSE(即测试已结束)时才把 u_single_board_test_result.u32_all 的高 16 位 / 低 16 位返回,测试进行中返回的是默认值,避免读到中间态。

4.2 各子测试的算法

flash_test()(board_test.c:397-413) — 唯一的 ID 判定,无算法:

Flash_ReadID(l_u8_buf);                    // 读 3 字节 JEDEC ID
if ((buf[0]==0xc2) && (buf[1]==0x20) &&
    ((buf[2]==0x18)||(buf[2]==0x19)||(buf[2]==0x1A)))
    u8_flash = 0, u8_eeprom = 0;           // 通过
else
    u8_flash = 1, u8_eeprom = 1;           // 失败

0xC2 是 Macronix 厂商码,0x20 是 SPI 存储类型码,容量字节接受 0x18/0x19/0x1A 三种(对应 16/32/64 Mbit 级别)。EEPROM 与 Flash 共用同一个判据,即一次 SPI Flash 读 ID 成功就同时判定 I2C3 上的 EEPROM 也正常——两者物理上并不相关,这是明显的偷懒写法。

sdram_test()(board_test.c:422-449) — 逐字节写读校验:

memset((u8*)SDRAM_SDRAM_TEST_ADDR, 0xFF, 1024);   // 先铺 0xFF
for (i = 0; i < 1024; i++) {
    addr = SDRAM_SDRAM_TEST_ADDR + i;
    if (addr >= ADDR_MAX_SDRAM) break;            // 越界保护
    write(addr, 0x65); read(addr, &l_u8_read);
    if (l_u8_read != 0x65) { u8_sdram = 1; return; }   // 立即判失败
}
u8_sdram = 0;

注意写法差异:memset 用的是 CPU 直接寻址,而写读校验走 FMC_SDRAM_WriteBuffer_02/ReadBuffer_02()。地址粒度为字节(不是字),1024 次单字节访问会有一定耗时。

can_test()(board_test.c:139-203) — 双通道回环。构造固定报文:

字节01234567
值0xFF0x050x020x000x010x030x010x04

frame_id = 0xA0,frame_type = CAN_FT_DATA,frame_len = 8,标准帧(调用的 API 是 *_send_standard_pack)。时序为:

can0_clear_standard_pack(); can1_clear_standard_pack();
for (i = 0; i < 4; i++) {
    can0_send_standard_pack(&txMsg);  OSTimeDlyHMSM(0,0,0,1);   // 1 ms
    can1_send_standard_pack(&txMsg);  OSTimeDlyHMSM(0,0,0,1);
}
// 判 CAN0 收到的是否等于发出的 → u8_can1
// 判 CAN1 收到的是否等于发出的 → u8_can2

即"自己发、对端收",用 memcmp(rxMsg.frame_data, txMsg.frame_data, 8) 比对数据域。若 get_can*_standard_pack() 返回 FALSE(超时无报文)也直接判失败。注意写入的位名是 u8_can1/u8_can2,但测试对象是 CAN0 与 CAN1 两个控制器。

usart_test()(board_test.c:212-278) — 两段同构的自发自收。以 USART2 段为例:

usart2_clear_buffer();
usart2_send_data(g_u8_usart_buf, 16, 300);      // timeout=300
忙等 50 ms(期间 IWDG_Feed)
l_u8_len = usart2_get_data_len();  if (len > 16) len = 16;   // 上限钳位
for (i < len) l_u8_receive_buf[i] = usart2_read_data_async();
strncmp(recv, g_u8_usart_buf, 16) == 0 ? u8_usart2 = 0 : u8_usart2 = 1;

USART5 段结构完全相同,只把收发函数换成 usart5_*,超时参数由 300 改为 1000,结果写进 u8_usart6。l_u8_receive_buf 是 16 字节,读之前做了 len > 16 → 16 的钳位,不存在溢出。但 strncmp(..., 16) 在 len < 16 时会比较到 buf 中未写入的位置——由于每次进来都 memset 清零,等价于"长度不足即判失败",行为是安全的。

rs485_test()(board_test.c:287-364) — 双向测试,两次比对同一个结果位:

rtc_test()(board_test.c:373-388) — 重试式判定:

for (i = 0; i < 5; i++) {
    PCF8563_GetDateTime(PCF_Format_BIN, 0, &tm);
    if (PCF8563_Check_Time_Is_Valid(tm) == 0) { u8_rtc = 0; return; }   // 有效即通过
}
u8_rtc = 1;   // 5 次都无效

tm 在循环外只初始化一次(= {0}),PCF8563_GetDateTime 每次覆盖,无累积问题。

w5500_test()(board_test.c:49-63) — 用 WIZnet 官方 API 把 W5500 芯片内部寄存器的 MAC 读回来,与软件侧保存的 IP_Address.u8_mac_addr[6] 做 strcmp。这里用 strcmp 比较 6 字节二进制 MAC,只要有 0x00 字节就会提前返回相等——MAC 地址出现 0x00 是合法的(例如 00:08:DC:...),存在漏检风险。

drm_test()(board_test.c:458-493) — 板型分流 + 双条件:

if (get_board_type() == BOARD_EMS3_6_2_E) { u8_drm = 0; return; }   // 该板型无 DRM,直接通过
ad = get_drm0_data();            // DRM 口 AD 中间值
st[0..3] = get_drm1_flag() ... get_drm4_flag();
if (2000 < ad < 2100 && 四路标志全 TRUE) u8_drm = 0; else u8_drm = 1;

2000 ~ 2100 是硬编码的 ADC 期望窗口(约 2050 ± 50 的中间值,说明 DRM0 被固定在分压中点),无任何宏定义或注释解释。

dry_node_test()(board_test.c:502-593) — 干接点回环,两段 for(;;) 只跑一轮的写法:

4.3 定时与调度时序

本文件没有参与任何 uCOS-II 同步原语(不用信号量、不用消息邮箱),全部靠忙等 + 喂狗:

位置等待时长实现方式等价时长
led_test() 各阶段abs(OSTimeGet() - tick) < 1000忙等 + IWDG_Feed() ×44 × 1000 ms
usart_test() / rs485_test() 等待< 50忙等 + IWDG_Feed()4 × 50 ms
can_test() 帧间隔OSTimeDlyHMSM(0,0,0,1)uCOS-II 主动让出8 × 1 ms
dry_node_test() 电平建立< 100忙等 + IWDG_Feed()3 × 100 ms

uCOS-II 的 OS_TICKS_PER_SEC = 1000(UCOSII/uCOS-CONFIG/os_cfg.h:51),所以 OSTimeGet() 的单位就是 1 ms,abs(...) < 1000 即 1 秒。

整轮耗时估算:LED 4 s + RS485/USART 4×50 ms + 干接点 300 ms + 各诊断函数耗时,约 5 秒左右。这段时间内 task_system_aux(优先级见 UCOSII/uCOS-CONFIG/app_cfg.h)无法处理其它工作,因此 board_test() 实际上是对整个系统的一次"停顿",只有反复 IWDG_Feed() 保证不被看门狗打断。

4.4 关键全局变量读写

变量写者读者触发条件
g_u8_single_board_test_enableboard_test() 清 0(board_test.c:636);Modbus 置 1(modbus.c:6937)board_test()(board_test.c:615)、ate.c:138/172、modbus.c:4212/4223Modbus 工装写寄存器
u_single_board_test_result除 led_test() 外所有子测试Modbus 寄存器高低 16 位(modbus.c:4214/4225)测试结束且使能标志为 0 时可读
g_u8_usart_buf / g_u8_rs485_buf编译期常量初始化usart_test()、rs485_test()仅本文件内部使用

5. 对外接口

本文件导出

名称类型供谁使用
board_test(void)函数(声明在 board_test.h:70)midware/auxiliary/system_aux.c:2083
g_u8_single_board_test_enable全局变量(board_test.h:67)midware/debug/ate.c:138/172、midware/interface/modbus/modbus.c:4212/4223/6937
u_single_board_test_result全局变量(board_test.h:68)midware/interface/modbus/modbus.c:4214/4225

本文件调用的外部函数/变量

来源模块使用内容
hardware/led(hmi/led 体系)LED_Green_ON/OFF、LED_Red_ON/OFF、LED_Yellow_ON/OFF、LED_Blue_ON/OFF
hardware/gpio/gpio.hled_db0_on()~led_db7_on() 及对应 _off、get_drm0_data()、get_drm1_flag()~get_drm4_flag()
midware/interface/dry_node/dry_node.h宏 DRY_NODE1_ON/OFF、DRY_NODE2_ON/OFF、DI_1_ACCESS_POINT、DI_2_ACCESS_POINT
hardware/pcf8563PCF8563_GetDateTime()、PCF8563_Check_Time_Is_Valid()、_PCF8563_DateTime_Typedef
hardware/flashFlash_ReadID()
hardware/sdramFMC_SDRAM_WriteBuffer_02()、FMC_SDRAM_ReadBuffer_02()、SDRAM_SDRAM_TEST_ADDR、ADDR_MAX_SDRAM
hardware/usart0/1/2/5、hardware/usart4usart2_send_data/clear_buffer/get_data_len/read_data_async、usart5_*、usart1_*、uart4_*
hardware/can0、hardware/can1can0_send_standard_pack()、can1_send_standard_pack()、get_can0_standard_pack()、get_can1_standard_pack()、can0_clear_standard_pack()、can1_clear_standard_pack()、类型 CAN_MSG_TYPEDEF_T
midware/ethernt/lan/wizchip_conf.hctlnetwork(CN_GET_NETINFO, ...)、类型 wiz_NetInfo
midware/ethernt/ethernet.hIP_Address.u8_mac_addr
system/board_init/board_init.hget_board_type()、BOARD_EMS3_6_1_E/2_E/3_E/4_E
hardware/iwdgIWDG_Feed()
uCOS-IIOSTimeGet()、OSTimeDlyHMSM()
标准库strcmp、strncmp、memcmp、memset

6. 依赖关系

依赖

board_test.h、can0.h、can1.h、flash.h、led.h、debug.h、usart0.h、usart1.h、usart2.h、usart3.h、usart4.h、usart5.h、lcd.h、spi1.h、spi2.h、w5500.h、pcf8563.h、sdram.h、ethernet.h、system_config.h、board_init.h、gpio.h、dry_node.h、iwdg.h(board_test.c:8-31)。

其中 usart3.h、lcd.h、spi1.h、spi2.h、system_config.h 在本文件中被包含但没有任何符号被实际使用,属于历史遗留包含。

被依赖

7. 备注与风险点

  1. board_test() 内注释与实际位号全部错位(board_test.c:622-631):注释写 w5500 bit6(实际 bit8)、can1 bit7, can2 bit8(实际 bit9/bit10)、led bit10(LED 根本不写位,而 bit10 恰是 u8_can2)。按注释理解结果字会得到完全错误的结论。
  2. led_test() 不产生任何结果位,是纯目检项;但 board_test() 调用点给它标了个 "led bit10",容易让上位机以为 LED 有自动判定。
  3. u8_usart3 与 u8_can3 被无条件置 0(通过)(board_test.c:633-634),没有做任何测试——即这两项对工装永远显示"通过"。
  4. USART5 的测试结果写进了名为 u8_usart6 的位(board_test.c:271-275)。hardware/usart5/usart5.c 用的是 USART5(PC6/PC7,usart5.c:24-29),所以位名与实际外设不符。
  5. w5500_test() 用 strcmp 比较 6 字节二进制 MAC(board_test.c:55):MAC 中出现 0x00 时 strcmp 提前返回相等,会漏判。正确做法是 memcmp(..., 6)。
  6. flash_test() 用一次 SPI Flash 读 ID 同时判定 Flash 与 EEPROM(board_test.c:405-406),EEPROM(I2C3)实际上从未被访问,"EEPROM 测试"名不副实。
  7. drm_test() 的 AD 窗口 2000 ~ 2100 是硬编码魔法数字(board_test.c:475),无宏、无注释、未随板型区分。
  8. 整轮测试是长时间忙等:led_test() 单次就忙等 4 秒,加上其它项总共约 5 秒。期间 task_system_aux 被独占,系统其它业务停摆,只靠 IWDG_Feed() 续命;若测试中途死循环(例如 CAN 收发 API 阻塞在更长超时里),看门狗仍会被喂住,无法复位。
  9. dry_node_test() 用 for(;;) { ... break; } 实现"只跑一次"(board_test.c:506、board_test.c:547),是 goto/do-while 的绕写;DO1 段末尾的 break 位于 while 延时循环之后,可读性差但语义正确。
  10. sdram_test() 的越界保护只在读取循环内(board_test.c:432),而 memset((u8*)SDRAM_SDRAM_TEST_ADDR, 0xFF, 1024)(board_test.c:428)在保护之前无条件执行 1024 字节写入;如果测试区紧贴地址上界,memset 仍可能越界。
  11. dry_node_test() 对 DO2 的板型判断写成 || get_board_type() == BOARD_EMS3_6_4_E(board_test.c:549),前三个条件都带括号、最后一个没带,虽运算符优先级正确(== 高于 ||),但风格不一致。
  12. FLASH_TEST__LEN 宏(=32)在 board_test.h:15 定义后从未使用,是死宏。
  13. g_u8_usart_buf / g_u8_rs485_buf 声明为全局但只在本文件使用,没有 static,符号被外部可见;board_test.h 也未 extern 它们,属于"泄漏的非 static 内部数据"。
  14. rs485_test() 第一段失败时提前 return(board_test.c:328-331),此时 u8_rs485 已是 1,但其余测试项(USART/W5500/CAN/干接点等)仍会被 board_test() 继续调用——只在 rs485_test() 内部提前结束,不会跳过后续项。
  15. memset((u8*)SDRAM_SDRAM_TEST_ADDR, 0xFF, 1024) 绕过 FMC_SDRAM_WriteBuffer_02(),直接按 CPU 地址写;如果 SDRAM 尚未完成 SystemInit 中的控制器初始化,这一句会直接产生总线错误,而后续的写读校验才走正规 API,风格不统一。
  16. board_test.c 的文件头注释写的是 @file :singleboardtest.c(board_test.c:2),文件名与注释不符。