fix(esc_lan9252): 修复LAN9252 HBI访问模型和IRQ输出使能问题

- 修正HBI访问模型:将ESC核心CSR(0x0000-0x0FFF)改为通过ECAT_CSR_CMD/DATA
  间接端口访问,解决此前直接寻址导致核心寄存器恒读0/写丢弃的问题

- 新增csrRead/csrWrite/pramRead/pramWrite接口,正确实现LAN9252访问端口操作

- 添加PDI就绪检测:等待DL_STATUS bit0置位确认EEPROM加载完成和EtherCAT核心使能

- 实现IRQ引脚输出使能:配置IRQ_CFG和INT_EN寄存器,使EtherCAT事件能到达PB1,
  解决PDI_Isr未触发导致SAFEOP->OP无限挂起的问题

- 为ESC_EXMC硬件诊断添加CONFIG_ESC_EXMC_DIAG门控,默认关闭避免影响正常运行

- 修正system_gd32e50x.c中HXTAL故障诊断的寄存器偏移和位设置错
This commit is contained in:
cbb
2026-09-02 02:38:35 +08:00
parent cfe7725b26
commit 44d407390b
29 changed files with 1178 additions and 292 deletions
@@ -56,6 +56,7 @@ project:
- file: ./device/spi/app_spi_cmd_boot_request.cpp
- file: ./device/spi/app_spi_cmd_ota_wait.cpp
- file: ./device/spi/app_spi_cmd_ping.cpp
- file: ./device/spi/app_spi_cmd_init_status.cpp
- file: ./device/spi/app_spi_cmd_can_bridge.cpp
- file: ./device/spi/app_spi_cmd_can_burst.cpp
- file: ./device/spi/app_spi_cmd_can_feedback.cpp
@@ -657,13 +657,15 @@ static void system_clock_hxtal(void)
/* if fail */
if(0U == (RCU_CTL & RCU_CTL_HXTALSTB)){
/* HXTAL failed: output diagnostic via USART0 at IRC8M */
*((volatile uint32_t *)0x40021018U) |= (1U << 2) | (1U << 0);
/* HXTAL failed: output diagnostic via USART0 at IRC8M.
* 修正(1.5.34):原段把 CTL0 写到 STAT0 偏移(0x40013800)且漏开 USART0EN(APB2EN bit14),
* 该诊断从未真正发出;正确布局:CTL0 @ USART0+0x0C,APB2EN bit14|bit2|bit0。 */
*((volatile uint32_t *)0x40021018U) |= (1U << 14) | (1U << 2) | (1U << 0);
{ volatile uint32_t _ctl = *((volatile uint32_t *)0x40010804U);
_ctl &= ~(0xFU << 4); _ctl |= (0xBU << 4);
*((volatile uint32_t *)0x40010804U) = _ctl; }
*((volatile uint32_t *)0x40013808U) = 69U; /* 8MHz/115200 */
*((volatile uint32_t *)0x40013800U) = (1U << 3) | (1U << 13);
*((volatile uint32_t *)0x4001380CU) = (1U << 3) | (1U << 13);
{ const char *_msg = "HXTAL FAIL\r\n";
for(int i=0; _msg[i]; i++){
*((volatile uint32_t *)0x40013804U) = _msg[i];
@@ -713,13 +715,15 @@ static void system_clock_72m_hxtal(void)
/* if fail */
if(0U == (RCU_CTL & RCU_CTL_HXTALSTB)){
/* HXTAL failed: output diagnostic via USART0 at IRC8M */
*((volatile uint32_t *)0x40021018U) |= (1U << 2) | (1U << 0);
/* HXTAL failed: output diagnostic via USART0 at IRC8M.
* 修正(1.5.34):原段把 CTL0 写到 STAT0 偏移(0x40013800)且漏开 USART0EN(APB2EN bit14),
* 该诊断从未真正发出;正确布局:CTL0 @ USART0+0x0C,APB2EN bit14|bit2|bit0。 */
*((volatile uint32_t *)0x40021018U) |= (1U << 14) | (1U << 2) | (1U << 0);
{ volatile uint32_t _ctl = *((volatile uint32_t *)0x40010804U);
_ctl &= ~(0xFU << 4); _ctl |= (0xBU << 4);
*((volatile uint32_t *)0x40010804U) = _ctl; }
*((volatile uint32_t *)0x40013808U) = 69U; /* 8MHz/115200 */
*((volatile uint32_t *)0x40013800U) = (1U << 3) | (1U << 13);
*((volatile uint32_t *)0x4001380CU) = (1U << 3) | (1U << 13);
{ const char *_msg = "HXTAL FAIL\r\n";
for(int i=0; _msg[i]; i++){
*((volatile uint32_t *)0x40013804U) = _msg[i];
@@ -831,13 +835,15 @@ static void system_clock_120m_hxtal(void)
/* if fail */
if(0U == (RCU_CTL & RCU_CTL_HXTALSTB)){
/* HXTAL failed: output diagnostic via USART0 at IRC8M */
*((volatile uint32_t *)0x40021018U) |= (1U << 2) | (1U << 0);
/* HXTAL failed: output diagnostic via USART0 at IRC8M.
* 修正(1.5.34):原段把 CTL0 写到 STAT0 偏移(0x40013800)且漏开 USART0EN(APB2EN bit14),
* 该诊断从未真正发出;正确布局:CTL0 @ USART0+0x0C,APB2EN bit14|bit2|bit0。 */
*((volatile uint32_t *)0x40021018U) |= (1U << 14) | (1U << 2) | (1U << 0);
{ volatile uint32_t _ctl = *((volatile uint32_t *)0x40010804U);
_ctl &= ~(0xFU << 4); _ctl |= (0xBU << 4);
*((volatile uint32_t *)0x40010804U) = _ctl; }
*((volatile uint32_t *)0x40013808U) = 69U; /* 8MHz/115200 */
*((volatile uint32_t *)0x40013800U) = (1U << 3) | (1U << 13);
*((volatile uint32_t *)0x4001380CU) = (1U << 3) | (1U << 13);
{ const char *_msg = "HXTAL FAIL\r\n";
for(int i=0; _msg[i]; i++){
*((volatile uint32_t *)0x40013804U) = _msg[i];
@@ -950,13 +956,15 @@ static void system_clock_168m_hxtal(void)
/* if fail */
if(0U == (RCU_CTL & RCU_CTL_HXTALSTB)){
/* HXTAL failed: output diagnostic via USART0 at IRC8M */
*((volatile uint32_t *)0x40021018U) |= (1U << 2) | (1U << 0);
/* HXTAL failed: output diagnostic via USART0 at IRC8M.
* 修正(1.5.34):原段把 CTL0 写到 STAT0 偏移(0x40013800)且漏开 USART0EN(APB2EN bit14),
* 该诊断从未真正发出;正确布局:CTL0 @ USART0+0x0C,APB2EN bit14|bit2|bit0。 */
*((volatile uint32_t *)0x40021018U) |= (1U << 14) | (1U << 2) | (1U << 0);
{ volatile uint32_t _ctl = *((volatile uint32_t *)0x40010804U);
_ctl &= ~(0xFU << 4); _ctl |= (0xBU << 4);
*((volatile uint32_t *)0x40010804U) = _ctl; }
*((volatile uint32_t *)0x40013808U) = 69U; /* 8MHz/115200 */
*((volatile uint32_t *)0x40013800U) = (1U << 3) | (1U << 13);
*((volatile uint32_t *)0x4001380CU) = (1U << 3) | (1U << 13);
{ const char *_msg = "HXTAL FAIL\r\n";
for(int i=0; _msg[i]; i++){
*((volatile uint32_t *)0x40013804U) = _msg[i];
@@ -1069,13 +1077,15 @@ static void system_clock_180m_hxtal(void)
/* if fail */
if(0U == (RCU_CTL & RCU_CTL_HXTALSTB)){
/* HXTAL failed: output diagnostic via USART0 at IRC8M */
*((volatile uint32_t *)0x40021018U) |= (1U << 2) | (1U << 0);
/* HXTAL failed: output diagnostic via USART0 at IRC8M.
* 修正(1.5.34):原段把 CTL0 写到 STAT0 偏移(0x40013800)且漏开 USART0EN(APB2EN bit14),
* 该诊断从未真正发出;正确布局:CTL0 @ USART0+0x0C,APB2EN bit14|bit2|bit0。 */
*((volatile uint32_t *)0x40021018U) |= (1U << 14) | (1U << 2) | (1U << 0);
{ volatile uint32_t _ctl = *((volatile uint32_t *)0x40010804U);
_ctl &= ~(0xFU << 4); _ctl |= (0xBU << 4);
*((volatile uint32_t *)0x40010804U) = _ctl; }
*((volatile uint32_t *)0x40013808U) = 69U; /* 8MHz/115200 */
*((volatile uint32_t *)0x40013800U) = (1U << 3) | (1U << 13);
*((volatile uint32_t *)0x4001380CU) = (1U << 3) | (1U << 13);
{ const char *_msg = "HXTAL FAIL\r\n";
for(int i=0; _msg[i]; i++){
*((volatile uint32_t *)0x40013804U) = _msg[i];
+13 -1
View File
@@ -51,6 +51,9 @@ constexpr uint32_t kPinWr = 5U; /* PD5 */
constexpr uint32_t kPinA1 = 11U; /* PD11 */
constexpr uint32_t kPinA2 = 12U; /* PD12 */
/* bbDelay/bbReadData 仅被 diagDump 使用,随之一起门控,避免默认关闭时
* 触发 -Wunused-function;bbDriveData 被 continuityDrive 复用,保持常开。 */
#if CONFIG_ESC_EXMC_DIAG
void bbDelay() {
for (volatile uint32_t i = 0U; i < 3000U; i++) {
} /* ~40-80us @180MHz,远大于 HBI 时序要求 */
@@ -78,6 +81,7 @@ uint16_t bbReadData() {
if (d & (1U << 10)) v |= (uint16_t)(1U << 15); /* D15 */
return v;
}
#endif /* CONFIG_ESC_EXMC_DIAG (bbDelay + bbReadData) */
void bbDriveData(uint16_t v) {
uint32_t dset = 0U, dclr = 0U, eset = 0U, eclr = 0U;
@@ -233,8 +237,12 @@ bool EscExmcBus::init() {
ECAT_LOGI("[ECAT][EXMC] init: Region0 enabled, chip ID will be verified by "
"ESC driver\r\n");
/* ---- 硬件诊断: 转储关键寄存器 + GPIO 手动输出测试 ---- */
/* ---- 硬件诊断: 转储关键寄存器 + GPIO 手动输出测试 ----
* 1.5.33: 默认关闭。硬件层已确认全通,探针仅换板/补焊排查浮空时经
* CONFIG_ESC_EXMC_DIAG 临时开启(会拆 EXMC 引脚成 GPIO,正常运行不该跑)。 */
#if CONFIG_ESC_EXMC_DIAG
diagDump();
#endif
s_ok = true;
ECAT_LOGI("[ECAT][EXMC] init: OK, bus enabled\r\n");
@@ -243,7 +251,10 @@ bool EscExmcBus::init() {
/* ========================================================================
* 硬件诊断: 精简版 —— 只保留核心探活 + bit-bang + 位级回显
* 1.5.33: 整段由 CONFIG_ESC_EXMC_DIAG 门控,默认不编译。bbDelay/bbReadData/
* bbDriveData 等位拍辅助函数在上方匿名 namespace 中,continuityDrive 仍复用,保留。
* ======================================================================== */
#if CONFIG_ESC_EXMC_DIAG
void EscExmcBus::diagDump() {
/* EtherCAT 同步 ISR 激活期间禁止诊断:本函数会把 EXMC 数据/控制线切成 GPIO,
* 并大量 printf,会周期性拆毁运行中的 EtherCAT 总线。 */
@@ -389,6 +400,7 @@ void EscExmcBus::diagDump() {
printf("[EXMC-DIAG] === HBI ALIVE ===\r\n");
}
}
#endif /* CONFIG_ESC_EXMC_DIAG */
/* ========================================================================
* 通断测试: 网页逐个 pattern 触发,GD32 驱动数据线/控制线输出已知电平,
+186 -8
View File
@@ -14,7 +14,12 @@
* EXMC_A[24:0],即 EXMC_A16=CPU 偏移 bit17(0x20000)、EXMC_A17=bit18(0x40000)。
* LAN9252 手册 Table 9-3:A2=1 选 HBI_DATA_0,A1 选低/高半字。
*
* 所有 ESC 寄存器/PDI 内存访问均通过"写 IDX -> 读写 DATA"完成。
* 访问模型(1.5.39 按 DS 12.11/表 12-13 修正):
* HBI 索引直接寻址的只有 LAN9252 系统 CSR(BYTE_TEST 0x064 等)与访问端口;
* EtherCAT 核心 CSR(0x0000-0x0FFF)经 ECAT_CSR_CMD(0x304)/ECAT_CSR_DATA(0x300)
* 间接访问,过程 RAM(0x1000-0x1FFF)经 PRAM FIFO(0x000-0x03C + 0x308-0x314) 访问。
* 1.5.37 及之前把索引空间当统一寻址,导致全部核心 CSR 读 0/写丢弃——
* 主站恒读 AL=INIT 的真正根因(网络侧寄存器诊断证实 EEPROM/核心一直正常)。
*/
#include "comm/esc_lan9252.h"
#include "comm/esc_exmc_bus.h"
@@ -39,6 +44,34 @@ constexpr uint32_t HBI_DATA0_HIGH_OFF = 0x60000U; /* A2=1(EXMC_A17), A1=1(EXMC
* 芯片身份探针用 BYTE_TEST 寄存器(0x064),不是 ESC 0x0000(那是类型寄存器=0xC0)。 */
constexpr uint16_t LAN9252_REG_BYTE_TEST = 0x0064U; /**< 字节顺序测试寄存器(默认 0x87654321,LAN9252 手册 16.1.2)。 */
constexpr uint32_t LAN9252_BYTE_TEST_VALUE = 0x87654321U; /**< BYTE_TEST 默认值(小端序)。 */
constexpr uint16_t ESC_REG_DL_STATUS = 0x0110U; /**< DL Status:bit0 = PDI operational / EEPROM loaded。 */
constexpr uint16_t ESC_REG_EEP_CSR = 0x0502U; /**< EEPROM Control/Status:bit11=校验错,bit12=加载错(0=OK)。 */
/* LAN9252 系统中断寄存器(HBI 索引直接可达,DS 8.3 表 0.1)。
* IRQ 引脚上电默认全关:IRQ_CFG.IRQ_EN=0 且 INT_EN.ECAT_INT_EN=0,
* 不配这两个寄存器则 PB1 永远收不到中断,SSC 的 PDI_Isr 从不执行。 */
constexpr uint16_t LAN9252_REG_IRQ_CFG = 0x0054U; /**< 中断配置:bit8=IRQ_EN,bit4=IRQ_POL,bit0=IRQ_TYPE。 */
constexpr uint16_t LAN9252_REG_INT_STS = 0x0058U; /**< 中断状态(R/WC):bit0=ECAT_INT。 */
constexpr uint16_t LAN9252_REG_INT_EN = 0x005CU; /**< 中断使能:bit0=ECAT_INT_EN。 */
constexpr uint32_t IRQ_CFG_IRQ_EN = 1UL << 8; /**< IRQ 引脚输出总闸。 */
constexpr uint32_t IRQ_CFG_IRQ_POL = 1UL << 4; /**< 1=高有效,0=低有效(开漏时忽略本位)。 */
constexpr uint32_t IRQ_CFG_IRQ_TYPE = 1UL << 0; /**< 1=推挽,0=开漏(开漏强制低有效)。 */
constexpr uint32_t INT_EN_ECAT_INT = 1UL << 0; /**< 允许 EtherCAT 事件驱动 IRQ 引脚。 */
/* LAN9252 直接映射的 ESC 访问端口(DS 表 12-13;经 HBI 索引直接读写) */
constexpr uint16_t ECAT_PRAM_RD_DATA = 0x0000U; /**< PRAM 读 FIFO(0x00-0x1C 别名,功能相同,突发用) */
constexpr uint16_t ECAT_PRAM_WR_DATA = 0x0020U; /**< PRAM 写 FIFO(0x20-0x3C 别名) */
constexpr uint16_t ECAT_CSR_DATA_REG = 0x0300U; /**< ESC 核心 CSR 间接访问数据寄存器 */
constexpr uint16_t ECAT_CSR_CMD_REG = 0x0304U; /**< ESC 核心 CSR 间接访问命令寄存器 */
constexpr uint16_t ECAT_PRAM_RD_ADDR_LEN = 0x0308U; /**< PRAM 读地址/长度 */
constexpr uint16_t ECAT_PRAM_RD_CMD = 0x030CU; /**< PRAM 读命令/状态 */
constexpr uint16_t ECAT_PRAM_WR_ADDR_LEN = 0x0310U; /**< PRAM 写地址/长度 */
constexpr uint16_t ECAT_PRAM_WR_CMD = 0x0314U; /**< PRAM 写命令/状态 */
constexpr uint32_t CSR_BUSY = 1UL << 31; /**< CSR_CMD:写 1 启动,完成自清 */
constexpr uint32_t CSR_RNW = 1UL << 30; /**< CSR_CMD:1=读 0=写 */
constexpr uint32_t PRAM_BUSY = 1UL << 31; /**< PRAM_*_CMD:写 1 启动,完成自清 */
constexpr uint32_t PRAM_AVAIL = 1UL << 0; /**< PRAM_*_CMD:数据可用(读)/空间可用(写) */
constexpr uint32_t POLL_MAX = 2000UL; /**< busy/avail 轮询上限(~ms 级,防芯片异常时死等) */
__attribute__((section(".ramfunc"), noinline))
void setHbiIndex(uint16_t addr)
@@ -65,10 +98,9 @@ void writeDataLow(uint16_t v)
}
__attribute__((section(".ramfunc"), noinline))
uint16_t readReg16(uint16_t addr)
void writeDataHigh(uint16_t v)
{
setHbiIndex(addr);
return readDataLow();
EscExmcBus::write16(HBI_DATA0_HIGH_OFF, v);
}
__attribute__((section(".ramfunc"), noinline))
@@ -81,14 +113,16 @@ uint32_t readReg32(uint16_t addr)
}
__attribute__((section(".ramfunc"), noinline))
void writeReg16(uint16_t addr, uint16_t v)
void writeReg32(uint16_t addr, uint32_t v)
{
setHbiIndex(addr);
writeDataLow(v);
writeDataLow((uint16_t)(v & 0xFFFFU));
writeDataHigh((uint16_t)(v >> 16));
}
} /* anonymous namespace */
bool EscLan9252::s_busReady = false;
bool EscLan9252::s_coreReady = false;
bool EscLan9252::s_resetDone = false;
void EscLan9252::hardwareReset()
@@ -155,6 +189,7 @@ uint8_t EscLan9252::nrstDrive(uint8_t action)
bool EscLan9252::init()
{
s_busReady = false; /* 1.5.27:s_busReady 语义 = init 完整成功(EXMC+探针+BYTE_TEST) */
s_coreReady = false; /* 1.5.39:s_coreReady 语义 = EEPROM 加载完成、EtherCAT 核心使能 */
/* 使能 DWT 周期计数器,供 hw_gd32exmc/SSC 执行时间测量 */
CoreDebug->DEMCR |= CoreDebug_DEMCR_TRCENA_Msk;
@@ -203,9 +238,151 @@ bool EscLan9252::init()
}
printf("[ECAT][ESC] init: OK, HBI alive (IDX=0x1234, BYTE_TEST=0x87654321)\r\n");
s_busReady = true;
/* 等待 PDI 就绪、EtherCAT 核心可交互(1.5.37 新增,1.5.39 改经 CSR 间接端口读——
* 此前直读 HBI 索引,DL_STATUS 属 EtherCAT 核心寄存器,根本不在直接地址空间)。
* 判据:DL_STATUS(0x0110) bit0 = PDI operational / EEPROM loaded。
* 网络侧诊断已证实本板 EEPROM 加载与核心均正常,此等待应几乎立即通过;
* 超时意味着 PDI 真的未就绪(EEPROM 内容/I2C/strap 问题),此时 SSC 的
* AL Event Mask/邮箱 SM 配置会落空,由 initial.cpp 按 isCoreReady() 门控跳过。 */
uint16_t dlStatus = (uint16_t)csrRead(ESC_REG_DL_STATUS, 2U);
uint32_t waited = 0U;
while ((dlStatus & 0x0001U) == 0U && waited < CONFIG_ESC_HBI_POLL_TIMEOUT_MS) {
SysTickTimer::delayMs(CONFIG_ESC_HBI_POLL_STEP_MS);
waited += CONFIG_ESC_HBI_POLL_STEP_MS;
dlStatus = (uint16_t)csrRead(ESC_REG_DL_STATUS, 2U);
}
if ((dlStatus & 0x0001U) != 0U) {
s_coreReady = true;
printf("[ECAT][ESC] PDI operational, EtherCAT core ready (DL_STATUS=0x%04X, waited %ums)\r\n",
(unsigned)dlStatus, (unsigned)waited);
enableEcatIrqOutput();
} else {
printf("[ECAT][ESC] init: FAIL, PDI not operational after %ums "
"(DL_STATUS=0x%04X EEP_CSR=0x%04X)\r\n",
(unsigned)CONFIG_ESC_HBI_POLL_TIMEOUT_MS, (unsigned)dlStatus,
(unsigned)csrRead(ESC_REG_EEP_CSR, 2U));
printf("[ECAT][ESC] hint: 用 tools/ecat/ecat_esc_regs.py 从网络侧读 0x0502 错误位区分 EEPROM/I2C/内容问题\r\n");
}
return true;
}
/**
* @brief 使能 LAN9252 的 IRQ 引脚输出,让 EtherCAT 事件真正到达 GD32 的 PB1。
* @note 1.5.43 新增。IRQ_CFG(0x054)/INT_EN(0x05C) 都是 LAN9252 系统 CSR
* (HBI 索引直接可达,不走 CSR 间接端口),复位默认值均为 0,即
* IRQ_EN=0(引脚永久去断言) 且 ECAT_INT_EN=0(EtherCAT 事件不上报)。
* 此前固件从未配置它们,导致 PDI_Isr 一次都没被调用过:SSC 因此
* 永远等不到 PROCESS_OUTPUT_EVENT、bEcatFirstOutputsReceived 恒 FALSE,
* SAFEOP->OP 被无限挂起(表现为主站 "进 OP 失败 code=0x0000")。
* 极性必须与 ethercat_sync.cpp 的 EXTI 配置一致:那里是上拉输入 +
* 下降沿触发,故此处选推挽(IRQ_TYPE=1) + 低有效(IRQ_POL=0)。
* 注意 AL Event Mask(0x204) 由 SSC 在状态迁移时自行维护,不在此处设置。
*/
void EscLan9252::enableEcatIrqOutput()
{
/* 先清掉可能悬留的状态位(R/WC:写 1 清零),避免使能瞬间就有一个陈旧电平。 */
writeReg32(LAN9252_REG_INT_STS, INT_EN_ECAT_INT);
/* 允许 EtherCAT 事件汇入 IRQ。 */
writeReg32(LAN9252_REG_INT_EN, INT_EN_ECAT_INT);
/* 打开引脚总闸:推挽 + 低有效(IRQ_POL=0 即不置该位),去断言间隔保持 0=禁用。 */
writeReg32(LAN9252_REG_IRQ_CFG, IRQ_CFG_IRQ_EN | IRQ_CFG_IRQ_TYPE);
const uint32_t cfg = readReg32(LAN9252_REG_IRQ_CFG);
const uint32_t en = readReg32(LAN9252_REG_INT_EN);
if ((cfg & IRQ_CFG_IRQ_EN) != 0U && (en & INT_EN_ECAT_INT) != 0U) {
printf("[ECAT][ESC] IRQ output enabled (IRQ_CFG=0x%08lX INT_EN=0x%08lX, push-pull active-low)\r\n",
(unsigned long)cfg, (unsigned long)en);
} else {
printf("[ECAT][ESC] WARN: IRQ enable readback failed (IRQ_CFG=0x%08lX INT_EN=0x%08lX)"
" -> PDI_Isr 不会触发,SAFEOP->OP 会卡住\r\n",
(unsigned long)cfg, (unsigned long)en);
}
}
uint32_t EscLan9252::csrRead(uint16_t addr, uint8_t size)
{
/* DS 12.11.1:写 CSR_CMD(BUSY|R_nW|SIZE|ADDR) 启动,busy 自清后读 CSR_DATA;
* 有效数据总在 CSR_DATA 低位,硬件处理字节对齐。 */
uint32_t n = POLL_MAX;
while ((readReg32(ECAT_CSR_CMD_REG) & CSR_BUSY) != 0U && --n != 0U) {}
writeReg32(ECAT_CSR_CMD_REG, CSR_BUSY | CSR_RNW | ((uint32_t)size << 16) | addr);
n = POLL_MAX;
while ((readReg32(ECAT_CSR_CMD_REG) & CSR_BUSY) != 0U && --n != 0U) {}
return readReg32(ECAT_CSR_DATA_REG);
}
void EscLan9252::csrWrite(uint16_t addr, uint32_t data, uint8_t size)
{
/* DS 12.11.2:先写 CSR_DATA(数据在低位),再写 CSR_CMD(BUSY|SIZE|ADDR,R_nW=0)。 */
uint32_t n = POLL_MAX;
while ((readReg32(ECAT_CSR_CMD_REG) & CSR_BUSY) != 0U && --n != 0U) {}
writeReg32(ECAT_CSR_DATA_REG, data);
writeReg32(ECAT_CSR_CMD_REG, CSR_BUSY | ((uint32_t)size << 16) | addr);
n = POLL_MAX;
while ((readReg32(ECAT_CSR_CMD_REG) & CSR_BUSY) != 0U && --n != 0U) {}
}
void EscLan9252::pramRead(uint16_t addr, uint8_t *buf, uint16_t len)
{
/* DS 12.11.3:写 ADDR_LEN 再置 BUSY 启动;引擎按 DWORD 向下对齐填充读 FIFO,
* 首 DWORD 有效字节从 (addr&3) 起(表 12-7),末 DWORD 尾部无效(表 12-8)。
* 别名口 0x00-0x1C 功能相同,轮换寻址即官方突发用法;每读满一个 DWORD 弹一项。 */
if (len == 0U) {
return;
}
writeReg32(ECAT_PRAM_RD_ADDR_LEN, ((uint32_t)len << 16) | addr);
writeReg32(ECAT_PRAM_RD_CMD, PRAM_BUSY);
uint16_t done = 0U;
uint16_t alias = 0U;
while (done < len) {
uint32_t n = POLL_MAX;
while ((readReg32(ECAT_PRAM_RD_CMD) & PRAM_AVAIL) == 0U && --n != 0U) {}
if (n == 0U) {
break; /* 芯片异常:避免死等,交出残缺数据 */
}
const uint32_t dw = readReg32((uint16_t)(ECAT_PRAM_RD_DATA + alias));
alias = (uint16_t)((alias + 4U) & 0x1FU);
const uint8_t lead = (done == 0U) ? (uint8_t)(addr & 3U) : 0U;
const uint8_t take = (uint8_t)((len - done) < (4U - lead) ? (len - done) : (4U - lead));
for (uint8_t i = 0U; i < take; ++i) {
buf[done + i] = (uint8_t)(dw >> (8U * (lead + i)));
}
done = (uint16_t)(done + take);
}
}
void EscLan9252::pramWrite(uint16_t addr, const uint8_t *buf, uint16_t len)
{
/* DS 12.11.4:写 ADDR_LEN 再置 BUSY 启动;首 DWORD 有效字节从 (addr&3) 起,
* 引擎只取有效字节;别名口 0x20-0x3C 轮换;写满一个 DWORD 推一项。 */
if (len == 0U) {
return;
}
writeReg32(ECAT_PRAM_WR_ADDR_LEN, ((uint32_t)len << 16) | addr);
writeReg32(ECAT_PRAM_WR_CMD, PRAM_BUSY);
uint16_t done = 0U;
uint16_t alias = 0U;
while (done < len) {
uint32_t n = POLL_MAX;
while ((readReg32(ECAT_PRAM_WR_CMD) & PRAM_AVAIL) == 0U && --n != 0U) {}
if (n == 0U) {
break; /* 芯片异常:放弃剩余字节 */
}
const uint8_t lead = (done == 0U) ? (uint8_t)((uint16_t)(addr + done) & 3U) : 0U;
const uint8_t take = (uint8_t)((len - done) < (4U - lead) ? (len - done) : (4U - lead));
uint32_t dw = 0U;
for (uint8_t i = 0U; i < take; ++i) {
dw |= (uint32_t)buf[done + i] << (8U * (lead + i));
}
writeReg32((uint16_t)(ECAT_PRAM_WR_DATA + alias), dw);
alias = (uint16_t)((alias + 4U) & 0x1FU);
done = (uint16_t)(done + take);
}
uint32_t n = POLL_MAX;
while ((readReg32(ECAT_PRAM_WR_CMD) & PRAM_BUSY) != 0U && --n != 0U) {}
}
uint32_t EscLan9252::readChipId()
{
/* INIT-ONLY:init 阶段中断未使能,直接读取 BYTE_TEST(32-bit) */
@@ -214,10 +391,11 @@ uint32_t EscLan9252::readChipId()
uint16_t EscLan9252::initOnlyReadReg(uint16_t addr)
{
return readReg16(addr);
/* 1.5.39:ESC 核心 CSR 经间接端口读(此前直读 HBI 索引,核心寄存器不在直接空间,恒 0) */
return (uint16_t)csrRead(addr, 2U);
}
void EscLan9252::initOnlyWriteReg(uint16_t addr, uint16_t v)
{
writeReg16(addr, v);
csrWrite(addr, v, 2U);
}
+40 -3
View File
@@ -1,8 +1,14 @@
/**
* @file esc_lan9252.h
* @brief LAN9252 EtherCAT 从站控制器(ESC)驱动接口。
* @note 通过 EscExmcBus 16-bit 并行总线,以 HBI Indexed Address Mode 访问
* LAN9252 寄存器与 PDI 内存:先写 HBI_IDX_0,再读写 HBI_DATA_0。
* @note 通过 EscExmcBus 16-bit 并行总线,以 HBI Indexed Address Mode 访问 LAN9252。
* 访问模型(1.5.39 按 DS 12.11 修正,此前误以为整个 16 位索引空间统一可寻址):
* - HBI 直接地址空间:仅 LAN9252 系统 CSR(BYTE_TEST 0x064 等)+ 访问端口
* (PRAM FIFO 0x000-0x03C、ECAT_CSR_DATA/CMD 0x300/0x304、PRAM_ADDR_LEN/CMD
* 0x308-0x314)——经 HBI_IDX_0/HBI_DATA_0 直接读写;
* - EtherCAT 核心 CSR(0x0000-0x0FFF:AL Status、SyncManager、EEPROM 接口等)
* 不在直接空间,必须经 ECAT_CSR_CMD/CSR_DATA 间接访问(csrRead/csrWrite);
* - 过程 RAM(0x1000-0x1FFF:邮箱/PDO)必须经 PRAM FIFO(pramRead/pramWrite)。
*
* 接口调用约束(避免 EXMC 总线争用):
* - 运行期 ESC 寄存器/PDI 访问统一走 SSC 移植层 hw_gd32exmc.cpp 的 HW 系列函数
@@ -46,18 +52,49 @@ public:
*/
static uint32_t readChipId();
/** @brief INIT-ONLY:仅允许在 EthercatSync::init() 使能 EXTI NVIC 之前调用;运行期禁止调用,需 ESC 寄存器访问须通过 SSC 移植层(HW_*)。 */
/** @brief INIT-ONLY:仅允许在 EthercatSync::init() 使能 EXTI NVIC 之前调用;运行期禁止调用,需 ESC 寄存器访问须通过 SSC 移植层(HW_*)。
* @note 1.5.39 起经 CSR_CMD/CSR_DATA 间接端口读 ESC 核心 CSR(此前直读 HBI 索引,
* 而 EtherCAT 核心寄存器根本不在 HBI 直接地址空间,恒读 0)。 */
static uint16_t initOnlyReadReg(uint16_t addr);
/** @brief INIT-ONLY:仅允许在 EthercatSync::init() 使能 EXTI NVIC 之前调用;运行期禁止调用。 */
static void initOnlyWriteReg(uint16_t addr, uint16_t v);
/** @brief ESC 核心 CSR 间接读(ECAT_CSR_CMD/DATA 端口,0x0000-0x0FFF 地址空间)。
* @note 1.5.39:LAN9252 的 HBI/SPI 直接地址空间只有 LAN9252 系统 CSR 与访问端口
* (DS 12.11/表 12-13),EtherCAT 核心寄存器(0x0000-0x0FFF)必须经
* CSR_CMD(0x304)/CSR_DATA(0x300) 间接访问;过程 RAM(0x1000-0x1FFF)
* 必须经 PRAM FIFO。size=1/2/4(size2 需偶地址,size4 需 4 字节对齐)。
* 调用方负责 HBI 互斥(主循环 EscExmcBus::maskEnter / 已在 ISR 上下文)。 */
static uint32_t csrRead(uint16_t addr, uint8_t size);
/** @brief ESC 核心 CSR 间接写(ECAT_CSR_CMD/DATA 端口)。约束同 csrRead。 */
static void csrWrite(uint16_t addr, uint32_t data, uint8_t size);
/** @brief 过程 RAM(0x1000-0x1FFF)经 PRAM 读 FIFO 读取 len 字节到 buf。 */
static void pramRead(uint16_t addr, uint8_t *buf, uint16_t len);
/** @brief 过程 RAM(0x1000-0x1FFF)经 PRAM 写 FIFO 写入 len 字节。 */
static void pramWrite(uint16_t addr, const uint8_t *buf, uint16_t len);
/** @brief EXMC 总线 + HBI_IDX_0 探针 + BYTE_TEST 全部通过(init 成功)后为 true。
* @note 与 init() 返回值一致,可作运行期查询。 */
static bool isBusReady() { return s_busReady; }
/** @brief EEPROM(SII) 加载完成、EtherCAT 核心使能(1.5.39)。
* @note false 时核心寄存器对 PDI 读全 0、写静默丢弃,SSC MainInit 会全部
* 落空(AL Event Mask/邮箱 SM 配置写不进),调用方必须跳过 SSC 初始化。 */
static bool isCoreReady() { return s_coreReady; }
private:
/** @brief 使能 LAN9252 的 IRQ 引脚输出(IRQ_CFG 0x054 / INT_EN 0x05C)。
* @note 1.5.43 新增,由 init() 在 PDI/核心就绪后调用。两寄存器复位默认全 0,
* 不配则 EtherCAT 事件永不到达 PB1、SSC 的 PDI_Isr 从不执行,
* bEcatFirstOutputsReceived 恒 FALSE 使 SAFEOP->OP 被无限挂起。 */
static void enableEcatIrqOutput();
static bool s_busReady; /**< init 完整成功(EXMC+IDX 探针+BYTE_TEST)后置位。 */
static bool s_coreReady; /**< EEPROM 加载完成、EtherCAT 核心使能后置位(1.5.39)。 */
static bool s_resetDone; /**< hardwareReset 已执行门控(防二次复位清掉 SSC 配置)。 */
};
+97 -4
View File
@@ -63,16 +63,109 @@ void EthercatSlave::init()
/* AL Status Code(寄存器 0x0130 高 8 位):非 0 表示从站进入错误状态
* (如 SM Watchdog=0x1B、FMMU 配置错、邮箱协议错),需主站介入。 */
/* ============================================================
* bring-up 诊断(1.5.35 引入,1.5.39 重新定性):经 CSR 间接端口读三个关键寄存器
* + 一次写回环,确认 EtherCAT 核心侧状态。
* 注意主站读身份/AL 走 ESC 以太网侧 + EEPROM(SII),完全不经过 GD32 的 PDI,
* 所以"主站能读到身份"不能证明下面任何一项成立。
* 1.5.39 起访问已改经 ECAT_CSR_CMD/CSR_DATA(EtherCAT 核心寄存器不在 HBI 直接
* 地址空间,DS 12.11);此前直读索引使本段恒读全 0,曾被误判为"EEPROM 加载竞态"
* 与"HBI 写路径损坏"两个错误结论。现在读到全 0 才真正指向 EEPROM/PDI 故障,
* 交叉验证用 tools/ecat/ecat_esc_regs.py 从网络侧读同样的寄存器。
* 本段在 EthercatSync::init() 使能 EXTI 之前执行,满足 initOnly* 的 INIT-ONLY 约束。
* ============================================================ */
uint16_t dlStatus = 0U;
if (chipIdOk) {
/* (1) DL Status 0x0110 bit0 = PDI operational。
* ESC 只有认为 PDI 就绪时,才接受 PDI 侧(GD32 经 HBI)写入的 AL Status;
* 该位为 0 时 SSC 的 SetALStatus 被静默丢弃 -> 主站恒读 INIT/code=0。 */
dlStatus = EscLan9252::initOnlyReadReg(0x0110U);
/* (2) ESC Configuration 0x0140:PDI 类型是否真被 EEPROM 加载为 HBI 16-bit(0x8D)。
* 读回 0x00/0xFF 说明 EEPROM 的 PDI Control 未生效、ESC 的 PDI 没启用
* (此时 HBI 读寄存器仍可能照常工作,极易误判为"硬件全通")。 */
const uint16_t escCfg = EscLan9252::initOnlyReadReg(0x0140U);
/* (3) EEPROM 接口状态 0x0502-0x0503(16 位字)。位定义按 ETG.1000.4 表 49
* 与 SSC esc.h:bit11=Checksum Error、bit12=EEPROM loading status(0=OK)、
* bit13=Missing EEPROM Acknowledge、bit14=Write Error、bit15=Busy。
* 注意 1.5.40 及之前本行把 bit11 打成 "loaded",语义还正好反了
* (bit11=1 才是校验错),于是正常板子恒显示 "loaded=0",字面像"EEPROM 未加载",
* 正是历史上 1.5.37 那个已撤回的错误结论的诱因;现按位分别命名,0 全部代表正常。
* 真正的"EEPROM 已加载/PDI 可用"判据是 DL_STATUS(0x0110) bit0,见上方 (1)。 */
const uint16_t eepCsr = EscLan9252::initOnlyReadReg(0x0502U);
LOG_ERR("[ECAT][DIAG] DL_STATUS(0x110)=0x%04X pdi_oper=%u | "
"ESC_CFG(0x140)=0x%04X pdi_type=0x%02X(expect 0x8D) | "
"EEP_CSR(0x502)=0x%04X crc_err=%u load_err=%u ack_err=%u wr_err=%u busy=%u"
" (全 0=正常)\r\n",
(unsigned)dlStatus, (unsigned)(dlStatus & 0x0001U),
(unsigned)escCfg, (unsigned)(escCfg & 0x00FFU),
(unsigned)eepCsr,
(unsigned)((eepCsr >> 11U) & 0x1U),
(unsigned)((eepCsr >> 12U) & 0x1U),
(unsigned)((eepCsr >> 13U) & 0x1U),
(unsigned)((eepCsr >> 14U) & 0x1U),
(unsigned)((eepCsr >> 15U) & 0x1U));
/* (4) CSR 端口写回环:用 AL Event Mask(0x0204)做写-读-还原。该寄存器按设计
* 就是 PDI 侧可写(PDI 用它选择哪些 AL Event 产生 IRQ),不像 0x0012 Station
* Alias 在部分 ESC 上对 PDI 只读(会造成假 FAIL);本段在 MainInit/HW_Init
* 之前执行,还原后不影响 SSC 后续建立的 mask 影子。
* 1.5.39 起本回环经 ECAT_CSR_CMD/CSR_DATA 间接端口执行。历史注意:1.5.35
* 该回环报 FAIL 曾被误判为"HBI 写路径损坏",真因是当时直读直写 HBI 索引——
* EtherCAT 核心寄存器不在 HBI 直接地址空间(DS 12.11),读恒 0、写被丢弃。
* 网络侧诊断(tools/ecat/ecat_esc_regs.py)已证实 ESC 本体与 EEPROM 一直正常。 */
const uint16_t maskSaved = EscLan9252::initOnlyReadReg(0x0204U);
EscLan9252::initOnlyWriteReg(0x0204U, 0x0A50U);
const uint16_t maskRead = EscLan9252::initOnlyReadReg(0x0204U);
EscLan9252::initOnlyWriteReg(0x0204U, maskSaved); /* 还原,交回 SSC 管理 */
if (maskRead == 0x0A50U) {
LOG_ERR("[ECAT][DIAG] CSR write loopback PASS (0x204 wrote 0x0A50 read back OK)\r\n");
} else {
LOG_ERR("[ECAT][DIAG] CSR write loopback FAIL: wrote 0x0A50 read 0x%04X"
" -> 先查 CSR_CMD(0x304) busy 是否自清/EXMC 时序,再疑硬件\r\n",
(unsigned)maskRead);
}
}
/* 是否有主站在线:DL_STATUS(0x0110) 的 Physical Link 位 bit4/5/6 对应 Port0/1/2
* (DS 12.14.19)。三个端口都无 link = 网线未插/无主站,此时 AL 状态机根本没人驱动
* (AL 迁移由主站写 AL Control 0x0120 触发,从站不自跳),停在 INIT 是唯一正确行为。
* 参考:接主站时实测 DL_STATUS=0x5611(Port0 有 link 有 comm);
* 空载时为 0x5503(三口 loop closed、link/comm 全 0)。 */
const bool anyPhyLink = chipIdOk && ((dlStatus & 0x0070U) != 0U);
if (!chipIdOk) {
LOG_ERR("[ECAT][SLAVE] ESC NOT ready, sync ISR disabled (check EXMC wiring/timing)\r\n");
} else if (s_alState < AL_STATE_SAFEOP) {
LOG_ERR("[ECAT][SLAVE] chip id PASS, but AL state=0x%02X(%s) < SAFEOP(0x%02X), code=0x%02X\r\n",
(unsigned)s_alState, alStateName, (unsigned)AL_STATE_SAFEOP, (unsigned)alCode);
LOG_ERR("[ECAT][SLAVE] SYNC0 will not fire, full ESC config requires SSC integration\r\n");
/* 区分"等主站"与"真故障":两者 AL 都 < SAFEOP,但只有后者需要排查。
* 1.5.40 及之前一律打 FAIL(partial),而冷启动无主站时必然走到这里,
* 与紧随其后的 "SSC MainInit OK" 自相矛盾,极易被误读为初始化失败。 */
if (!anyPhyLink) {
LOG_ERR("[ECAT][SLAVE] WAIT_MASTER: no PHY link (DL_STATUS=0x%04X), "
"AL=0x%02X(%s) 由主站驱动,空载停在 INIT 属预期\r\n",
(unsigned)dlStatus, (unsigned)s_alState, alStateName);
} else {
LOG_ERR("[ECAT][SLAVE] chip id PASS, PHY link up (DL_STATUS=0x%04X) but "
"AL state=0x%02X(%s) < SAFEOP(0x%02X), code=0x%02X\r\n",
(unsigned)dlStatus, (unsigned)s_alState, alStateName,
(unsigned)AL_STATE_SAFEOP, (unsigned)alCode);
LOG_ERR("[ECAT][SLAVE] SYNC0 未触发:检查主站是否逐级 write_state"
" (PREOP->config_map->SAFEOP->OP) 并已配置 DC\r\n");
}
} else {
ECAT_LOGI("[ECAT][SLAVE] ESC ready, chip id=0x%08lX, AL state=0x%02X(%s), code=0x%02X\r\n",
(unsigned long)s_chipId, (unsigned)s_alState, alStateName, (unsigned)alCode);
}
LOG_ERR("[ECAT][SLAVE] init: %s\r\n", s_escReady ? "OK" : "FAIL(partial)");
/* 终判措辞:PDI/EEPROM 就绪但只差主站时报 WAIT_MASTER 而非 FAIL——本函数的
* s_escReady 要求 AL>=SAFEOP,无主站永远达不到,而 SSC 放行门控实际用的是
* isBusReady()&&isCoreReady()(见 initial.cpp),两者结论相反不应都叫 FAIL。 */
const char *verdict;
if (s_escReady) {
verdict = "OK";
} else if (chipIdOk && EscLan9252::isCoreReady() && !anyPhyLink) {
verdict = "WAIT_MASTER (PDI 就绪,等主站接入)";
} else {
verdict = "FAIL(partial)";
}
LOG_ERR("[ECAT][SLAVE] init: %s\r\n", verdict);
}
+52 -13
View File
@@ -20,6 +20,7 @@
#include "dc_app.h"
#include <string.h>
#include <stddef.h> /* offsetof:下方布局守卫断言使用 */
/*---------------------------------------------------------------------------
* el9800appl.h 桩 include (SSC 内部需要)
@@ -49,6 +50,13 @@ TOBJ6000;
static TOBJ7000 s_outputObj = {4, {0, 0, 0, 0}};
static TOBJ6000 s_inputObj = {4, {0, 0, 0, 0}};
/* 布局守卫(1.5.42):SSC 的 SDO 单条目访问按"si0 占 16bit,其后按 BitLength 紧排"
* 计算偏移(OBJ_GetEntryOffset),故 UINT64 数组必须紧跟在 UINT16 之后落在字节 2。
* OBJ_STRUCT_PACKED_START 曾被留空导致其落在字节 8、SDO 读写错位,加断言防回归。 */
_Static_assert(offsetof(TOBJ7000, u64Data) == 2, "TOBJ7000 未紧排:检查 OBJ_STRUCT_PACKED_START");
_Static_assert(offsetof(TOBJ6000, u64Data) == 2, "TOBJ6000 未紧排:检查 OBJ_STRUCT_PACKED_START");
_Static_assert(sizeof(TOBJ7000) == 34, "TOBJ7000 尺寸应为 2+32");
/*---------------------------------------------------------------------------
* SYNC 诊断对象 0x2000 (只读, 主站 SDO 轮询)
* sub1=SYNC0 计数 sub2=SYNC1 计数 sub3=ESC IRQ 计数 sub4=uptime 秒
@@ -66,6 +74,9 @@ TOBJ2000;
static TOBJ2000 s_syncDiag = {4, 0, 0, 0, 0};
/* 同上:UINT32 首条目必须落在字节 2(未紧排时为 4)。 */
_Static_assert(offsetof(TOBJ2000, u32Sync0) == 2, "TOBJ2000 未紧排:检查 OBJ_STRUCT_PACKED_START");
/* EthercatSync 计数器 getter (C++ 侧 extern "C" 导出) */
extern UINT32 EcatDbg_GetSync0(void);
extern UINT32 EcatDbg_GetSync1(void);
@@ -113,29 +124,57 @@ static TOBJ_PDO_MAPPING sTxPDOMap = {4, {
/*---------------------------------------------------------------------------
* Entry Descriptions
*
* 重要(1.5.42 修正): OBJ_GetEntryDesc()(objdef.c:554) 只对 OBJCODE_ARR 复用
* pEntryDesc[1] 描述所有子索引; 对 OBJCODE_REC 是逐下标索引 pEntryDesc[Subindex]。
* 因此 REC 对象的描述数组必须给满(Sub0..SubN 共 N+1 项), 否则 SDO Info 越界读到
* 相邻常量数组的垃圾, 主站 config_map 报 "Unsupported access to an object" /
* "Attempt to read to a write only object"(实测 0x1600/0x1A00 的 sub0/3/4 报错
* 而 sub1/2 侥幸通过, 正是越界特征)。
* 另: 可被 PDO 映射的数据子条目必须带 OBJACCESS_RxPDOMAPPING/TxPDOMAPPING,
* 否则主站校验 0x1600/0x1A00 的映射目标时会拒绝(官方 sampleappl.h 同此)。
*---------------------------------------------------------------------------*/
/* PDO Assign (0x1C12/0x1C13): Sub0=UINT8 count, Sub1..n=UINT16 index */
/* PDO Assign (0x1C12/0x1C13): ARR, Sub0=UINT8 count, Sub1..n=UINT16 index。
* ARR 只需 2 项(Sub0 + 通用项), 由 OBJ_GetEntryDesc 复用 [1]。 */
OBJCONST TSDOINFOENTRYDESC OBJMEM asPDOAssignEntryDesc[] = {
{DEFTYPE_UNSIGNED8, 0x08, ACCESS_READ},
{DEFTYPE_UNSIGNED16, 0x10, ACCESS_READ}
};
/* PDO Mapping (0x1600/0x1A00): Sub0=UINT8 count, Sub1..n=UINT32 mapping */
/* PDO Mapping (0x1600/0x1A00): REC 含 4 个映射条目 -> 必须给 Sub0..Sub4 共 5 项。 */
OBJCONST TSDOINFOENTRYDESC OBJMEM asPDOMappingEntryDesc[] = {
{DEFTYPE_UNSIGNED8, 0x08, ACCESS_READ},
{DEFTYPE_UNSIGNED32, 0x20, ACCESS_READ}
{DEFTYPE_UNSIGNED8, 0x08, ACCESS_READ}, /* Sub0: 条目数 */
{DEFTYPE_UNSIGNED32, 0x20, ACCESS_READ}, /* Sub1 */
{DEFTYPE_UNSIGNED32, 0x20, ACCESS_READ}, /* Sub2 */
{DEFTYPE_UNSIGNED32, 0x20, ACCESS_READ}, /* Sub3 */
{DEFTYPE_UNSIGNED32, 0x20, ACCESS_READ} /* Sub4 */
};
/* Data object 0x7000/0x6000: Sub0=UINT8 count, Sub1..4=UINT64 */
OBJCONST TSDOINFOENTRYDESC OBJMEM asDataEntryDesc[] = {
/* 0x7000 Output Data: REC, Sub0..Sub4 共 5 项; 子条目可被 RxPDO 映射。 */
OBJCONST TSDOINFOENTRYDESC OBJMEM asOutputEntryDesc[] = {
{DEFTYPE_UNSIGNED8, 0x08, ACCESS_READ},
{DEFTYPE_UNSIGNED64, 0x40, ACCESS_READWRITE}
{DEFTYPE_UNSIGNED64, 0x40, ACCESS_READWRITE | OBJACCESS_RXPDOMAPPING},
{DEFTYPE_UNSIGNED64, 0x40, ACCESS_READWRITE | OBJACCESS_RXPDOMAPPING},
{DEFTYPE_UNSIGNED64, 0x40, ACCESS_READWRITE | OBJACCESS_RXPDOMAPPING},
{DEFTYPE_UNSIGNED64, 0x40, ACCESS_READWRITE | OBJACCESS_RXPDOMAPPING}
};
/* SYNC 诊断 0x2000: Sub0=UINT8 count, Sub1..4=UINT32 (只读) */
/* 0x6000 Input Data: REC, Sub0..Sub4 共 5 项; 子条目可被 TxPDO 映射, 输入侧只读。 */
OBJCONST TSDOINFOENTRYDESC OBJMEM asInputEntryDesc[] = {
{DEFTYPE_UNSIGNED8, 0x08, ACCESS_READ},
{DEFTYPE_UNSIGNED64, 0x40, ACCESS_READ | OBJACCESS_TXPDOMAPPING},
{DEFTYPE_UNSIGNED64, 0x40, ACCESS_READ | OBJACCESS_TXPDOMAPPING},
{DEFTYPE_UNSIGNED64, 0x40, ACCESS_READ | OBJACCESS_TXPDOMAPPING},
{DEFTYPE_UNSIGNED64, 0x40, ACCESS_READ | OBJACCESS_TXPDOMAPPING}
};
/* SYNC 诊断 0x2000: REC, Sub0..Sub4 共 5 项, 纯只读不参与 PDO 映射。 */
OBJCONST TSDOINFOENTRYDESC OBJMEM asSyncDiagEntryDesc[] = {
{DEFTYPE_UNSIGNED8, 0x08, ACCESS_READ},
{DEFTYPE_UNSIGNED32, 0x20, ACCESS_READ},
{DEFTYPE_UNSIGNED32, 0x20, ACCESS_READ},
{DEFTYPE_UNSIGNED32, 0x20, ACCESS_READ},
{DEFTYPE_UNSIGNED32, 0x20, ACCESS_READ}
};
@@ -156,29 +195,29 @@ OBJCONST UCHAR OBJMEM aName0x2000[] = "SYNC Diag";
*---------------------------------------------------------------------------*/
TOBJECT OBJMEM ApplicationObjDicList[] = {
/* 0x1C12: RxPDO Assign */
/* 0x1C12: RxPDO Assign (ARR of UINT16; DEFTYPE 为元素类型) */
{NULL, NULL, 0x1C12, {DEFTYPE_UNSIGNED16, 1 | (OBJCODE_ARR << 8)},
asPDOAssignEntryDesc, aName0x1C12, &sRxPDOassign, NULL, NULL, 0x0000},
/* 0x1600: RxPDO Mapping */
/* 0x1600: RxPDO Mapping (REC, 4 条目 -> 描述数组必须 5 项) */
{NULL, NULL, 0x1600, {DEFTYPE_PDOMAPPING, 4 | (OBJCODE_REC << 8)},
asPDOMappingEntryDesc, aName0x1600, &sRxPDOMap, NULL, NULL, 0x0000},
/* 0x7000: Output Data (主站→从站) */
{NULL, NULL, 0x7000, {DEFTYPE_RECORD, 4 | (OBJCODE_REC << 8)},
asDataEntryDesc, aName0x7000, &s_outputObj, NULL, NULL, 0x0000},
asOutputEntryDesc, aName0x7000, &s_outputObj, NULL, NULL, 0x0000},
/* 0x1C13: TxPDO Assign */
{NULL, NULL, 0x1C13, {DEFTYPE_UNSIGNED16, 1 | (OBJCODE_ARR << 8)},
asPDOAssignEntryDesc, aName0x1C13, &sTxPDOassign, NULL, NULL, 0x0000},
/* 0x1A00: TxPDO Mapping */
/* 0x1A00: TxPDO Mapping (REC, 4 条目 -> 描述数组必须 5 项) */
{NULL, NULL, 0x1A00, {DEFTYPE_PDOMAPPING, 4 | (OBJCODE_REC << 8)},
asPDOMappingEntryDesc, aName0x1A00, &sTxPDOMap, NULL, NULL, 0x0000},
/* 0x6000: Input Data (从站→主站) */
{NULL, NULL, 0x6000, {DEFTYPE_RECORD, 4 | (OBJCODE_REC << 8)},
asDataEntryDesc, aName0x6000, &s_inputObj, NULL, NULL, 0x0000},
asInputEntryDesc, aName0x6000, &s_inputObj, NULL, NULL, 0x0000},
/* 0x2000: SYNC 诊断 (只读, 主站 SDO 轮询验证 DC 通路) */
{NULL, NULL, 0x2000, {DEFTYPE_RECORD, 4 | (OBJCODE_REC << 8)},
+13 -4
View File
@@ -517,12 +517,21 @@
/*-----------------------------------------------------------------------------------------
------
------ Struct Packing (ARM AC6: __attribute__((packed)) 不需要前后宏,留空即可)
------ Struct Packing
------
------ 1.5.42 修正:此前三对宏全部留空,注释误以为"AC6 不需要"。实际这些宏就是 SSC
------ 用来紧排结构体的唯一手段——SSC 的 SDO 读写按"si0 占 16bit,其后各条目按
------ BitLength 紧排"计算字节偏移(OBJ_GetEntryOffset, objdef.c:609),留空则结构体
------ 按编译器自然对齐,与该假设不符。armclang 实测:{UINT16; UINT64[4]} 的 UINT64
------ 落在字节 8 而非 2(编译期断言验证),{UINT16; UINT32 x4} 落在字节 4 而非 2,
------ 于是 0x7000/0x6000/0x2000 的 SDO 单条目访问全部读写错位内存;SSC 内部
------ 0x1C3x(TSYNCMANPAR)/0x10F1 等同样受影响。
------ AC6/armclang 支持 GCC 风格 __attribute__((packed)),置于 struct 关键字之后
------ (即 START 位置)对整个结构体生效。
-----------------------------------------------------------------------------------------*/
#ifndef STRUCT_PACKED_START
#define STRUCT_PACKED_START
#define STRUCT_PACKED_START __attribute__((packed))
#endif
#ifndef STRUCT_PACKED_END
@@ -530,7 +539,7 @@
#endif
#ifndef MBX_STRUCT_PACKED_START
#define MBX_STRUCT_PACKED_START
#define MBX_STRUCT_PACKED_START __attribute__((packed))
#endif
#ifndef MBX_STRUCT_PACKED_END
@@ -538,7 +547,7 @@
#endif
#ifndef OBJ_STRUCT_PACKED_START
#define OBJ_STRUCT_PACKED_START
#define OBJ_STRUCT_PACKED_START __attribute__((packed))
#endif
#ifndef OBJ_STRUCT_PACKED_END
+67 -135
View File
@@ -2,20 +2,20 @@
* @file hw_gd32exmc.cpp
* @brief SSC V5.11 硬件 port 层 — GD32E508 + LAN9252 (EXMC 16-bit HBI Indexed Mode)
*
* 实现 el9800hw.h 中声明的全部 HW_* 函数,将 SSC 的 ESC 访问请求
* 转发到 EscExmcBus (EXMC NOR/SRAM Region0, 16-bit 内存映射)。
* 实现 el9800hw.h 中声明的全部 HW_* 函数,将 SSC 的 ESC 访问请求
* 转发到 EscLan9252 的间接访问端口(1.5.39 按 DS 12.11 修正访问模型):
* - EtherCAT 核心 CSR(0x0000-0x0FFF:AL Status/Event、SyncManager、LED 等)
* → ECAT_CSR_CMD(0x304)/ECAT_CSR_DATA(0x300) 间接端口;
* - 过程 RAM(0x1000-0x1FFF:邮箱 0x1000+、PDO 0x1400+)
* → PRAM FIFO(0x000-0x03C 数据口 + 0x308-0x314 地址/命令)。
* 1.5.37 及之前直读/直写 HBI 索引(误以为索引空间统一可寻址),核心 CSR 全部
* 读 0/写丢弃——SSC 对主站 AL Control 完全失聪,主站恒读 AL=INIT。
*
* HBI Indexed 寻址协议(A2=LAN9252 Pin29←PD12=EXMC_A17, A1=Pin25←PD11=EXMC_A16;
* EXMC 16-bit 模式地址左移 1 位,见 esc_lan9252.cpp 头注释):
* 1. 写 HBI_IDX_0 (EXMC offset 0x00000) = ESC 寄存器字节地址
* 2. 读/写 HBI_DATA_0 低 word (EXMC offset 0x40000, A2=1/A1=0)
* 3. 读/写 HBI_DATA_0 高 word (EXMC offset 0x60000, A2=1/A1=1)
*
* 并发保护:
* - HW_EscRead/HW_EscWrite 等主循环函数:内部经 EscExmcBus::maskEnter/Exit
* 屏蔽 EXTI0+EXTI1(HBI Indexed 是两步事务,不可被 ISR 打断)。
* - HW_EscReadIsr/HW_EscWriteIsr (ISR):在 preempt=0 的 SYNC0 ISR 内调用,
* 天然不会被抢占;PDI_Isr/Sync1_Isr(preempt=1) 由 ethercat_sync.cpp 包装器
* 并发保护(不变):
* - HW_EscRead/HW_EscWrite 等主循环函数:内部经 EscExmcBus::maskEnter/Exit
* 屏蔽 EXTI0+EXTI1(访问端口是多步事务,不可被 ISR 打断)。
* - HW_EscReadIsr/HW_EscWriteIsr (ISR):在 preempt=0 的 SYNC0 ISR 内调用,
* 天然不会被抢占;PDI_Isr/Sync1_Isr(preempt=1) 由 ethercat_sync.cpp 包装器
* 屏蔽 EXTI0 后调用。
*/
@@ -28,126 +28,76 @@
/* 项目硬件驱动 */
#include "comm/esc_exmc_bus.h"
#include "comm/esc_lan9252.h"
#include "sys/config.h"
#include <cstdio>
#include <cstring>
/*---------------------------------------------------------------------------
* HBI Indexed Mode EXMC 偏移 (与 esc_lan9252.cpp 一致)
*---------------------------------------------------------------------------*/
namespace {
constexpr uint32_t HBI_IDX0_OFFSET = 0x00000U; /* A2=0, A1=0 */
constexpr uint32_t HBI_DATA0_LOW_OFF = 0x40000U; /* A2=1(EXMC_A17), A1=0(EXMC_A16) */
constexpr uint32_t HBI_DATA0_HIGH_OFF = 0x60000U; /* A2=1(EXMC_A17), A1=1(EXMC_A16) */
/* AL Event Mask 寄存器影子 (ESC 0x0204) */
volatile uint16_t s_alEventMask = 0xFFFFU;
/* SSC 看门狗软件定时器 (DWT CYCCNT 基准) */
volatile uint32_t s_timerBase = 0U;
/*---------------------------------------------------------------------------
* 内部 HBI 访问原语 (无锁,调用者负责并发保护)
*---------------------------------------------------------------------------*/
constexpr uint16_t ESC_PRAM_BASE = 0x1000U; /**< 过程 RAM 起点(DS 12.11):≥此地址走 PRAM FIFO */
inline void hbiSetIndex(uint16_t addr)
{
EscExmcBus::write16(HBI_IDX0_OFFSET, addr);
}
inline uint16_t hbiReadLow(void)
{
return EscExmcBus::read16(HBI_DATA0_LOW_OFF);
}
inline uint16_t hbiReadHigh(void)
{
return EscExmcBus::read16(HBI_DATA0_HIGH_OFF);
}
inline void hbiWriteLow(uint16_t v)
{
EscExmcBus::write16(HBI_DATA0_LOW_OFF, v);
}
inline void hbiWriteHigh(uint16_t v)
{
EscExmcBus::write16(HBI_DATA0_HIGH_OFF, v);
}
/** 从 ESC 地址读取 Len 字节到 pData (按 16-bit word 对齐处理) */
void hbiRead(void *pData, uint16_t address, uint16_t len)
/** 从 ESC 地址读取 Len 字节到 pData(按地址自动路由 CSR 端口 / PRAM FIFO) */
void escRead(MEM_ADDR *pData, uint16_t address, uint16_t len)
{
uint8_t *dst = (uint8_t *)pData;
if (address >= ESC_PRAM_BASE) {
EscLan9252::pramRead(address, dst, len);
return;
}
/* CSR 端口:size 1 任意地址,size 2 需偶地址(DS 表 12-14) */
uint16_t addr = address;
uint16_t remaining = len;
/* 奇数起始地址:读低 byte */
if ((addr & 1U) != 0U && remaining > 0U) {
hbiSetIndex(addr & 0xFFFEU);
uint16_t w = hbiReadLow();
*dst++ = (uint8_t)((w >> 8) & 0xFFU);
*dst++ = (uint8_t)EscLan9252::csrRead(addr, 1U);
addr++;
remaining--;
}
/* 对齐的 16-bit word 读取 */
while (remaining >= 2U) {
hbiSetIndex(addr);
uint16_t w = hbiReadLow();
const uint16_t w = (uint16_t)EscLan9252::csrRead(addr, 2U);
dst[0] = (uint8_t)(w & 0xFFU);
dst[1] = (uint8_t)((w >> 8) & 0xFFU);
dst[1] = (uint8_t)(w >> 8);
dst += 2;
addr += 2;
remaining -= 2;
}
/* 尾部奇数字节 */
if (remaining > 0U) {
hbiSetIndex(addr);
uint16_t w = hbiReadLow();
*dst = (uint8_t)(w & 0xFFU);
*dst = (uint8_t)EscLan9252::csrRead(addr, 1U);
}
}
/** 向 ESC 地址写入 Len 字节 (按 16-bit word 对齐处理) */
void hbiWrite(const void *pData, uint16_t address, uint16_t len)
/** 向 ESC 地址写入 Len 字节(按地址自动路由 CSR 端口 / PRAM FIFO) */
void escWrite(const MEM_ADDR *pData, uint16_t address, uint16_t len)
{
const uint8_t *src = (const uint8_t *)pData;
if (address >= ESC_PRAM_BASE) {
EscLan9252::pramWrite(address, src, len);
return;
}
uint16_t addr = address;
uint16_t remaining = len;
/* 奇数起始地址:RMW 高 byte */
if ((addr & 1U) != 0U && remaining > 0U) {
uint16_t aligned = addr & 0xFFFEU;
hbiSetIndex(aligned);
uint16_t w = hbiReadLow();
w = (w & 0x00FFU) | ((uint16_t)(*src++) << 8);
hbiSetIndex(aligned);
hbiWriteLow(w);
EscLan9252::csrWrite(addr, *src, 1U);
src++;
addr++;
remaining--;
}
/* 对齐的 16-bit word 写入 */
while (remaining >= 2U) {
uint16_t w = (uint16_t)src[0] | ((uint16_t)src[1] << 8);
hbiSetIndex(addr);
hbiWriteLow(w);
const uint16_t w = (uint16_t)src[0] | ((uint16_t)src[1] << 8);
EscLan9252::csrWrite(addr, w, 2U);
src += 2;
addr += 2;
remaining -= 2;
}
/* 尾部奇数字节:RMW 低 byte */
if (remaining > 0U) {
hbiSetIndex(addr);
uint16_t w = hbiReadLow();
w = (w & 0xFF00U) | (uint16_t)(*src);
hbiSetIndex(addr);
hbiWriteLow(w);
EscLan9252::csrWrite(addr, *src, 1U);
}
}
@@ -173,8 +123,7 @@ UINT8 HW_Init(void)
s_timerBase = DWT->CYCCNT;
/* 读取当前 AL Event Mask */
hbiSetIndex(ESC_AL_EVENTMASK_OFFSET);
s_alEventMask = hbiReadLow();
s_alEventMask = (uint16_t)EscLan9252::csrRead(ESC_AL_EVENTMASK_OFFSET, 2U);
return 0; /* 0 = success */
}
@@ -186,13 +135,12 @@ void HW_Release(void)
/**
* @brief 读取 AL Event 寄存器 (0x0220),主循环上下文。
* @note 内部屏蔽 EXTI0+EXTI1 保证 HBI 事务完整。
* @note 内部屏蔽 EXTI0+EXTI1 保证访问端口事务完整。
*/
UINT16 HW_GetALEventRegister(void)
{
const uint32_t prev = EscExmcBus::maskEnter();
hbiSetIndex(ESC_AL_EVENT_OFFSET);
const UINT16 v = hbiReadLow();
const UINT16 v = (UINT16)EscLan9252::csrRead(ESC_AL_EVENT_OFFSET, 2U);
EscExmcBus::maskExit(prev);
return v;
}
@@ -202,8 +150,7 @@ UINT16 HW_GetALEventRegister(void)
*/
UINT16 HW_GetALEventRegister_Isr(void)
{
hbiSetIndex(ESC_AL_EVENT_OFFSET);
return hbiReadLow();
return (UINT16)EscLan9252::csrRead(ESC_AL_EVENT_OFFSET, 2U);
}
/**
@@ -213,8 +160,7 @@ void HW_ResetALEventMask(UINT16 intMask)
{
const uint32_t prev = EscExmcBus::maskEnter();
s_alEventMask &= ~intMask;
hbiSetIndex(ESC_AL_EVENTMASK_OFFSET);
hbiWriteLow(s_alEventMask);
EscLan9252::csrWrite(ESC_AL_EVENTMASK_OFFSET, s_alEventMask, 2U);
EscExmcBus::maskExit(prev);
}
@@ -225,8 +171,7 @@ void HW_SetALEventMask(UINT16 intMask)
{
const uint32_t prev = EscExmcBus::maskEnter();
s_alEventMask |= intMask;
hbiSetIndex(ESC_AL_EVENTMASK_OFFSET);
hbiWriteLow(s_alEventMask);
EscLan9252::csrWrite(ESC_AL_EVENTMASK_OFFSET, s_alEventMask, 2U);
EscExmcBus::maskExit(prev);
}
@@ -239,12 +184,9 @@ void HW_SetLed(UINT8 RunLed, UINT8 ErrLed)
{
const uint32_t prev = EscExmcBus::maskEnter();
/* Run LED override (0x0138): bit0 = LED on */
hbiSetIndex(ESC_RUN_LED_OVERRIDE);
hbiWriteLow(RunLed ? 0x0001U : 0x0000U);
EscLan9252::csrWrite(ESC_RUN_LED_OVERRIDE, RunLed ? 0x0001U : 0x0000U, 2U);
/* Error LED override (0x0139): bit0 = LED on */
hbiSetIndex(ESC_ERROR_LED_OVERRIDE);
hbiWriteLow(ErrLed ? 0x0001U : 0x0000U);
EscLan9252::csrWrite(ESC_ERROR_LED_OVERRIDE, ErrLed ? 0x0001U : 0x0000U, 2U);
EscExmcBus::maskExit(prev);
}
@@ -255,14 +197,14 @@ void HW_SetLed(UINT8 RunLed, UINT8 ErrLed)
void HW_EscRead(MEM_ADDR *pData, UINT16 Address, UINT16 Len)
{
const uint32_t prev = EscExmcBus::maskEnter();
hbiRead(pData, Address, Len);
escRead(pData, Address, Len);
EscExmcBus::maskExit(prev);
}
void HW_EscWrite(MEM_ADDR *pData, UINT16 Address, UINT16 Len)
{
const uint32_t prev = EscExmcBus::maskEnter();
hbiWrite(pData, Address, Len);
escWrite(pData, Address, Len);
EscExmcBus::maskExit(prev);
}
@@ -272,12 +214,12 @@ void HW_EscWrite(MEM_ADDR *pData, UINT16 Address, UINT16 Len)
void HW_EscReadIsr(MEM_ADDR *pData, UINT16 Address, UINT16 Len)
{
hbiRead(pData, Address, Len);
escRead(pData, Address, Len);
}
void HW_EscWriteIsr(MEM_ADDR *pData, UINT16 Address, UINT16 Len)
{
hbiWrite(pData, Address, Len);
escWrite(pData, Address, Len);
}
/*---------------------------------------------------------------------------
@@ -285,59 +227,49 @@ void HW_EscWriteIsr(MEM_ADDR *pData, UINT16 Address, UINT16 Len)
*---------------------------------------------------------------------------*/
/**
* @brief 禁用指定 SM 通道 (清 SM Activate 寄存器 bit0)。
* @brief 禁用指定 SM 通道 (清 SM Activate 寄存器 0x0806+8n bit0)。
* @param channel SM 通道号 (0-15)
* @note 1.5.39:偏移由 +4 改为 +6——+4 是 SM Control(操作模式字段),
* Activate(bit0=SM Enable)在 +6;旧值从未真正生效过(访问层全空)。
*/
void HW_DisableSyncManChannel(UINT8 channel)
{
/* SM 寄存器基址 0x0800,每个 SM 占 8 字节,Activate 在 offset +4 (bit0) */
uint16_t smBase = 0x0800U + (uint16_t)channel * 8U;
uint16_t activateAddr = smBase + 4U;
const uint16_t activateAddr = (uint16_t)(ESC_SYNCMAN_REG_OFFSET
+ (uint16_t)channel * SIZEOF_SM_REGISTER + 6U);
const uint32_t prev = EscExmcBus::maskEnter();
hbiSetIndex(activateAddr);
uint16_t val = hbiReadLow();
val &= ~0x0001U; /* 清 bit0 = deactivate */
hbiSetIndex(activateAddr);
hbiWriteLow(val);
uint8_t val = (uint8_t)EscLan9252::csrRead(activateAddr, 1U);
val &= (uint8_t)~0x01U; /* 清 bit0 = deactivate */
EscLan9252::csrWrite(activateAddr, val, 1U);
EscExmcBus::maskExit(prev);
}
/**
* @brief 使能指定 SM 通道 (置 SM Activate 寄存器 bit0)。
* @brief 使能指定 SM 通道 (置 SM Activate 寄存器 0x0806+8n bit0)。
*/
void HW_EnableSyncManChannel(UINT8 channel)
{
uint16_t smBase = 0x0800U + (uint16_t)channel * 8U;
uint16_t activateAddr = smBase + 4U;
const uint16_t activateAddr = (uint16_t)(ESC_SYNCMAN_REG_OFFSET
+ (uint16_t)channel * SIZEOF_SM_REGISTER + 6U);
const uint32_t prev = EscExmcBus::maskEnter();
hbiSetIndex(activateAddr);
uint16_t val = hbiReadLow();
val |= 0x0001U;
hbiSetIndex(activateAddr);
hbiWriteLow(val);
uint8_t val = (uint8_t)EscLan9252::csrRead(activateAddr, 1U);
val |= 0x01U;
EscLan9252::csrWrite(activateAddr, val, 1U);
EscExmcBus::maskExit(prev);
}
/**
* @brief 获取指定 SM 通道的寄存器块指针。
* @note SSC 用此函数读取 SM 配置 (StartAddr, Length, Control, Status, Activate)。
* 由于 HBI 是间接访问,无法返回真实指针。SSC 在 EL9800_HW 模式下
* 实际通过 HW_EscRead 读取 SM 寄存器,此函数返回 NULL 并由调用者
* 使用 HW_EscRead 替代。
*
* 检查 ecatslv.c 中 HW_GetSyncMan 的使用方式:
* 如果 SSC 直接解引用返回的指针,则需要提供一个本地缓冲。
* 这里提供一个静态缓冲,每次调用时从 ESC 读入。
* 访问是间接端口,无法返回真实指针;提供静态缓冲,每次调用时从 ESC 读入。
*/
static TSYNCMAN s_smBuf;
TSYNCMAN ESCMEM *HW_GetSyncMan(UINT8 channel)
{
uint16_t smBase = 0x0800U + (uint16_t)channel * 8U;
const uint16_t smBase = (uint16_t)(ESC_SYNCMAN_REG_OFFSET
+ (uint16_t)channel * SIZEOF_SM_REGISTER);
const uint32_t prev = EscExmcBus::maskEnter();
hbiRead(&s_smBuf, smBase, SIZEOF_SM_REGISTER);
escRead((MEM_ADDR *)&s_smBuf, smBase, SIZEOF_SM_REGISTER);
EscExmcBus::maskExit(prev);
return &s_smBuf;
}
+50 -6
View File
@@ -65,14 +65,51 @@ void Adc::initDma()
}
/**
* @brief 初始化 ADC、DMA 与规则通道扫描配置。
* @brief 带超时等待 ADC_CTL1 指定位清零。
* @param[in] bit ADC_CTL1 位(RSTCLB/CLB)。
* @param[in] timeout_ms 超时毫秒数。
* @retval bool true=位已清零;false=超时(硬件未响应)。
* @note 替代库函数 adc_calibration_enable 内部的无超时死等——
* ADC 硬件异常时库函数会永久挂住 init,且无任何诊断。
*/
static bool waitAdcCtl1BitClear(uint32_t bit, uint32_t timeout_ms)
{
const uint32_t start = SysTickTimer::getMs();
while ((ADC_CTL1(CONFIG_ADC_INSTANCE) & bit) != 0U) {
if ((SysTickTimer::getMs() - start) >= timeout_ms) {
return false;
}
}
return true;
}
/**
* @brief 带超时执行 ADC 校准(RSTCLB → CLB 两段各等清零)。
* @retval uint8_t Adc::InitFault 故障码(0=校准完成;1=RSTCLB 超时;2=CLB 超时)。
*/
static uint8_t runCalibrationWithTimeout()
{
constexpr uint32_t kCalTimeoutMs = 10U; /* 校准实际仅数十 µs,10ms 裕量极大 */
ADC_CTL1(CONFIG_ADC_INSTANCE) |= ADC_CTL1_RSTCLB;
if (!waitAdcCtl1BitClear(ADC_CTL1_RSTCLB, kCalTimeoutMs)) {
return Adc::kFaultCalRstTimeout;
}
ADC_CTL1(CONFIG_ADC_INSTANCE) |= ADC_CTL1_CLB;
if (!waitAdcCtl1BitClear(ADC_CTL1_CLB, kCalTimeoutMs)) {
return Adc::kFaultCalTimeout;
}
return Adc::kFaultOk;
}
/**
* @brief 初始化 ADC、DMA 与规则通道扫描配置,并验证采样链有效。
* @param[in] 无。
* @param[out] 无。
* @retval 无返回值。
* @retval uint8_t Adc::InitFault 故障码(0=校准完成且 VREFINT→VDDA 换算在合理区间)。
* @note 按 `VIN_ADC / TEMP / VREFINT` 三通道顺序启用连续扫描与 DMA 采样。
* 参考 GD32E508 文档要求,内部温度与 VREFINT 通道使用长采样时间,以满足 ts_temp 要求。
*/
void Adc::init()
uint8_t Adc::init()
{
rcu_periph_clock_enable(RCU_ADC0);
rcu_periph_clock_enable(RCU_GPIOA);
@@ -103,10 +140,17 @@ void Adc::init()
adc_enable(CONFIG_ADC_INSTANCE);
SysTickTimer::delayMs(CONFIG_ADC_STABILIZE_DELAY_MS);
adc_calibration_enable(CONFIG_ADC_INSTANCE);
SysTickTimer::delayMs(CONFIG_ADC_STABILIZE_DELAY_MS);
const uint8_t calFault = runCalibrationWithTimeout();
if (calFault != kFaultOk) {
return calFault;
}
adc_software_trigger_enable(CONFIG_ADC_INSTANCE, ADC_REGULAR_CHANNEL);
SysTickTimer::delayMs(1U);
SysTickTimer::delayMs(CONFIG_ADC_STABILIZE_DELAY_MS);
/* 验证采样链:DMA 循环搬运在跑且 VREFINT 换算 VDDA 落在合理区间。
* calculateVdda 对非法 raw/超范围返回 0.0f。 */
const float vdda = calculateVdda(readVrefRaw());
return ((vdda >= kMinValidVdda) && (vdda <= kMaxValidVdda)) ? kFaultOk : kFaultVddaInvalid;
}
/**
+47 -40
View File
@@ -399,19 +399,24 @@ void CanFd::initChannelHardware(uint8_t i)
const uint8_t rxLevelAfter = (gpio_input_bit_get(ch.rxPort, ch.rxPin) != RESET) ? 1U : 0U;
const uint32_t tecAfterExit = (errAfterExit >> 16) & 0xFFU;
const uint32_t recAfterExit = (errAfterExit >> 24) & 0xFFU;
CAN_LOGI("[CAN-DIAG] CH%u init_nom=%u init_fd=%u exit_ok=%u wait=%lu rx_before=%u rx_after=%u stb=%u STAT=0x%08lX ERR=0x%08lX TEC=%lu REC=%lu\r\n",
static_cast<unsigned>(i),
nominalOk ? 1U : 0U,
fdOk ? 1U : 0U,
((statAfterExit & CAN_STAT_IWS) == 0U) ? 1U : 0U,
static_cast<unsigned long>(exitWait),
static_cast<unsigned>(rxLevelBefore),
static_cast<unsigned>(rxLevelAfter),
static_cast<unsigned>(stbLevel),
static_cast<unsigned long>(statAfterExit),
static_cast<unsigned long>(errAfterExit),
static_cast<unsigned long>(tecAfterExit),
static_cast<unsigned long>(recAfterExit));
/* 正常上电静默:只在本端控制器初始化失败(can_init/can_fd_init 失败或卡 init mode)
* 时打印一行诊断。TEC/REC 属总线侧状态,未接从站时浮空噪声会累积,不作为打印条件。 */
const bool exitedInitMode = ((statAfterExit & CAN_STAT_IWS) == 0U);
if (!nominalOk || !fdOk || !exitedInitMode) {
LOG_ERR("[CAN-DIAG] CH%u init_nom=%u init_fd=%u exit_ok=%u wait=%lu rx_before=%u rx_after=%u stb=%u STAT=0x%08lX ERR=0x%08lX TEC=%lu REC=%lu\r\n",
static_cast<unsigned>(i),
nominalOk ? 1U : 0U,
fdOk ? 1U : 0U,
exitedInitMode ? 1U : 0U,
static_cast<unsigned long>(exitWait),
static_cast<unsigned>(rxLevelBefore),
static_cast<unsigned>(rxLevelAfter),
static_cast<unsigned>(stbLevel),
static_cast<unsigned long>(statAfterExit),
static_cast<unsigned long>(errAfterExit),
static_cast<unsigned long>(tecAfterExit),
static_cast<unsigned long>(recAfterExit));
}
CAN_TSTAT(ch.canPeriph) = CAN_TSTAT_MTF0 | CAN_TSTAT_MTF1 | CAN_TSTAT_MTF2;
can_interrupt_enable(ch.canPeriph, CAN_INT_TME | CAN_INT_RFNE0 | CAN_INT_RFF0 | CAN_INT_RFO0);
@@ -782,13 +787,16 @@ void CanFd::init()
}
/**
* @brief L0 轻量自检:无总线发帧,仅校验使能通道硬件与收发器状态。
* @brief L0 轻量自检:无总线发帧,仅校验使能通道控制器自身状态(GD32 侧故障)。
* @note 1.5.35:不再检查 BUS_OFF/TEC/REC——未接从站的通道 RX 浮空噪声会累积 REC,
* 「没接设备」不等于错误;总线侧健康由 0xC1 反馈帧运行期上报。
*/
bool CanFd::selfTestL0()
bool CanFd::selfTestL0(uint8_t *fault_code, uint8_t *fault_channel)
{
constexpr uint32_t kTecRecLimit = 16U;
bool anyEnabled = false;
bool allOk = true;
uint8_t firstCode = kSelfTestOk;
uint8_t firstCh = 0U;
for (uint8_t i = 0U; i < static_cast<uint8_t>(kChannelCount); ++i) {
if (!kChannelEnabled[i]) {
@@ -796,38 +804,37 @@ bool CanFd::selfTestL0()
}
anyEnabled = true;
const auto &ch = kChannels[i];
uint8_t code = kSelfTestOk;
if (!s_channelHwOk[i]) {
allOk = false;
continue;
code = kSelfTestHwInit;
} else if ((CAN_STAT(ch.canPeriph) & CAN_STAT_IWS) != 0U) {
/* 仍停在初始化模式 */
code = kSelfTestStuckInit;
} else if (gpio_output_bit_get(ch.slpPort, ch.slpPin) != RESET) {
/* STB=0 为 Normal;输出寄存器回读确认配置生效 */
code = kSelfTestStbLevel;
}
/* 仍停在初始化模式 */
if ((CAN_STAT(ch.canPeriph) & CAN_STAT_IWS) != 0U) {
if (code != kSelfTestOk) {
allOk = false;
continue;
}
/* STB=0 为 Normal;高电平视为仍待机 */
if (gpio_output_bit_get(ch.slpPort, ch.slpPin) != RESET) {
allOk = false;
continue;
}
const uint32_t errReg = CAN_ERR(ch.canPeriph);
if ((errReg & CAN_ERR_BOERR) != 0U) {
allOk = false;
continue;
}
const uint32_t tec = (errReg >> 16) & 0xFFU;
const uint32_t rec = (errReg >> 24) & 0xFFU;
if (tec > kTecRecLimit || rec > kTecRecLimit) {
allOk = false;
continue;
if (firstCode == kSelfTestOk) {
firstCode = code;
firstCh = i;
}
LOG_ERR("[CAN] L0 selftest CH%u FAIL code=%u (STAT=0x%08lX ERR=0x%08lX)\r\n",
static_cast<unsigned>(i), static_cast<unsigned>(code),
static_cast<unsigned long>(CAN_STAT(ch.canPeriph)),
static_cast<unsigned long>(CAN_ERR(ch.canPeriph)));
}
}
if (fault_code != nullptr) {
*fault_code = firstCode;
}
if (fault_channel != nullptr) {
*fault_channel = firstCh;
}
return anyEnabled && allOk;
}
+27 -4
View File
@@ -6,6 +6,7 @@
#include "main.h"
#include "device/timer.h"
#include "sys/task_manager.h"
#include "sys/systick.h"
#include "sys/config.h"
/**
@@ -48,16 +49,38 @@ void SchedulerTimer::configureNvic()
}
/**
* @brief 初始化用户调度定时器模块。
* @brief 初始化用户调度定时器模块并验证节拍运行。
* @param[in] 无。
* @param[out] 无。
* @retval 无返回值。
* @note 依次完成定时器本体配置与 NVIC 中断配置。
* @retval uint8_t SchedulerTimer::InitFault 故障码(0=回读一致且 tick_100us 已前进)。
* @note 依次完成定时器本体配置与 NVIC 中断配置;
* 验证覆盖"中断向量/优先级配错导致调度器静默停摆"这类纯写配置发现不了的故障。
*/
void SchedulerTimer::init()
uint8_t SchedulerTimer::init()
{
configureTimer();
configureNvic();
/* 回读:计数器已使能、PSC/CAR 与配置一致 */
if ((TIMER_CTL0(CONFIG_SCHED_TIMER_INSTANCE) & TIMER_CTL0_CEN) == 0U) {
return kFaultNotEnabled;
}
if (TIMER_PSC(CONFIG_SCHED_TIMER_INSTANCE) != CONFIG_SCHED_TIMER_PRESCALER) {
return kFaultPscMismatch;
}
if (TIMER_CAR(CONFIG_SCHED_TIMER_INSTANCE) != CONFIG_SCHED_TIMER_PERIOD) {
return kFaultCarMismatch;
}
/* 有界等待 100us 中断节拍前进(50ms 预算 = 500 个 tick) */
const uint32_t start = g_scheduler.tick_100us;
const uint32_t deadline = SysTickTimer::getMs() + 50U;
while (g_scheduler.tick_100us == start) {
if ((int32_t)(SysTickTimer::getMs() - deadline) >= 0) {
return kFaultNoTick;
}
}
return kFaultOk;
}
/**
+12 -3
View File
@@ -7,14 +7,23 @@
*/
class SchedulerTimer {
public:
/** init 故障码(0=成功;经 0xA3 INIT_STATUS fault_codes[] 上报) */
enum InitFault : uint8_t {
kFaultOk = 0U, /**< 成功 */
kFaultNotEnabled = 1U, /**< CTL0.CEN 未置位(定时器未使能) */
kFaultPscMismatch = 2U, /**< PSC 回读与配置不一致 */
kFaultCarMismatch = 3U, /**< CAR 回读与配置不一致 */
kFaultNoTick = 4U, /**< 100us 中断节拍未前进(NVIC/向量/优先级异常) */
};
/**
* @brief 初始化用户调度定时器及其中断。
* @brief 初始化用户调度定时器及其中断,并验证节拍真实运行。
* @param[in] 无。
* @param[out] 无。
* @retval 无返回值。
* @retval uint8_t InitFault 故障码(0=寄存器回读一致且 tick_100us 中断节拍已前进)。
* @note 当前用于建立 100us 基准节拍,驱动 TaskManager::tick100us()。
*/
static void init();
static uint8_t init();
/**
* @brief TIMER2 中断处理入口。
+10 -2
View File
@@ -23,14 +23,22 @@ public:
uint8_t valid; /**< 采样是否有效:1=有效,0=存在原始值/换算异常。 */
};
/** init 故障码(0=成功;经 0xA3 INIT_STATUS fault_codes[] 上报,网页解码文字见 initial.h 注释) */
enum InitFault : uint8_t {
kFaultOk = 0U, /**< 成功 */
kFaultCalRstTimeout = 1U, /**< 校准复位(RSTCLB)等待超时,ADC 硬件无响应 */
kFaultCalTimeout = 2U, /**< 校准(CLB)等待超时 */
kFaultVddaInvalid = 3U, /**< 校准后 VREFINT→VDDA 采样链无效(DMA/通道/供电异常) */
};
/**
* @brief 初始化 ADC 硬件、扫描通道与 DMA 循环缓存。
* @param[in] 无。
* @param[out] 无。
* @retval 无返回值。
* @retval uint8_t InitFault 故障码(0=校准完成且 VDDA 采样链有效)。
* @note 完成 ADC 三通道扫描、DMA 循环搬运、PA6(VIN_ADC) 模拟输入配置以及内部通道校准。
*/
static void init();
static uint8_t init();
/**
* @brief 读取指定通道在 DMA 缓存中的原始 ADC 值。
+16 -3
View File
@@ -118,12 +118,25 @@ public:
static void init();
/** L0 自检故障原因码(selfTestL0 输出;经 0xA3 fault_codes[] 上报,高 3 位带通道号见 initial.h) */
enum SelfTestFault : uint8_t {
kSelfTestOk = 0U, /**< 通过 */
kSelfTestHwInit = 1U, /**< 控制器初始化失败(重试 5 次后仍不成功) */
kSelfTestStuckInit = 2U, /**< 卡在初始化模式(IWS 未清零) */
kSelfTestStbLevel = 3U, /**< 收发器 STB 输出未拉低(配置未生效) */
};
/**
* @brief L0 轻量自检(不向物理总线发帧)。
* @brief L0 轻量自检(不向物理总线发帧),仅校验 GD32 侧控制器自身状态。
* @param[out] fault_code 首个失败通道的 SelfTestFault 原因码;全部通过时为 kSelfTestOk。允许空。
* @param[out] fault_channel 首个失败通道号 0/1/2;全部通过时无意义。允许空。
* @retval true 所有已使能通道通过;false 无使能通道或任一路失败。
* @note 检查:硬件 init 结果、退出 init mode、收发器 Normal、无 BUS_OFF、TEC/REC 阈值。
* @note 检查:硬件 init 结果、退出 init mode、收发器 STB 已置 Normal。
* 1.5.35 移除 BUS_OFF 与 TEC/REC 检查:它们是总线侧状态,
* 通道未接从站(RX 浮空噪声)时会误报——「没接设备」不等于错误;
* 总线健康经 0xC1 反馈帧(bus_state/TEC/REC)运行期上报,网页 CAN 页可见。
*/
static bool selfTestL0();
static bool selfTestL0(uint8_t *fault_code = nullptr, uint8_t *fault_channel = nullptr);
/**
* @brief 查询指定通道是否已使能。
@@ -0,0 +1,61 @@
/**
* @file app_spi_cmd_init_status.cpp
* @brief SPI 0xA3 INIT_STATUS 命令:向 ESP32 上报 APP 初始化状态(1.5.34 新增,1.5.35 扩展故障码)。
*
* 响应 ACK 25B 布局(与 app_spi_command_defs.h kTypeInitStatus 注释一致):
* [0..3] module_mask LE AppInitializer::ModuleBit 成功位图
* [4..7] critical_fail_mask LE 失败的 critical 模块位图(0=无 critical 失败)
* [8] reset_cause ResetCauseBit 复位源位图
* [9] init_state 0=OK 1=degraded 2=critical_fail
* [10..11] reserved (0)
* [12..24] fault_codes[13] 每模块 1B 故障码(0=成功;编码见 sys/initial.h,
* canfd 高 4 位为首个失败通道号)
* 1.5.34 只回前 12B;ESP32 按 resp_len≥25 判定故障码数组是否存在,前后兼容。
*/
#include "device/spi/spi_command_handler.h"
#include "device/spi/app_spi_command.h"
#include "device/spi/app_spi_command_defs.h"
#include "sys/initial.h"
namespace {
/** @brief 0xA3 响应 payload 长度(12B 基础 + 13B 故障码)。 */
static constexpr size_t kInitStatusLen = 12U + 13U;
/** @brief SPI 0xA3 INIT_STATUS 处理器:应答初始化状态 25B。 */
class SpiInitStatusHandler final : public ISpiCommandHandler {
public:
/** @brief 是否处理该帧类型。 */
bool canHandle(uint8_t frame_type) const override
{
return frame_type == SpiDefs::kTypeInitStatus;
}
/**
* @brief 处理 INIT_STATUS:填充初始化状态与模块故障码并回 0x80。
* @param bus 命令总线
* @param ctx 请求上下文
*/
void handle(AppSpiCommand &bus, const SpiCommandContext &ctx) override
{
uint8_t payload[kInitStatusLen] = {0U};
writeU32Le(&payload[0], AppInitializer::moduleMask());
writeU32Le(&payload[4], AppInitializer::criticalFailMask());
payload[8] = AppInitializer::resetCause();
payload[9] = AppInitializer::initState();
AppInitializer::faultCodes(&payload[12]);
bus.buildResponse(ctx.tx_buf, ctx.tx_buf_size, ctx.tx_len,
SpiDefs::kRspAck, payload, sizeof(payload));
}
};
static SpiInitStatusHandler s_handler;
} // namespace
/** @brief 注册 0xA3 INIT_STATUS 命令处理器。 */
void registerSpiInitStatusCommand(AppSpiCommand &bus)
{
bus.registerHandler(&s_handler);
}
@@ -2,6 +2,7 @@
#include "device/spi/app_spi_command.h"
#include "device/spi/app_spi_command_defs.h"
#include "sys/config.h"
#include "sys/initial.h"
#include "sys/ota_boot_request.h"
#include "sys/systick.h"
#include "main.h"
@@ -68,7 +69,10 @@ void fillGd32AppIdentityPayload(uint8_t *out)
out[12] = V2_FEAT_MAP_VER;
writeU16Le(&out[13], buildAppFeatureFlags());
/* [15] reserved = 0 */
/* [15] init_state(1.5.34 起):0=OK 1=degraded 2=critical_fail。
* 复用原 reserved 字节——旧 ESP32 忽略该字节、旧 GD32 恒 0(恰与 OK 同义),
* 因此不 bump feat_map_ver 即前后兼容;详情经 0xA3 INIT_STATUS 查询。 */
out[15] = AppInitializer::initState();
}
namespace {
@@ -382,6 +382,7 @@ void AppSpiCommand::configureExti()
extern void registerSpiBootRequestCommand(AppSpiCommand &bus);
extern void registerSpiOtaWaitCommand(AppSpiCommand &bus);
extern void registerSpiAppPingCommand(AppSpiCommand &bus);
extern void registerSpiInitStatusCommand(AppSpiCommand &bus);
extern void registerSpiCanBridgeCommand(AppSpiCommand &bus);
extern void registerSpiCanBurstCommand(AppSpiCommand &bus);
extern void registerSpiCanFeedbackCommand(AppSpiCommand &bus);
@@ -407,6 +408,7 @@ void AppSpiCommand::init()
registerSpiBootRequestCommand(AppSpiCommandBus);
registerSpiOtaWaitCommand(AppSpiCommandBus);
registerSpiAppPingCommand(AppSpiCommandBus);
registerSpiInitStatusCommand(AppSpiCommandBus);
registerSpiCanBridgeCommand(AppSpiCommandBus);
registerSpiCanBurstCommand(AppSpiCommandBus);
registerSpiCanFeedbackCommand(AppSpiCommandBus);
@@ -23,6 +23,15 @@ static constexpr uint8_t kTypeAppPing = 0xA1U;
* 0=stop(恢复AF_PP), 1-4=数据pattern(0xFFFF/0x0000/0xAAAA/0x5555),
* 5=CS低, 6=RD低, 7=WR低, 8=A1高, 9=A2高。用于 LAN9252 通断测试。 */
static constexpr uint8_t kTypeExmcDiag = 0xA2U;
/** @brief APP 初始化状态查询(ESP32→GD32,1.5.34 新增):无 payload。
* 响应 ACK 25B(1.5.35 起;1.5.34 仅前 12B):
* [0..3] module_mask LE(AppInitializer::ModuleBit 成功位图)
* [4..7] critical_fail_mask LE(失败的 critical 模块位图,0=无)
* [8] reset_cause(ResetCauseBit 位图) [9] init_state(0=OK 1=degraded 2=critical_fail)
* [10..11] reserved
* [12..24] fault_codes[13](每模块 1B 故障码,0=成功;编码见 sys/initial.h,
* canfd = 原因码|(首个失败通道<<4))。位定义与 sys/initial.h 一致。 */
static constexpr uint8_t kTypeInitStatus = 0xA3U;
static constexpr uint8_t kTypeBootReq = 0xB0U;
static constexpr uint8_t kTypeCanSend = 0xC0U;
static constexpr uint8_t kTypeCanFeedback = 0xC1U; /**< ESP32 -> GD32 轮询 CAN 反馈快照 */
+45 -8
View File
@@ -11,6 +11,7 @@
uint8_t Usart0::s_txBuf[Usart0::kTxBufSize];
volatile uint16_t Usart0::s_txHead = 0U;
volatile uint16_t Usart0::s_txTail = 0U;
volatile uint32_t Usart0::s_txDropped = 0U;
/**
* @brief 计算发送环形缓冲区中的下一个索引。
@@ -35,19 +36,54 @@ bool Usart0::isBufferEmpty()
}
/**
* @brief 以非阻塞方式向 USART0 发送缓冲写入一个字符。
* @brief 在临界区内直接轮询 TBE 发出一个字节,腾出一个缓冲槽位。
* @param[in] 无。
* @param[out] 无。
* @retval bool true 表示成功发出一字节(s_txTail 已前进),false 表示等待 TBE 超时。
* @note 调用者必须已持有临界区:USART0 中断为 preempt=3,已被 BASEPRI 屏蔽,
* 故此处独占 s_txTail,不会与 onInterrupt() 取到同一字节。
* 走轮询而非等中断,因此在中断上下文里调用 printf 也不会死锁。
*/
bool Usart0::drainOneByteLocked()
{
if(isBufferEmpty()) {
return false;
}
uint32_t spin = kTbeSpinMax;
while(usart_flag_get(USART0, USART_FLAG_TBE) == RESET) {
if(--spin == 0U) {
return false; /* USART 未在发送(外设异常/时钟丢失),交由调用者计丢弃 */
}
}
usart_data_transmit(USART0, s_txBuf[s_txTail]);
s_txTail = nextIndex(s_txTail);
return true;
}
/**
* @brief 向 USART0 发送缓冲写入一个字符,缓冲满时自旋腾位而非丢弃。
* @param[in] ch 待发送字符。
* @param[out] 无。
* @retval bool true 表示成功写入发送缓冲,false 表示缓冲已满而被丢弃。
* @note 当前统一由 TBE 中断从队列中取字节发送;此处只负责入队并使能发送中断。
* @retval bool true 表示成功写入发送缓冲,false 表示 USART 异常导致该字节被丢弃。
* @note 正常路径仍是"入队 + 使能 TBE 中断"的非阻塞发送。仅在缓冲写满时
* (3M 波特率下 init 阶段诊断突发写入会写满)就地轮询发出一字节腾位,
* 以字节级背压换取日志完整——旧版此处静默丢弃且 putChar 忽略返回值,
* 实测使 [ECAT] 诊断出现 "bus eaabled"/"pdi_type=0x8Dexect" 等掉字,
* 曾导致 LAN9252 故障被两次误定性,故日志完整性优先于此处的等待开销。
* 仅当 USART 外设本身不发送时才真正丢弃并累加 s_txDropped。
*/
bool Usart0::putCharNonBlocking(int ch)
{
uint32_t save = RT_CRITICAL_SAVE();
const uint16_t next = nextIndex(s_txHead);
uint16_t next = nextIndex(s_txHead);
if(next == s_txTail) {
RT_CRITICAL_RESTORE(save);
return false;
/* 缓冲已满:就地发出最老的一字节腾出槽位,失败则说明 USART 真的没在发。 */
if(!drainOneByteLocked()) {
++s_txDropped;
RT_CRITICAL_RESTORE(save);
return false;
}
next = nextIndex(s_txHead);
}
s_txBuf[s_txHead] = static_cast<uint8_t>(ch);
@@ -110,10 +146,10 @@ void Usart0::configurePins()
* @brief 初始化 USART0 外设与中断。
* @param[in] 无。
* @param[out] 无。
* @retval 无返回值。
* @retval uint8_t Usart0::InitFault 故障码(0=USART 已使能,CTL0.UEN 回读置位)。
* @note 使用 `CONFIG_USART0_BAUDRATE` 配置串口参数,并使能 USART0 TBE 中断用于队列发送。
*/
void Usart0::init()
uint8_t Usart0::init()
{
rcu_periph_clock_enable(RCU_USART0);
usart_deinit(USART0);
@@ -125,6 +161,7 @@ void Usart0::init()
usart_transmit_config(USART0, USART_TRANSMIT_ENABLE);
usart_enable(USART0);
nvic_irq_enable(USART0_IRQn, CONFIG_IRQ_PREEMPT_SPI_USART, 0U);
return ((USART_CTL0(USART0) & USART_CTL0_UEN) != 0U) ? kFaultOk : kFaultNotEnabled;
}
/* ----- 中断入口 / printf 重定向的 C 包装 --------------------------- */
+35 -3
View File
@@ -19,14 +19,20 @@ public:
*/
static void configurePins();
/** init 故障码(0=成功;经 0xA3 INIT_STATUS fault_codes[] 上报) */
enum InitFault : uint8_t {
kFaultOk = 0U, /**< 成功 */
kFaultNotEnabled = 1U, /**< CTL0.UEN 回读未置位(使能未生效) */
};
/**
* @brief 初始化 USART0 外设与 NVIC。
* @param[in] 无。
* @param[out] 无。
* @retval 无返回值。
* @retval uint8_t InitFault 故障码(0=USART 已使能,CTL0.UEN 回读置位)。
* @note 使用 8N1 格式,收发使能,同步配置 NVIC 以便 TXE 中断驱动发送。
*/
static void init();
static uint8_t init();
/**
* @brief 将字符写入 USART0 发送环形缓冲。
@@ -66,10 +72,36 @@ private:
static bool putCharNonBlocking(int ch);
static constexpr uint16_t kTxBufSize = 1024U;
/**
* @brief 在临界区内直接轮询 TBE 并发出一个字节,腾出一个缓冲槽位。
* @param[in] 无。
* @param[out] 无。
* @retval bool true 表示成功发出一字节(tail 已前进),false 表示等待 TBE 超时。
* @note 调用者必须已持有临界区(USART0 中断 preempt=3 被 BASEPRI 屏蔽),
* 因此这里独占 s_txTail,不会与 onInterrupt() 争用同一字节。
* 不依赖中断投递,故在任意上下文(含 ISR 内 printf)均可安全使用。
*/
static bool drainOneByteLocked();
/** @brief 3M 波特率下 init 阶段诊断是突发写入,1024 字节会溢出致日志掉字。 */
static constexpr uint16_t kTxBufSize = 4096U;
/** @brief 等待 TBE 的自旋上限:3M 下单字节约 3.3us,此值远大于之,仅防 USART 失效死等。 */
static constexpr uint32_t kTbeSpinMax = 200000U;
static uint8_t s_txBuf[kTxBufSize];
static volatile uint16_t s_txHead;
static volatile uint16_t s_txTail;
static volatile uint32_t s_txDropped;
public:
/**
* @brief 读取因发送缓冲溢出而被丢弃的字节数。
* @param[in] 无。
* @param[out] 无。
* @retval uint32_t 累计丢弃字节数(0 表示日志完整可信)。
* @note 非 0 说明串口日志有掉字,诊断结论不可全信——历史上掉字日志曾导致
* LAN9252 故障两次误定性,故显式暴露该计数供 init 报告核对。
*/
static uint32_t droppedBytes() { return s_txDropped; }
};
#endif /* DEVICE_USART0_H */
+29 -3
View File
@@ -11,13 +11,39 @@
#include "sys/config.h"
#include "main.h"
void Watchdog::init()
uint8_t Watchdog::init()
{
/* FWDGT 的 PSC/RLD 写入经 LSI(IRC40K) 时钟域同步(PUD/RUD 由硬件清零)。
* BOOT 只检测 FWDGT 复位标志、从不启动看门狗,冷启动时 LSI 未开——
* 直接配置会让 PUD/RUD 永远等不到清零(库超时返回 ERROR,1.5.34 起暴露为 init 失败;
* 此前静默:FWDGT 实际按复位默认值 DIV4/0xFFF 运行,超时就与配置不符)。
* 必须先手动开 LSI 并等就绪,再配置。 */
rcu_osci_on(RCU_IRC40K);
if (rcu_osci_stab_wait(RCU_IRC40K) != SUCCESS) {
return kFaultLsiNotReady;
}
/* PSC/RLD 配置函数自带 ErrStatus(等待 PUD/RUD 清零带超时);
* 此前返回值被忽略,写入失败会无声变成"看门狗实际未按预期运行"。 */
fwdgt_write_enable();
fwdgt_prescaler_value_config(CONFIG_FWDGT_PSC);
fwdgt_reload_value_config(CONFIG_FWDGT_RLD);
uint8_t fault = kFaultOk;
if (fwdgt_prescaler_value_config((uint16_t)CONFIG_FWDGT_PSC) != SUCCESS) {
fault = kFaultPsc;
}
if (fwdgt_reload_value_config((uint16_t)CONFIG_FWDGT_RLD) != SUCCESS) {
if (fault == kFaultOk) {
fault = kFaultRld;
}
}
fwdgt_write_disable();
fwdgt_enable();
/* 使能后确认无挂起更新(PUD/RUD 应已清零) */
if ((FWDGT_STAT & (FWDGT_STAT_PUD | FWDGT_STAT_RUD)) != 0U) {
if (fault == kFaultOk) {
fault = kFaultPending;
}
}
return fault;
}
void Watchdog::feed()
+12 -2
View File
@@ -12,8 +12,18 @@
class Watchdog {
public:
/** @brief 初始化并启动 FWDGT,超时约 CONFIG_FWDGT_TIMEOUT_MS 毫秒。 */
static void init();
/** init 故障码(0=成功;经 0xA3 INIT_STATUS fault_codes[] 上报) */
enum InitFault : uint8_t {
kFaultOk = 0U, /**< 成功 */
kFaultPsc = 1U, /**< 预分频写入未被硬件确认(PUD 超时) */
kFaultRld = 2U, /**< 重载值写入未被硬件确认(RUD 超时) */
kFaultPending = 3U, /**< 使能后仍有挂起的配置更新(PUD/RUD 未清零) */
kFaultLsiNotReady = 4U, /**< LSI(IRC40K) 使能后稳定等待超时 */
};
/** @brief 初始化并启动 FWDGT,超时约 CONFIG_FWDGT_TIMEOUT_MS 毫秒。
* @retval uint8_t InitFault 故障码(0=PSC/RLD 写入均经硬件确认且无挂起更新)。 */
static uint8_t init();
/** @brief 喂狗(重载计数器),应在主循环中周期调用。 */
static void feed();
File diff suppressed because one or more lines are too long
+182 -20
View File
@@ -3,9 +3,12 @@
* @brief 应用启动初始化:模块成功置位,结束统一报告结果。
*
* 顺序:
* NVIC → SysTick → ESP32_EN 保持 → USART0 → AppSpi(尽早 HELLO 探活)→
* 复位源采集 → NVIC → SysTick → ESP32_EN 保持 → USART0 → AppSpi(尽早 HELLO 探活)→
* ADC → CANFD → OtaBootRequest → TaskManager → Scheduler →
* EtherCAT(+Sync 可选) → Watchdog → report
*
* 1.5.34:各模块 init 从"盲配置"改为带检查(寄存器回读 / 节拍验证 / 校准超时),
* 失败不置位;复位源/模块位图/critical 失败位图经 getter 导出供 SPI 0xA3 上报。
*/
#include "sys/initial.h"
@@ -30,6 +33,9 @@ void MainLoop(void);
}
uint32_t AppInitializer::s_mask = 0U;
uint8_t AppInitializer::s_initState = AppInitializer::kInitOk;
uint8_t AppInitializer::s_resetCause = 0U;
uint8_t AppInitializer::s_faultCodes[AppInitializer::kModuleCount] = {0U};
namespace {
@@ -62,6 +68,71 @@ void AppInitializer::mark(ModuleBit b)
s_mask |= static_cast<uint32_t>(b);
}
/** @brief ModuleBit(1<<N) → 数组索引 N。 */
static uint8_t moduleBitIndex(uint32_t bit)
{
uint8_t idx = 0U;
while (bit > 1U) {
bit >>= 1U;
++idx;
}
return idx;
}
void AppInitializer::setFault(ModuleBit b, uint8_t code)
{
const uint8_t idx = moduleBitIndex(static_cast<uint32_t>(b));
if (idx < kModuleCount) {
s_faultCodes[idx] = code;
}
}
uint8_t AppInitializer::faultCode(uint8_t module_index)
{
return (module_index < kModuleCount) ? s_faultCodes[module_index] : 0U;
}
uint8_t AppInitializer::faultCodes(uint8_t *out)
{
if (out == nullptr) {
return 0U;
}
for (uint8_t i = 0U; i < kModuleCount; ++i) {
out[i] = s_faultCodes[i];
}
return kModuleCount;
}
uint32_t AppInitializer::criticalFailMask()
{
uint32_t fail = 0U;
for (size_t i = 0; i < sizeof(kEntries) / sizeof(kEntries[0]); ++i) {
const InitEntry &e = kEntries[i];
if (e.critical && (s_mask & e.bit) == 0U) {
fail |= e.bit;
}
}
return fail;
}
/**
* @brief 采集 RCU_RSTSCK 复位源位图并清硬件标志。
* @note 必须在 init 最开头调用(早于任何可能读标志的模块);
* 清除不影响 BOOT 的 FWDGT 停留 OTA 判定——新的看门狗复位会由硬件重新置位。
*/
void AppInitializer::captureResetCause()
{
uint8_t cause = 0U;
if (rcu_flag_get(RCU_FLAG_PORRST) != RESET) cause |= kRstPor;
if (rcu_flag_get(RCU_FLAG_EPRST) != RESET) cause |= kRstEpr;
if (rcu_flag_get(RCU_FLAG_FWDGTRST) != RESET) cause |= kRstFwdgt;
if (rcu_flag_get(RCU_FLAG_WWDGTRST) != RESET) cause |= kRstWwdgt;
if (rcu_flag_get(RCU_FLAG_SWRST) != RESET) cause |= kRstSw;
if (rcu_flag_get(RCU_FLAG_LPRST) != RESET) cause |= kRstLp;
rcu_all_reset_flag_clear();
s_resetCause = cause;
}
void AppInitializer::report()
{
bool critical_ok = true;
@@ -89,51 +160,119 @@ void AppInitializer::report()
}
fail_list[fail_len] = '\0';
}
/* 故障码后缀 #XX(16 进制;canfd 高 4 位为通道号),0=成功不追加。
* 码值含义见 initial.h 注释与各模块头文件;网页按同一编码解码中文原因。 */
const uint8_t fc = s_faultCodes[moduleBitIndex(e.bit)];
if (fc != 0U && fail_len + 3U < sizeof(fail_list)) {
const char *hex = "0123456789ABCDEF";
fail_list[fail_len++] = '#';
fail_list[fail_len++] = hex[(fc >> 4) & 0xFU];
fail_list[fail_len++] = hex[fc & 0xFU];
fail_list[fail_len] = '\0';
}
}
/* 复位源随 init 报告输出一次(位图多位置位属正常,如 POR 常伴随 EPRST/SWRST) */
printf("[APP] reset_cause: 0x%02X (por=%d epr=%d fwdgt=%d wwdgt=%d sw=%d lp=%d)\r\n",
(unsigned)s_resetCause,
(s_resetCause & kRstPor) ? 1 : 0,
(s_resetCause & kRstEpr) ? 1 : 0,
(s_resetCause & kRstFwdgt) ? 1 : 0,
(s_resetCause & kRstWwdgt) ? 1 : 0,
(s_resetCause & kRstSw) ? 1 : 0,
(s_resetCause & kRstLp) ? 1 : 0);
if (critical_ok && all_ok) {
s_initState = kInitOk;
printf("[APP] init: OK (all modules)\r\n");
} else if (critical_ok) {
s_initState = kInitDegraded;
printf("[APP] init: OK (critical), skipped/failed: %s\r\n",
fail_list[0] != '\0' ? fail_list : "?");
} else {
s_initState = kInitCriticalFail;
printf("[APP] init: FAIL, modules: %s\r\n",
fail_list[0] != '\0' ? fail_list : "?");
}
/* 日志完整性自证:串口 TX 缓冲溢出会静默吞字节,1.5.40 及之前实测使 [ECAT] 诊断
* 出现 "bus eaabled"/"pdi_type=0x8Dexect" 等掉字,并两次导致 LAN9252 故障误定性。
* 1.5.41 起缓冲满时自旋腾位,仅 USART 外设异常才丢弃;非 0 即代表上面的日志不可全信。 */
const uint32_t dropped = Usart0::droppedBytes();
if (dropped != 0U) {
printf("[APP] WARN: uart dropped %u bytes, 以上日志可能不完整\r\n",
(unsigned)dropped);
}
}
void AppInitializer::init()
{
s_mask = 0U;
s_initState = kInitOk;
for (uint8_t i = 0U; i < kModuleCount; ++i) {
s_faultCodes[i] = 0U;
}
captureResetCause();
/* NVIC 优先级分组:写入后回读 AIRCR.PRIGROUP 验证(纯寄存器写,回读确认分组生效) */
nvic_priority_group_set(CONFIG_NVIC_PRIORITY_GROUP);
mark(kNvic);
if ((SCB->AIRCR & (0x7UL << 8U)) == (CONFIG_NVIC_PRIORITY_GROUP & (0x7UL << 8U))) {
mark(kNvic);
} else {
setFault(kNvic, kFaultNvicPrigroupMismatch);
}
SysTickTimer::initMs();
mark(kSysTick);
/* SysTick:内部验证节拍真实前进;失败不返回(FATAL 停机) */
if (SysTickTimer::initMs()) {
mark(kSysTick);
}
rcu_periph_clock_enable(RCU_GPIOC);
gpio_bit_set(CONFIG_ESP32_RESET_PORT, CONFIG_ESP32_RESET_PIN);
gpio_init(CONFIG_ESP32_RESET_PORT, GPIO_MODE_OUT_PP, GPIO_OSPEED_50MHZ, CONFIG_ESP32_RESET_PIN);
mark(kEsp32Hold);
/* ESP32 EN 自检:日志里能直接看出是上电即拉高,还是被外部钳住 */
APP_LOGI("[APP] ESP32_EN PC9=%d (1=released)\r\n",
(int)gpio_input_bit_get(CONFIG_ESP32_RESET_PORT, CONFIG_ESP32_RESET_PIN));
/* ESP32 EN 自检:回读为 1 才算成功(被外部钳低属硬件异常,标记失败但不阻塞其余模块) */
const bool esp32_released =
(gpio_input_bit_get(CONFIG_ESP32_RESET_PORT, CONFIG_ESP32_RESET_PIN) == SET);
if (esp32_released) {
mark(kEsp32Hold);
} else {
setFault(kEsp32Hold, kFaultEsp32HoldClamped);
}
APP_LOGI("[APP] ESP32_EN PC9=%d (1=released)\r\n", esp32_released ? 1 : 0);
Usart0::configurePins();
Usart0::init();
mark(kUsart0);
const uint8_t usartFault = Usart0::init();
if (usartFault == Usart0::kFaultOk) {
mark(kUsart0);
} else {
setFault(kUsart0, usartFault);
}
/* SPI 尽早就绪:OTA FINISH 跳转后 ESP32 用 HELLO 探活,不能等 CAN 上电延时 */
/* SPI 尽早就绪:OTA FINISH 跳转后 ESP32 用 HELLO 探活,不能等 CAN 上电延时。
* 从机无硬件自证手段(配置即生效),探活依赖 ESP32 侧 HELLO/PING(运行期)。 */
AppSpiCommandBus.init();
mark(kAppSpi);
Adc::init();
mark(kAdc);
/* ADC:带超时校准 + VREFINT→VDDA 采样链验证 */
const uint8_t adcFault = Adc::init();
if (adcFault == Adc::kFaultOk) {
mark(kAdc);
} else {
setFault(kAdc, adcFault);
LOG_ERR("[APP] ADC init FAIL fault=%u (1=RSTCLB timeout 2=CLB timeout 3=VDDA invalid)\r\n",
(unsigned)adcFault);
}
CanFd::init();
/* L0 通过才置 canfd 位 */
if (CanFd::selfTestL0()) {
/* L0 通过才置 canfd 位;故障码 = 原因 | (首个失败通道 << 4)。
* 无使能通道(canFault==Ok)属合法配置而非故障,照样置位。 */
uint8_t canFault = CanFd::kSelfTestOk;
uint8_t canFaultCh = 0U;
(void)CanFd::selfTestL0(&canFault, &canFaultCh);
if (canFault != CanFd::kSelfTestOk) {
setFault(kCanFd, (uint8_t)(canFault | (uint8_t)(canFaultCh << 4U)));
} else {
mark(kCanFd);
}
@@ -143,17 +282,28 @@ void AppInitializer::init()
TaskManager::init();
mark(kTaskManager);
SchedulerTimer::init();
mark(kScheduler);
/* 调度定时器:回读 + 100us 中断节拍前进验证 */
const uint8_t schedFault = SchedulerTimer::init();
if (schedFault == SchedulerTimer::kFaultOk) {
mark(kScheduler);
} else {
setFault(kScheduler, schedFault);
LOG_ERR("[APP] SchedulerTimer init FAIL fault=%u (1=CEN 2=PSC 3=CAR 4=no tick)\r\n",
(unsigned)schedFault);
}
/* ---- EtherCAT: EXMC 总线探活 → SSC 协议栈初始化 → SYNC0/IRQ 中断使能 ----
* 1.5.27:改经 EthercatSlave::init()(内部调用 EscLan9252::init 并记录
* chipId/AL 状态诊断,此前该函数无调用点、s_escReady 恒为 false)。
* 门控用 EscLan9252::isBusReady()(BYTE_TEST 通过)而非 isEscReady():
* 冷启动时 AL=INIT,SSC 必须照常启动并等待主站配置。
* 1.5.39:追加 isCoreReady() 门控——EscLan9252::init() 已等待 EEPROM(SII)
* 加载完成(DL_STATUS bit0);未加载时 EtherCAT 核心寄存器写静默丢弃,
* SSC 的 AL Event Mask/邮箱 SM 配置会全部落空(主站恒读 AL=INIT 的根因),
* 此时跳过 SSC 并以独立故障码上报。
* EEPROM 校验/烧录延迟到主循环执行,避免阻塞 init(烧录需 ~20s)。 */
EthercatSlave::init();
if (EscLan9252::isBusReady()) {
if (EscLan9252::isBusReady() && EscLan9252::isCoreReady()) {
const uint16_t sscResult = MainInit();
if (sscResult == 0U) {
mark(kEthercat);
@@ -161,14 +311,26 @@ void AppInitializer::init()
mark(kEthercatSync);
printf("[ECAT] SSC MainInit OK, sync ISR enabled\r\n");
} else {
setFault(kEthercat, kFaultEthercatSscFail);
printf("[ECAT] SSC MainInit FAIL (code=0x%04X)\r\n", (unsigned)sscResult);
}
} else if (EscLan9252::isBusReady()) {
setFault(kEthercat, kFaultEthercatCoreNotReady);
printf("[ECAT] EtherCAT core not ready (EEPROM not loaded), SSC skipped\r\n");
} else {
setFault(kEthercat, kFaultEthercatBusNotReady);
printf("[ECAT] EXMC bus not ready, SSC skipped\r\n");
}
Watchdog::init();
mark(kWatchdog);
/* 看门狗放最后:PSC/RLD 写入经硬件确认才置位 */
const uint8_t wdgFault = Watchdog::init();
if (wdgFault == Watchdog::kFaultOk) {
mark(kWatchdog);
} else {
setFault(kWatchdog, wdgFault);
LOG_ERR("[APP] Watchdog init FAIL fault=%u (1=PSC 2=RLD 3=pending 4=LSI not ready)\r\n",
(unsigned)wdgFault);
}
report();
}
+80 -1
View File
@@ -6,10 +6,12 @@
/**
* @brief 应用初始化器:按固定顺序启动模块,成功位置 1,末尾汇总。
* @note 汇总结果(模块位图 / critical 失败位图 / 复位源 / init 状态)经静态 getter 导出,
* 供 SPI 0xA3 INIT_STATUS 命令与身份块 [15] 上报 ESP32。
*/
class AppInitializer {
public:
/** 启动模块位(成功置 1) */
/** 启动模块位(成功置 1);与 SPI 0xA3 上报位图共用,改动须同步 ESP32 侧注释 */
enum ModuleBit : uint32_t {
kNvic = 1u << 0,
kSysTick = 1u << 1,
@@ -26,15 +28,92 @@ public:
kWatchdog = 1u << 12,
};
/** init 总体状态(身份块 [15] 与 0xA3 payload[9] 共用编码) */
enum InitState : uint8_t {
kInitOk = 0U, /**< 全部模块成功 */
kInitDegraded = 1U, /**< critical 全成功,非 critical 有失败/跳过 */
kInitCriticalFail = 2U, /**< 有 critical 模块失败 */
};
/** 复位源位图(0xA3 payload[8] 编码),多位置位属正常(如 POR 常伴随其它标志) */
enum ResetCauseBit : uint8_t {
kRstPor = 1u << 0, /**< 上电/掉电复位 */
kRstEpr = 1u << 1, /**< 外部引脚复位(NRST) */
kRstFwdgt = 1u << 2, /**< 独立看门狗复位 */
kRstWwdgt = 1u << 3, /**< 窗口看门狗复位 */
kRstSw = 1u << 4, /**< 软件复位(含 OTA 跳转) */
kRstLp = 1u << 5, /**< 低功耗复位 */
};
/** 模块数量(fault code 数组长度,与 ModuleBit 位序一致:bit N → 索引 N) */
static constexpr uint8_t kModuleCount = 13U;
/**
* @brief 模块故障码(0xA3 payload[12..24],每模块 1B,0=成功)。
*
* 各模块码值定义位置:
* - nvic : kFaultNvic*(本类下)
* - systick : kFaultSystick*(本类下;实际失败时 FATAL 停机不上报,仅备查)
* - esp32_hold : kFaultEsp32Hold*(本类下)
* - usart0 : Usart0::InitFault(device/usart0.h)
* - adc : Adc::InitFault(device/adc.h)
* - canfd : CanFd::SelfTestFault(device/canfd.h),
* **编码 = 原因码 | (首个失败通道号 << 4)**,通道 0/1/2
* - ota_boot_req: 纯软件,恒 0
* - app_spi : 无自证手段,恒 0
* - task_manager: 纯软件,恒 0
* - scheduler : SchedulerTimer::InitFault(device/timer.h)
* - ethercat : kFaultEthercat*(本类下)
* - ethercat_sync: 跟随 ethercat,恒 0
* - watchdog : Watchdog::InitFault(device/watchdog.h)
*/
enum NvicFault : uint8_t {
kFaultNvicOk = 0U,
kFaultNvicPrigroupMismatch = 1U, /**< AIRCR.PRIGROUP 回读与配置不一致 */
};
enum SystickFault : uint8_t {
kFaultSystickOk = 0U,
kFaultSystickConfig = 1U, /**< SysTick_Config 失败(备查,实际 FATAL 停机) */
kFaultSystickNoTick = 2U, /**< 节拍未前进(备查,实际 FATAL 停机) */
};
enum Esp32HoldFault : uint8_t {
kFaultEsp32HoldOk = 0U,
kFaultEsp32HoldClamped = 1U, /**< PC9 输出高后回读仍为 0(被外部钳低) */
};
enum EthercatFault : uint8_t {
kFaultEthercatOk = 0U,
kFaultEthercatBusNotReady = 1U, /**< EXMC/HBI 总线探活失败(BYTE_TEST 未通过) */
kFaultEthercatSscFail = 2U, /**< SSC MainInit 返回非 0 */
kFaultEthercatCoreNotReady = 3U, /**< EEPROM 未加载完成,EtherCAT 核心未使能(1.5.39) */
};
/**
* @brief 按固定顺序完成模块初始化(过程只置位,结束统一报告成败/失败模块)。
*/
static void init();
/** @brief 已成功初始化的模块位图。 */
static uint32_t moduleMask() { return s_mask; }
/** @brief 初始化失败的 critical 模块位图(0=无 critical 失败)。 */
static uint32_t criticalFailMask();
/** @brief init 总体状态(InitState 编码)。 */
static uint8_t initState() { return s_initState; }
/** @brief 本次上电复位源位图(ResetCauseBit 编码,init 开头采集并已清硬件标志)。 */
static uint8_t resetCause() { return s_resetCause; }
/** @brief 指定模块(ModuleBit 位序索引 0..12)的故障码,0=成功。 */
static uint8_t faultCode(uint8_t module_index);
/** @brief 将全部模块故障码拷贝到 out(至少 kModuleCount 字节),返回长度。 */
static uint8_t faultCodes(uint8_t *out);
private:
static uint32_t s_mask;
static uint8_t s_initState;
static uint8_t s_resetCause;
static uint8_t s_faultCodes[kModuleCount];
static void mark(ModuleBit b);
static void setFault(ModuleBit b, uint8_t code);
static void captureResetCause();
static void report();
};
+44 -5
View File
@@ -34,16 +34,55 @@ void SysTickTimer::delayMs(uint32_t ms)
}
/**
* @brief 初始化 1ms SysTick 时基。
* @brief SysTick 失效时的致命停机:裸寄存器 USART0(PA9) 输出 FATAL 后挂死。
* @param[in] 无。
* @param[out] 无。
* @retval 无返回值。
* @note 先更新 `SystemCoreClock`,再按 1kHz 频率配置 SysTick。
* @retval 无返回值(不返回)。
* @note 此时 USART0 驱动尚未初始化,只能裸写寄存器;
* 时钟树已是 180MHz(SystemInit 完成),BAUD=60 对应 3M(与工程 printf 一致)。
* 注意 system_gd32e50x.c 的 HXTAL FAIL 段有寄存器 bug(CTL0 写到 STAT0 偏移、
* 漏开 USART0EN),本段使用修正后的地址:APB2EN bit14=USART0EN、CTL0 @ +0x0C。
*/
void SysTickTimer::initMs()
static void fatalSystickHalt()
{
*((volatile uint32_t *)0x40021018U) |= (1U << 14) | (1U << 2) | (1U << 0); /* USART0EN|PAEN|AFEN */
{ volatile uint32_t _ctl = *((volatile uint32_t *)0x40010804U); /* PA9 → AF_PP */
_ctl &= ~(0xFU << 4); _ctl |= (0xBU << 4);
*((volatile uint32_t *)0x40010804U) = _ctl; }
*((volatile uint32_t *)0x40013808U) = 60U; /* BAUD: 180MHz/3M */
*((volatile uint32_t *)0x4001380CU) = (1U << 3) | (1U << 13); /* CTL0: TEN | UEN */
{ const char *_msg = "[APP] init: FATAL systick\r\n";
for(int i=0; _msg[i]; i++){
*((volatile uint32_t *)0x40013804U) = _msg[i]; /* DATA */
while((*((volatile uint32_t *)0x40013800U) & (1U << 7)) == 0U) {} } } /* STAT0.TBE */
while(1){ }
}
/**
* @brief 初始化 1ms SysTick 时基并验证中断真实触发。
* @param[in] 无。
* @param[out] 无。
* @retval bool true=节拍已前进;false=配置失败(不返回,直接 FATAL 停机)。
* @note 先更新 `SystemCoreClock`,再按 1kHz 频率配置 SysTick。
* 节拍验证失败时系统失去一切超时/延时基础,继续运行等于盲跑,
* 按致命错误处理(裸串口报告 + 挂死,与 HXTAL FAIL 同级)。
*/
bool SysTickTimer::initMs()
{
SystemCoreClockUpdate();
SysTick_Config(SystemCoreClock / 1000U);
if (SysTick_Config(SystemCoreClock / 1000U) != 0U) {
fatalSystickHalt();
}
/* 有界等待节拍前进:guard 预算约 50ms+,覆盖 1ms 节拍;
* 若无界等待,中断未触发时此处即死循环且无任何诊断。 */
const uint32_t start = s_ms;
volatile uint32_t guard = 2000000U;
while (s_ms == start) {
if (--guard == 0U) {
fatalSystickHalt();
}
}
return true;
}
/* ----- 中断入口 C 包装 ---------------------------------------------- */
+3 -2
View File
@@ -13,9 +13,10 @@ public:
* @brief 初始化 SysTick 为 1ms 系统节拍。
* @param[in] 无。
* @param[out] 无。
* @retval 无返回值。
* @retval bool true=配置成功且中断真实触发;false=配置失败或节拍未前进。
* @note 失败时系统失去时基(后续 delayMs 不可用),调用方应按致命错误处理。
*/
static void initMs();
static bool initMs();
/**
* @brief 执行阻塞式毫秒延时。
+5 -1
View File
@@ -502,7 +502,10 @@ void TaskRunner::runPending()
/* === HBI 周期诊断:每 20 秒重新跑一次 EXMC/HBI 探活,方便热插拔/焊接后观察 ===
* 1.5.27:EtherCAT 同步 ISR 激活后停止(diagDump 会把 EXMC 引脚切成 GPIO,
* 拆毁运行中的总线;诊断的 bit-bang 也与 HBI 事务互斥冲突)。 */
* 拆毁运行中的总线;诊断的 bit-bang 也与 HBI 事务互斥冲突)。
* 1.5.33:整块由 CONFIG_ESC_EXMC_DIAG 门控,默认关闭——硬件已确认全通,
* 不再每 20s 拆总线做探针;换板/补焊排查浮空时置 1 恢复。 */
#if CONFIG_ESC_EXMC_DIAG
static uint32_t s_lastDiagSec = 0xFFFFFFFFU;
if (!EthercatSync::isActive()
&& (g_app_uptime_sec >= s_lastDiagSec + 20U || s_lastDiagSec == 0xFFFFFFFFU)) {
@@ -511,6 +514,7 @@ void TaskRunner::runPending()
(unsigned long)g_app_uptime_sec);
EscExmcBus::diagDump();
}
#endif
/* 延迟 EEPROM 烧录已删除(1.5.29):ESI 镜像改由 EtherCAT 主站侧刷写
* (tools/ecat/ecat_flash_eeprom.py 经 ESC 0x0500 EEPROM 接口),