feat(ethercat): LAN9252 bring-up完成,EEPROM烧录改主站侧并增强DC/AL可观测性
- 新增ESC硬件复位序列(1.5.25):PB10驱动LAN9252 RST#,拉低30ms+释放后300ms轮询HBI就绪, 满足tpurstd/trstia时序;此前RST#仅靠R44上拉自然释放 - 修正EXMC 16-bit地址左移(1.5.27):HBI DATA偏移0x20000/0x30000→0x40000/0x60000; ADDSET 2→4 HCLK;片选更名NE1→NE0(LQFP100仅Region0) - 按LAN9252手册/AN1907修正EEPROM镜像(1.5.28):word1高字节0x00→0xCC(SYNC0/1 Map+Output, 否则SYNC0留LATCH态DC过程数据死)、word2=5000(脉宽50µs)、尾部补真实0xFF; gen_eeprom.py按ETG.1000.6重写;DCU_ESI.xml PDI 0x05→0x8D、RxPdo/TxPdo互换改4×64bit对齐 - 删除GD32侧EEPROM烧录路径(1.5.29):移除EscEeprom/esc_eeprom、eeprom_image.h及主循环 烧录块,ESI镜像改由主站ecat_flash_eeprom.py经ESC 0x0500刷写(162字全校验通过); HBI互斥补屏蔽EXTI2(SYNC1),防Sync1_Isr两步事务打断主循环HBI访问致错址 - DC SYNC0通路可观测(1.5.30/31):EthercatSync新增sync0/sync1/escirq三个volatile计数器, task_runner 5s按增量打印速率;SSC OD新增只读0x2000 SYNC Diag,主站SDO轮询网页显示 - AL通路诊断(1.5.32):ECAT_Main/SetALStatus打印主站AL请求与写回状态+错误码, task_runner新增5s [ECAT][HB]心跳(仅同步ISR激活时);dc_app.c导出EcatDbg_ReadAlDiag - 删除死代码EthercatSlave::periodicIsr三缓冲PDO桥,PDO交换由SSC完成(4×64bit=32B); EthercatSlave::init()恢复调用并以isBusReady()(BYTE_TEST)门控 - CANFD修复同通道突发只发3帧:transmitFrame新增allow_abort区分突发/软件队列路径, 邮箱满落入软件TX队列(深度13)由TX完成中断drainTxQueueIsr回灌;BRS重新启用(0→1) - motor_state新增CONFIG帧缓存(0x00000101, DLC 24)记录目标值回报,条目44B→76B、每页5→3 - SPI 0xA2诊断扩展action 20~24(ECAT_NRST拉低/拉高/浮空/回读);新增ESP32 EN自检打印 - 版本号更新至1.5.32
This commit is contained in:
@@ -74,8 +74,6 @@ project:
|
||||
- file: ./comm/esc_exmc_bus.cpp
|
||||
- file: ./comm/esc_lan9252.h
|
||||
- file: ./comm/esc_lan9252.cpp
|
||||
- file: ./comm/esc_eeprom.h
|
||||
- file: ./comm/esc_eeprom.cpp
|
||||
- file: ./comm/ethercat_slave.h
|
||||
- file: ./comm/ethercat_slave.cpp
|
||||
- file: ./comm/ethercat_sync.h
|
||||
|
||||
@@ -1,396 +0,0 @@
|
||||
/**
|
||||
* @file esc_eeprom.cpp
|
||||
* @brief 通过 LAN9252 HBI Indexed 间接访问烧录外部 AT24C32 EEPROM (ESI) 实现。
|
||||
*
|
||||
* HBI Indexed 间接访问协议:
|
||||
* 1. 写 HBI_IDX_0 = 目标 ESC 寄存器字节地址
|
||||
* 2. 读/写 HBI_DATA_0 = 数据(低 word + 高 word)
|
||||
*
|
||||
* HBI 寄存器 EXMC 偏移(A1=EXMC_A16=PD11, A2=EXMC_A17=PD12):
|
||||
* HBI_IDX_0 低 word: A2=0, A1=0 → EXMC 偏移 0x00000
|
||||
* HBI_DATA_0 低 word: A2=1, A1=0 → EXMC 偏移 0x20000
|
||||
* HBI_DATA_0 高 word: A2=1, A1=1 → EXMC 偏移 0x30000
|
||||
*
|
||||
* 注意:32-bit 寄存器访问时,写一次 IDX 后连续写/读两次 DATA_0(低 word 在前),
|
||||
* HBI 内部 BYTE/WORD 计数器会自动组装 DWORD,无需改 IDX 地址。
|
||||
*/
|
||||
#include "comm/esc_eeprom.h"
|
||||
#include "comm/esc_exmc_bus.h"
|
||||
#include "sys/config.h"
|
||||
#include <cstdio>
|
||||
#include <cstring>
|
||||
|
||||
namespace {
|
||||
/* HBI Indexed Mode 寄存器 EXMC 偏移
|
||||
* 地址解码(A2 是 IDX/DATA 选择位,必须接到 EXMC_A17=PD12):
|
||||
* A2=0, A1=0 → word 0 → 偏移 0x00000 → HBI_IDX_0 低 word
|
||||
* A2=0, A1=1 → word 1 → 偏移 0x10000 → HBI_IDX_0 高 word(A1=EXMC_A16=PD11)
|
||||
* A2=1, A1=0 → word 2 → 偏移 0x20000 → HBI_DATA_0 低 word(A2=EXMC_A17=PD12)
|
||||
* A2=1, A1=1 → word 3 → 偏移 0x30000 → HBI_DATA_0 高 word
|
||||
* 注:EXMC A16 对应 byte 偏移 0x10000,A17 对应 0x20000。 */
|
||||
constexpr uint32_t HBI_IDX0_OFFSET = 0x00000U; /* A2=0, A1=0 */
|
||||
constexpr uint32_t HBI_DATA0_LOW_OFF = 0x20000U; /* A2=1, A1=0 */
|
||||
constexpr uint32_t HBI_DATA0_HIGH_OFF = 0x30000U; /* A2=1, A1=1 */
|
||||
|
||||
/* ESC EEPROM 寄存器字节地址 */
|
||||
constexpr uint16_t ESC_EEP_CFG = 0x0500U; /* 8-bit */
|
||||
constexpr uint16_t ESC_EEP_PDI_ACC = 0x0501U; /* 8-bit */
|
||||
constexpr uint16_t ESC_EEP_CTRL = 0x0502U; /* 16-bit */
|
||||
constexpr uint16_t ESC_EEP_ADDR = 0x0504U; /* 32-bit */
|
||||
constexpr uint16_t ESC_EEP_DATA = 0x0508U; /* 32-bit (低 16 位为读写数据) */
|
||||
|
||||
/* EEPROM Control/Status bit 定义 */
|
||||
constexpr uint16_t EEP_CTRL_BUSY = 0x8000U;
|
||||
constexpr uint16_t EEP_CTRL_ERR_CSUM = 0x1000U;
|
||||
constexpr uint16_t EEP_CTRL_CMD_MASK = 0x0700U;
|
||||
constexpr uint16_t EEP_CTRL_CMD_READ = 0x0100U;
|
||||
constexpr uint16_t EEP_CTRL_CMD_WRITE = 0x0200U;
|
||||
constexpr uint16_t EEP_CTRL_CMD_RELOAD = 0x0400U;
|
||||
constexpr uint16_t EEP_CTRL_LOADING_STATUS = 0x0800U; /* 1=未加载/失败 */
|
||||
constexpr uint16_t EEP_CTRL_2ADDR = 0x0080U; /* 1=2 地址字节(32Kbit~4Mbit) */
|
||||
constexpr uint16_t EEP_CTRL_ECAT_WE = 0x0001U;
|
||||
|
||||
/* EEPROM Configuration bit */
|
||||
constexpr uint8_t EEP_CFG_PDI_CTRL = 0x01U;
|
||||
constexpr uint8_t EEP_CFG_FORCE_ECAT = 0x02U;
|
||||
|
||||
/* EEPROM PDI Access State bit */
|
||||
constexpr uint8_t EEP_PDI_ACC_ACCESS = 0x01U;
|
||||
|
||||
/* 等待 EEPROM 接口空闲(Busy=0)。
|
||||
* 写操作后 AT24C32 内部需要 ~5ms 页写时间,控制器会保持 Busy。
|
||||
* 超时设为 20ms(200000 次空循环约 20ms @180MHz)。 */
|
||||
bool waitIdle(uint32_t timeout_loops = 500000U)
|
||||
{
|
||||
while (timeout_loops-- > 0U) {
|
||||
EscExmcBus::write16(HBI_IDX0_OFFSET, ESC_EEP_CTRL);
|
||||
uint16_t ctrl = EscExmcBus::read16(HBI_DATA0_LOW_OFF);
|
||||
if ((ctrl & EEP_CTRL_BUSY) == 0U) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/* === HBI Indexed 间接访问 ESC 寄存器 === */
|
||||
|
||||
uint8_t readEsc8(uint16_t esc_addr)
|
||||
{
|
||||
const uint16_t idx = esc_addr & 0xFFFCU;
|
||||
EscExmcBus::write16(HBI_IDX0_OFFSET, idx);
|
||||
const uint8_t byte_offset = esc_addr & 0x3U;
|
||||
if (byte_offset < 2U) {
|
||||
uint16_t w = EscExmcBus::read16(HBI_DATA0_LOW_OFF);
|
||||
return (byte_offset == 0U) ? (uint8_t)(w & 0xFFU) : (uint8_t)((w >> 8) & 0xFFU);
|
||||
} else {
|
||||
uint16_t w = EscExmcBus::read16(HBI_DATA0_HIGH_OFF);
|
||||
return (byte_offset == 2U) ? (uint8_t)(w & 0xFFU) : (uint8_t)((w >> 8) & 0xFFU);
|
||||
}
|
||||
}
|
||||
|
||||
void writeEsc8(uint16_t esc_addr, uint8_t val)
|
||||
{
|
||||
const uint16_t idx = esc_addr & 0xFFFCU;
|
||||
EscExmcBus::write16(HBI_IDX0_OFFSET, idx);
|
||||
const uint8_t byte_offset = esc_addr & 0x3U;
|
||||
if (byte_offset < 2U) {
|
||||
uint16_t w = EscExmcBus::read16(HBI_DATA0_LOW_OFF);
|
||||
w = (byte_offset == 0U) ? ((w & 0xFF00U) | (uint16_t)val)
|
||||
: ((w & 0x00FFU) | ((uint16_t)val << 8));
|
||||
EscExmcBus::write16(HBI_IDX0_OFFSET, idx);
|
||||
EscExmcBus::write16(HBI_DATA0_LOW_OFF, w);
|
||||
} else {
|
||||
uint16_t w = EscExmcBus::read16(HBI_DATA0_HIGH_OFF);
|
||||
w = (byte_offset == 2U) ? ((w & 0xFF00U) | (uint16_t)val)
|
||||
: ((w & 0x00FFU) | ((uint16_t)val << 8));
|
||||
EscExmcBus::write16(HBI_IDX0_OFFSET, idx);
|
||||
EscExmcBus::write16(HBI_DATA0_HIGH_OFF, w);
|
||||
}
|
||||
}
|
||||
|
||||
uint16_t readEsc16(uint16_t esc_addr)
|
||||
{
|
||||
EscExmcBus::write16(HBI_IDX0_OFFSET, esc_addr);
|
||||
return EscExmcBus::read16(HBI_DATA0_LOW_OFF);
|
||||
}
|
||||
|
||||
void writeEsc16(uint16_t esc_addr, uint16_t val)
|
||||
{
|
||||
EscExmcBus::write16(HBI_IDX0_OFFSET, esc_addr);
|
||||
EscExmcBus::write16(HBI_DATA0_LOW_OFF, val);
|
||||
}
|
||||
|
||||
uint32_t readEsc32(uint16_t esc_addr)
|
||||
{
|
||||
EscExmcBus::write16(HBI_IDX0_OFFSET, esc_addr);
|
||||
uint16_t lo = EscExmcBus::read16(HBI_DATA0_LOW_OFF);
|
||||
uint16_t hi = EscExmcBus::read16(HBI_DATA0_HIGH_OFF); /* HBI_DATA_0 高 16 位 */
|
||||
return ((uint32_t)hi << 16) | lo;
|
||||
}
|
||||
|
||||
void writeEsc32(uint16_t esc_addr, uint32_t val)
|
||||
{
|
||||
EscExmcBus::write16(HBI_IDX0_OFFSET, esc_addr);
|
||||
EscExmcBus::write16(HBI_DATA0_LOW_OFF, (uint16_t)(val & 0xFFFFU)); /* 低 word */
|
||||
EscExmcBus::write16(HBI_DATA0_HIGH_OFF, (uint16_t)((val >> 16) & 0xFFFFU));/* 高 word */
|
||||
}
|
||||
|
||||
} /* anonymous namespace */
|
||||
|
||||
bool EscEeprom::s_inited = false;
|
||||
|
||||
namespace {
|
||||
/* 简单延时(~0.5ms @180MHz,volatile 防优化) */
|
||||
void delayApprox(void)
|
||||
{
|
||||
volatile uint32_t n = 22000U;
|
||||
while (n-- > 0U) { __NOP(); }
|
||||
}
|
||||
} /* anonymous namespace */
|
||||
|
||||
bool EscEeprom::init()
|
||||
{
|
||||
s_inited = false;
|
||||
|
||||
if (!EscExmcBus::isOk()) {
|
||||
printf("[EEPROM] init: FAIL, EXMC bus not ready\r\n");
|
||||
return false;
|
||||
}
|
||||
|
||||
/* LAN9252 上电后 EEPROM 控制器先做初始加载(读 AT24C32),
|
||||
* 即使 EEPROM 空白也需要几毫秒完成/超时。此期间 PDI 写 0x0500 会被忽略。
|
||||
* 重试最多 50 次(~100ms),等待控制器释放后 PDI 才能接管。 */
|
||||
bool acquired = false;
|
||||
for (uint32_t attempt = 0U; attempt < 50U; ++attempt) {
|
||||
/* 首次重试前打印诊断:raw 读 + 直接 word 写(绕过 writeEsc8 字节 RMW) */
|
||||
if (attempt == 0U) {
|
||||
EscExmcBus::write16(HBI_IDX0_OFFSET, ESC_EEP_CFG);
|
||||
uint16_t raw_cfg = EscExmcBus::read16(HBI_DATA0_LOW_OFF);
|
||||
EscExmcBus::write16(HBI_IDX0_OFFSET, ESC_EEP_CTRL);
|
||||
uint16_t raw_ctrl = EscExmcBus::read16(HBI_DATA0_LOW_OFF);
|
||||
printf("[EEPROM] diag: raw cfg(0x0500)=0x%04X ctrl(0x0502)=0x%04X\r\n",
|
||||
(unsigned)raw_cfg, (unsigned)raw_ctrl);
|
||||
if (raw_cfg == ESC_EEP_CFG && raw_ctrl == ESC_EEP_CTRL) {
|
||||
/* 读回值 == 刚写入的 IDX 值 => 数据总线回显,LAN9252 完全没有驱动总线。
|
||||
* 注意:LAN9252 的 HBI 在 EEPROM 空白时也工作(手册 9.5.4.2),
|
||||
* 所以这不是 EEPROM 问题,而是:CS(PD7/NE0)未拉低、
|
||||
* CHIP_MODE0/1 strap 非 00(三端口模式=SPI only)、
|
||||
* 或芯片未复位/未上电。先看 [ECAT][ESC] HBI_IDX_0 探针。 */
|
||||
printf("[EEPROM] diag: *** LAN9252 NOT RESPONDING (bus echo of IDX) ***\r\n");
|
||||
printf("[EEPROM] diag: *** check CS(PD7) pulse / CHIP_MODE straps=00 / RST# ***\r\n");
|
||||
}
|
||||
/* 尝试直接 16-bit word 写 0x0001 到 0x0500 */
|
||||
EscExmcBus::write16(HBI_IDX0_OFFSET, ESC_EEP_CFG);
|
||||
EscExmcBus::write16(HBI_DATA0_LOW_OFF, 0x0001U);
|
||||
EscExmcBus::write16(HBI_IDX0_OFFSET, ESC_EEP_CFG);
|
||||
uint16_t raw_after = EscExmcBus::read16(HBI_DATA0_LOW_OFF);
|
||||
printf("[EEPROM] diag: after direct word write, cfg=0x%04X\r\n", (unsigned)raw_after);
|
||||
}
|
||||
|
||||
/* 步骤 1:置 PDI EEPROM Control = 1,把 EEPROM 接口交给 PDI(GD32) */
|
||||
writeEsc8(ESC_EEP_CFG, EEP_CFG_PDI_CTRL);
|
||||
|
||||
/* 步骤 2:置 Access to EEPROM = 1,获取访问权 */
|
||||
writeEsc8(ESC_EEP_PDI_ACC, EEP_PDI_ACC_ACCESS);
|
||||
|
||||
/* 步骤 3:等待接口空闲 */
|
||||
if (!waitIdle()) {
|
||||
delayApprox();
|
||||
continue;
|
||||
}
|
||||
|
||||
/* 读回 Configuration 寄存器确认 PDI 控制已生效 */
|
||||
uint8_t cfg = readEsc8(ESC_EEP_CFG);
|
||||
if ((cfg & EEP_CFG_PDI_CTRL) != 0U) {
|
||||
acquired = true;
|
||||
|
||||
/* 确认 EEPROM 算法:AT24C32 = 32Kbit,需要 2 地址字节模式 */
|
||||
uint16_t ctrl = readEsc16(ESC_EEP_CTRL);
|
||||
if ((ctrl & EEP_CTRL_2ADDR) == 0U) {
|
||||
printf("[EEPROM] init: WARN, 2-address-byte mode not set (ctrl=0x%04X)\r\n",
|
||||
(unsigned)ctrl);
|
||||
}
|
||||
|
||||
s_inited = true;
|
||||
printf("[EEPROM] init: OK (attempt %u, cfg=0x%02X, ctrl=0x%04X)\r\n",
|
||||
(unsigned)(attempt + 1U), (unsigned)cfg, (unsigned)ctrl);
|
||||
return true;
|
||||
}
|
||||
|
||||
/* PDI 控制位未生效,等待后重试 */
|
||||
delayApprox();
|
||||
}
|
||||
|
||||
printf("[EEPROM] init: FAIL, PDI control not acquired after 50 retries\r\n");
|
||||
return false;
|
||||
}
|
||||
|
||||
bool EscEeprom::readWord(uint16_t word_addr, uint16_t &out)
|
||||
{
|
||||
if (!s_inited) {
|
||||
return false;
|
||||
}
|
||||
if (word_addr >= EEPROM_WORD_COUNT) {
|
||||
printf("[EEPROM] readWord: addr %u out of range\r\n", (unsigned)word_addr);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!waitIdle()) {
|
||||
printf("[EEPROM] readWord: busy timeout before read (addr=%u)\r\n", (unsigned)word_addr);
|
||||
return false;
|
||||
}
|
||||
|
||||
/* 写 EEPROM 地址寄存器(word 地址,bit0=first word) */
|
||||
writeEsc32(ESC_EEP_ADDR, (uint32_t)word_addr);
|
||||
|
||||
/* 发 Read 命令 */
|
||||
writeEsc16(ESC_EEP_CTRL, EEP_CTRL_CMD_READ);
|
||||
|
||||
if (!waitIdle()) {
|
||||
printf("[EEPROM] readWord: busy timeout after read (addr=%u)\r\n", (unsigned)word_addr);
|
||||
return false;
|
||||
}
|
||||
|
||||
/* 读 EEPROM Data 寄存器低 16 位 */
|
||||
uint32_t data_reg = readEsc32(ESC_EEP_DATA);
|
||||
out = (uint16_t)(data_reg & 0xFFFFU);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool EscEeprom::writeWord(uint16_t word_addr, uint16_t data)
|
||||
{
|
||||
if (!s_inited) {
|
||||
return false;
|
||||
}
|
||||
if (word_addr >= EEPROM_WORD_COUNT) {
|
||||
printf("[EEPROM] writeWord: addr %u out of range\r\n", (unsigned)word_addr);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!waitIdle()) {
|
||||
printf("[EEPROM] writeWord: busy timeout before write (addr=%u)\r\n", (unsigned)word_addr);
|
||||
return false;
|
||||
}
|
||||
|
||||
/* 写 EEPROM 地址寄存器 */
|
||||
writeEsc32(ESC_EEP_ADDR, (uint32_t)word_addr);
|
||||
|
||||
/* 写 EEPROM Data 寄存器低 16 位(高 16 位无关) */
|
||||
writeEsc32(ESC_EEP_DATA, (uint32_t)data);
|
||||
|
||||
/* 发 Write 命令 */
|
||||
writeEsc16(ESC_EEP_CTRL, EEP_CTRL_CMD_WRITE);
|
||||
|
||||
/* 等待写完成(AT24C32 页写 ~5ms,控制器会保持 Busy) */
|
||||
if (!waitIdle()) {
|
||||
printf("[EEPROM] writeWord: busy timeout after write (addr=%u)\r\n", (unsigned)word_addr);
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool EscEeprom::writeEsi(const uint8_t *data, size_t len)
|
||||
{
|
||||
if (!s_inited) {
|
||||
printf("[EEPROM] writeEsi: not initialized\r\n");
|
||||
return false;
|
||||
}
|
||||
if (data == nullptr || len == 0U) {
|
||||
return false;
|
||||
}
|
||||
if (len > EEPROM_SIZE_BYTES) {
|
||||
printf("[EEPROM] writeEsi: len %u exceeds EEPROM size %u\r\n",
|
||||
(unsigned)len, (unsigned)EEPROM_SIZE_BYTES);
|
||||
return false;
|
||||
}
|
||||
|
||||
printf("[EEPROM] writeEsi: writing %u bytes (%u words)...\r\n",
|
||||
(unsigned)len, (unsigned)((len + 1U) / 2U));
|
||||
|
||||
size_t word_count = (len + 1U) / 2U;
|
||||
for (size_t i = 0U; i < word_count; ++i) {
|
||||
uint16_t word = 0xFFFFU; /* 不足 2 字节时高位补 0xFF */
|
||||
word = (uint16_t)data[i * 2U];
|
||||
if ((i * 2U + 1U) < len) {
|
||||
word |= ((uint16_t)data[i * 2U + 1U] << 8);
|
||||
}
|
||||
|
||||
if (!writeWord((uint16_t)i, word)) {
|
||||
printf("[EEPROM] writeEsi: FAIL at word %u\r\n", (unsigned)i);
|
||||
return false;
|
||||
}
|
||||
|
||||
/* 每 256 word(512 字节)打印一次进度 */
|
||||
if ((i % 256U) == 0U && i > 0U) {
|
||||
printf("[EEPROM] writeEsi: %u/%u words\r\n", (unsigned)i, (unsigned)word_count);
|
||||
}
|
||||
}
|
||||
|
||||
printf("[EEPROM] writeEsi: done (%u words written)\r\n", (unsigned)word_count);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool EscEeprom::verifyEsi(const uint8_t *data, size_t len)
|
||||
{
|
||||
if (!s_inited) {
|
||||
return false;
|
||||
}
|
||||
if (data == nullptr || len == 0U) {
|
||||
return false;
|
||||
}
|
||||
|
||||
size_t word_count = (len + 1U) / 2U;
|
||||
for (size_t i = 0U; i < word_count; ++i) {
|
||||
uint16_t expected = 0xFFFFU;
|
||||
expected = (uint16_t)data[i * 2U];
|
||||
if ((i * 2U + 1U) < len) {
|
||||
expected |= ((uint16_t)data[i * 2U + 1U] << 8);
|
||||
}
|
||||
|
||||
uint16_t actual = 0U;
|
||||
if (!readWord((uint16_t)i, actual)) {
|
||||
printf("[EEPROM] verifyEsi: read FAIL at word %u\r\n", (unsigned)i);
|
||||
return false;
|
||||
}
|
||||
if (actual != expected) {
|
||||
printf("[EEPROM] verifyEsi: MISMATCH at word %u: expected=0x%04X actual=0x%04X\r\n",
|
||||
(unsigned)i, (unsigned)expected, (unsigned)actual);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
printf("[EEPROM] verifyEsi: OK (%u words verified)\r\n", (unsigned)word_count);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool EscEeprom::reload()
|
||||
{
|
||||
if (!s_inited) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!waitIdle()) {
|
||||
printf("[EEPROM] reload: busy timeout\r\n");
|
||||
return false;
|
||||
}
|
||||
|
||||
/* 发 Reload 命令(100b) */
|
||||
writeEsc16(ESC_EEP_CTRL, EEP_CTRL_CMD_RELOAD);
|
||||
printf("[EEPROM] reload: command sent\r\n");
|
||||
return true;
|
||||
}
|
||||
|
||||
bool EscEeprom::isLoaded()
|
||||
{
|
||||
uint16_t ctrl = readEsc16(ESC_EEP_CTRL);
|
||||
/* Loading Status bit=0 表示已加载且设备信息正常 */
|
||||
bool loaded = ((ctrl & EEP_CTRL_LOADING_STATUS) == 0U);
|
||||
bool csum_ok = ((ctrl & EEP_CTRL_ERR_CSUM) == 0U);
|
||||
if (!loaded) {
|
||||
printf("[EEPROM] isLoaded: not loaded (ctrl=0x%04X)\r\n", (unsigned)ctrl);
|
||||
} else if (!csum_ok) {
|
||||
printf("[EEPROM] isLoaded: loaded but checksum error (ctrl=0x%04X)\r\n", (unsigned)ctrl);
|
||||
}
|
||||
return (loaded && csum_ok);
|
||||
}
|
||||
@@ -1,74 +0,0 @@
|
||||
/**
|
||||
* @file esc_eeprom.h
|
||||
* @brief 通过 LAN9252 HBI Indexed 间接访问烧录外部 AT24C32 EEPROM (ESI)。
|
||||
*
|
||||
* 硬件前提(必须满足,否则模块不可用):
|
||||
* - LAN9252 A2 (Pin 29) 必须接到 GD32 EXMC A17 (PD12),以访问 HBI_DATA_0 寄存器。
|
||||
* 注意:LAN9252 引脚顺序为 Pin25=A1, Pin26=A3, Pin27=A4, Pin28=CS, Pin29=A2。
|
||||
* A2 是区分 IDX/DATA 寄存器的关键位(A2=0→IDX, A2=1→DATA),不接 A2 无法访问 DATA 寄存器。
|
||||
* - A3/A4 可不接(仅访问 HBI_IDX_0/DATA_0 时不需要)。
|
||||
* - LAN9252 处于 16-bit HBI Indexed Mode(CHIP_MODE 配置)。
|
||||
*
|
||||
* ESC EEPROM 寄存器映射(参见 LAN9252 DS00001909A 12.14.47~12.14.51):
|
||||
* 0x0500 EEPROM Configuration (8-bit)
|
||||
* 0x0501 EEPROM PDI Access State (8-bit)
|
||||
* 0x0502 EEPROM Control/Status (16-bit)
|
||||
* 0x0504 EEPROM Address (32-bit)
|
||||
* 0x0508 EEPROM Data (32-bit, 低 16 位为读写数据)
|
||||
*
|
||||
* 调用约束:INIT-ONLY,仅允许在 EthercatSync::init() 使能 EXTI NVIC 之前调用。
|
||||
*/
|
||||
#ifndef COMM_ESC_EEPROM_H
|
||||
#define COMM_ESC_EEPROM_H
|
||||
|
||||
#include <cstdint>
|
||||
#include <cstddef>
|
||||
|
||||
class EscEeprom {
|
||||
public:
|
||||
/** @brief 初始化:获取 PDI 对 EEPROM 接口的控制权并验证总线。
|
||||
* @return true 表示成功获取 EEPROM 控制权。 */
|
||||
static bool init();
|
||||
|
||||
/** @brief 读取 EEPROM 一个 16-bit word。
|
||||
* @param word_addr word 地址(0 ~ 2047,对应 AT24C32 的 4KB 空间)。
|
||||
* @param out 输出读取到的 16-bit 数据。
|
||||
* @return true 表示读取成功。 */
|
||||
static bool readWord(uint16_t word_addr, uint16_t &out);
|
||||
|
||||
/** @brief 写入 EEPROM 一个 16-bit word。
|
||||
* @param word_addr word 地址。
|
||||
* @param data 要写入的 16-bit 数据。
|
||||
* @return true 表示写入成功。 */
|
||||
static bool writeWord(uint16_t word_addr, uint16_t data);
|
||||
|
||||
/** @brief 批量烧录 ESI 数据。
|
||||
* @param data 数据缓冲区(按 byte 流给出,内部按 word 对齐处理)。
|
||||
* @param len 数据长度(字节,应为 2 的倍数;奇数时末尾补 0xFF)。
|
||||
* @return true 表示全部写入成功。 */
|
||||
static bool writeEsi(const uint8_t *data, size_t len);
|
||||
|
||||
/** @brief 回读校验 ESI 数据。
|
||||
* @param data 期望数据缓冲区。
|
||||
* @param len 校验长度(字节)。
|
||||
* @return true 表示回读数据与期望一致。 */
|
||||
static bool verifyEsi(const uint8_t *data, size_t len);
|
||||
|
||||
/** @brief 触发 EEPROM 重新加载(写完 ESI 后调用,使 LAN9252 重新读取配置)。
|
||||
* @return true 表示 Reload 命令已发出。 */
|
||||
static bool reload();
|
||||
|
||||
/** @brief 查询 EEPROM 加载状态。
|
||||
* @return true 表示 EEPROM 已加载且设备信息正常。 */
|
||||
static bool isLoaded();
|
||||
|
||||
/** @brief AT24C32 容量(字节)。 */
|
||||
static constexpr size_t EEPROM_SIZE_BYTES = 4096U;
|
||||
/** @brief AT24C32 word 数。 */
|
||||
static constexpr uint16_t EEPROM_WORD_COUNT = 2048U;
|
||||
|
||||
private:
|
||||
static bool s_inited;
|
||||
};
|
||||
|
||||
#endif /* COMM_ESC_EEPROM_H */
|
||||
@@ -7,9 +7,12 @@
|
||||
* 源文件放在 RTE/Device/... 并由 cproject group device_exmc 本地编译。
|
||||
*/
|
||||
#include "comm/esc_exmc_bus.h"
|
||||
#include "comm/ethercat_sync.h"
|
||||
#include "sys/config.h"
|
||||
#include "device/watchdog.h"
|
||||
#include <stdio.h>
|
||||
#include "RTE_Components.h"
|
||||
#include CMSIS_device_header
|
||||
|
||||
extern "C" {
|
||||
#include "gd32e50x_exmc.h" /* RTE/Device/GD32E508VE 或 -I 同目录 */
|
||||
@@ -17,7 +20,10 @@ extern "C" {
|
||||
#include "gd32e50x_rcu.h"
|
||||
}
|
||||
|
||||
/* 内存映射访问宏:基址 + 偏移(按 16-bit 半字对齐,LAN9252 HBI 地址左移 1 位)。 */
|
||||
/* 内存映射访问宏:基址 + CPU 字节偏移。
|
||||
* 注意 EXMC 16-bit 模式地址左移 1 位(用户手册 p869:HADDR[25:1]→EXMC_A[24:0]):
|
||||
* EXMC_A16(PD11) 引脚对应偏移 bit17=0x20000,EXMC_A17(PD12) 对应 bit18=0x40000,
|
||||
* 即 1<<16 的 CPU 偏移驱动的是 A15 引脚而非 A16。 */
|
||||
#define ESC_EXMC_REG16(offset) \
|
||||
(*(volatile uint16_t *)(CONFIG_ESC_EXMC_BASE_ADDR + (offset)))
|
||||
|
||||
@@ -44,10 +50,6 @@ constexpr uint32_t kPinRd = 4U; /* PD4 */
|
||||
constexpr uint32_t kPinWr = 5U; /* PD5 */
|
||||
constexpr uint32_t kPinA1 = 11U; /* PD11 */
|
||||
constexpr uint32_t kPinA2 = 12U; /* PD12 */
|
||||
/* LAN9252 MODE0/1 strap 引脚:临时用 PB8/PB9 模拟拉高/拉低测试。
|
||||
* 实际硬件 MODE0(Pin48)=PB8, MODE1(Pin46)=PB9(若未接 PB8/PB9 则此测试无效)。 */
|
||||
constexpr uint32_t kPinMode0 = 8U; /* PB8 -> LAN9252 Pin48 MODE0 */
|
||||
constexpr uint32_t kPinMode1 = 9U; /* PB9 -> LAN9252 Pin46 MODE1 */
|
||||
|
||||
void bbDelay() {
|
||||
for (volatile uint32_t i = 0U; i < 3000U; i++) {
|
||||
@@ -142,9 +144,10 @@ bool EscExmcBus::init() {
|
||||
gpio_init(CONFIG_EXMC_D15_PORT, GPIO_MODE_AF_PP, GPIO_OSPEED_50MHZ,
|
||||
CONFIG_EXMC_D15_PIN);
|
||||
|
||||
/* 控制线:A16 / A17 / NOE / NWE / NE1
|
||||
* A17(PD12) 用于 HBI Indexed Mode 访问 HBI_DATA_0,已将 LAN9252 A2 (Pin 29)
|
||||
* 接到 PD12。 */
|
||||
/* 控制线:A16 / A17 / NOE / NWE / NE0
|
||||
* A17(PD12) → LAN9252 A2 (Pin 29,IDX/DATA 选择);A16(PD11) → LAN9252 A1 (Pin 25)。
|
||||
* 注意 EXMC 16-bit 模式地址左移 1 位:HBI_DATA_0 的 CPU 偏移是 0x40000/0x60000,
|
||||
* 见 esc_lan9252.cpp 头注释。 */
|
||||
gpio_init(CONFIG_EXMC_A16_PORT, GPIO_MODE_AF_PP, GPIO_OSPEED_50MHZ,
|
||||
CONFIG_EXMC_A16_PIN);
|
||||
gpio_init(CONFIG_EXMC_A17_PORT, GPIO_MODE_AF_PP, GPIO_OSPEED_50MHZ,
|
||||
@@ -153,10 +156,10 @@ bool EscExmcBus::init() {
|
||||
CONFIG_EXMC_NOE_PIN);
|
||||
gpio_init(CONFIG_EXMC_NWE_PORT, GPIO_MODE_AF_PP, GPIO_OSPEED_50MHZ,
|
||||
CONFIG_EXMC_NWE_PIN);
|
||||
gpio_init(CONFIG_EXMC_NE1_PORT, GPIO_MODE_AF_PP, GPIO_OSPEED_50MHZ,
|
||||
CONFIG_EXMC_NE1_PIN);
|
||||
gpio_init(CONFIG_EXMC_NE0_PORT, GPIO_MODE_AF_PP, GPIO_OSPEED_50MHZ,
|
||||
CONFIG_EXMC_NE0_PIN);
|
||||
ECAT_LOGI(
|
||||
"[ECAT][EXMC] init: GPIO configured (D0-D15/A16/A17/NOE/NWE/NE1)\r\n");
|
||||
"[ECAT][EXMC] init: GPIO configured (D0-D15/A16/A17/NOE/NWE/NE0)\r\n");
|
||||
|
||||
/*
|
||||
* EXMC NOR/SRAM Region0:16-bit、非复用、异步写。
|
||||
@@ -189,13 +192,17 @@ bool EscExmcBus::init() {
|
||||
norsram_init.memory_type = EXMC_MEMORY_TYPE_SRAM;
|
||||
norsram_init.address_data_mux = DISABLE;
|
||||
|
||||
/* 读/写共用时序(非 extended mode) */
|
||||
/* 读/写共用时序(非 extended mode)
|
||||
* 2026-08-20 慢档测试(16/16/256 HCLK)无效,已还原:慢档时序未带来任何
|
||||
* 读数变化,速度因素彻底排除(与 bit-bang 40-80us 仍失败一致)。 */
|
||||
rw_timing.asyn_access_mode = EXMC_ACCESS_MODE_A;
|
||||
rw_timing.syn_data_latency = EXMC_DATALAT_2_CLK;
|
||||
rw_timing.syn_clk_division = EXMC_SYN_CLOCK_RATIO_DISABLE;
|
||||
rw_timing.bus_latency = 1U;
|
||||
rw_timing.asyn_address_setuptime = 2U; /* ~11ns */
|
||||
rw_timing.asyn_address_holdtime = 2U; /* min 2 */
|
||||
rw_timing.asyn_address_setuptime = 4U; /* ~22ns。DS 表9-4/9-5: trdrd/twrwr/twrrd
|
||||
≥13ns——选通间隔=ADDSET 相,2 HCLK≈11.1ns
|
||||
不达标(仅靠 AHB 间隙兜底),4 HCLK 稳过 */
|
||||
rw_timing.asyn_address_holdtime = 2U; /* min 2,DS tah≥0 */
|
||||
rw_timing.asyn_data_setuptime = 20U; /* ~111ns ≥ 100ns HBI */
|
||||
|
||||
wr_timing = rw_timing;
|
||||
@@ -238,12 +245,23 @@ bool EscExmcBus::init() {
|
||||
* 硬件诊断: 精简版 —— 只保留核心探活 + bit-bang + 位级回显
|
||||
* ======================================================================== */
|
||||
void EscExmcBus::diagDump() {
|
||||
/* EtherCAT 同步 ISR 激活期间禁止诊断:本函数会把 EXMC 数据/控制线切成 GPIO,
|
||||
* 并大量 printf,会周期性拆毁运行中的 EtherCAT 总线。 */
|
||||
if (EthercatSync::isActive()) {
|
||||
printf("[EXMC-DIAG] skipped: EtherCAT sync ISR active (bus in use)\r\n");
|
||||
return;
|
||||
}
|
||||
/* 通断测试模式活跃时跳过诊断,避免 20s 周期 probe 覆盖用户设置的电平 */
|
||||
if (s_continuityActive) {
|
||||
printf("[EXMC-DIAG] skipped: continuity test mode active\r\n");
|
||||
return;
|
||||
}
|
||||
printf("[EXMC-DIAG] --- bit-bang probe (bypass EXMC) ---\r\n");
|
||||
/* NRST 实际电平回读(1.5.26):输入通路常连引脚,输出态亦读真实电压。
|
||||
* 驱动高却 LOW => ECAT_NRST 被外部按低 => LAN9252 一直在复位态。 */
|
||||
printf("[EXMC-DIAG] NRST(PB10) reads %s\r\n",
|
||||
(gpio_input_bit_get(CONFIG_ESC_NRST_PORT, CONFIG_ESC_NRST_PIN) == SET)
|
||||
? "HIGH" : "LOW");
|
||||
|
||||
/* 保存原 GPIO 配置 */
|
||||
uint32_t save_ctl0_d = GPIO_CTL0(GPIOD);
|
||||
@@ -289,6 +307,39 @@ void EscExmcBus::diagDump() {
|
||||
(unsigned)bb_idx0);
|
||||
}
|
||||
|
||||
/* --- 预驱动/释放/读周期三段采样: 区分"被动拉低"与"芯片主动驱动" ---
|
||||
* 背景: bit-bang 读到稳定 0x0000, 而纯浮空输入应保持上次残留值或漂移。
|
||||
* 两种解释: (a) 板上下拉/对地漏电(被动) (b) 芯片在 RD# 有效期驱动 0(主动)。
|
||||
* 序列: 驱动 pattern -> 切输入快采样(电容应保持) -> 慢采样(检测下拉)
|
||||
* -> 拉低 CS#+RD# 采样(芯片若响应应驱动总线, 例如 0x1234)。 */
|
||||
printf("[EXMC-DIAG] --- predrive/release/read probe ---\r\n");
|
||||
static const uint16_t probe_pats[2] = {0xAAAAU, 0x5555U};
|
||||
for (int p = 0; p < 2; ++p) {
|
||||
uint16_t pat = probe_pats[p];
|
||||
/* 1) 数据线推挽驱动 pattern(控制线保持空闲: CS/RD/WR 高, A1/A2 低) */
|
||||
gpio_init(GPIOD, GPIO_MODE_OUT_PP, GPIO_OSPEED_50MHZ, kDataMaskD);
|
||||
gpio_init(GPIOE, GPIO_MODE_OUT_PP, GPIO_OSPEED_50MHZ, kDataMaskE);
|
||||
bbDriveData(pat);
|
||||
bbDelay();
|
||||
/* 2) 切浮空输入, 快采样(自身电容应保持 pattern) */
|
||||
gpio_init(GPIOD, GPIO_MODE_IN_FLOATING, 0, kDataMaskD);
|
||||
gpio_init(GPIOE, GPIO_MODE_IN_FLOATING, 0, kDataMaskE);
|
||||
for (volatile int d = 0; d < 50; d++) {} /* ~微秒级 */
|
||||
uint16_t s_fast = bbReadData();
|
||||
/* 3) 慢采样(约 40-80us 后: 若板上有下拉会漏向 0) */
|
||||
bbDelay();
|
||||
uint16_t s_slow = bbReadData();
|
||||
/* 4) 读周期采样(CS#+RD# 低: 芯片若驱动会在总线上给出真实数据) */
|
||||
GPIO_BC(GPIOD) = (1U << 7) | (1U << 4);
|
||||
bbDelay();
|
||||
uint16_t s_rd = bbReadData();
|
||||
GPIO_BOP(GPIOD) = (1U << 4) | (1U << 7);
|
||||
bbDelay();
|
||||
printf("[EXMC-DIAG] pat=0x%04X fast=0x%04X slow=0x%04X rd=0x%04X\r\n",
|
||||
(unsigned)pat, (unsigned)s_fast, (unsigned)s_slow,
|
||||
(unsigned)s_rd);
|
||||
}
|
||||
|
||||
/* 恢复 GPIO */
|
||||
GPIO_CTL0(GPIOD) = save_ctl0_d;
|
||||
GPIO_CTL1(GPIOD) = save_ctl1_d;
|
||||
@@ -354,6 +405,7 @@ void EscExmcBus::diagDump() {
|
||||
|
||||
static uint32_t s_save_ctl0_d = 0, s_save_ctl1_d = 0;
|
||||
static uint32_t s_save_ctl0_e = 0, s_save_ctl1_e = 0;
|
||||
static uint32_t s_maskPrev = 0U;
|
||||
|
||||
static void continuityEnterTestMode() {
|
||||
if (s_continuityActive) return;
|
||||
@@ -361,6 +413,9 @@ static void continuityEnterTestMode() {
|
||||
s_save_ctl1_d = GPIO_CTL1(GPIOD);
|
||||
s_save_ctl0_e = GPIO_CTL0(GPIOE);
|
||||
s_save_ctl1_e = GPIO_CTL1(GPIOE);
|
||||
/* 测试期屏蔽 SYNC0/ESC IRQ/SYNC1:ISR 的 HBI 访问落在已被切成 GPIO 态的
|
||||
* 总线上只会读回垃圾并错写 IDX,SSC 状态可能被污染(1.5.29)。 */
|
||||
s_maskPrev = EscExmcBus::maskEnter();
|
||||
/* 全部数据线 + 控制线设为 OUT_PP */
|
||||
gpio_init(GPIOD, GPIO_MODE_OUT_PP, GPIO_OSPEED_50MHZ,
|
||||
kDataMaskD | (1U<<kPinCs) | (1U<<kPinRd) | (1U<<kPinWr) |
|
||||
@@ -370,7 +425,7 @@ static void continuityEnterTestMode() {
|
||||
GPIO_BOP(GPIOD) = (1U<<kPinCs) | (1U<<kPinRd) | (1U<<kPinWr);
|
||||
GPIO_BC(GPIOD) = (1U<<kPinA1) | (1U<<kPinA2);
|
||||
s_continuityActive = true;
|
||||
printf("[CONT] test mode entered (GPIO -> OUT_PP)\r\n");
|
||||
printf("[CONT] test mode entered (GPIO -> OUT_PP, ECAT ISRs masked)\r\n");
|
||||
}
|
||||
|
||||
static void continuityExitTestMode() {
|
||||
@@ -379,8 +434,9 @@ static void continuityExitTestMode() {
|
||||
GPIO_CTL1(GPIOD) = s_save_ctl1_d;
|
||||
GPIO_CTL0(GPIOE) = s_save_ctl0_e;
|
||||
GPIO_CTL1(GPIOE) = s_save_ctl1_e;
|
||||
EscExmcBus::maskExit(s_maskPrev);
|
||||
s_continuityActive = false;
|
||||
printf("[CONT] test mode exited (GPIO -> AF_PP restored)\r\n");
|
||||
printf("[CONT] test mode exited (GPIO -> AF_PP restored, ISRs unmasked)\r\n");
|
||||
}
|
||||
|
||||
uint8_t EscExmcBus::continuityDrive(uint8_t action) {
|
||||
@@ -432,28 +488,22 @@ uint8_t EscExmcBus::continuityDrive(uint8_t action) {
|
||||
(unsigned long)pin, drive_high ? "HIGH" : "LOW");
|
||||
return 0;
|
||||
}
|
||||
|
||||
case 10: /* MODE0 high (LAN9252 Pin48) via PB8 */
|
||||
case 11: /* MODE1 high (LAN9252 Pin46) via PB9 */
|
||||
case 12: /* MODE0 low (LAN9252 Pin48) via PB8 */
|
||||
case 13: { /* MODE1 low (LAN9252 Pin46) via PB9 */
|
||||
continuityEnterTestMode();
|
||||
/* 先把 MODE0/MODE1 恢复空闲(低),再驱动目标线 */
|
||||
GPIO_BOP(GPIOB) = 0U;
|
||||
GPIO_BC(GPIOB) = (1U<<kPinMode0) | (1U<<kPinMode1);
|
||||
/* 确保 PB8/PB9 为输出 */
|
||||
gpio_init(GPIOB, GPIO_MODE_OUT_PP, GPIO_OSPEED_50MHZ,
|
||||
(1U<<kPinMode0) | (1U<<kPinMode1));
|
||||
uint32_t pin = (action==10 || action==12) ? kPinMode0 : kPinMode1;
|
||||
bool high = (action==10 || action==11);
|
||||
if (high) {
|
||||
GPIO_BOP(GPIOB) = (1U << pin);
|
||||
} else {
|
||||
GPIO_BC(GPIOB) = (1U << pin);
|
||||
}
|
||||
printf("[CONT] MODE%d line PB%lu driven %s (for strap check)\r\n",
|
||||
(action==10||action==12)?0:1, (unsigned long)pin,
|
||||
high ? "HIGH" : "LOW");
|
||||
case 10: { /* READ HOLD: CS#+RD# 持续拉低, 数据线浮空输入
|
||||
* 用途: 万用表在 LAN9252 芯片侧引脚测量读周期数据电平。
|
||||
* 判据: 芯片 HBI 活着 => 驱动 IDX_0=0x1234 (0001 0010 0011 0100b) =>
|
||||
* 置位 bit2/4/5/9/12 => D2(Pin12)/D4(Pin49)/D5(Pin50)/D9(Pin19)/
|
||||
* D12(Pin21)=3.3V, 其余数据脚=0V(全部为确定电平)。
|
||||
* 全部中电平/漂移 => 芯片未驱动(区分"芯片不驱动"与"数据线断线":
|
||||
* GD32 侧 rd 采样无法区分两者, 芯片侧测量可以)。 */
|
||||
continuityEnterTestMode(); /* 幂等:已活跃则保持;内部已屏蔽 ECAT ISR */
|
||||
/* 数据线浮空输入(交给芯片驱动),控制线保持 OUT_PP */
|
||||
gpio_init(GPIOD, GPIO_MODE_IN_FLOATING, 0, kDataMaskD);
|
||||
gpio_init(GPIOE, GPIO_MODE_IN_FLOATING, 0, kDataMaskE);
|
||||
/* 持续读周期: WR 高, CS#+RD# 低, A1=A2=0 -> 读 IDX_0 */
|
||||
GPIO_BOP(GPIOD) = (1U<<kPinWr);
|
||||
GPIO_BC(GPIOD) = (1U<<kPinCs) | (1U<<kPinRd) | (1U<<kPinA1) | (1U<<kPinA2);
|
||||
printf("[CONT] READ-HOLD: CS#+RD# held low, data Hi-Z\r\n");
|
||||
printf("[CONT] measure at chip: D2(P12)/D4(P49)/D5(P50)/D9(P19)/D12(P21)=3.3V & others 0V => alive(0x1234); all floating => not driving\r\n");
|
||||
return 0;
|
||||
}
|
||||
default:
|
||||
@@ -469,4 +519,38 @@ EscExmcBus::read16(uint32_t offset) {
|
||||
__attribute__((section(".ramfunc"), noinline)) void
|
||||
EscExmcBus::write16(uint32_t offset, uint16_t data) {
|
||||
ESC_EXMC_REG16(offset) = data;
|
||||
}
|
||||
|
||||
uint32_t EscExmcBus::maskEnter() {
|
||||
uint32_t prev = 0U;
|
||||
/* EXTI0=SYNC0(preempt=0) 无法被 BASEPRI 屏蔽,只能关 NVIC;
|
||||
* EXTI1=ESC IRQ / EXTI2=SYNC1 (均 preempt=1) 的 PDI_Isr/Sync1_Isr 同样做
|
||||
* HBI 两步事务访问,一并屏蔽——否则主循环事务会被 Sync1_Isr 的
|
||||
* PDO_InputMapping→HW_EscWriteIsr 打断而错址(1.5.29 修复)。 */
|
||||
if (NVIC_GetEnableIRQ(EXTI0_IRQn) != 0U) {
|
||||
prev |= 1U;
|
||||
NVIC_DisableIRQ(EXTI0_IRQn);
|
||||
}
|
||||
if (NVIC_GetEnableIRQ(EXTI1_IRQn) != 0U) {
|
||||
prev |= 2U;
|
||||
NVIC_DisableIRQ(EXTI1_IRQn);
|
||||
}
|
||||
if (NVIC_GetEnableIRQ(EXTI2_IRQn) != 0U) {
|
||||
prev |= 4U;
|
||||
NVIC_DisableIRQ(EXTI2_IRQn);
|
||||
}
|
||||
return prev;
|
||||
}
|
||||
|
||||
void EscExmcBus::maskExit(uint32_t prev) {
|
||||
/* 按保存的状态恢复:外层已屏蔽的(如 SSC DISABLE_ESC_INT)不会被误开 */
|
||||
if ((prev & 4U) != 0U) {
|
||||
NVIC_EnableIRQ(EXTI2_IRQn);
|
||||
}
|
||||
if ((prev & 2U) != 0U) {
|
||||
NVIC_EnableIRQ(EXTI1_IRQn);
|
||||
}
|
||||
if ((prev & 1U) != 0U) {
|
||||
NVIC_EnableIRQ(EXTI0_IRQn);
|
||||
}
|
||||
}
|
||||
@@ -30,13 +30,24 @@ public:
|
||||
/** @brief init 后查询:EXMC Region0 是否使能且探活通过(供上层诊断汇总)。 */
|
||||
static bool isOk() { return s_ok; }
|
||||
|
||||
/** @brief 硬件诊断:bit-bang 探活 + 位级回显分析(精简版)。 */
|
||||
/** @brief HBI 事务互斥入口:屏蔽 EXTI0(SYNC0,preempt=0)+ EXTI1(ESC IRQ,preempt=1)。
|
||||
* @note HBI Indexed 是两步事务(先写 IDX 再读写 DATA),不可被其他 HBI 用户打断,
|
||||
* 而 SYNC0 优先级 0 无法用 BASEPRI 屏蔽,只能关 NVIC。返回先前使能状态,
|
||||
* 配合 maskExit() 使用;保存/恢复语义不会破坏外层(如 SSC DISABLE_ESC_INT)的屏蔽。
|
||||
* 仅供主循环上下文的 HBI 访问使用(HW_* 非 Isr 版本)。 */
|
||||
static uint32_t maskEnter();
|
||||
|
||||
/** @brief HBI 事务互斥出口:按 maskEnter() 返回的状态恢复 NVIC 使能。 */
|
||||
static void maskExit(uint32_t prev);
|
||||
|
||||
/** @brief 硬件诊断:bit-bang 探活 + 位级回显分析(精简版)。
|
||||
* @note EtherCAT 同步 ISR 激活期间自动跳过(诊断会把 EXMC 引脚切成 GPIO)。 */
|
||||
static void diagDump();
|
||||
|
||||
/** @brief 通断测试:逐个 pattern 驱动(不延时,保持到下次调用)。
|
||||
* @param action 0=stop(恢复AF_PP), 1=0xFFFF, 2=0x0000, 3=0xAAAA, 4=0x5555,
|
||||
* 5=CS低, 6=RD低, 7=WR低, 8=A1高, 9=A2高,
|
||||
* 10=MODE0高(Pin48), 11=MODE1高(Pin46), 12=MODE0低(Pin48), 13=MODE1低(Pin46)
|
||||
* 10=读保持(CS#+RD#持续低+数据线Hi-Z,万用表在芯片侧量读周期电平)
|
||||
* @return 0=OK, 0xEE=无效 action */
|
||||
static uint8_t continuityDrive(uint8_t action);
|
||||
|
||||
|
||||
@@ -3,25 +3,35 @@
|
||||
* @brief LAN9252 EtherCAT ESC 驱动实现。
|
||||
*
|
||||
* 硬件使用 16-bit HBI Indexed Address Mode:
|
||||
* - A2=0,A1=0 (偏移 0x00000) -> HBI_IDX_0 低 16 位
|
||||
* - A2=1,A1=0 (偏移 0x20000) -> HBI_DATA_0 低 16 位
|
||||
* - A2=1,A1=1 (偏移 0x30000) -> HBI_DATA_0 高 16 位
|
||||
* - A2=0,A1=0 (偏移 0x00000) -> HBI_IDX_0
|
||||
* - A2=1,A1=0 (偏移 0x40000) -> HBI_DATA_0 低 16 位
|
||||
* - A2=1,A1=1 (偏移 0x60000) -> HBI_DATA_0 高 16 位
|
||||
*
|
||||
* 地址线换算(1.5.27 修正,此前 0x20000/0x30000 是按 8-bit 规则算的,DATA 访问全部
|
||||
* 落在 HBI_IDX_0 高半字,IDX_0=0x1234 探针假通过而 BYTE_TEST 永远失败):
|
||||
* 板线:LAN9252 A1(Pin25)→PD11=EXMC_A16,A2(Pin29)→PD12=EXMC_A17。
|
||||
* GD32E50x 用户手册 p869:16-bit 数据宽度时 EXMC 内部将 HADDR[25:1] 接到
|
||||
* 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"完成。
|
||||
*/
|
||||
#include "comm/esc_lan9252.h"
|
||||
#include "comm/esc_exmc_bus.h"
|
||||
#include "sys/config.h"
|
||||
#include "sys/systick.h"
|
||||
#include <stdio.h>
|
||||
#include "RTE_Components.h"
|
||||
#include CMSIS_device_header
|
||||
#include "gd32e50x_gpio.h"
|
||||
#include "gd32e50x_rcu.h"
|
||||
|
||||
namespace {
|
||||
/* HBI Indexed Mode 下 EXMC 偏移(A2=EXMC_A17=PD12, A1=EXMC_A16=PD11)
|
||||
* 注意: A16=bit16=0x10000, A17=bit17=0x20000 (不是 0x40000!) */
|
||||
/* HBI Indexed Mode 下 EXMC 偏移(A2=LAN9252 Pin29←PD12=EXMC_A17, A1=Pin25←PD11=EXMC_A16)
|
||||
* EXMC 16-bit 模式地址左移 1 位:A16 引脚=CPU 偏移 0x20000,A17 引脚=CPU 偏移 0x40000。 */
|
||||
constexpr uint32_t HBI_IDX0_OFFSET = 0x00000U; /* A2=0, A1=0 */
|
||||
constexpr uint32_t HBI_DATA0_LOW_OFF = 0x20000U; /* A2=1(A17), A1=0(A16) */
|
||||
constexpr uint32_t HBI_DATA0_HIGH_OFF = 0x30000U; /* A2=1(A17), A1=1(A16) */
|
||||
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) */
|
||||
|
||||
/* ESC 关键寄存器字节地址。
|
||||
* 注: LAN9252 的 HBI 在 EEPROM 空白/无效时依然工作(数据手册 9.5.4.2:
|
||||
@@ -30,11 +40,6 @@ constexpr uint32_t HBI_DATA0_HIGH_OFF = 0x30000U; /* A2=1(A17), A1=1(A16) */
|
||||
constexpr uint16_t LAN9252_REG_BYTE_TEST = 0x0064U; /**< 字节顺序测试寄存器(默认 0x87654321,LAN9252 手册 16.1.2)。 */
|
||||
constexpr uint32_t LAN9252_BYTE_TEST_VALUE = 0x87654321U; /**< BYTE_TEST 默认值(小端序)。 */
|
||||
|
||||
/* PDI 内存基址偏移。
|
||||
* CONFIG_ESC_PDI_INPUT_OFFSET / CONFIG_ESC_PDI_OUTPUT_OFFSET 已包含 SyncManager
|
||||
* 在 ESC 内存空间中的实际地址,因此这里基址为 0。 */
|
||||
constexpr uint16_t LAN9252_PDI_BASE_OFFSET = 0x0000U;
|
||||
|
||||
__attribute__((section(".ramfunc"), noinline))
|
||||
void setHbiIndex(uint16_t addr)
|
||||
{
|
||||
@@ -59,12 +64,6 @@ void writeDataLow(uint16_t v)
|
||||
EscExmcBus::write16(HBI_DATA0_LOW_OFF, v);
|
||||
}
|
||||
|
||||
__attribute__((section(".ramfunc"), noinline))
|
||||
void writeDataHigh(uint16_t v)
|
||||
{
|
||||
EscExmcBus::write16(HBI_DATA0_HIGH_OFF, v);
|
||||
}
|
||||
|
||||
__attribute__((section(".ramfunc"), noinline))
|
||||
uint16_t readReg16(uint16_t addr)
|
||||
{
|
||||
@@ -87,36 +86,105 @@ void writeReg16(uint16_t addr, uint16_t v)
|
||||
setHbiIndex(addr);
|
||||
writeDataLow(v);
|
||||
}
|
||||
|
||||
__attribute__((section(".ramfunc"), noinline))
|
||||
void writeReg32(uint16_t addr, uint32_t v)
|
||||
{
|
||||
setHbiIndex(addr);
|
||||
writeDataLow((uint16_t)(v & 0xFFFFU));
|
||||
writeDataHigh((uint16_t)((v >> 16) & 0xFFFFU));
|
||||
}
|
||||
} /* anonymous namespace */
|
||||
|
||||
bool EscLan9252::s_busReady = false;
|
||||
bool EscLan9252::s_resetDone = false;
|
||||
|
||||
void EscLan9252::hardwareReset()
|
||||
{
|
||||
/* 受控复位 ECAT_NRST(PB10 → LAN9252 Pin11 RST#,SCH 第1页 Pin47)。
|
||||
* 2026-08-22(1.5.25) 前固件从未初始化 PB10,RST# 仅靠 R44 10k 上拉自然释放,
|
||||
* 不满足手册 18.6.3:tpurstd(电源稳定→RST#释放)≥25ms、trstia(低脉宽)≥200µs,
|
||||
* 且要求释放前时钟输入已稳定。固件执行到此点时上电早已完成、晶振已稳定,
|
||||
* 统一 30ms 低脉宽同时覆盖两个下限。RST# 释放瞬间重锁 CHIP_MODE0/1 strap
|
||||
* (LED_LINK1/2 经 R35/R36 10k 下拉 → 00b = 2 端口 HBI 模式)。
|
||||
* 释放后保持推挽输出高:空闲电平确定,并保留运行期软件复位手段;
|
||||
* GD32 自身复位期间引脚回浮空,由 R44 上拉维持高,不会误复位 ESC。 */
|
||||
rcu_periph_clock_enable(RCU_GPIOB);
|
||||
gpio_init(CONFIG_ESC_NRST_PORT, GPIO_MODE_OUT_PP, GPIO_OSPEED_50MHZ,
|
||||
CONFIG_ESC_NRST_PIN);
|
||||
gpio_bit_reset(CONFIG_ESC_NRST_PORT, CONFIG_ESC_NRST_PIN); /* RST# 拉低 */
|
||||
SysTickTimer::delayMs(CONFIG_ESC_NRST_ASSERT_MS);
|
||||
gpio_bit_set(CONFIG_ESC_NRST_PORT, CONFIG_ESC_NRST_PIN); /* 释放复位 */
|
||||
SysTickTimer::delayMs(CONFIG_ESC_HBI_POLL_STEP_MS);
|
||||
/* 回读实际电平:GD32 输入通路常连引脚,输出态也能读到真实电压。
|
||||
* 驱动高却读回低 => ECAT_NRST 网络被外部短路到地(或引脚损伤),
|
||||
* LAN9252 被按死在复位态 —— 直接解释 HBI 全浮空 + 晶振照振。 */
|
||||
const bool pinHigh = (gpio_input_bit_get(CONFIG_ESC_NRST_PORT,
|
||||
CONFIG_ESC_NRST_PIN) == SET);
|
||||
printf("[ECAT][ESC] hardwareReset: RST# low %ums -> released, pin reads %s\r\n",
|
||||
(unsigned)CONFIG_ESC_NRST_ASSERT_MS,
|
||||
pinHigh ? "HIGH" : "*** LOW (net shorted to GND / pin damaged) ***");
|
||||
}
|
||||
|
||||
uint8_t EscLan9252::nrstDrive(uint8_t action)
|
||||
{
|
||||
/* SPI 0xA2 action 20+ 的 NRST 手动诊断入口。
|
||||
* 返回值 = 实际引脚电平(0/1),非 OK 码;0xEE = 无效 action。 */
|
||||
switch (action) {
|
||||
case 0: /* 恢复自动态:推挽输出高(与 hardwareReset 释放后一致) */
|
||||
case 2: /* 持续拉高 */
|
||||
gpio_init(CONFIG_ESC_NRST_PORT, GPIO_MODE_OUT_PP, GPIO_OSPEED_50MHZ,
|
||||
CONFIG_ESC_NRST_PIN);
|
||||
gpio_bit_set(CONFIG_ESC_NRST_PORT, CONFIG_ESC_NRST_PIN);
|
||||
break;
|
||||
case 1: /* 持续拉低(手动保持复位) */
|
||||
gpio_init(CONFIG_ESC_NRST_PORT, GPIO_MODE_OUT_PP, GPIO_OSPEED_50MHZ,
|
||||
CONFIG_ESC_NRST_PIN);
|
||||
gpio_bit_reset(CONFIG_ESC_NRST_PORT, CONFIG_ESC_NRST_PIN);
|
||||
break;
|
||||
case 3: /* 释放:浮空输入,网络交给 R44 10k 上拉 */
|
||||
gpio_init(CONFIG_ESC_NRST_PORT, GPIO_MODE_IN_FLOATING, 0,
|
||||
CONFIG_ESC_NRST_PIN);
|
||||
break;
|
||||
case 4: /* 只读回,不改驱动 */
|
||||
break;
|
||||
default:
|
||||
return 0xEE;
|
||||
}
|
||||
const bool level = (gpio_input_bit_get(CONFIG_ESC_NRST_PORT,
|
||||
CONFIG_ESC_NRST_PIN) == SET);
|
||||
static const char *const kName[5] = {
|
||||
"auto(high)", "drive LOW", "drive HIGH", "float", "read-only"};
|
||||
printf("[ECAT][NRST] %s -> pin reads %s\r\n", kName[action],
|
||||
level ? "HIGH" : "LOW");
|
||||
return level ? 1U : 0U;
|
||||
}
|
||||
|
||||
bool EscLan9252::init()
|
||||
{
|
||||
/* 使能 DWT 周期计数器,供 periodicIsr 执行时间测量 */
|
||||
s_busReady = false; /* 1.5.27:s_busReady 语义 = init 完整成功(EXMC+探针+BYTE_TEST) */
|
||||
|
||||
/* 使能 DWT 周期计数器,供 hw_gd32exmc/SSC 执行时间测量 */
|
||||
CoreDebug->DEMCR |= CoreDebug_DEMCR_TRCENA_Msk;
|
||||
DWT->CYCCNT = 0U;
|
||||
DWT->CTRL |= DWT_CTRL_CYCCNTENA_Msk;
|
||||
|
||||
s_busReady = EscExmcBus::init();
|
||||
if (!s_busReady) {
|
||||
/* 受控 RST# 复位(仅首次;重复调用 init 不得二次复位,否则清掉 SSC 配置) */
|
||||
if (!s_resetDone) {
|
||||
hardwareReset();
|
||||
s_resetDone = true;
|
||||
}
|
||||
|
||||
if (!EscExmcBus::init()) {
|
||||
printf("[ECAT][ESC] init: FAIL, EXMC bus not ready (see [ECAT][EXMC] log)\r\n");
|
||||
return false;
|
||||
}
|
||||
|
||||
/* HBI 存活探针(数据手册 9.5.4.2):先直接读 HBI_IDX_0(offset 0,不写任何东西),
|
||||
* 其复位默认值 = 0x1234。若读回 != 0x1234(通常是 0x0000/0xFFFF 或上次写入的回显),
|
||||
* 其复位默认值 = 0x1234。RST# 释放后 ESC 需完成内部启动与 EEPROM 加载,
|
||||
* 故按手册建议轮询(此前为单次读,复位后立即读可能仍在启动中)。
|
||||
* 若超时仍 != 0x1234(通常是 0x0000/0xFFFF 或上次写入的回显),
|
||||
* 说明 LAN9252 完全没有驱动总线——CS 未拉低、CHIP_MODE strap 非 00(三端口=SPI only)
|
||||
* 或芯片未复位/未上电,而不是 EEPROM 问题(HBI 在 EEPROM 空白时也工作)。 */
|
||||
const uint16_t idx0 = EscExmcBus::read16(HBI_IDX0_OFFSET);
|
||||
* 或芯片未出复位/未上电,而不是 EEPROM 问题(HBI 在 EEPROM 空白时也工作)。 */
|
||||
uint16_t idx0 = EscExmcBus::read16(HBI_IDX0_OFFSET);
|
||||
for (uint32_t waited = 0U;
|
||||
idx0 != 0x1234U && waited < CONFIG_ESC_HBI_POLL_TIMEOUT_MS;
|
||||
waited += CONFIG_ESC_HBI_POLL_STEP_MS) {
|
||||
SysTickTimer::delayMs(CONFIG_ESC_HBI_POLL_STEP_MS);
|
||||
idx0 = EscExmcBus::read16(HBI_IDX0_OFFSET);
|
||||
}
|
||||
if (idx0 != 0x1234U) {
|
||||
printf("[ECAT][ESC] init: FAIL, HBI_IDX_0=0x%04X (expect 0x1234) -> LAN not driving bus\r\n",
|
||||
(unsigned)idx0);
|
||||
@@ -124,14 +192,17 @@ bool EscLan9252::init()
|
||||
return false;
|
||||
}
|
||||
|
||||
/* 再读 BYTE_TEST(0x064)确认接口完全就绪,默认 0x87654321(小端)。 */
|
||||
/* 再读 BYTE_TEST(0x064)确认接口完全就绪,默认 0x87654321(小端)。
|
||||
* 该值只有 HBI_DATA_0 偏移正确(EXMC 16-bit 地址左移 1 位)才可能读到。 */
|
||||
const uint32_t byteTest = readReg32(LAN9252_REG_BYTE_TEST);
|
||||
if (byteTest != LAN9252_BYTE_TEST_VALUE) {
|
||||
printf("[ECAT][ESC] init: FAIL, BYTE_TEST=0x%08lX (expect 0x87654321)\r\n",
|
||||
(unsigned long)byteTest);
|
||||
printf("[ECAT][ESC] hint: IDX probe passed but DATA access failed -> check HBI_DATA_0 offsets (0x40000/0x60000)\r\n");
|
||||
return false;
|
||||
}
|
||||
printf("[ECAT][ESC] init: OK, HBI alive (IDX=0x1234, BYTE_TEST=0x87654321)\r\n");
|
||||
s_busReady = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -141,90 +212,6 @@ uint32_t EscLan9252::readChipId()
|
||||
return readReg32(LAN9252_REG_BYTE_TEST);
|
||||
}
|
||||
|
||||
uint16_t EscLan9252::readReg(uint16_t addr)
|
||||
{
|
||||
return readReg16(addr);
|
||||
}
|
||||
|
||||
void EscLan9252::writeReg(uint16_t addr, uint16_t v)
|
||||
{
|
||||
writeReg16(addr, v);
|
||||
}
|
||||
|
||||
__attribute__((section(".ramfunc"), noinline))
|
||||
void EscLan9252::readPdi(uint16_t offset, uint8_t *buf, size_t len)
|
||||
{
|
||||
if (buf == nullptr || len == 0U) {
|
||||
return;
|
||||
}
|
||||
|
||||
uint16_t addr = LAN9252_PDI_BASE_OFFSET + offset;
|
||||
size_t i = 0U;
|
||||
|
||||
/* 按 32-bit 字读取 */
|
||||
while ((i + 4U) <= len) {
|
||||
const uint32_t w = readReg32(addr);
|
||||
buf[i + 0U] = (uint8_t)(w & 0xFFU);
|
||||
buf[i + 1U] = (uint8_t)((w >> 8) & 0xFFU);
|
||||
buf[i + 2U] = (uint8_t)((w >> 16) & 0xFFU);
|
||||
buf[i + 3U] = (uint8_t)((w >> 24) & 0xFFU);
|
||||
addr += 4U;
|
||||
i += 4U;
|
||||
}
|
||||
|
||||
/* 剩余 2 字节 */
|
||||
if ((i + 2U) <= len) {
|
||||
const uint16_t w = readReg16(addr);
|
||||
buf[i + 0U] = (uint8_t)(w & 0xFFU);
|
||||
buf[i + 1U] = (uint8_t)((w >> 8) & 0xFFU);
|
||||
addr += 2U;
|
||||
i += 2U;
|
||||
}
|
||||
|
||||
/* 剩余 1 字节 */
|
||||
if (i < len) {
|
||||
const uint16_t w = readReg16(addr);
|
||||
buf[i] = (uint8_t)(w & 0xFFU);
|
||||
}
|
||||
}
|
||||
|
||||
__attribute__((section(".ramfunc"), noinline))
|
||||
void EscLan9252::writePdi(uint16_t offset, const uint8_t *buf, size_t len)
|
||||
{
|
||||
if (buf == nullptr || len == 0U) {
|
||||
return;
|
||||
}
|
||||
|
||||
uint16_t addr = LAN9252_PDI_BASE_OFFSET + offset;
|
||||
size_t i = 0U;
|
||||
|
||||
/* 按 32-bit 字写入 */
|
||||
while ((i + 4U) <= len) {
|
||||
const uint32_t w = ((uint32_t)buf[i + 3U] << 24) |
|
||||
((uint32_t)buf[i + 2U] << 16) |
|
||||
((uint32_t)buf[i + 1U] << 8) |
|
||||
(uint32_t)buf[i + 0U];
|
||||
writeReg32(addr, w);
|
||||
addr += 4U;
|
||||
i += 4U;
|
||||
}
|
||||
|
||||
/* 剩余 2 字节 */
|
||||
if ((i + 2U) <= len) {
|
||||
const uint16_t w = ((uint16_t)buf[i + 1U] << 8) |
|
||||
(uint16_t)buf[i + 0U];
|
||||
writeReg16(addr, w);
|
||||
addr += 2U;
|
||||
i += 2U;
|
||||
}
|
||||
|
||||
/* 剩余 1 字节 */
|
||||
if (i < len) {
|
||||
const uint16_t w = (uint16_t)buf[i];
|
||||
writeReg16(addr, w);
|
||||
}
|
||||
}
|
||||
|
||||
uint16_t EscLan9252::initOnlyReadReg(uint16_t addr)
|
||||
{
|
||||
return readReg16(addr);
|
||||
@@ -234,13 +221,3 @@ void EscLan9252::initOnlyWriteReg(uint16_t addr, uint16_t v)
|
||||
{
|
||||
writeReg16(addr, v);
|
||||
}
|
||||
|
||||
void EscLan9252::initOnlyReadPdi(uint16_t offset, uint8_t *buf, size_t len)
|
||||
{
|
||||
readPdi(offset, buf, len);
|
||||
}
|
||||
|
||||
void EscLan9252::initOnlyWritePdi(uint16_t offset, const uint8_t *buf, size_t len)
|
||||
{
|
||||
writePdi(offset, buf, len);
|
||||
}
|
||||
|
||||
@@ -5,11 +5,10 @@
|
||||
* LAN9252 寄存器与 PDI 内存:先写 HBI_IDX_0,再读写 HBI_DATA_0。
|
||||
*
|
||||
* 接口调用约束(避免 EXMC 总线争用):
|
||||
* - readReg/writeReg/readPdi/writePdi:ISR-only,仅供 preempt=0 的 periodicIsr() 调用,无锁。
|
||||
* - initOnly* 系列:INIT-ONLY,仅允许在 EthercatSync::init() 使能 EXTI NVIC 之前调用;
|
||||
* 运行期禁止调用,需要访问 ESC 寄存器须通过 SYNC0 ISR 路径(本 spec 不涵盖)。
|
||||
*
|
||||
* PDI offset 语义:offset 参数为 SyncManager 内存区相对偏移,输入 PDO(TxPDO)与输出 PDO(RxPDO)必须使用不同 offset,参见 CONFIG_ESC_PDI_INPUT_OFFSET / CONFIG_ESC_PDI_OUTPUT_OFFSET。
|
||||
* - 运行期 ESC 寄存器/PDI 访问统一走 SSC 移植层 hw_gd32exmc.cpp 的 HW 系列函数
|
||||
* (主循环路径带 HBI 互斥守卫,ISR 路径由 ethercat_sync.cpp 包装器保证互斥)。
|
||||
* - initOnly 系列:INIT-ONLY,仅允许在 EthercatSync::init() 使能 EXTI NVIC 之前调用
|
||||
* (如 EthercatSlave::init 的芯片 ID/AL 状态探针)。
|
||||
*/
|
||||
#ifndef COMM_ESC_LAN9252_H
|
||||
#define COMM_ESC_LAN9252_H
|
||||
@@ -24,44 +23,42 @@
|
||||
class EscLan9252 {
|
||||
public:
|
||||
/** @brief 初始化 EXMC 总线与 ESC 基本寄存器。
|
||||
* @note 1.5.25 起内部先执行一次 hardwareReset() 受控复位(s_resetDone 门控,
|
||||
* 重复调用不会二次复位 SSC 已配置好的芯片),再探活 EXMC/HBI。
|
||||
* @return bool true 表示 EXMC 总线探活通过(可继续读取芯片 ID)。 */
|
||||
static bool init();
|
||||
|
||||
/** @brief INIT-ONLY:ECAT_NRST(PB10)受控复位序列(拉低 CONFIG_ESC_NRST_ASSERT_MS 后释放)。
|
||||
* @note 满足 LAN9252 手册 18.6.3 tpurstd≥25ms/trstia≥200µs;RST# 释放瞬间
|
||||
* 重锁 CHIP_MODE strap。运行期调用会打断 EtherCAT 通信,仅供 init/诊断用。 */
|
||||
static void hardwareReset();
|
||||
|
||||
/** @brief 诊断:手动控制/回读 ECAT_NRST(PB10)实际电平(SPI 0xA2 action 20+ 转发到此)。
|
||||
* @param action 0=恢复自动态(推挽输出高) 1=持续拉低 2=持续拉高 3=浮空输入(交给 R44) 4=只读回
|
||||
* @return 实际引脚电平 0/1(GD32 输入通路常连引脚,输出态亦读真实电压);0xEE=无效 action。
|
||||
* 判读:action 1→2 切换后引脚电平不变 => 网络被外部短路或驱动器损伤;
|
||||
* 驱动高读回低 => ECAT_NRST 被按死在复位,LAS9252 永在复位态。 */
|
||||
static uint8_t nrstDrive(uint8_t action);
|
||||
|
||||
/**
|
||||
* @brief 读取 ESC 芯片 ID(INIT-ONLY,供 init 打印诊断用)。
|
||||
* @return uint32_t 32-bit 芯片 ID(期望值 0x87654321)。
|
||||
*/
|
||||
static uint32_t readChipId();
|
||||
|
||||
/** @brief ISR-only:仅供 preempt=0 的 periodicIsr() 调用,无锁。主循环必须用 initOnly 版本。 */
|
||||
static uint16_t readReg(uint16_t addr);
|
||||
|
||||
/** @brief ISR-only:仅供 preempt=0 的 periodicIsr() 调用,无锁。主循环必须用 initOnly 版本。 */
|
||||
static void writeReg(uint16_t addr, uint16_t v);
|
||||
|
||||
/** @brief ISR-only:仅供 preempt=0 的 periodicIsr() 调用,无锁。主循环必须用 initOnly 版本。 */
|
||||
static void readPdi(uint16_t offset, uint8_t *buf, size_t len);
|
||||
|
||||
/** @brief ISR-only:仅供 preempt=0 的 periodicIsr() 调用,无锁。主循环必须用 initOnly 版本。 */
|
||||
static void writePdi(uint16_t offset, const uint8_t *buf, size_t len);
|
||||
|
||||
/** @brief INIT-ONLY:仅允许在 EthercatSync::init() 使能 EXTI NVIC 之前调用;运行期禁止调用,需 ESC 寄存器访问须通过 SYNC0 ISR 路径。 */
|
||||
/** @brief INIT-ONLY:仅允许在 EthercatSync::init() 使能 EXTI NVIC 之前调用;运行期禁止调用,需 ESC 寄存器访问须通过 SSC 移植层(HW_*)。 */
|
||||
static uint16_t initOnlyReadReg(uint16_t addr);
|
||||
|
||||
/** @brief INIT-ONLY:仅允许在 EthercatSync::init() 使能 EXTI NVIC 之前调用;运行期禁止调用。 */
|
||||
static void initOnlyWriteReg(uint16_t addr, uint16_t v);
|
||||
|
||||
/** @brief INIT-ONLY:仅允许在 EthercatSync::init() 使能 EXTI NVIC 之前调用;运行期禁止调用。 */
|
||||
static void initOnlyReadPdi(uint16_t offset, uint8_t *buf, size_t len);
|
||||
|
||||
/** @brief INIT-ONLY:仅允许在 EthercatSync::init() 使能 EXTI NVIC 之前调用;运行期禁止调用。 */
|
||||
static void initOnlyWritePdi(uint16_t offset, const uint8_t *buf, size_t len);
|
||||
|
||||
/** @brief EXMC 总线是否就绪(init 后可查询)。 */
|
||||
/** @brief EXMC 总线 + HBI_IDX_0 探针 + BYTE_TEST 全部通过(init 成功)后为 true。
|
||||
* @note 与 init() 返回值一致,可作运行期查询。 */
|
||||
static bool isBusReady() { return s_busReady; }
|
||||
|
||||
private:
|
||||
static bool s_busReady; /**< EXMC 探活结果(init 后置位,供上层诊断汇总)。 */
|
||||
static bool s_busReady; /**< init 完整成功(EXMC+IDX 探针+BYTE_TEST)后置位。 */
|
||||
static bool s_resetDone; /**< hardwareReset 已执行门控(防二次复位清掉 SSC 配置)。 */
|
||||
};
|
||||
|
||||
#endif /* COMM_ESC_LAN9252_H */
|
||||
@@ -1,35 +1,20 @@
|
||||
/**
|
||||
* @file ethercat_slave.cpp
|
||||
* @brief EtherCAT 从站周期处理实现。
|
||||
* @brief EtherCAT 从站 init 探针实现。
|
||||
*
|
||||
* 运行期 PDO 交换由 SSC 协议栈完成(Sync0_Isr → ECAT_Application →
|
||||
* dc_app.c 的 APPL_InputMapping/APPL_OutputMapping),本文件只负责
|
||||
* init 阶段的 HBI 探活、芯片 ID 与 AL 状态诊断。
|
||||
*/
|
||||
#include "comm/ethercat_slave.h"
|
||||
#include "comm/esc_lan9252.h"
|
||||
#include "sys/config.h"
|
||||
#include "sys/task_manager.h"
|
||||
#include "main.h"
|
||||
#include <cstdio>
|
||||
|
||||
PdoBuffer EthercatSlave::s_inputs[3] = {};
|
||||
PdoBuffer EthercatSlave::s_outputs[3] = {};
|
||||
PdoBuffer EthercatSlave::s_appInputs = {};
|
||||
PdoBuffer EthercatSlave::s_appOutputs = {};
|
||||
volatile uint8_t EthercatSlave::s_inputWriteIdx = 0U;
|
||||
volatile uint8_t EthercatSlave::s_outputReadIdx = 0U;
|
||||
volatile uint16_t EthercatSlave::s_periodicSeq = 0U;
|
||||
uint16_t EthercatSlave::s_processedSeq = 0U;
|
||||
volatile uint32_t EthercatSlave::s_isrMaxCycles = 0U;
|
||||
volatile uint32_t EthercatSlave::s_isrAvgCycles = 0U;
|
||||
volatile uint32_t EthercatSlave::s_isrCount = 0U;
|
||||
bool EthercatSlave::s_escReady = false;
|
||||
uint32_t EthercatSlave::s_chipId = 0U;
|
||||
uint8_t EthercatSlave::s_alState = 0U;
|
||||
|
||||
/* ISR 执行时间评估:
|
||||
* - 若 DWT 实测 max > CONFIG_ETHERCAT_ISR_MAX_US_THRESHOLD(5µs),需评估:
|
||||
* a) 减小 CONFIG_ETHERCAT_PDO_SIZE(默认 32 字节)
|
||||
* b) 优化 EXMC 时序参数(esc_exmc_bus.cpp 中 read_latency/write_latency)
|
||||
* - 若 max ≤ 5µs,接受现状;preempt=0 阻塞窗口 ≤ 5µs,CAN/SPI 中断需容忍此窗口。
|
||||
* SPI0 从机在此窗口内不会 RX 溢出(假设 SPI 时钟 ≤ 10MHz,5µs ≥ 50 bit,远小于一帧)。 */
|
||||
|
||||
void EthercatSlave::init()
|
||||
{
|
||||
ECAT_LOGI("[ECAT][SLAVE] init: begin\r\n");
|
||||
@@ -40,10 +25,6 @@ void EthercatSlave::init()
|
||||
s_alState = 0U;
|
||||
s_escReady = false;
|
||||
LOG_ERR("[ECAT][SLAVE] init: FAIL, ESC bus not ready\r\n");
|
||||
s_inputWriteIdx = 0U;
|
||||
s_outputReadIdx = 0U;
|
||||
s_periodicSeq = 0U;
|
||||
s_processedSeq = 0U;
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -93,98 +74,5 @@ void EthercatSlave::init()
|
||||
(unsigned long)s_chipId, (unsigned)s_alState, alStateName, (unsigned)alCode);
|
||||
}
|
||||
|
||||
s_inputWriteIdx = 0U;
|
||||
s_outputReadIdx = 0U;
|
||||
s_periodicSeq = 0U;
|
||||
s_processedSeq = 0U;
|
||||
LOG_ERR("[ECAT][SLAVE] init: %s\r\n", s_escReady ? "OK" : "FAIL(partial)");
|
||||
}
|
||||
|
||||
__attribute__((section(".ramfunc"), noinline))
|
||||
void EthercatSlave::periodicIsr()
|
||||
{
|
||||
const uint32_t cycStart = DWT->CYCCNT;
|
||||
|
||||
/* 1. 读取输入 PDO 到当前写入缓冲 */
|
||||
EscLan9252::readPdi(CONFIG_ESC_PDI_INPUT_OFFSET, s_inputs[s_inputWriteIdx].data, CONFIG_ETHERCAT_PDO_SIZE);
|
||||
s_inputs[s_inputWriteIdx].len = CONFIG_ETHERCAT_PDO_SIZE;
|
||||
s_inputWriteIdx = (s_inputWriteIdx + 1U) % 3U;
|
||||
|
||||
/* 2. 写入输出 PDO(从当前读取缓冲) */
|
||||
EscLan9252::writePdi(CONFIG_ESC_PDI_OUTPUT_OFFSET, s_outputs[s_outputReadIdx].data, s_outputs[s_outputReadIdx].len);
|
||||
s_outputReadIdx = (s_outputReadIdx + 1U) % 3U;
|
||||
|
||||
/* 3. 递增周期序列号通知主循环(替代 TaskManager::set,避免 BASEPRI 竞态) */
|
||||
s_periodicSeq++;
|
||||
|
||||
const uint32_t elapsed = DWT->CYCCNT - cycStart;
|
||||
s_isrCount++;
|
||||
if (elapsed > s_isrMaxCycles) {
|
||||
s_isrMaxCycles = elapsed;
|
||||
}
|
||||
/* 滑动平均:avg = avg + (elapsed - avg) / 16 (近似 EMA,避免除法精度问题) */
|
||||
s_isrAvgCycles = s_isrAvgCycles + ((int32_t)elapsed - (int32_t)s_isrAvgCycles) / 16;
|
||||
}
|
||||
|
||||
const PdoBuffer* EthercatSlave::getLatestInputs()
|
||||
{
|
||||
/* 返回最新完成的缓冲 = (writeIdx + 2) % 3,与 ISR 当前写入位置隔一个 */
|
||||
return &s_inputs[(s_inputWriteIdx + 2U) % 3U];
|
||||
}
|
||||
|
||||
void EthercatSlave::setOutputs(const PdoBuffer *outputs)
|
||||
{
|
||||
if (outputs == nullptr) {
|
||||
return;
|
||||
}
|
||||
/* 写入与 ISR 当前读取位置隔 2 个缓冲,提供 2 周期宽限。
|
||||
* 2 周期宽限下拷贝竞态窗口消除,前提是主循环处理时间 < 2 × 周期时间。 */
|
||||
const uint8_t write_idx = (s_outputReadIdx + 2U) % 3U;
|
||||
s_outputs[write_idx] = *outputs;
|
||||
}
|
||||
|
||||
void EthercatSlave::process()
|
||||
{
|
||||
/* 应用层 PDO 桥接(非实时,由主循环周期序列号或 ESC IRQ 触发)。
|
||||
* 运行期 PDO 交换仅通过 getLatestInputs()/setOutputs() 三缓冲进行;ESC 寄存器访问仅限 init 阶段。
|
||||
*
|
||||
* 框架级桥接:从三缓冲取最新输入到 s_appInputs,把 s_appOutputs 推回三缓冲。
|
||||
* TODO(spec): 应用对象字典映射(具体输入字节对应哪个应用变量,如字节 0-3=温度浮点)
|
||||
* 待后续应用层 spec 定义;
|
||||
* 应用层通过 getApplicationInputs()/setApplicationOutputs() 访问快照。 */
|
||||
const PdoBuffer *latest = getLatestInputs();
|
||||
if (latest != nullptr) {
|
||||
s_appInputs = *latest;
|
||||
}
|
||||
setOutputs(&s_appOutputs);
|
||||
}
|
||||
|
||||
const PdoBuffer* EthercatSlave::getApplicationInputs()
|
||||
{
|
||||
return &s_appInputs;
|
||||
}
|
||||
|
||||
void EthercatSlave::setApplicationOutputs(const PdoBuffer *outputs)
|
||||
{
|
||||
if (outputs == nullptr) {
|
||||
return;
|
||||
}
|
||||
s_appOutputs = *outputs;
|
||||
}
|
||||
|
||||
bool EthercatSlave::hasPendingPeriodic()
|
||||
{
|
||||
return s_periodicSeq != s_processedSeq;
|
||||
}
|
||||
|
||||
void EthercatSlave::markPeriodicProcessed()
|
||||
{
|
||||
s_processedSeq = s_periodicSeq;
|
||||
}
|
||||
|
||||
void EthercatSlave::getIsrStats(uint32_t *maxCycles, uint32_t *avgCycles, uint32_t *count)
|
||||
{
|
||||
if (maxCycles) *maxCycles = s_isrMaxCycles;
|
||||
if (avgCycles) *avgCycles = s_isrAvgCycles;
|
||||
if (count) *count = s_isrCount;
|
||||
}
|
||||
|
||||
@@ -1,141 +1,38 @@
|
||||
/**
|
||||
* @file ethercat_slave.h
|
||||
* @brief EtherCAT 从站周期处理接口与 PDO 三缓冲(triple buffer)结构。
|
||||
* @note 周期 ISR 调用 periodicIsr() 完成输入 PDO 读取 + 输出 PDO 写入;
|
||||
* 应用层通过 getLatestInputs()/setOutputs() 与周期路径无锁交换数据。
|
||||
*
|
||||
* 通知路径分离:
|
||||
* 周期路径(periodicIsr)用 s_periodicSeq 序列号通知主循环;
|
||||
* 事件路径(ESC IRQ)用 TASK_ETHERCAT_PROCESS 标志。
|
||||
* 两者独立,避免 preempt=0 ISR 与 BASEPRI 临界区的 RMW 竞态。
|
||||
* @brief EtherCAT 从站 init 探针与运行状态查询接口。
|
||||
* @note 1.5.27:原先的 periodicIsr()/三缓冲 PDO 桥是死代码(无任何调用点),
|
||||
* 运行期 PDO 交换实际由 SSC 协议栈完成 —— 周期路径为 Sync0_Isr →
|
||||
* ECAT_Application → APPL_InputMapping/OutputMapping(comm/ssc/port/dc_app.c),
|
||||
* 主循环路径为 MainLoop → ECAT_Main。本类仅保留 init 探针与诊断查询。
|
||||
*/
|
||||
#ifndef COMM_ETHERCAT_SLAVE_H
|
||||
#define COMM_ETHERCAT_SLAVE_H
|
||||
|
||||
#include <cstdint>
|
||||
#include <cstddef>
|
||||
#include "sys/config.h" /* CONFIG_ETHERCAT_PDO_SIZE */
|
||||
|
||||
/** @brief EtherCAT AL 状态码(EtherCAT 协议定义,ESC AL Status 寄存器低 4 位)。 */
|
||||
constexpr uint8_t AL_STATE_SAFEOP = 0x04U; /**< SAFEOP 状态(SYNC0 可触发下限)。 */
|
||||
|
||||
/**
|
||||
* @brief PDO 数据缓冲(输入/输出共用结构)。
|
||||
* @note 周期 ISR 与应用层通过三缓冲无锁交换。
|
||||
*/
|
||||
struct PdoBuffer {
|
||||
uint8_t data[CONFIG_ETHERCAT_PDO_SIZE] = {0}; /**< 数据载荷。 */
|
||||
size_t len = 0U; /**< 有效数据长度。 */
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief EtherCAT 从站周期处理。
|
||||
* @note 周期路径(periodicIsr)必须极简:读输入→写输出→模 3 轮转指针→递增序列号。
|
||||
* @brief EtherCAT 从站 init 探针与状态查询。
|
||||
*/
|
||||
class EthercatSlave {
|
||||
public:
|
||||
/** @brief 初始化 ESC 与三缓冲。 */
|
||||
/** @brief 初始化 ESC(HBI 探活 + 芯片 ID + AL 状态),供 init 汇总诊断。 */
|
||||
static void init();
|
||||
|
||||
/**
|
||||
* @brief 周期中断处理(由 SYNC0 EXTI 触发)。
|
||||
* @note 放入 RAM 执行段(.ramfunc)以降低 Flash 等待带来的抖动。
|
||||
* 流程:读输入 PDO → 写输出 PDO → 模 3 轮转索引 → 递增 s_periodicSeq。
|
||||
* 不再调用 TaskManager::set,避免 preempt=0 与 BASEPRI 临界区的 RMW 竞态。
|
||||
*/
|
||||
static void periodicIsr();
|
||||
|
||||
/**
|
||||
* @brief 获取最新输入 PDO(主循环调用,无锁)。
|
||||
* @return const PdoBuffer* 最新输入缓冲只读指针;返回与 ISR 当前写入位置隔一个的缓冲。
|
||||
* @note 三缓冲容忍主循环跨 1 个周期,主循环处理必须 < 2 × 周期时间。
|
||||
*/
|
||||
static const PdoBuffer* getLatestInputs();
|
||||
|
||||
/**
|
||||
* @brief 设置下次周期要发送的输出 PDO(主循环调用)。
|
||||
* @param[in] outputs 输出数据指针(内容会被拷贝到内部输出缓冲)。
|
||||
* @note 写入与 ISR 当前读取位置隔一个的缓冲,无需任何临界区。
|
||||
*/
|
||||
static void setOutputs(const PdoBuffer *outputs);
|
||||
|
||||
/**
|
||||
* @brief 应用层 PDO 映射入口(非实时,由 TASK_ETHERCAT_PROCESS 或周期序列号触发)。
|
||||
* @note 留接口,具体映射逻辑由协议栈后续实现。
|
||||
*/
|
||||
static void process();
|
||||
|
||||
/**
|
||||
* @brief 获取应用层输入 PDO 快照(主循环调用)。
|
||||
* @return const PdoBuffer* 最近一次 process() 拷贝的输入快照只读指针。
|
||||
* @note 返回的指针在下次 process() 调用前有效;应用层应在 process() 后立即读取。
|
||||
* @note TODO(app-layer): 应用层 PDO 字典映射 spec 启用前无外部调用方;
|
||||
* 保留接口契约,待未来 spec 实现后由应用层调用。 */
|
||||
static const PdoBuffer* getApplicationInputs();
|
||||
|
||||
/**
|
||||
* @brief 设置应用层输出 PDO 快照(主循环调用)。
|
||||
* @param[in] outputs 输出数据指针(内容会被拷贝到内部快照)。
|
||||
* @note 下次 process() 调用时,内部快照通过 setOutputs() 推入三缓冲。
|
||||
* @note TODO(app-layer): 应用层 PDO 字典映射 spec 启用前无外部调用方;
|
||||
* 保留接口契约,待未来 spec 实现后由应用层调用。 */
|
||||
static void setApplicationOutputs(const PdoBuffer *outputs);
|
||||
|
||||
/**
|
||||
* @brief 检查是否有新的周期数据待主循环处理。
|
||||
* @return bool true 表示 s_periodicSeq 与已处理序号不同。
|
||||
* @note 主循环 runPending() 优先调用此接口,替代原 TASK_ETHERCAT_PROCESS 周期路径。
|
||||
*/
|
||||
static bool hasPendingPeriodic();
|
||||
|
||||
/**
|
||||
* @brief 标记当前周期数据已被主循环处理。
|
||||
* @note 调用此接口前应已调用 taskEthercatProcess() 完成本次 PDO 映射。
|
||||
*/
|
||||
static void markPeriodicProcessed();
|
||||
|
||||
/** @brief 读取 ISR 执行时间诊断(max/avg 单位:CPU 周期数,除以 180 得 µs)。 */
|
||||
static void getIsrStats(uint32_t *maxCycles, uint32_t *avgCycles, uint32_t *count);
|
||||
|
||||
/** @brief ESC 自检是否通过(init 后可查询,用于决定是否使能同步中断)。 */
|
||||
static bool isEscReady() { return s_escReady; }
|
||||
|
||||
/** @brief 读取 ESC 芯片 ID(供诊断打印;未就绪时为上次读到的值)。 */
|
||||
static uint32_t getChipId() { return s_chipId; }
|
||||
|
||||
/** @brief 读取 ESC AL 状态码(供诊断打印与 SSC 集成;未就绪时为上次读到的值)。
|
||||
* @note TODO(app-layer): 当前仅 init 内部用作 SAFEOP 守卫,外部诊断未启用;
|
||||
* SSC 集成后由应用层查询。 */
|
||||
/** @brief 读取 ESC AL 状态码(供诊断打印;未就绪时为上次读到的值)。 */
|
||||
static uint8_t getAlState() { return s_alState; }
|
||||
|
||||
private:
|
||||
/**
|
||||
* @brief 输入三缓冲。
|
||||
* ISR 按 s_inputWriteIdx 模 3 轮转写入;getLatestInputs() 读取 (writeIdx + 2) % 3。
|
||||
*/
|
||||
static PdoBuffer s_inputs[3];
|
||||
/**
|
||||
* @brief 输出三缓冲。
|
||||
* ISR 按 s_outputReadIdx 模 3 轮转读取;setOutputs() 写入 (readIdx + 2) % 3,
|
||||
* 与 ISR 当前读取位置隔 2 个缓冲,提供 2 周期宽限消除拷贝竞态。
|
||||
*/
|
||||
static PdoBuffer s_outputs[3];
|
||||
/** @brief 应用层输入快照(主循环访问,process() 从三缓冲拷贝;无需三缓冲)。 */
|
||||
static PdoBuffer s_appInputs;
|
||||
/** @brief 应用层输出快照(主循环访问,process() 推入三缓冲;无需三缓冲)。 */
|
||||
static PdoBuffer s_appOutputs;
|
||||
static volatile uint8_t s_inputWriteIdx; /**< ISR 当前写入索引;模 3 轮转。 */
|
||||
static volatile uint8_t s_outputReadIdx; /**< ISR 当前读取索引;模 3 轮转。 */
|
||||
|
||||
/** @brief 周期路径通知序列号(ISR 单字原子递增,主循环比对;uint16_t 提供 6.55s 回绕周期)。 */
|
||||
static volatile uint16_t s_periodicSeq;
|
||||
/** @brief 主循环已处理序号(由 hasPendingPeriodic/markPeriodicProcessed 维护)。 */
|
||||
static uint16_t s_processedSeq;
|
||||
|
||||
static volatile uint32_t s_isrMaxCycles; /**< ISR 最大执行周期数(180MHz 基准)。 */
|
||||
static volatile uint32_t s_isrAvgCycles; /**< ISR 平均执行周期数(滑动累计)。 */
|
||||
static volatile uint32_t s_isrCount; /**< ISR 执行次数计数。 */
|
||||
|
||||
static bool s_escReady; /**< ESC 自检结果(init 后置位)。 */
|
||||
static uint32_t s_chipId; /**< ESC 芯片 ID(诊断用)。 */
|
||||
static uint8_t s_alState; /**< ESC AL 状态码(init 后可查询,供 isEscReady 守卫)。 */
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
*/
|
||||
#include "comm/ethercat_sync.h"
|
||||
#include "sys/config.h"
|
||||
#include "main.h" /* g_app_uptime_sec */
|
||||
#include <cstdio>
|
||||
|
||||
extern "C" {
|
||||
@@ -25,6 +26,11 @@ void Sync0_Isr(void);
|
||||
void Sync1_Isr(void);
|
||||
}
|
||||
|
||||
bool EthercatSync::s_active = false;
|
||||
volatile uint32_t EthercatSync::s_sync0Count = 0U;
|
||||
volatile uint32_t EthercatSync::s_sync1Count = 0U;
|
||||
volatile uint32_t EthercatSync::s_escIrqCount = 0U;
|
||||
|
||||
void EthercatSync::init()
|
||||
{
|
||||
#if (CONFIG_ETHERCAT_SYNC_MODE == 0)
|
||||
@@ -96,6 +102,9 @@ void EthercatSync::init()
|
||||
(unsigned)CONFIG_IRQ_PREEMPT_ETHERCAT_SYNC1);
|
||||
|
||||
printf("[ECAT][SYNC] init: OK\r\n");
|
||||
/* 全部 EXTI/NVIC 使能完成,HBI 出现并发用户(主循环 vs SYNC0/IRQ ISR),
|
||||
* 之后 EXMC 诊断必须让位(diagDump 内部据此跳过)。 */
|
||||
s_active = true;
|
||||
}
|
||||
|
||||
__attribute__((section(".ramfunc"), noinline))
|
||||
@@ -104,6 +113,7 @@ void EthercatSync::onSync0Exti()
|
||||
/* 直接寄存器访问,绕过 GD32 库函数以消除可变抖动 */
|
||||
if ((EXTI_PD & (uint32_t)CONFIG_ESC_SYNC0_EXTI) != RESET) {
|
||||
EXTI_PD = (uint32_t)CONFIG_ESC_SYNC0_EXTI;
|
||||
++s_sync0Count;
|
||||
Sync0_Isr();
|
||||
}
|
||||
}
|
||||
@@ -112,7 +122,17 @@ void EthercatSync::onEscIrqExti()
|
||||
{
|
||||
if (exti_interrupt_flag_get(CONFIG_ESC_IRQ_EXTI) != RESET) {
|
||||
exti_interrupt_flag_clear(CONFIG_ESC_IRQ_EXTI);
|
||||
++s_escIrqCount;
|
||||
/* PDI_Isr(preempt=1) 可被 Sync0_Isr(preempt=0) 抢占;HBI Indexed 是两步
|
||||
* 事务(IDX 写 + DATA 读),被抢占即错址。此处屏蔽 EXTI0 实现互斥:
|
||||
* SYNC0 边沿在屏蔽期到来会锁存在 pending,退出后立即补偿执行,
|
||||
* 代价是 PDI_Isr 剩余时长(µs 级)计入 SYNC0 抖动。 */
|
||||
const uint32_t prevSync0 = NVIC_GetEnableIRQ(CONFIG_ESC_SYNC0_IRQn);
|
||||
NVIC_DisableIRQ(CONFIG_ESC_SYNC0_IRQn);
|
||||
PDI_Isr();
|
||||
if (prevSync0 != 0U) {
|
||||
NVIC_EnableIRQ(CONFIG_ESC_SYNC0_IRQn);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -120,7 +140,14 @@ void EthercatSync::onSync1Exti()
|
||||
{
|
||||
if (exti_interrupt_flag_get(CONFIG_ESC_SYNC1_EXTI) != RESET) {
|
||||
exti_interrupt_flag_clear(CONFIG_ESC_SYNC1_EXTI);
|
||||
++s_sync1Count;
|
||||
/* 同 onEscIrqExti:Sync1_Isr(preempt=1) 内的 HBI 访问需与 SYNC0 互斥 */
|
||||
const uint32_t prevSync0 = NVIC_GetEnableIRQ(CONFIG_ESC_SYNC0_IRQn);
|
||||
NVIC_DisableIRQ(CONFIG_ESC_SYNC0_IRQn);
|
||||
Sync1_Isr();
|
||||
if (prevSync0 != 0U) {
|
||||
NVIC_EnableIRQ(CONFIG_ESC_SYNC0_IRQn);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -150,4 +177,10 @@ void EXTI1_IRQHandler(void) { EthercatSync::onEscIrqExti(); }
|
||||
void EXTI2_IRQHandler(void) { EthercatSync::onSync1Exti(); }
|
||||
#endif
|
||||
|
||||
/* ---- SSC 诊断对象 0x2000 的 C 侧访问 (dc_app.c APPL_InputMapping 每周期拉取) ---- */
|
||||
uint32_t EcatDbg_GetSync0(void) { return EthercatSync::getSync0Count(); }
|
||||
uint32_t EcatDbg_GetSync1(void) { return EthercatSync::getSync1Count(); }
|
||||
uint32_t EcatDbg_GetEscIrq(void) { return EthercatSync::getEscIrqCount(); }
|
||||
uint32_t EcatDbg_GetUptimeSec(void){ return g_app_uptime_sec; }
|
||||
|
||||
} /* extern "C" */
|
||||
|
||||
@@ -8,11 +8,24 @@
|
||||
#ifndef COMM_ETHERCAT_SYNC_H
|
||||
#define COMM_ETHERCAT_SYNC_H
|
||||
|
||||
#include <cstdint>
|
||||
|
||||
class EthercatSync {
|
||||
public:
|
||||
/** @brief 初始化 SYNC0/IRQ/SYNC1 EXTI 引脚与 NVIC(或降级模式 TIMER0)。 */
|
||||
static void init();
|
||||
|
||||
/** @brief 同步 ISR 是否已激活(init 成功使能 EXTI NVIC 后为 true)。
|
||||
* @note EXMC/HBI 诊断(会拆总线)必须在此返回 false 时才允许执行。 */
|
||||
static bool isActive() { return s_active; }
|
||||
|
||||
/** @brief SYNC0 中断累计次数(DC 通路验证:主站设 1ms 周期时应 ~1000/s)。 */
|
||||
static uint32_t getSync0Count() { return s_sync0Count; }
|
||||
/** @brief SYNC1 中断累计次数。 */
|
||||
static uint32_t getSync1Count() { return s_sync1Count; }
|
||||
/** @brief ESC IRQ(PDI AL Event)中断累计次数。 */
|
||||
static uint32_t getEscIrqCount() { return s_escIrqCount; }
|
||||
|
||||
/** @brief SYNC0 EXTI 中断处理(EXTI0)。 */
|
||||
static void onSync0Exti();
|
||||
/** @brief ESC IRQ EXTI 中断处理(EXTI1)。 */
|
||||
@@ -21,6 +34,12 @@ public:
|
||||
static void onSync1Exti();
|
||||
/** @brief TIMER0 降级模式中断处理。 */
|
||||
static void onTimerIrq();
|
||||
|
||||
private:
|
||||
static bool s_active; /**< init() 成功后置位(EXTI NVIC 已使能,HBI 有并发用户)。 */
|
||||
static volatile uint32_t s_sync0Count; /**< SYNC0 ISR 累计次数(ISR 内自增)。 */
|
||||
static volatile uint32_t s_sync1Count; /**< SYNC1 ISR 累计次数。 */
|
||||
static volatile uint32_t s_escIrqCount; /**< ESC IRQ ISR 累计次数。 */
|
||||
};
|
||||
|
||||
#endif /* COMM_ETHERCAT_SYNC_H */
|
||||
|
||||
@@ -5,10 +5,11 @@
|
||||
* 本文件替换 Beckhoff EL9800 SPI 版本,提供 SSC 所需的 HW_EscRead/Write 宏
|
||||
* 和中断/定时器控制宏。实际实现在 hw_gd32exmc.cpp。
|
||||
*
|
||||
* HBI Indexed 寻址(A2=PD12=A17, A1=PD11=A16):
|
||||
* HBI Indexed 寻址(A2=LAN9252 Pin29←PD12=EXMC_A17, A1=Pin25←PD11=EXMC_A16;
|
||||
* EXMC 16-bit 模式地址左移 1 位,EXMC_A16=CPU 偏移 0x20000、A17=0x40000):
|
||||
* IDX_0 = EXMC offset 0x00000 (A2=0, A1=0)
|
||||
* DATA_0 = EXMC offset 0x20000 (A2=1, A1=0) 低 16 位
|
||||
* = EXMC offset 0x30000 (A2=1, A1=1) 高 16 位
|
||||
* DATA_0 = EXMC offset 0x40000 (A2=1, A1=0) 低 16 位
|
||||
* = EXMC offset 0x60000 (A2=1, A1=1) 高 16 位
|
||||
*/
|
||||
#ifndef _EL9800HW_H_
|
||||
#define _EL9800HW_H_
|
||||
@@ -51,10 +52,11 @@
|
||||
|
||||
/*-----------------------------------------------------------------------------------------
|
||||
------
|
||||
------ AL Event Interrupt Mask (主循环保护)
|
||||
------ AL Event Interrupt Mask (保留宏)
|
||||
------
|
||||
------ SSC 在主循环中访问 ESC 时需要 mask SYNC0 ISR (EXTI0, preempt=0)
|
||||
------ 防止 HBI IDX 寄存器被 ISR 抢占导致地址错乱。
|
||||
------ 1.5.27 起主循环 HBI 访问的互斥已内置到 hw_gd32exmc.cpp 的 HW_* 函数
|
||||
------ (EscExmcBus::maskEnter/maskExit),SSC 源码不再需要显式调用本组宏。
|
||||
------ 保留定义仅为兼容 SSC 变体中可能出现的引用。
|
||||
-----------------------------------------------------------------------------------------*/
|
||||
|
||||
#ifndef DISABLE_AL_EVENT_INT
|
||||
|
||||
@@ -26,30 +26,73 @@
|
||||
<Sm StartAddress="#x1400" ControlByte="#x64" Enable="1">Outputs</Sm>
|
||||
<!-- SM3: Process Data Input (TxPDO, Slave -> Master) -->
|
||||
<Sm StartAddress="#x1480" ControlByte="#x20" Enable="1">Inputs</Sm>
|
||||
<!-- RxPDO (Output) -->
|
||||
<TxPdo Fixed="1" Mandatory="1" Sm="2">
|
||||
<!-- RxPDO (Output, Master -> Slave): 索引 0x1600-0x17FF,映射 0x7000:01..04
|
||||
(1.5.27:与 dc_app.c 对齐,4×64bit;原 TxPdo/RxPdo 元素互换 + 1×256bit 均已修正) -->
|
||||
<RxPdo Fixed="1" Mandatory="1" Sm="2">
|
||||
<Index>#x1600</Index>
|
||||
<Name>Output</Name>
|
||||
<Entry>
|
||||
<Index>#x7000</Index>
|
||||
<SubIndex>1</SubIndex>
|
||||
<BitLen>256</BitLen>
|
||||
<Name>Control Data</Name>
|
||||
<DataType>ARRAY [0..31] OF BYTE</DataType>
|
||||
<BitLen>64</BitLen>
|
||||
<Name>Control Data 1</Name>
|
||||
<DataType>ULINT</DataType>
|
||||
</Entry>
|
||||
</TxPdo>
|
||||
<!-- TxPDO (Input) -->
|
||||
<RxPdo Fixed="1" Mandatory="1" Sm="3">
|
||||
<Entry>
|
||||
<Index>#x7000</Index>
|
||||
<SubIndex>2</SubIndex>
|
||||
<BitLen>64</BitLen>
|
||||
<Name>Control Data 2</Name>
|
||||
<DataType>ULINT</DataType>
|
||||
</Entry>
|
||||
<Entry>
|
||||
<Index>#x7000</Index>
|
||||
<SubIndex>3</SubIndex>
|
||||
<BitLen>64</BitLen>
|
||||
<Name>Control Data 3</Name>
|
||||
<DataType>ULINT</DataType>
|
||||
</Entry>
|
||||
<Entry>
|
||||
<Index>#x7000</Index>
|
||||
<SubIndex>4</SubIndex>
|
||||
<BitLen>64</BitLen>
|
||||
<Name>Control Data 4</Name>
|
||||
<DataType>ULINT</DataType>
|
||||
</Entry>
|
||||
</RxPdo>
|
||||
<!-- TxPDO (Input, Slave -> Master): 索引 0x1A00-0x1BFF,映射 0x6000:01..04 -->
|
||||
<TxPdo Fixed="1" Mandatory="1" Sm="3">
|
||||
<Index>#x1A00</Index>
|
||||
<Name>Input</Name>
|
||||
<Entry>
|
||||
<Index>#x6000</Index>
|
||||
<SubIndex>1</SubIndex>
|
||||
<BitLen>256</BitLen>
|
||||
<Name>Status Data</Name>
|
||||
<DataType>ARRAY [0..31] OF BYTE</DataType>
|
||||
<BitLen>64</BitLen>
|
||||
<Name>Status Data 1</Name>
|
||||
<DataType>ULINT</DataType>
|
||||
</Entry>
|
||||
</RxPdo>
|
||||
<Entry>
|
||||
<Index>#x6000</Index>
|
||||
<SubIndex>2</SubIndex>
|
||||
<BitLen>64</BitLen>
|
||||
<Name>Status Data 2</Name>
|
||||
<DataType>ULINT</DataType>
|
||||
</Entry>
|
||||
<Entry>
|
||||
<Index>#x6000</Index>
|
||||
<SubIndex>3</SubIndex>
|
||||
<BitLen>64</BitLen>
|
||||
<Name>Status Data 3</Name>
|
||||
<DataType>ULINT</DataType>
|
||||
</Entry>
|
||||
<Entry>
|
||||
<Index>#x6000</Index>
|
||||
<SubIndex>4</SubIndex>
|
||||
<BitLen>64</BitLen>
|
||||
<Name>Status Data 4</Name>
|
||||
<DataType>ULINT</DataType>
|
||||
</Entry>
|
||||
</TxPdo>
|
||||
<Mailbox DataLinkLayer="true">
|
||||
<CoE SdoInfo="true" CompleteAccess="true" SegmentedSdo="true"/>
|
||||
</Mailbox>
|
||||
@@ -60,6 +103,7 @@
|
||||
<AssignActivate>#x0300</AssignActivate>
|
||||
<CycleTimeSync0 Factor="1">0</CycleTimeSync0>
|
||||
<ShiftTimeSync0>0</ShiftTimeSync0>
|
||||
<CycleTimeSync1 Factor="1">0</CycleTimeSync1>
|
||||
</OpMode>
|
||||
<OpMode>
|
||||
<Name>Free Run</Name>
|
||||
@@ -69,7 +113,17 @@
|
||||
</Dc>
|
||||
<Eeprom>
|
||||
<ByteSize>4096</ByteSize>
|
||||
<ConfigData>050C000000000000</ConfigData>
|
||||
<!-- SII 前 14 字节 = word0-6(word7 校验和由 TwinCAT 写 EEPROM 时自算,
|
||||
与 eeprom_image.h 保持一致):
|
||||
word0 = PDI Control 0x008D(HBI 16-bit indexed,DS 表14-2)
|
||||
word1 = PDI Config 0x0150=0x00(CS/RD/WR 低有效,EXMC 匹配)
|
||||
+ Sync/Latch 0x0151=0xCC(SYNC0/1 映射+输出,推挽低有效,
|
||||
配 EXTI 下降沿;1.5.28 修复——原 0x00 会把 SYNC0 留在
|
||||
LATCH 输入态,DC 模式下 SYNC0 引脚永不输出、过程数据死)
|
||||
word2 = Sync 脉宽 0x1388=5000x10ns=50us(0=确认模式,不可用)
|
||||
word3-6 = 0。
|
||||
1.5.27 修复:原 0x05 首字节是 LAN9252 保留 PDI 值,会废掉 HBI。 -->
|
||||
<ConfigData>8D0000CC88130000000000000000</ConfigData>
|
||||
</Eeprom>
|
||||
</Device>
|
||||
</Devices>
|
||||
|
||||
Binary file not shown.
@@ -2,13 +2,14 @@
|
||||
* @file dc_app.c
|
||||
* @brief DCU EtherCAT 应用层 — SSC APPL_ 回调 + 应用对象字典
|
||||
*
|
||||
* 对象字典结构 (匹配 DCU_ESI.xml / eeprom_image.h):
|
||||
* 对象字典结构 (匹配 DCU_ESI.xml / gen_eeprom.py):
|
||||
* 0x1C12 RxPDO Assign → 0x1600
|
||||
* 0x1600 RxPDO Mapping → 0x7000:01..04 (4×64bit = 32 bytes)
|
||||
* 0x7000 Output Data (主站→从站, 32 bytes)
|
||||
* 0x1C13 TxPDO Assign → 0x1A00
|
||||
* 0x1A00 TxPDO Mapping → 0x6000:01..04 (4×64bit = 32 bytes)
|
||||
* 0x6000 Input Data (从站→主站, 32 bytes)
|
||||
* 0x2000 SYNC Diag (只读: sync0/sync1/escirq/uptime, 主站 SDO 轮询看 DC 通路)
|
||||
*/
|
||||
|
||||
#include "ecat_def.h"
|
||||
@@ -48,6 +49,29 @@ TOBJ6000;
|
||||
static TOBJ7000 s_outputObj = {4, {0, 0, 0, 0}};
|
||||
static TOBJ6000 s_inputObj = {4, {0, 0, 0, 0}};
|
||||
|
||||
/*---------------------------------------------------------------------------
|
||||
* SYNC 诊断对象 0x2000 (只读, 主站 SDO 轮询)
|
||||
* sub1=SYNC0 计数 sub2=SYNC1 计数 sub3=ESC IRQ 计数 sub4=uptime 秒
|
||||
* 值在 APPL_InputMapping 每周期从 EthercatSync 拉取(见 ethercat_sync.cpp 的
|
||||
* extern "C" getter)。主站用相邻两次 sync0 差 / uptime 差即得 SYNC0 速率。
|
||||
*---------------------------------------------------------------------------*/
|
||||
typedef struct OBJ_STRUCT_PACKED_START {
|
||||
UINT16 u16SubIndex0;
|
||||
UINT32 u32Sync0; /* sub1 */
|
||||
UINT32 u32Sync1; /* sub2 */
|
||||
UINT32 u32EscIrq; /* sub3 */
|
||||
UINT32 u32UptimeS; /* sub4 */
|
||||
} OBJ_STRUCT_PACKED_END
|
||||
TOBJ2000;
|
||||
|
||||
static TOBJ2000 s_syncDiag = {4, 0, 0, 0, 0};
|
||||
|
||||
/* EthercatSync 计数器 getter (C++ 侧 extern "C" 导出) */
|
||||
extern UINT32 EcatDbg_GetSync0(void);
|
||||
extern UINT32 EcatDbg_GetSync1(void);
|
||||
extern UINT32 EcatDbg_GetEscIrq(void);
|
||||
extern UINT32 EcatDbg_GetUptimeSec(void);
|
||||
|
||||
/*---------------------------------------------------------------------------
|
||||
* PDO Assign 对象 (0x1C12 / 0x1C13)
|
||||
*---------------------------------------------------------------------------*/
|
||||
@@ -109,6 +133,12 @@ OBJCONST TSDOINFOENTRYDESC OBJMEM asDataEntryDesc[] = {
|
||||
{DEFTYPE_UNSIGNED64, 0x40, ACCESS_READWRITE}
|
||||
};
|
||||
|
||||
/* SYNC 诊断 0x2000: Sub0=UINT8 count, Sub1..4=UINT32 (只读) */
|
||||
OBJCONST TSDOINFOENTRYDESC OBJMEM asSyncDiagEntryDesc[] = {
|
||||
{DEFTYPE_UNSIGNED8, 0x08, ACCESS_READ},
|
||||
{DEFTYPE_UNSIGNED32, 0x20, ACCESS_READ}
|
||||
};
|
||||
|
||||
/*---------------------------------------------------------------------------
|
||||
* Object Names
|
||||
*---------------------------------------------------------------------------*/
|
||||
@@ -119,6 +149,7 @@ OBJCONST UCHAR OBJMEM aName0x1600[] = "RxPDO mapping";
|
||||
OBJCONST UCHAR OBJMEM aName0x1A00[] = "TxPDO mapping";
|
||||
OBJCONST UCHAR OBJMEM aName0x7000[] = "Output Data";
|
||||
OBJCONST UCHAR OBJMEM aName0x6000[] = "Input Data";
|
||||
OBJCONST UCHAR OBJMEM aName0x2000[] = "SYNC Diag";
|
||||
|
||||
/*---------------------------------------------------------------------------
|
||||
* Application Object Dictionary
|
||||
@@ -149,6 +180,10 @@ TOBJECT OBJMEM ApplicationObjDicList[] = {
|
||||
{NULL, NULL, 0x6000, {DEFTYPE_RECORD, 4 | (OBJCODE_REC << 8)},
|
||||
asDataEntryDesc, aName0x6000, &s_inputObj, NULL, NULL, 0x0000},
|
||||
|
||||
/* 0x2000: SYNC 诊断 (只读, 主站 SDO 轮询验证 DC 通路) */
|
||||
{NULL, NULL, 0x2000, {DEFTYPE_RECORD, 4 | (OBJCODE_REC << 8)},
|
||||
asSyncDiagEntryDesc, aName0x2000, &s_syncDiag, NULL, NULL, 0x0000},
|
||||
|
||||
/* 终止条目 */
|
||||
{NULL, NULL, 0xFFFF, {0, 0}, NULL, NULL, NULL, NULL, NULL, 0x000}
|
||||
};
|
||||
@@ -201,6 +236,14 @@ void APPL_AckErrorInd(UINT16 stateTrans)
|
||||
*---------------------------------------------------------------------------*/
|
||||
void APPL_InputMapping(UINT16 *pInputData)
|
||||
{
|
||||
/* 每 PDO 周期刷新 SYNC 诊断快照(free-run 走 MainLoop、DC 走 Sync0_Isr,
|
||||
* 两条路径都会到这里)。UINT32 对齐读写在 CM4 上原子,SDO 读侧最坏拿到
|
||||
* 跨字段相差一个周期的快照,对速率测量无影响。 */
|
||||
s_syncDiag.u32Sync0 = EcatDbg_GetSync0();
|
||||
s_syncDiag.u32Sync1 = EcatDbg_GetSync1();
|
||||
s_syncDiag.u32EscIrq = EcatDbg_GetEscIrq();
|
||||
s_syncDiag.u32UptimeS = EcatDbg_GetUptimeSec();
|
||||
|
||||
memcpy(pInputData, &s_inputObj.u64Data[0], 32);
|
||||
}
|
||||
|
||||
@@ -227,3 +270,16 @@ void APPL_Application(void)
|
||||
s_inputObj.u64Data[2] = s_outputObj.u64Data[2];
|
||||
s_inputObj.u64Data[3] = s_outputObj.u64Data[3];
|
||||
}
|
||||
|
||||
/*---------------------------------------------------------------------------
|
||||
* AL 诊断快照 — task_runner 5s 心跳用 (C 链接, C++ 侧 extern "C" 声明)
|
||||
* [19:16]=AL 状态(0x0130 低 4 位) [31:24]=AL 错误码(0x0130 高 8 位)
|
||||
* [15:0] =AL 事件寄存器(0x0220, bit0=主站 AL 控制请求待处理)
|
||||
*---------------------------------------------------------------------------*/
|
||||
UINT32 EcatDbg_ReadAlDiag(void)
|
||||
{
|
||||
UINT16 alStatus = 0U;
|
||||
HW_EscReadWord(alStatus, ESC_AL_STATUS_OFFSET);
|
||||
alStatus = SWAPWORD(alStatus);
|
||||
return ((UINT32)alStatus << 16) | (UINT32)SWAPWORD(HW_GetALEventRegister());
|
||||
}
|
||||
|
||||
@@ -563,7 +563,7 @@
|
||||
|
||||
/*-----------------------------------------------------------------------------------------
|
||||
------
|
||||
------ PDO Size (match CONFIG_ETHERCAT_PDO_SIZE in sys/config.h)
|
||||
------ PDO Size (需与 DCU_ESI.xml / gen_eeprom.py 的 PDO 尺寸一致: 4×64bit=32B)
|
||||
------
|
||||
-----------------------------------------------------------------------------------------*/
|
||||
|
||||
|
||||
@@ -1,53 +0,0 @@
|
||||
/**
|
||||
* @file eeprom_image.h
|
||||
* @brief LAN9252 EEPROM (ESI) binary image - auto-generated by gen_eeprom.py
|
||||
*
|
||||
* Vendor ID: 0x00000000
|
||||
* Product Code: 0x00009252
|
||||
* Revision: 0x00010000
|
||||
* Serial: 0x00000001
|
||||
* Size: 4096 bytes (AT24C32)
|
||||
*
|
||||
* DO NOT EDIT - regenerate with: python gen_eeprom.py > eeprom_image.h
|
||||
*/
|
||||
#ifndef COMM_SSC_PORT_EEPROM_IMAGE_H
|
||||
#define COMM_SSC_PORT_EEPROM_IMAGE_H
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
static const uint8_t ESI_EEPROM_IMAGE[4096] = {
|
||||
/* 0x0000 */ 0x8D, 0x0C, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x52, 0x92, 0xDF, 0x9E,
|
||||
/* 0x0010 */ 0x00, 0x00, 0x01, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
/* 0x0020 */ 0x00, 0x10, 0x00, 0x01, 0x00, 0x12, 0x00, 0x01, 0x00, 0x10, 0x00, 0x01, 0x00, 0x12, 0x00, 0x01,
|
||||
/* 0x0030 */ 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
/* 0x0040 */ 0x00, 0x10, 0x00, 0x01, 0x26, 0x00, 0x01, 0x00, 0x00, 0x12, 0x00, 0x01, 0x22, 0x00, 0x01, 0x00,
|
||||
/* 0x0050 */ 0x00, 0x14, 0x20, 0x00, 0x64, 0x00, 0x01, 0x00, 0x80, 0x14, 0x20, 0x00, 0x20, 0x00, 0x01, 0x00,
|
||||
/* 0x0060 */ 0x01, 0x02, 0x0A, 0x0D, 0x02, 0x0B, 0x44, 0x43, 0x55, 0x20, 0x50, 0x72, 0x6F, 0x6A, 0x65, 0x63,
|
||||
/* 0x0070 */ 0x74, 0x0C, 0x47, 0x44, 0x33, 0x32, 0x45, 0x35, 0x30, 0x38, 0x20, 0x44, 0x43, 0x55, 0x14, 0x08,
|
||||
/* 0x0080 */ 0x01, 0x02, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
/* 0x0090 */ 0x1E, 0x01, 0x01, 0x02, 0x28, 0x10, 0x00, 0x10, 0x00, 0x01, 0x26, 0x00, 0x01, 0x00, 0x00, 0x12,
|
||||
/* 0x00A0 */ 0x00, 0x01, 0x22, 0x00, 0x01, 0x00, 0x00, 0x14, 0x20, 0x00, 0x64, 0x00, 0x01, 0x00, 0x80, 0x14,
|
||||
/* 0x00B0 */ 0x20, 0x00, 0x20, 0x00, 0x01, 0x00, 0x32, 0x11, 0x00, 0x16, 0x02, 0x00, 0x00, 0x00, 0x01, 0x00,
|
||||
/* 0x00C0 */ 0x00, 0x70, 0x01, 0x00, 0x0F, 0x00, 0x01, 0x00, 0x00, 0x00, 0x1A, 0x03, 0x00, 0x00, 0x00, 0x01,
|
||||
/* 0x00D0 */ 0x00, 0x00, 0x60, 0x01, 0x00, 0x0F, 0x00, 0x01, 0x00, 0x00, 0x3C, 0x09, 0x00, 0x03, 0x00, 0x00,
|
||||
/* 0x00E0 */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
/* 0x0100: (0xFF padding) */
|
||||
/* 0x0200: (0xFF padding) */
|
||||
/* 0x0300: (0xFF padding) */
|
||||
/* 0x0400: (0xFF padding) */
|
||||
/* 0x0500: (0xFF padding) */
|
||||
/* 0x0600: (0xFF padding) */
|
||||
/* 0x0700: (0xFF padding) */
|
||||
/* 0x0800: (0xFF padding) */
|
||||
/* 0x0900: (0xFF padding) */
|
||||
/* 0x0A00: (0xFF padding) */
|
||||
/* 0x0B00: (0xFF padding) */
|
||||
/* 0x0C00: (0xFF padding) */
|
||||
/* 0x0D00: (0xFF padding) */
|
||||
/* 0x0E00: (0xFF padding) */
|
||||
/* 0x0F00: (0xFF padding) */
|
||||
};
|
||||
|
||||
static const uint32_t ESI_EEPROM_SIZE = 4096U;
|
||||
|
||||
#endif /* COMM_SSC_PORT_EEPROM_IMAGE_H */
|
||||
@@ -1,13 +1,45 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
Generate LAN9252 EEPROM binary image (ESI) for GD32E508 DCU.
|
||||
Generate LAN9252 EEPROM binary image (ESI/SII) for GD32E508 DCU.
|
||||
Outputs a C header file with the EEPROM content as a byte array.
|
||||
|
||||
EEPROM format per ETG.1000.6 (EtherCAT Slave Information):
|
||||
- All multi-byte values are little-endian
|
||||
- Addressing is in bytes (the ESC EEPROM interface uses word addresses internally,
|
||||
but the content is byte-addressed)
|
||||
- Total size: 4096 bytes (AT24C32)
|
||||
Layout per ETG.1000.6 section 5.4 "SII coding" (Table 16/17, verified against the
|
||||
local spec copy ETG1000_6_V1i0i3; 1.5.27 rewrite of the previous generator whose
|
||||
layout was shifted by 2 words from word 4 onwards and used XOR instead of CRC-8):
|
||||
|
||||
Word 0x00 PDI Control (byte0=PDI type 0x8D HBI 16-bit indexed,
|
||||
byte1=ESC Configuration 0x00)
|
||||
Word 0x01 PDI Configuration (0x0150 HBI mode: CS/RD/WR active-low,
|
||||
RD/WR strobe mode -> 0x0000 matches EXMC)
|
||||
Word 0x02 SyncImpulseLen 0x0000
|
||||
Word 0x03 PDI Configuration2 0x0000
|
||||
Word 0x04 Configured Station Alias
|
||||
Word 0x05-0x06 Reserved (zero)
|
||||
Word 0x07 Checksum: low byte = CRC-8 (poly 0x07, init 0xFF, no reflection,
|
||||
no final XOR) over bytes 0..13; high byte = 0x00
|
||||
Word 0x08-0x09 Vendor ID
|
||||
Word 0x0A-0x0B Product Code
|
||||
Word 0x0C-0x0D Revision Number
|
||||
Word 0x0E-0x0F Serial Number
|
||||
Word 0x10-0x13 Reserved (zero)
|
||||
Word 0x14/0x15 Bootstrap Receive Mailbox Offset/Size
|
||||
Word 0x16/0x17 Bootstrap Send Mailbox Offset/Size
|
||||
Word 0x18/0x19 Standard Receive Mailbox Offset/Size
|
||||
Word 0x1A/0x1B Standard Send Mailbox Offset/Size
|
||||
Word 0x1C Mailbox Protocol (0x0004 = CoE per Table 18; NOT 0x0001)
|
||||
Word 0x1D-0x3D Reserved (zero)
|
||||
Word 0x3E Size of EEPROM = size in KiBit + 1 (AT24C32 32 KiBit -> 33)
|
||||
Word 0x3F Version = 1
|
||||
Word 0x40 (byte 128) first category. Category header is FOUR bytes:
|
||||
[type word LE][following data size in words LE]
|
||||
(old generator used a 2-byte header and started at byte 98).
|
||||
Terminator: 0xFFFF word (Table 19 "End").
|
||||
|
||||
Category types (Table 19): STRINGS=10, General=30, FMMU=40, SyncM=41,
|
||||
TxPDO=50, RxPDO=51, DC=60 ("future use", omitted - DC config comes from ESI XML).
|
||||
RxPDO indexes 0x1600-0x17FF (outputs, SM2); TxPDO indexes 0x1A00-0x1BFF (inputs, SM3)
|
||||
per Table 24. PDO entry datatype byte = CoE/ETG.5101 datatype code (UNSIGNED64=0x1B,
|
||||
matches dc_app.c DEFTYPE_UNSIGNED64); BitLen is an Unsigned8 in the SII entry.
|
||||
|
||||
Usage: python gen_eeprom.py > eeprom_image.h
|
||||
"""
|
||||
@@ -15,19 +47,18 @@ import struct
|
||||
import sys
|
||||
|
||||
# ============================================================
|
||||
# Configuration (must match ecat_def.h and DCU_ESI.xml)
|
||||
# Configuration (must match ecat_def.h / dc_app.c / DCU_ESI.xml)
|
||||
# ============================================================
|
||||
VENDOR_ID = 0x00000000
|
||||
PRODUCT_CODE = 0x00009252
|
||||
REVISION_NO = 0x00010000
|
||||
SERIAL_NO = 0x00000001
|
||||
|
||||
# Mailbox configuration
|
||||
# Mailbox configuration (ESC process RAM: mailbox 0x1000-0x13FF, PDO 0x1400-0x14BF)
|
||||
MBX_RX_OFFSET = 0x1000 # SM0: Master -> Slave (receive mailbox)
|
||||
MBX_RX_SIZE = 256
|
||||
MBX_TX_OFFSET = 0x1200 # SM1: Slave -> Master (send mailbox)
|
||||
MBX_TX_SIZE = 256
|
||||
MBX_PROTOCOL = 0x0001 # CoE
|
||||
|
||||
# Process Data configuration
|
||||
PD_OUT_OFFSET = 0x1400 # SM2: RxPDO (Master -> Slave outputs)
|
||||
@@ -35,23 +66,38 @@ PD_OUT_SIZE = 32
|
||||
PD_IN_OFFSET = 0x1480 # SM3: TxPDO (Slave -> Master inputs)
|
||||
PD_IN_SIZE = 32
|
||||
|
||||
# PDI type: LAN9252 HBI 16-bit indexed mode
|
||||
# 数据手册 LAN9252_en.pdf 表 14-2 (PDI 模式选择):
|
||||
# 0x04=DIG I/O, 0x80=SPI, 0x88~0x8B=HBI 复用, 0x8C=HBI 变址8位, 0x8D=HBI 变址16位
|
||||
# 本设计用 16-bit HBI Indexed Mode(A1/A2 选 IDX/DAT),必须写 0x8D。
|
||||
# 注意:0x05 是保留值!若 EEPROM 写入 0x05 或 EEPROM 空白(默认 0x00 保留),
|
||||
# LAN9252 的 PDI 无效,主机总线(HBI)完全不响应——表现为所有总线读回都是上次写入的回显。
|
||||
# PDI type: LAN9252 HBI 16-bit indexed mode (DS Table 14-2: 0x8C=HBI 8-bit,
|
||||
# 0x8D=HBI 16-bit). 0x05 is a reserved value on LAN9252 and disables the HBI PDI.
|
||||
PDI_CONTROL = 0x8D
|
||||
PDI_CONFIG = 0x0C
|
||||
# Word 0 high byte = ESC Configuration register (0x0141): all zero
|
||||
# (the old 0x0C hit "NOT set by EEPROM" DC bits - removed in 1.5.27).
|
||||
ESC_CONFIG = 0x00
|
||||
# Word 1 low byte = PDI Configuration register (0x0150) HBI-mode bits: ALE
|
||||
# qual / RD_WR mode / CS / RD / WR / ALE polarities. 0x00 = normal active-low
|
||||
# CS,RD,WR and RD/WR strobe mode - exactly what EXMC NE0/NOE/NWE provides
|
||||
# (DS p22 pin table: RD/WR/CS "normally active low", polarity bit 0 = normal).
|
||||
PDI_CONFIG = 0x00
|
||||
# Word 1 high byte = Sync/Latch PDI Configuration register (0x0151), loaded
|
||||
# from EEPROM word 1 bits 15:8 (DS Table 12-3 p202). 0xCC = SYNC1 Map=1,
|
||||
# SYNC1 Output=1, SYNC0 Map=1, SYNC0 Output=1, both Driver/Polarity=00
|
||||
# (push-pull ACTIVE LOW, DS p247). Active-low pulses pair with the firmware's
|
||||
# falling-edge EXTI; with 0x00 the SYNC0/1 pins stay LATCH inputs and a DC
|
||||
# SYNC0 event would never reach PB0 -> process data dead in DC mode (1.5.28).
|
||||
# Same value as the official LAN9252-EVB-HBIXML.xml ConfigData 890E80CC...
|
||||
SYNC_LATCH_CFG = 0xCC
|
||||
# Word 2 = Pulse Length of SyncSignals register (0x0982), units of 10ns
|
||||
# (DS p284). 5000 = 50us pulse, the LAN9252 EVB default. 0 = Acknowledge Mode
|
||||
# (level held until PDI reads the SYNC0 status register) - unusable with an
|
||||
# edge-triggered EXTI sampling design.
|
||||
SYNC_PULSE_LEN = 5000
|
||||
|
||||
EEPROM_SIZE = 4096
|
||||
EEPROM_SIZE = 4096 # AT24C32
|
||||
EEPROM_KBIT = 32 # 4096 bytes = 32 KiBit
|
||||
|
||||
# ============================================================
|
||||
# Build EEPROM image
|
||||
# ============================================================
|
||||
eeprom = bytearray(EEPROM_SIZE)
|
||||
# Fill with 0xFF (erased state)
|
||||
eeprom[:] = b'\xFF' * EEPROM_SIZE
|
||||
eeprom = bytearray(b'\xFF' * EEPROM_SIZE)
|
||||
|
||||
def w16(offset, value):
|
||||
"""Write 16-bit little-endian value at byte offset."""
|
||||
@@ -61,274 +107,193 @@ def w32(offset, value):
|
||||
"""Write 32-bit little-endian value at byte offset."""
|
||||
struct.pack_into('<I', eeprom, offset, value & 0xFFFFFFFF)
|
||||
|
||||
# --- Header (bytes 0-63) ---
|
||||
# Word 0: PDI Control + PDI Config
|
||||
eeprom[0] = PDI_CONTROL
|
||||
eeprom[1] = PDI_CONFIG
|
||||
def sii_crc8(data):
|
||||
"""ETG.1000.6 word-7 checksum: CRC-8 poly 0x07, init 0xFF, no reflection."""
|
||||
crc = 0xFF
|
||||
for b in data:
|
||||
crc ^= b
|
||||
for _ in range(8):
|
||||
crc = ((crc << 1) ^ 0x07) & 0xFF if (crc & 0x80) else (crc << 1) & 0xFF
|
||||
return crc
|
||||
|
||||
# Word 1: ESC Configuration (Sync Watchdog divider etc.) - 0x0000
|
||||
w16(2, 0x0000)
|
||||
# --- Fixed area, words 0x00-0x3F (bytes 0-127) ---
|
||||
w16(0, (ESC_CONFIG << 8) | PDI_CONTROL) # PDI Control
|
||||
w16(2, (SYNC_LATCH_CFG << 8) | PDI_CONFIG) # PDI Cfg 0x0150 + Sync/Latch 0x0151
|
||||
w16(4, SYNC_PULSE_LEN) # Pulse Length of SyncSignals (x10ns)
|
||||
w16(6, 0x0000) # PDI Configuration2
|
||||
w16(8, 0x0000) # Configured Station Alias
|
||||
w16(10, 0x0000) # Reserved
|
||||
w16(12, 0x0000) # Reserved
|
||||
# word 7 checksum written below (needs bytes 0-13 finalized first)
|
||||
|
||||
# Word 2: PDI Config 2 + Sync Impulse Length - 0x0000
|
||||
w16(4, 0x0000)
|
||||
w32(16, VENDOR_ID) # Vendor ID (words 0x08-0x09)
|
||||
w32(20, PRODUCT_CODE) # Product Code (words 0x0A-0x0B)
|
||||
w32(24, REVISION_NO) # Revision (words 0x0C-0x0D)
|
||||
w32(28, SERIAL_NO) # Serial (words 0x0E-0x0F)
|
||||
|
||||
# Word 3: Alias + Reserved
|
||||
w16(6, 0x0000)
|
||||
w16(32, 0x0000) # Reserved (words 0x10-0x13)
|
||||
w16(34, 0x0000)
|
||||
w16(36, 0x0000)
|
||||
w16(38, 0x0000)
|
||||
|
||||
# Words 4-5: Vendor ID
|
||||
w32(8, VENDOR_ID)
|
||||
|
||||
# Words 6-7: Product Code
|
||||
w32(12, PRODUCT_CODE)
|
||||
|
||||
# Words 8-9: Revision Number
|
||||
w32(16, REVISION_NO)
|
||||
|
||||
# Words 10-11: Serial Number
|
||||
w32(20, SERIAL_NO)
|
||||
|
||||
# Words 12-15: Reserved (0x0000)
|
||||
for i in range(24, 32, 2):
|
||||
w16(i, 0x0000)
|
||||
|
||||
# Word 16: Bootstrap RX mailbox offset
|
||||
w16(32, MBX_RX_OFFSET)
|
||||
# Word 17: Bootstrap RX mailbox size
|
||||
w16(34, MBX_RX_SIZE)
|
||||
# Word 18: Bootstrap TX mailbox offset
|
||||
w16(36, MBX_TX_OFFSET)
|
||||
# Word 19: Bootstrap TX mailbox size
|
||||
w16(38, MBX_TX_SIZE)
|
||||
|
||||
# Word 20: Standard RX mailbox offset
|
||||
w16(40, MBX_RX_OFFSET)
|
||||
# Word 21: Standard RX mailbox size
|
||||
w16(40, MBX_RX_OFFSET) # Bootstrap RX mailbox (words 0x14-0x15)
|
||||
w16(42, MBX_RX_SIZE)
|
||||
# Word 22: Standard TX mailbox offset
|
||||
w16(44, MBX_TX_OFFSET)
|
||||
# Word 23: Standard TX mailbox size
|
||||
w16(44, MBX_TX_OFFSET) # Bootstrap TX mailbox (words 0x16-0x17)
|
||||
w16(46, MBX_TX_SIZE)
|
||||
w16(48, MBX_RX_OFFSET) # Standard RX mailbox (words 0x18-0x19)
|
||||
w16(50, MBX_RX_SIZE)
|
||||
w16(52, MBX_TX_OFFSET) # Standard TX mailbox (words 0x1A-0x1B)
|
||||
w16(54, MBX_TX_SIZE)
|
||||
w16(56, 0x0004) # Mailbox Protocol: CoE (word 0x1C)
|
||||
|
||||
# Word 24: Mailbox protocol (CoE = bit 0)
|
||||
w16(48, MBX_PROTOCOL)
|
||||
|
||||
# Words 25-31: Reserved
|
||||
for i in range(50, 64, 2):
|
||||
for i in range(58, 124, 2): # Reserved (words 0x1D-0x3D)
|
||||
w16(i, 0x0000)
|
||||
w16(124, EEPROM_KBIT + 1) # Size = KiBit + 1 (word 0x3E)
|
||||
w16(126, 0x0001) # Version = 1 (word 0x3F)
|
||||
|
||||
# --- SM Configuration (starts at byte 64, word 32) ---
|
||||
# Each SM entry: 8 bytes
|
||||
# [0:2] Start address (16-bit LE)
|
||||
# [2:4] Length (16-bit LE)
|
||||
# [4] Control byte
|
||||
# [5] Status (0)
|
||||
# [6] Activate (1=enabled)
|
||||
# [7] PDI control (0)
|
||||
sm_base = 64
|
||||
# Checksum (word 0x07) over bytes 0..13 - must come after word 0-6 are final
|
||||
w16(14, sii_crc8(eeprom[0:14]))
|
||||
|
||||
def write_sm(idx, start, length, control, activate=1):
|
||||
off = sm_base + idx * 8
|
||||
w16(off, start)
|
||||
w16(off + 2, length)
|
||||
eeprom[off + 4] = control
|
||||
eeprom[off + 5] = 0 # status
|
||||
eeprom[off + 6] = activate
|
||||
eeprom[off + 7] = 0 # PDI control
|
||||
# --- Categories start at word 0x40 = byte 128 ---
|
||||
cat = bytearray()
|
||||
|
||||
# SM0: Mailbox Receive (Master -> Slave), 3-buffer mode
|
||||
write_sm(0, MBX_RX_OFFSET, MBX_RX_SIZE, 0x26)
|
||||
# SM1: Mailbox Send (Slave -> Master), 3-buffer mode
|
||||
write_sm(1, MBX_TX_OFFSET, MBX_TX_SIZE, 0x22)
|
||||
# SM2: Process Data Output (RxPDO), 3-buffer, watchdog trigger
|
||||
write_sm(2, PD_OUT_OFFSET, PD_OUT_SIZE, 0x64)
|
||||
# SM3: Process Data Input (TxPDO), 3-buffer
|
||||
write_sm(3, PD_IN_OFFSET, PD_IN_SIZE, 0x20)
|
||||
def append_category(cat_type, data):
|
||||
"""ETG.1000.6 Table 17: [type word][size-in-words word][data...]"""
|
||||
if len(data) % 2 != 0:
|
||||
data += b'\x00' # pad to word boundary
|
||||
cat.extend(struct.pack('<H', cat_type))
|
||||
cat.extend(struct.pack('<H', len(data) // 2))
|
||||
cat.extend(data)
|
||||
|
||||
# --- FMMU Configuration (after SM, byte 64 + 4*8 = 96) ---
|
||||
# Each FMMU entry: 1 byte (type: 0x01=used for outputs, 0x02=used for inputs)
|
||||
fmmu_base = sm_base + 4 * 8 # = 96
|
||||
eeprom[fmmu_base] = 0x01 # FMMU0: outputs
|
||||
eeprom[fmmu_base + 1] = 0x02 # FMMU1: inputs
|
||||
|
||||
# --- Categories (after FMMU) ---
|
||||
# Category format: [Type (1 byte)] [Size in words (1 byte)] [Data...]
|
||||
# Type 0 = end of categories
|
||||
# Type 10 = Strings
|
||||
# Type 20 = General
|
||||
# Type 30 = FMMU
|
||||
# Type 40 = SM
|
||||
# Type 50 = PDO (TxPDO/RxPDO)
|
||||
# Type 60 = DC
|
||||
|
||||
cat_offset = fmmu_base + 2 # = 98
|
||||
|
||||
# --- String Category (Type 10) ---
|
||||
# First byte = number of strings, then for each: [len(1)] [chars...]
|
||||
# STRINGS (10): count, then per string [len][chars]; indices 1-based
|
||||
strings = [b"DCU Project", b"GD32E508 DCU"]
|
||||
str_data = bytearray()
|
||||
str_data.append(len(strings))
|
||||
for s in strings:
|
||||
str_data.append(len(s))
|
||||
str_data.extend(s)
|
||||
# Pad to even length
|
||||
if len(str_data) % 2 != 0:
|
||||
str_data.append(0)
|
||||
str_words = len(str_data) // 2
|
||||
append_category(10, bytes(str_data))
|
||||
|
||||
eeprom[cat_offset] = 10 # Type: Strings
|
||||
eeprom[cat_offset + 1] = str_words
|
||||
eeprom[cat_offset + 2: cat_offset + 2 + len(str_data)] = str_data
|
||||
cat_offset += 2 + len(str_data)
|
||||
# General (30): Table 21, 32 bytes
|
||||
gen = bytearray(32)
|
||||
gen[0] = 1 # GroupIdx -> "DCU Project"
|
||||
gen[1] = 2 # ImgIdx -> "GD32E508 DCU"
|
||||
gen[2] = 2 # OrderIdx -> "GD32E508 DCU" (matches XML <Type>)
|
||||
gen[3] = 2 # NameIdx
|
||||
gen[4] = 0 # Reserved
|
||||
gen[5] = 0x23 # CoE Details: SDO(bit0)+SDO Info(bit1)+CompleteAccess(bit5)
|
||||
gen[6] = 0 # FoE Details
|
||||
gen[7] = 0 # EoE Details
|
||||
gen[8] = 0 # SoEChannels
|
||||
gen[9] = 0 # DS402Channels
|
||||
gen[10] = 0 # SysmanClass
|
||||
gen[11] = 0 # Flags
|
||||
struct.pack_into('<h', gen, 12, 0) # CurrentOnEBus
|
||||
struct.pack_into('<H', gen, 16, 0x0011) # Physical Port: port0/port1 = MII ("YY")
|
||||
append_category(30, bytes(gen))
|
||||
|
||||
# --- General Category (Type 20) ---
|
||||
# Format: [GroupInfo(1)] [ImageName(1)] [OrderIdx(1)] [CoE details... ]
|
||||
# Simplified: just enough for TwinCAT to display
|
||||
gen_data = bytearray(16)
|
||||
gen_data[0] = 1 # Group info index (1-based into strings? or group ID)
|
||||
gen_data[1] = 2 # Image name string index
|
||||
gen_data[2] = 0 # Order index
|
||||
gen_data[3] = 0 # CoE details: SDO info supported
|
||||
gen_data[4] = 0x01 # CoE: SDO info
|
||||
gen_data[5] = 0x00
|
||||
gen_data[6] = 0 # FoE/EoE/etc flags
|
||||
gen_data[7] = 0
|
||||
# Physics: YY ( EtherCAT on both ports)
|
||||
gen_data[8] = 0x03 # Physics byte: port0=YY, port1=YY
|
||||
gen_data[9] = 0
|
||||
# Watchdog divider
|
||||
gen_data[10] = 0
|
||||
gen_data[11] = 0
|
||||
# SM watchdog
|
||||
gen_data[12] = 0
|
||||
gen_data[13] = 0
|
||||
# SM watchdog divider
|
||||
gen_data[14] = 0
|
||||
gen_data[15] = 0
|
||||
gen_words = len(gen_data) // 2
|
||||
# FMMU (40): FMMU0=outputs(0x01), FMMU1=inputs(0x02)
|
||||
append_category(40, bytes([0x01, 0x02]))
|
||||
|
||||
eeprom[cat_offset] = 20 # Type: General
|
||||
eeprom[cat_offset + 1] = gen_words
|
||||
eeprom[cat_offset + 2: cat_offset + 2 + len(gen_data)] = gen_data
|
||||
cat_offset += 2 + len(gen_data)
|
||||
# SyncM (41): Table 23, 8 bytes per SM:
|
||||
# [StartAddr(2)][Length(2)][Control(1)][Status(1)][Enable(1)][Type(1)]
|
||||
# Type: 1=mailbox out, 2=mailbox in, 3=process outputs, 4=process inputs
|
||||
sm_entries = [
|
||||
(MBX_RX_OFFSET, MBX_RX_SIZE, 0x26, 0x01, 0x01), # SM0 mailbox out
|
||||
(MBX_TX_OFFSET, MBX_TX_SIZE, 0x22, 0x01, 0x02), # SM1 mailbox in
|
||||
(PD_OUT_OFFSET, PD_OUT_SIZE, 0x64, 0x01, 0x03), # SM2 process outputs
|
||||
(PD_IN_OFFSET, PD_IN_SIZE, 0x20, 0x01, 0x04), # SM3 process inputs
|
||||
]
|
||||
sm_data = bytearray()
|
||||
for start, length, control, enable, sm_type in sm_entries:
|
||||
sm_data.extend(struct.pack('<H', start))
|
||||
sm_data.extend(struct.pack('<H', length))
|
||||
sm_data.append(control)
|
||||
sm_data.append(0) # Status: don't care
|
||||
sm_data.append(enable) # Enable: bit0 = enable
|
||||
sm_data.append(sm_type)
|
||||
append_category(41, bytes(sm_data))
|
||||
|
||||
# --- FMMU Category (Type 30) ---
|
||||
# List of FMMU usages (1 byte each): 0x01=Outputs, 0x02=Inputs
|
||||
fmmu_cat = bytearray([0x01, 0x02])
|
||||
if len(fmmu_cat) % 2 != 0:
|
||||
fmmu_cat.append(0)
|
||||
fmmu_words = len(fmmu_cat) // 2
|
||||
def append_pdo(cat_type, pdo_index, sm_index, obj_index):
|
||||
"""Table 24/25: header [index(2)][nEntry(1)][SyncM(1)][Sync(1)][NameIdx(1)]
|
||||
[Flags(2)] + entries [idx(2)][sub(1)][name(1)][dtype(1)][bitlen(1)][flags(2)]."""
|
||||
data = bytearray()
|
||||
data.extend(struct.pack('<H', pdo_index))
|
||||
data.append(4) # nEntry: 0x7000/0x6000:01..04 (dc_app.c)
|
||||
data.append(sm_index) # SyncM
|
||||
data.append(0) # DC Synchronization
|
||||
data.append(0) # NameIdx
|
||||
data.extend(struct.pack('<H', 0)) # Flags
|
||||
for sub in range(1, 5):
|
||||
data.extend(struct.pack('<H', obj_index))
|
||||
data.append(sub) # SubIndex
|
||||
data.append(0) # Entry NameIdx
|
||||
data.append(0x1B) # DataType: UNSIGNED64 (ETG.5101 0x1B)
|
||||
data.append(64) # BitLen (Unsigned8 in SII)
|
||||
data.extend(struct.pack('<H', 0)) # Flags
|
||||
append_category(cat_type, bytes(data))
|
||||
|
||||
eeprom[cat_offset] = 30 # Type: FMMU
|
||||
eeprom[cat_offset + 1] = fmmu_words
|
||||
eeprom[cat_offset + 2: cat_offset + 2 + len(fmmu_cat)] = fmmu_cat
|
||||
cat_offset += 2 + len(fmmu_cat)
|
||||
append_pdo(51, 0x1600, 2, 0x7000) # RXPDO: outputs (SM2)
|
||||
append_pdo(50, 0x1A00, 3, 0x6000) # TXPDO: inputs (SM3)
|
||||
|
||||
# --- SM Category (Type 40) ---
|
||||
# Each SM: [StartAddr(2)] [Length(2)] [Control(1)] [Status(1)] [Activate(1)] [PDI(1)]
|
||||
sm_cat = bytearray()
|
||||
for idx in range(4):
|
||||
off = sm_base + idx * 8
|
||||
sm_cat.extend(eeprom[off:off+8])
|
||||
sm_words = len(sm_cat) // 2
|
||||
# DC (60) is "for future use" per Table 19 and has no defined payload;
|
||||
# DC configuration comes from DCU_ESI.xml <Dc> - omitted on purpose.
|
||||
|
||||
eeprom[cat_offset] = 40 # Type: SM
|
||||
eeprom[cat_offset + 1] = sm_words
|
||||
eeprom[cat_offset + 2: cat_offset + 2 + len(sm_cat)] = sm_cat
|
||||
cat_offset += 2 + len(sm_cat)
|
||||
# End of categories: word 0xFFFF (Table 19 "End")
|
||||
cat.extend(struct.pack('<H', 0xFFFF))
|
||||
|
||||
# --- PDO Category (Type 50) ---
|
||||
# TxPDO (RxPDO from master perspective, SM2):
|
||||
# [Index(2)] [SyncManager(1)] [DC Sync(1)] [NameIdx(1)] [Flags(1)] [EntryCount(1)] [Reserved(1)]
|
||||
# Then entries: [Index(2)] [SubIndex(1)] [NameIdx(1)] [DataType(1)] [BitLen(2)] [Flags(2)]
|
||||
pdo_cat = bytearray()
|
||||
|
||||
# TxPDO 0x1600 (assigned to SM2, outputs)
|
||||
pdo_cat.extend(struct.pack('<H', 0x1600)) # Index
|
||||
pdo_cat.append(2) # SyncManager = SM2
|
||||
pdo_cat.append(0) # DC Sync
|
||||
pdo_cat.append(0) # Name string index (0 = use index as name)
|
||||
pdo_cat.append(0x00) # Flags (fixed, mandatory)
|
||||
pdo_cat.append(1) # Entry count
|
||||
pdo_cat.append(0) # Reserved
|
||||
# Entry 1: 0x7000:01, 256 bits
|
||||
pdo_cat.extend(struct.pack('<H', 0x7000)) # Index
|
||||
pdo_cat.append(1) # SubIndex
|
||||
pdo_cat.append(0) # Name string index
|
||||
pdo_cat.append(0x0F) # DataType: ARRAY OF BYTE (0x0F = BYTE in CoE)
|
||||
pdo_cat.extend(struct.pack('<H', 256)) # BitLen
|
||||
pdo_cat.extend(struct.pack('<H', 0)) # Flags
|
||||
|
||||
# RxPDO 0x1A00 (assigned to SM3, inputs)
|
||||
pdo_cat.extend(struct.pack('<H', 0x1A00)) # Index
|
||||
pdo_cat.append(3) # SyncManager = SM3
|
||||
pdo_cat.append(0) # DC Sync
|
||||
pdo_cat.append(0) # Name string index
|
||||
pdo_cat.append(0x00) # Flags
|
||||
pdo_cat.append(1) # Entry count
|
||||
pdo_cat.append(0) # Reserved
|
||||
# Entry 1: 0x6000:01, 256 bits
|
||||
pdo_cat.extend(struct.pack('<H', 0x6000)) # Index
|
||||
pdo_cat.append(1) # SubIndex
|
||||
pdo_cat.append(0) # Name string index
|
||||
pdo_cat.append(0x0F) # DataType: BYTE
|
||||
pdo_cat.extend(struct.pack('<H', 256)) # BitLen
|
||||
pdo_cat.extend(struct.pack('<H', 0)) # Flags
|
||||
|
||||
if len(pdo_cat) % 2 != 0:
|
||||
pdo_cat.append(0)
|
||||
pdo_words = len(pdo_cat) // 2
|
||||
|
||||
eeprom[cat_offset] = 50 # Type: PDO
|
||||
eeprom[cat_offset + 1] = pdo_words
|
||||
eeprom[cat_offset + 2: cat_offset + 2 + len(pdo_cat)] = pdo_cat
|
||||
cat_offset += 2 + len(pdo_cat)
|
||||
|
||||
# --- DC Category (Type 60) ---
|
||||
# DC settings: [AssignActivate(2)] [Sync0 Cycle(4)] [Sync0 Shift(4)] [Sync1 Cycle(4)] [Sync1 Shift(4)]
|
||||
dc_cat = bytearray()
|
||||
dc_cat.extend(struct.pack('<H', 0x0300)) # AssignActivate: SYNC0 enabled
|
||||
dc_cat.extend(struct.pack('<I', 0)) # SYNC0 cycle time (0 = set by master)
|
||||
dc_cat.extend(struct.pack('<I', 0)) # SYNC0 shift time
|
||||
dc_cat.extend(struct.pack('<I', 0)) # SYNC1 cycle time (unused)
|
||||
dc_cat.extend(struct.pack('<I', 0)) # SYNC1 shift time
|
||||
dc_words = len(dc_cat) // 2
|
||||
|
||||
eeprom[cat_offset] = 60 # Type: DC
|
||||
eeprom[cat_offset + 1] = dc_words
|
||||
eeprom[cat_offset + 2: cat_offset + 2 + len(dc_cat)] = dc_cat
|
||||
cat_offset += 2 + len(dc_cat)
|
||||
|
||||
# --- End of categories ---
|
||||
eeprom[cat_offset] = 0 # Type 0 = end
|
||||
eeprom[cat_offset + 1] = 0
|
||||
if len(cat) > EEPROM_SIZE - 128:
|
||||
raise SystemExit("category area overflow")
|
||||
eeprom[128:128 + len(cat)] = cat
|
||||
|
||||
# ============================================================
|
||||
# Compute checksum (word 0x0007, byte offset 14)
|
||||
# Checksum = XOR of all words from offset 0 to (checksum_word - 1)
|
||||
# Then stored so that XOR of all words 0..7 == 0x0000
|
||||
# Self-check: re-parse the generated image
|
||||
# ============================================================
|
||||
# Actually per ETG.1000.6: checksum is at word 7 (byte 14-15)
|
||||
# It's computed as: XOR of words 0..6, then checksum = ~xor (bitwise NOT)
|
||||
# such that XOR of words 0..7 == 0xFFFF? No...
|
||||
# Per spec: "The checksum is calculated by XORing all words from 0x00 to 0x06.
|
||||
# The result is stored in word 0x07."
|
||||
# Verification: XOR of words 0x00..0x07 should equal 0x0000.
|
||||
xor_val = 0
|
||||
for i in range(0, 14, 2):
|
||||
xor_val ^= struct.unpack_from('<H', eeprom, i)[0]
|
||||
w16(14, xor_val)
|
||||
def u16(off):
|
||||
return struct.unpack_from('<H', eeprom, off)[0]
|
||||
|
||||
def u32(off):
|
||||
return struct.unpack_from('<I', eeprom, off)[0]
|
||||
|
||||
assert u16(0) == 0x008D, hex(u16(0))
|
||||
assert u16(14) == sii_crc8(eeprom[0:14]) and (u16(14) >> 8) == 0
|
||||
assert u32(16) == VENDOR_ID and u32(20) == PRODUCT_CODE
|
||||
assert u32(24) == REVISION_NO and u32(28) == SERIAL_NO
|
||||
assert u16(40) == MBX_RX_OFFSET and u16(48) == MBX_RX_OFFSET
|
||||
assert u16(52) == MBX_TX_OFFSET and u16(56) == 0x0004
|
||||
assert u16(124) == EEPROM_KBIT + 1 and u16(126) == 1
|
||||
off = 128
|
||||
types = []
|
||||
while off < EEPROM_SIZE:
|
||||
t, size = u16(off), u16(off + 2)
|
||||
if t == 0xFFFF:
|
||||
types.append('END')
|
||||
break
|
||||
types.append(t)
|
||||
off += 4 + size * 2
|
||||
assert types == [10, 30, 40, 41, 51, 50, 'END'], types
|
||||
print("[gen_eeprom] self-check OK: checksum=0x%02X, categories=%s" %
|
||||
(u16(14) & 0xFF, types), file=sys.stderr)
|
||||
|
||||
# ============================================================
|
||||
# Output as C header
|
||||
# ============================================================
|
||||
print("/**")
|
||||
print(" * @file eeprom_image.h")
|
||||
print(" * @brief LAN9252 EEPROM (ESI) binary image - auto-generated by gen_eeprom.py")
|
||||
print(" * @brief LAN9252 EEPROM (ESI/SII) binary image - auto-generated by gen_eeprom.py")
|
||||
print(" *")
|
||||
print(" * Layout per ETG.1000.6 section 5.4 (SII coding): identity at words 8-15,")
|
||||
print(" * mailbox descriptors at words 20-28, Size/Version at words 62-63,")
|
||||
print(" * categories from byte 128 with 4-byte headers, CRC-8 checksum in word 7.")
|
||||
print(" *")
|
||||
print(f" * Vendor ID: 0x{VENDOR_ID:08X}")
|
||||
print(f" * Product Code: 0x{PRODUCT_CODE:08X}")
|
||||
print(f" * Revision: 0x{REVISION_NO:08X}")
|
||||
print(f" * Serial: 0x{SERIAL_NO:08X}")
|
||||
print(f" * PDI Control: 0x{(ESC_CONFIG << 8) | PDI_CONTROL:04X} (HBI 16-bit indexed)")
|
||||
print(f" * PDI Config: 0x{(SYNC_LATCH_CFG << 8) | PDI_CONFIG:04X} (0150=active-low strobes, 0151=SYNC0/1 output)")
|
||||
print(f" * Sync Pulse: {SYNC_PULSE_LEN} (reg 0x0982, x10ns = {SYNC_PULSE_LEN * 10}us)")
|
||||
print(f" * Checksum: 0x{u16(14) & 0xFF:02X} (CRC-8 poly 0x07 init 0xFF over bytes 0-13)")
|
||||
print(f" * Size: {EEPROM_SIZE} bytes (AT24C32)")
|
||||
print(" *")
|
||||
print(" * DO NOT EDIT - regenerate with: python gen_eeprom.py > eeprom_image.h")
|
||||
@@ -343,10 +308,13 @@ print(f"static const uint8_t ESI_EEPROM_IMAGE[{EEPROM_SIZE}] = {{")
|
||||
for row in range(0, EEPROM_SIZE, 16):
|
||||
chunk = eeprom[row:row+16]
|
||||
hex_str = ", ".join(f"0x{b:02X}" for b in chunk)
|
||||
# Only print non-0xFF rows with annotation, skip bulk 0xFF
|
||||
if all(b == 0xFF for b in chunk):
|
||||
if row == 64 or row % 256 == 0:
|
||||
print(f" /* 0x{row:04X}: (0xFF padding) */")
|
||||
# 1.5.28: emit the real 0xFF bytes. A comment-only padding row leaves
|
||||
# those array elements zero-initialized (C static init) instead of
|
||||
# the erased-0xFF state, and the firmware burns the whole array.
|
||||
if row % 256 == 0:
|
||||
print(f" /* 0x{row:04X}: 0xFF erase fill */")
|
||||
print(f" /* 0x{row:04X} */ {hex_str},")
|
||||
continue
|
||||
print(f" /* 0x{row:04X} */ {hex_str},")
|
||||
|
||||
|
||||
@@ -5,16 +5,18 @@
|
||||
* 实现 el9800hw.h 中声明的全部 HW_* 函数,将 SSC 的 ESC 访问请求
|
||||
* 转发到 EscExmcBus (EXMC NOR/SRAM Region0, 16-bit 内存映射)。
|
||||
*
|
||||
* HBI Indexed 寻址协议:
|
||||
* 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 0x20000, A17=1)
|
||||
* 3. 读/写 HBI_DATA_0 高 word (EXMC offset 0x30000, A17=1, A16=1)
|
||||
* 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 (主循环):调用前 SSC 已执行 DISABLE_AL_EVENT_INT
|
||||
* (mask EXTI0+EXTI1),保证 HBI IDX 不被 ISR 抢占。
|
||||
* - 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 包装器
|
||||
* 屏蔽 EXTI0 后调用。
|
||||
*/
|
||||
|
||||
/* SSC 配置与类型 */
|
||||
@@ -37,8 +39,8 @@
|
||||
namespace {
|
||||
|
||||
constexpr uint32_t HBI_IDX0_OFFSET = 0x00000U; /* A2=0, A1=0 */
|
||||
constexpr uint32_t HBI_DATA0_LOW_OFF = 0x20000U; /* A2=1(A17), A1=0(A16) */
|
||||
constexpr uint32_t HBI_DATA0_HIGH_OFF = 0x30000U; /* A2=1(A17), A1=1(A16) */
|
||||
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;
|
||||
@@ -184,12 +186,15 @@ void HW_Release(void)
|
||||
|
||||
/**
|
||||
* @brief 读取 AL Event 寄存器 (0x0220),主循环上下文。
|
||||
* @note 调用者已执行 DISABLE_AL_EVENT_INT。
|
||||
* @note 内部屏蔽 EXTI0+EXTI1 保证 HBI 事务完整。
|
||||
*/
|
||||
UINT16 HW_GetALEventRegister(void)
|
||||
{
|
||||
const uint32_t prev = EscExmcBus::maskEnter();
|
||||
hbiSetIndex(ESC_AL_EVENT_OFFSET);
|
||||
return hbiReadLow();
|
||||
const UINT16 v = hbiReadLow();
|
||||
EscExmcBus::maskExit(prev);
|
||||
return v;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -206,9 +211,11 @@ UINT16 HW_GetALEventRegister_Isr(void)
|
||||
*/
|
||||
void HW_ResetALEventMask(UINT16 intMask)
|
||||
{
|
||||
const uint32_t prev = EscExmcBus::maskEnter();
|
||||
s_alEventMask &= ~intMask;
|
||||
hbiSetIndex(ESC_AL_EVENTMASK_OFFSET);
|
||||
hbiWriteLow(s_alEventMask);
|
||||
EscExmcBus::maskExit(prev);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -216,9 +223,11 @@ void HW_ResetALEventMask(UINT16 intMask)
|
||||
*/
|
||||
void HW_SetALEventMask(UINT16 intMask)
|
||||
{
|
||||
const uint32_t prev = EscExmcBus::maskEnter();
|
||||
s_alEventMask |= intMask;
|
||||
hbiSetIndex(ESC_AL_EVENTMASK_OFFSET);
|
||||
hbiWriteLow(s_alEventMask);
|
||||
EscExmcBus::maskExit(prev);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -228,6 +237,7 @@ void HW_SetALEventMask(UINT16 intMask)
|
||||
*/
|
||||
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);
|
||||
@@ -235,20 +245,25 @@ void HW_SetLed(UINT8 RunLed, UINT8 ErrLed)
|
||||
/* Error LED override (0x0139): bit0 = LED on */
|
||||
hbiSetIndex(ESC_ERROR_LED_OVERRIDE);
|
||||
hbiWriteLow(ErrLed ? 0x0001U : 0x0000U);
|
||||
EscExmcBus::maskExit(prev);
|
||||
}
|
||||
|
||||
/*---------------------------------------------------------------------------
|
||||
* ESC 内存读写 — 主循环版本 (SSC 已 mask 中断)
|
||||
* ESC 内存读写 — 主循环版本 (内部带 HBI 互斥守卫)
|
||||
*---------------------------------------------------------------------------*/
|
||||
|
||||
void HW_EscRead(MEM_ADDR *pData, UINT16 Address, UINT16 Len)
|
||||
{
|
||||
const uint32_t prev = EscExmcBus::maskEnter();
|
||||
hbiRead(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);
|
||||
EscExmcBus::maskExit(prev);
|
||||
}
|
||||
|
||||
/*---------------------------------------------------------------------------
|
||||
@@ -279,11 +294,13 @@ void HW_DisableSyncManChannel(UINT8 channel)
|
||||
uint16_t smBase = 0x0800U + (uint16_t)channel * 8U;
|
||||
uint16_t activateAddr = smBase + 4U;
|
||||
|
||||
const uint32_t prev = EscExmcBus::maskEnter();
|
||||
hbiSetIndex(activateAddr);
|
||||
uint16_t val = hbiReadLow();
|
||||
val &= ~0x0001U; /* 清 bit0 = deactivate */
|
||||
hbiSetIndex(activateAddr);
|
||||
hbiWriteLow(val);
|
||||
EscExmcBus::maskExit(prev);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -294,11 +311,13 @@ void HW_EnableSyncManChannel(UINT8 channel)
|
||||
uint16_t smBase = 0x0800U + (uint16_t)channel * 8U;
|
||||
uint16_t activateAddr = smBase + 4U;
|
||||
|
||||
const uint32_t prev = EscExmcBus::maskEnter();
|
||||
hbiSetIndex(activateAddr);
|
||||
uint16_t val = hbiReadLow();
|
||||
val |= 0x0001U;
|
||||
hbiSetIndex(activateAddr);
|
||||
hbiWriteLow(val);
|
||||
EscExmcBus::maskExit(prev);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -317,7 +336,9 @@ static TSYNCMAN s_smBuf;
|
||||
TSYNCMAN ESCMEM *HW_GetSyncMan(UINT8 channel)
|
||||
{
|
||||
uint16_t smBase = 0x0800U + (uint16_t)channel * 8U;
|
||||
const uint32_t prev = EscExmcBus::maskEnter();
|
||||
hbiRead(&s_smBuf, smBase, SIZEOF_SM_REGISTER);
|
||||
EscExmcBus::maskExit(prev);
|
||||
return &s_smBuf;
|
||||
}
|
||||
|
||||
|
||||
@@ -172,6 +172,7 @@ V4.00 ECAT 7: The return values for the AL-StatusCode were changed to UINT16
|
||||
|
||||
#define _ECATSLV_ 1
|
||||
#include "ecatslv.h"
|
||||
#include <stdio.h> /* 1.5.32 bring-up 诊断: AL 事件/状态迁移打印 */
|
||||
#undef _ECATSLV_
|
||||
/* ECATCHANGE_START(V5.11) ECAT10*/
|
||||
/*remove definition of _ECATSLV_ (#ifdef is used in ecatslv.h)*/
|
||||
@@ -1335,6 +1336,9 @@ void SetALStatus(UINT8 alStatus, UINT16 alStatusCode)
|
||||
Value = SWAPWORD(Value);
|
||||
HW_EscWriteWord(Value,ESC_AL_STATUS_OFFSET);
|
||||
|
||||
/* 1.5.32 bring-up: SSC 写回的 AL 状态+错误码(与主站读到的不一致=ESC 写路径问题) */
|
||||
printf("[ECAT][AL] -> 0x%02X code=0x%04X\r\n",
|
||||
(unsigned)nAlStatus, (unsigned)alStatusCode);
|
||||
}
|
||||
|
||||
/////////////////////////////////////////////////////////////////////////////////////////
|
||||
@@ -2408,6 +2412,10 @@ void ECAT_Main(void)
|
||||
HW_EscReadWord( EscAlControl, ESC_AL_CONTROL_OFFSET);
|
||||
EscAlControl = SWAPWORD(EscAlControl);
|
||||
|
||||
/* 1.5.32 bring-up: PDI 看到主站的 AL 控制请求(无此行=事件未到 PDI/主循环卡死) */
|
||||
printf("[ECAT][AL] evt=0x%04X req=0x%02X\r\n",
|
||||
(unsigned)ALEventReg, (unsigned)(EscAlControl & 0x0FU));
|
||||
|
||||
|
||||
/* reset AL Control event and the SM Change event (because the Sync Manager settings will be checked
|
||||
in AL_ControlInd, too)*/
|
||||
|
||||
@@ -157,6 +157,7 @@ static void hardResetCan(uint32_t canPeriph, rcu_periph_enum canRcu)
|
||||
|
||||
CanFd::Diag CanFd::s_diag{};
|
||||
CanFd::RxQueue CanFd::s_rxQueues[3]{};
|
||||
CanFd::TxQueue CanFd::s_txQueues[3]{};
|
||||
uint8_t CanFd::s_nextConsumeChannel = 0U;
|
||||
volatile CanFd::Mode CanFd::s_mode = CanFd::Mode::kModeAllPass;
|
||||
CanFd::ChannelStats CanFd::s_channelStats[3]{};
|
||||
@@ -420,6 +421,9 @@ void CanFd::initChannelHardware(uint8_t i)
|
||||
s_rxQueues[i].head = 0U;
|
||||
s_rxQueues[i].tail = 0U;
|
||||
s_rxQueues[i].count = 0U;
|
||||
s_txQueues[i].head = 0U;
|
||||
s_txQueues[i].tail = 0U;
|
||||
s_txQueues[i].count = 0U;
|
||||
|
||||
const bool exitOk = ((CAN_STAT(ch.canPeriph) & CAN_STAT_IWS) == 0U);
|
||||
s_channelHwOk[i] = (nominalOk && fdOk && exitOk);
|
||||
@@ -930,7 +934,8 @@ bool CanFd::consumeRxFrame(uint32_t *id, uint8_t *data, uint8_t *length, uint8_t
|
||||
* 网页发送数仅在 TX 中断确认 MTFNERR 后增加,不能用本函数返回值判断总线 ACK。
|
||||
*/
|
||||
CanFd::CanTxResult CanFd::transmitFrame(uint8_t channelIndex, uint32_t ext_id,
|
||||
const uint8_t *data, uint8_t dlc, bool periodic)
|
||||
const uint8_t *data, uint8_t dlc, bool periodic,
|
||||
bool allow_abort)
|
||||
{
|
||||
/* DLC=0 合法(仅 ID);DLC>0 时 data 必填 */
|
||||
if (channelIndex >= kChannelCount || !kChannelEnabled[channelIndex] || dlc > 64U) {
|
||||
@@ -990,8 +995,12 @@ CanFd::CanTxResult CanFd::transmitFrame(uint8_t channelIndex, uint32_t ext_id,
|
||||
|
||||
/* 非周期业务发送在邮箱全占时保持非阻塞:本函数可能处于 SPI CS 中断链路,不能等待
|
||||
* SysTick。若 auto_retrans 因总线无 ACK 卡住全部邮箱,只立即 abort
|
||||
* 一个 PENDING 邮箱并重投一次,把中断占用时间控制在寄存器访问级别。 */
|
||||
if (mailbox == CAN_NOMAILBOX) {
|
||||
* 一个 PENDING 邮箱并重投一次,把中断占用时间控制在寄存器访问级别。
|
||||
*
|
||||
* ★ allow_abort=false(突发/软件队列路径)时不做 abort:邮箱全满直接返回
|
||||
* kErrMailbox,由 enqueueTx 把帧送入软件队列、TX 完成中断回灌。
|
||||
* 否则 abort+重投会把同通道已装载的旧帧顶掉(7 帧同通道突发只发 3 帧)。 */
|
||||
if (mailbox == CAN_NOMAILBOX && allow_abort) {
|
||||
for (uint8_t mb = CAN_MAILBOX0; mb <= CAN_MAILBOX2; ++mb) {
|
||||
if (can_transmit_states(canPeriph, mb) != CAN_TRANSMIT_PENDING) {
|
||||
continue;
|
||||
@@ -1028,6 +1037,105 @@ CanFd::CanTxResult CanFd::transmitFrame(uint8_t channelIndex, uint32_t ext_id,
|
||||
return CanTxResult::kErrMailbox;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 突发排入软件 TX 队列(详见头声明)。
|
||||
*
|
||||
* 场景:电机控制台 0xC7 突发一次发 4 帧同通道命令,
|
||||
* GD32 只有 3 个硬件邮箱,第 4 帧必丢。此队列把超载帧留下来,
|
||||
* TX 完成中断里持续回灌,前端 /can/send_burst 不再因 >3 帧掉帧。
|
||||
*/
|
||||
CanFd::CanTxResult CanFd::enqueueTx(uint8_t channelIndex, uint32_t ext_id,
|
||||
const uint8_t *data, uint8_t dlc)
|
||||
{
|
||||
if (channelIndex >= kChannelCount || !kChannelEnabled[channelIndex] || dlc > 64U) {
|
||||
return CanTxResult::kErrInvalid;
|
||||
}
|
||||
if (dlc > 0U && data == nullptr) {
|
||||
return CanTxResult::kErrInvalid;
|
||||
}
|
||||
|
||||
TxQueue &q = s_txQueues[channelIndex];
|
||||
|
||||
/* 快速路径:队列空 → 直通硬件邮箱。装得下立即返回,不用等待 TX 完成中断。
|
||||
* ★ allow_abort=false:邮箱满时禁止 abort 旧帧顶替(那会把同通道已装载的
|
||||
* 旧帧静默丢弃,>3 帧同通道突发只剩最后 3 帧上线),一律落入软件队列,
|
||||
* 由 TX 完成中断 drainTxQueueIsr 回灌。 */
|
||||
if (q.count == 0U) {
|
||||
const CanTxResult r = transmitFrame(channelIndex, ext_id, data, dlc, false, false);
|
||||
if (r != CanTxResult::kErrMailbox) {
|
||||
return r; /* 装上了(kOk)或失败原因非邮箱满(kErrInvalid/kErrBusOff) */
|
||||
}
|
||||
/* 邮箱满:落入下面的入队路径 */
|
||||
}
|
||||
|
||||
/* 关中段:EXTI15(SPI burst) 与 CANx TX ISR 并发,保护 head/tail/count。
|
||||
* 用 CMSIS NVIC 接口(SPL 的 nvic_irq_enable 是 3 参版)。
|
||||
* 关的是该通道自身的 txIrq,避免关全局影响其他通道的实时性。 */
|
||||
const IRQn_Type irqn = kChannels[channelIndex].txIrq;
|
||||
NVIC_DisableIRQ(irqn);
|
||||
|
||||
if (q.count >= kTxQueueDepth) {
|
||||
/* 队列也满了:彻底装不下。此时不能再返回 kOk,
|
||||
* 否则前端会误判成功;只能丢弃并返回 kErrMailbox。 */
|
||||
NVIC_EnableIRQ(irqn);
|
||||
s_diag.error_count++;
|
||||
s_channelStats[channelIndex].error_count++;
|
||||
return CanTxResult::kErrMailbox;
|
||||
}
|
||||
|
||||
TxQueuedFrame &f = q.frames[q.tail];
|
||||
f.id = ext_id;
|
||||
f.dlc = dlc;
|
||||
if (dlc > 0U) {
|
||||
memcpy(f.data, data, dlc);
|
||||
}
|
||||
q.tail = static_cast<uint8_t>((q.tail + 1U) % kTxQueueDepth);
|
||||
q.count++;
|
||||
|
||||
NVIC_EnableIRQ(irqn);
|
||||
return CanTxResult::kOk;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief CAN TX 完成中断末尾调用:用硬件邮箱空闲出的位置,把队列里的帧装回去。
|
||||
* @note 只在 ISR 内调用;关中断保护已在 transmitFrame 返回值判定外层完成,
|
||||
* 本函数本身不关中(邮箱空位是"单调减少再单调增加",多读无害)。
|
||||
*/
|
||||
void CanFd::drainTxQueueIsr(uint8_t channelIndex)
|
||||
{
|
||||
if (channelIndex >= kChannelCount) {
|
||||
return;
|
||||
}
|
||||
TxQueue &q = s_txQueues[channelIndex];
|
||||
while (q.count > 0U) {
|
||||
TxQueuedFrame &f = q.frames[q.head];
|
||||
/* 复用 transmitFrame 的装载逻辑;allow_abort=false:这里邮箱刚空出来,
|
||||
* 必能装上,禁止 abort 其它 PENDING 邮箱(会静默丢弃同通道排队帧)。 */
|
||||
const CanTxResult r = transmitFrame(channelIndex, f.id, f.data, f.dlc, false, false);
|
||||
if (r == CanTxResult::kOk) {
|
||||
q.head = static_cast<uint8_t>((q.head + 1U) % kTxQueueDepth);
|
||||
q.count--;
|
||||
} else {
|
||||
/* kErrMailbox:理论上不该发生(刚刚空出一个邮箱),保守 break;
|
||||
* 其它错误(kErrInvalid)已经入队又因参数错必败,直接丢弃防死循环。 */
|
||||
if (r != CanTxResult::kErrMailbox) {
|
||||
q.head = static_cast<uint8_t>((q.head + 1U) % kTxQueueDepth);
|
||||
q.count--;
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
uint32_t CanFd::txQueueBacklog(uint8_t channelIndex)
|
||||
{
|
||||
if (channelIndex >= kChannelCount) {
|
||||
return 0U;
|
||||
}
|
||||
return s_txQueues[channelIndex].count;
|
||||
}
|
||||
|
||||
void CanFd::abortPendingTx(uint8_t channelIndex)
|
||||
{
|
||||
if (channelIndex >= kChannelCount || !kChannelEnabled[channelIndex]) {
|
||||
@@ -1334,6 +1442,10 @@ void CanFd::handleTxInterrupt(uint8_t channelIndex)
|
||||
}
|
||||
|
||||
CAN_TSTAT(canPeriph) = tstat & (CAN_TSTAT_MTF0 | CAN_TSTAT_MTF1 | CAN_TSTAT_MTF2);
|
||||
|
||||
/* 队列里有积压帧时,趁刚空出的邮箱位置回灌。必须在 TSTAT 清标志之后调,
|
||||
* 否则 transmitFrame 看到的还是旧 PENDING 状态。 */
|
||||
drainTxQueueIsr(channelIndex);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -142,8 +142,17 @@ public:
|
||||
|
||||
static bool hasRxFrame();
|
||||
static bool consumeRxFrame(uint32_t *id, uint8_t *data, uint8_t *length, uint8_t *channel = nullptr);
|
||||
/**
|
||||
* @brief 直接向指定 CAN 通道发送一帧(绕过软件队列)。
|
||||
* @param allow_abort 邮箱全占时是否允许 abort 一个 PENDING 邮箱并重投一次。
|
||||
* - true(0xC0 单帧路径默认):非阻塞顶替,适合"最新命令优先"的单帧发送;
|
||||
* - false(突发/队列路径):邮箱满直接返回 kErrMailbox,交由 enqueueTx
|
||||
* 的软件队列缓存,由 TX 完成中断回灌——避免 abort 把同通道已装载的
|
||||
* 旧帧静默丢弃(>3 帧同通道突发只剩最后 3 帧上线的根因)。
|
||||
*/
|
||||
static CanTxResult transmitFrame(uint8_t channelIndex, uint32_t ext_id,
|
||||
const uint8_t *data, uint8_t dlc, bool periodic = false);
|
||||
const uint8_t *data, uint8_t dlc, bool periodic = false,
|
||||
bool allow_abort = true);
|
||||
|
||||
/** @brief 立即停掉指定通道所有 PENDING 发送邮箱(阻止 auto_retrans 残留重发,供停等式协议用)。 */
|
||||
static void abortPendingTx(uint8_t channelIndex);
|
||||
@@ -185,8 +194,36 @@ public:
|
||||
static void handleRxInterrupt(uint8_t channelIndex);
|
||||
static void handleTxInterrupt(uint8_t channelIndex);
|
||||
|
||||
/**
|
||||
* @brief 突发排入软件 TX 队列(供 SPI 0xC7 burst handler / 突发场景使用)。
|
||||
*
|
||||
* 解决「3 个发送邮箱是物理上限、>3 帧突发必丢」的问题:
|
||||
* - 队列空时尝试直通 transmitFrame(不排队、零延迟)。
|
||||
* - 邮箱满时把本帧追加到软件队列,TX 完成中断里持续出队回灌,
|
||||
* 保证任意大小突发(同通道 ≤ 队列深度)都不丢帧。
|
||||
*
|
||||
* 同步:本函数可在 EXTI15 ISR 内运行(SPI burst handler),与 CAN TX ISR
|
||||
* 并发;队列 Push/Pop 通过关中段互斥,无需 LDREX/STREX。
|
||||
*
|
||||
* @param[in] channelIndex 目标通道(0/1/2)。
|
||||
* @param[in] ext_id 29 位扩展 ID。
|
||||
* @param[in] data 数据负载;dlc>0 时必填。
|
||||
* @param[in] dlc 数据长度 0..64。
|
||||
* @retval CanTxResult::kOk 已进入硬件邮箱 OR 已入软件队列(统一视为已受理)。
|
||||
* @retval CanTxResult::kErrMailbox 软件队列也满。
|
||||
* @retval CanTxResult::kErrInvalid 参数非法。
|
||||
*/
|
||||
static CanTxResult enqueueTx(uint8_t channelIndex, uint32_t ext_id,
|
||||
const uint8_t *data, uint8_t dlc);
|
||||
|
||||
/** @brief 软件 TX 队列当前积压帧数(统计/诊断用)。 */
|
||||
static uint32_t txQueueBacklog(uint8_t channelIndex);
|
||||
|
||||
private:
|
||||
static constexpr uint8_t kRxQueueDepth = 16U;
|
||||
/** 每通道软件突发队列深度:覆盖整块突发(CAN_BURST_HARD_MAX=16 帧)中同一通道的
|
||||
* 全部超载帧(3 邮箱之外最多 13 帧 = 16-3),保证任意 DLC 的整块突发不因队列满丢帧。 */
|
||||
static constexpr uint8_t kTxQueueDepth = 13U;
|
||||
|
||||
struct RxFrame {
|
||||
uint8_t channel;
|
||||
@@ -202,6 +239,22 @@ private:
|
||||
volatile uint8_t count;
|
||||
};
|
||||
|
||||
/** 排队等待硬件邮箱空位的 CAN 帧(仅 SDO 突发用,payload ≤ 64B)。 */
|
||||
struct TxQueuedFrame {
|
||||
uint32_t id;
|
||||
uint8_t data[64];
|
||||
uint8_t dlc;
|
||||
};
|
||||
struct TxQueue {
|
||||
TxQueuedFrame frames[kTxQueueDepth];
|
||||
volatile uint8_t head;
|
||||
volatile uint8_t tail;
|
||||
volatile uint8_t count;
|
||||
};
|
||||
|
||||
/** @brief CAN TX ISR 内驱动:把队列里能装的全部装进硬件邮箱。 */
|
||||
static void drainTxQueueIsr(uint8_t channelIndex);
|
||||
|
||||
static void initGpio();
|
||||
/**
|
||||
* @brief 根据当前模式为指定控制器配置接收过滤器。
|
||||
@@ -224,6 +277,7 @@ private:
|
||||
|
||||
static Diag s_diag;
|
||||
static RxQueue s_rxQueues[3];
|
||||
static TxQueue s_txQueues[3]; /**< CAN 突发软件队列(邮箱装不下时缓存) */
|
||||
static uint8_t s_nextConsumeChannel;
|
||||
static volatile Mode s_mode;
|
||||
static ChannelStats s_channelStats[3];
|
||||
|
||||
@@ -34,32 +34,54 @@ void onCanFrame(uint8_t channel, uint32_t id, const uint8_t *data, uint8_t len)
|
||||
return;
|
||||
}
|
||||
|
||||
/* 掩码匹配 STATUS ID: 0x000N0100 */
|
||||
if ((id & kMotorStatusIdMask) != kMotorStatusIdMatch) {
|
||||
const uint32_t masked = id & kMotorStatusIdMask;
|
||||
|
||||
/* STATUS 帧 0x000N0100 (DLC 32, 实时值) */
|
||||
if (masked == kMotorStatusIdMatch) {
|
||||
const uint8_t node = static_cast<uint8_t>((id >> 16) & 0x0FU);
|
||||
if (node < 1U || node > kMotorMaxNodes || channel >= kMotorChannels) {
|
||||
return;
|
||||
}
|
||||
|
||||
MotorSlot &slot = s_motorTable.slots[(node - 1U) * kMotorChannels + channel];
|
||||
|
||||
slot.node_id = node;
|
||||
slot.channel = channel;
|
||||
slot.online = 1U;
|
||||
slot.dlc = (len <= kMotorStatusDlc) ? len : kMotorStatusDlc;
|
||||
slot.rx_count++;
|
||||
slot.last_rx_sec = g_app_uptime_sec;
|
||||
slot.seq = ++s_motorTable.global_seq;
|
||||
|
||||
std::memcpy(slot.data, data, slot.dlc);
|
||||
if (slot.dlc < kMotorStatusDlc) {
|
||||
std::memset(&slot.data[slot.dlc], 0, kMotorStatusDlc - slot.dlc);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
const uint8_t node = static_cast<uint8_t>((id >> 16) & 0x0FU);
|
||||
if (node < 1U || node > kMotorMaxNodes || channel >= kMotorChannels) {
|
||||
/* CONFIG 帧 0x000N0101 (DLC 24, 目标值回报) */
|
||||
if (masked == kMotorConfigIdMatch) {
|
||||
const uint8_t node = static_cast<uint8_t>((id >> 16) & 0x0FU);
|
||||
if (node < 1U || node > kMotorMaxNodes || channel >= kMotorChannels) {
|
||||
return;
|
||||
}
|
||||
|
||||
MotorSlot &slot = s_motorTable.slots[(node - 1U) * kMotorChannels + channel];
|
||||
|
||||
/* CONFIG 帧不更新 online 状态(仅 STATUS 帧维护在线检测) */
|
||||
slot.node_id = node; /* 确保槽位不空 */
|
||||
slot.channel = channel;
|
||||
slot.config_rx_count++;
|
||||
slot.config_seq = ++s_motorTable.global_seq;
|
||||
|
||||
const uint8_t copyLen = (len <= kMotorConfigDlc) ? len : kMotorConfigDlc;
|
||||
std::memcpy(slot.config_data, data, copyLen);
|
||||
if (copyLen < kMotorConfigDlc) {
|
||||
std::memset(&slot.config_data[copyLen], 0, kMotorConfigDlc - copyLen);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
/* node×channel 双键槽位:同节点号在不同通道各自独立缓存,互不覆盖 */
|
||||
MotorSlot &slot = s_motorTable.slots[(node - 1U) * kMotorChannels + channel];
|
||||
|
||||
slot.node_id = node;
|
||||
slot.channel = channel;
|
||||
slot.online = 1U;
|
||||
slot.dlc = (len <= kMotorStatusDlc) ? len : kMotorStatusDlc;
|
||||
slot.rx_count++;
|
||||
slot.last_rx_sec = g_app_uptime_sec;
|
||||
slot.seq = ++s_motorTable.global_seq;
|
||||
|
||||
/* 拷贝原始 payload */
|
||||
std::memcpy(slot.data, data, slot.dlc);
|
||||
if (slot.dlc < kMotorStatusDlc) {
|
||||
std::memset(&slot.data[slot.dlc], 0, kMotorStatusDlc - slot.dlc);
|
||||
}
|
||||
}
|
||||
|
||||
void checkOffline()
|
||||
@@ -137,6 +159,9 @@ uint16_t buildPayload(uint8_t page, uint8_t *buf, uint16_t maxLen)
|
||||
writeU32Le(&buf[offset + 4U], slot.rx_count);
|
||||
writeU32Le(&buf[offset + 8U], slot.seq);
|
||||
std::memcpy(&buf[offset + 12U], slot.data, kMotorStatusDlc);
|
||||
writeU32Le(&buf[offset + 44U], slot.config_rx_count);
|
||||
writeU32Le(&buf[offset + 48U], slot.config_seq);
|
||||
std::memcpy(&buf[offset + 52U], slot.config_data, kMotorConfigDlc);
|
||||
|
||||
offset += kMotorEntrySize;
|
||||
count++;
|
||||
|
||||
@@ -25,6 +25,7 @@
|
||||
*/
|
||||
static constexpr uint32_t kMotorStatusIdMask = 0xFFF0FFFFU; /* 屏蔽 node nibble [19:16] */
|
||||
static constexpr uint32_t kMotorStatusIdMatch = 0x00000100U; /* STATUS 类型码 */
|
||||
static constexpr uint32_t kMotorConfigIdMatch = 0x00000101U; /* CONFIG 类型码 (Motor→Host, DLC 24) */
|
||||
|
||||
static constexpr uint8_t kMotorMaxNodes = CONFIG_MOTOR_MAX_NODES;
|
||||
/* 同一节点号可能同时挂在多个 CAN 通道上(两台电机同号异线),
|
||||
@@ -32,6 +33,7 @@ static constexpr uint8_t kMotorMaxNodes = CONFIG_MOTOR_MAX_NODES;
|
||||
static constexpr uint8_t kMotorChannels = 3U;
|
||||
static constexpr uint8_t kMotorSlotCount = kMotorMaxNodes * kMotorChannels; /* 9×3 = 27 */
|
||||
static constexpr uint8_t kMotorStatusDlc = 32U;
|
||||
static constexpr uint8_t kMotorConfigDlc = 24U; /* CONFIG 帧 DLC */
|
||||
static constexpr uint16_t kMotorOfflineSec = CONFIG_MOTOR_OFFLINE_MS / 1000U;
|
||||
|
||||
/* ── 0xC5 推送周期运行时可调(ESP32 经 0xC5 子命令 0xFE 下发)── */
|
||||
@@ -48,11 +50,11 @@ static constexpr uint32_t kBootHeartbeatTimeoutSec = 3U; /* >3s 无 0x103 →
|
||||
static constexpr uint8_t kMotorActiveBootBit = 0x80U; /* 0xC5 page0 header active 字节 bit7 = boot_active */
|
||||
|
||||
/* ── 序列化常量 (0xC5 payload) ── */
|
||||
static constexpr uint8_t kMotorEntrySize = 44U; /* 1+1+1+1+4+4+32 */
|
||||
static constexpr uint8_t kMotorEntrySize = 76U; /* 1+1+1+1+4+4+32+4+4+24 (STATUS+CONFIG) */
|
||||
static constexpr uint8_t kMotorPageHeaderLen = 4U; /* page+total+count+active */
|
||||
static constexpr uint8_t kMotorEntriesPerPage = 5U; /* (236-4)/44 = 5.27 → 5 */
|
||||
static constexpr uint8_t kMotorEntriesPerPage = 3U; /* (236-4)/76 = 3.05 → 3 */
|
||||
static constexpr uint8_t kMotorTotalPages =
|
||||
(kMotorSlotCount + kMotorEntriesPerPage - 1U) / kMotorEntriesPerPage; /* ceil(27/5) = 6 */
|
||||
(kMotorSlotCount + kMotorEntriesPerPage - 1U) / kMotorEntriesPerPage; /* ceil(27/3) = 9 */
|
||||
|
||||
/// 单电机状态槽
|
||||
struct MotorSlot {
|
||||
@@ -64,6 +66,9 @@ struct MotorSlot {
|
||||
uint32_t last_rx_sec; ///< 最后收到帧时的 g_app_uptime_sec
|
||||
uint32_t seq; ///< 更新序号 (每次刷新 +1)
|
||||
uint8_t data[kMotorStatusDlc]; ///< 原始 STATUS payload
|
||||
uint32_t config_rx_count; ///< 累计收到 CONFIG 帧数
|
||||
uint32_t config_seq; ///< CONFIG 更新序号
|
||||
uint8_t config_data[kMotorConfigDlc]; ///< 原始 CONFIG payload (目标值回报)
|
||||
};
|
||||
|
||||
/// 全局电机状态表
|
||||
|
||||
@@ -13,9 +13,11 @@ constexpr uint8_t kBurstMaxFrames = 16U;
|
||||
/**
|
||||
* @brief CAN 跨通道突发发送处理器(TYPE 0xC7)。
|
||||
*
|
||||
* 一次 SPI 事务携带多帧,GD32 在 CS 释放中断内对每一帧调用非阻塞的
|
||||
* CanFd::transmitFrame()(仅装载对应通道 CAN 邮箱、立即返回),各通道邮箱
|
||||
* 背靠背装载完成后几乎同时上线,实现跨通道「近同时」发送(组内偏差 <1ms)。
|
||||
* 一次 SPI 事务携带多帧,GD32 在 CS 释放中断内对每一帧调用 CanFd::enqueueTx():
|
||||
* - 邮箱有空位:立即装入硬件邮箱。
|
||||
* - 邮箱满(>3 帧突发同通道时必然发生):进入每通道 13 帧深的软件队列
|
||||
* (禁用 transmitFrame abort+重投,否则旧帧会被静默顶替),
|
||||
* CAN TX 完成中断里自动回灌,保证整块突发(<=16 帧同通道)不丢帧。
|
||||
*
|
||||
* payload:
|
||||
* [0] count 条目数(1..kBurstMaxFrames)
|
||||
@@ -24,7 +26,7 @@ constexpr uint8_t kBurstMaxFrames = 16U;
|
||||
* [4..] count 个条目,每条目 = chnode(1) + data[dlc]
|
||||
* chnode = (ch<<4) | node,id = (node<<16) | suffix
|
||||
*
|
||||
* 响应 ACK:count(1) + 每条目 1 字节状态(CanTxResult 码,0x00=成功)。
|
||||
* 响应 ACK:count(1) + 每条目 1 字节状态(CanTxResult 码,0x00=已受理;邮箱满+队列满才回 0xE4)。
|
||||
*/
|
||||
class SpiCanBurstHandler final : public ISpiCommandHandler {
|
||||
public:
|
||||
@@ -80,7 +82,7 @@ public:
|
||||
continue;
|
||||
}
|
||||
const CanFd::CanTxResult r =
|
||||
CanFd::transmitFrame(ch, id, (dlc > 0U) ? &p[1] : nullptr, dlc);
|
||||
CanFd::enqueueTx(ch, id, (dlc > 0U) ? &p[1] : nullptr, dlc);
|
||||
resp[1U + i] = static_cast<uint8_t>(r);
|
||||
}
|
||||
|
||||
|
||||
@@ -1,14 +1,18 @@
|
||||
/**
|
||||
* @file app_spi_cmd_exmc_diag.cpp
|
||||
* @brief SPI 0xA2 EXMC/HBI 诊断命令处理器:驱动 LAN9252 通断测试 pattern。
|
||||
* @brief SPI 0xA2 EXMC/HBI 诊断命令处理器:驱动 LAN9252 通断测试 pattern / NRST 手动诊断。
|
||||
*
|
||||
* 网页按钮 → ESP32 /spi/send → SPI 0xA2 → 本 handler → EscExmcBus::continuityDrive()
|
||||
* 网页按钮 → ESP32 /spi/send → SPI 0xA2 → 本 handler → 分流:
|
||||
* action 0~10 → EscExmcBus::continuityDrive() (EXMC 数据/控制线 pattern)
|
||||
* action 20~24 → EscLan9252::nrstDrive() (ECAT_NRST/PB10 拉低/拉高/浮空/回读)
|
||||
* 立即返回 ACK,不阻塞 SPI 总线。电平保持到下次调用或 action=0(stop)。
|
||||
* NRST action 返回的 status 字节 = 实际引脚电平(0/1),非 OK 码。
|
||||
*/
|
||||
#include "device/spi/spi_command_handler.h"
|
||||
#include "device/spi/app_spi_command.h"
|
||||
#include "device/spi/app_spi_command_defs.h"
|
||||
#include "comm/esc_exmc_bus.h"
|
||||
#include "comm/esc_lan9252.h"
|
||||
|
||||
namespace {
|
||||
|
||||
@@ -24,7 +28,12 @@ public:
|
||||
{
|
||||
uint8_t status = 0xEE;
|
||||
if (ctx.payload_len >= 1) {
|
||||
status = EscExmcBus::continuityDrive(ctx.payload[0]);
|
||||
const uint8_t action = ctx.payload[0];
|
||||
if (action >= 20U) {
|
||||
status = EscLan9252::nrstDrive(action - 20U);
|
||||
} else {
|
||||
status = EscExmcBus::continuityDrive(action);
|
||||
}
|
||||
}
|
||||
bus.buildResponse(ctx.tx_buf, ctx.tx_buf_size, ctx.tx_len,
|
||||
SpiDefs::kRspAck, &status, 1);
|
||||
|
||||
@@ -19,7 +19,6 @@ enum system_task_t {
|
||||
TASK_CAN_TX_SEND, /**< CANFD 周期发送任务,每 10ms 每通道发送一帧。 */
|
||||
TASK_HEARTBEAT, /**< 系统心跳维护任务。 */
|
||||
TASK_DIAGNOSTIC_PRINT, /**< 诊断信息打印任务。 */
|
||||
TASK_ETHERCAT_PROCESS, /**< EtherCAT 周期/事件处理任务(非实时应用层 PDO 映射)。 */
|
||||
TASK_COUNT /**< 任务总数,用于边界或统计场景。 */
|
||||
};
|
||||
|
||||
|
||||
@@ -52,7 +52,7 @@ constexpr uint16_t CONFIG_MOTOR_OFFLINE_MS = 3000U;
|
||||
/** @brief CAN-FD BRS 使能(canfd.cpp #if)。
|
||||
* 关闭 BRS: 数据段用仲裁段同速率,牢牢性更高。
|
||||
* 电机 BOOT 无法正确接收 BRS 高速段→nonce(16B FD+BRS)被拒绝→握手失败。 */
|
||||
#define CONFIG_CAN_FD_BRS_ENABLE 0U
|
||||
#define CONFIG_CAN_FD_BRS_ENABLE 1U
|
||||
/** @brief 启动时总线探测帧;正常固件关。 */
|
||||
#define CONFIG_CAN_STARTUP_BUS_TEST_ENABLE 0
|
||||
/** @brief 启动时内部回环自检;正常固件关。 */
|
||||
@@ -115,7 +115,7 @@ constexpr bool CONFIG_CAN_CH2_ENABLE = true;
|
||||
* ============================================================ */
|
||||
constexpr uint32_t APP_FW_VERSION_MAJOR = 1U;
|
||||
constexpr uint32_t APP_FW_VERSION_MINOR = 5U;
|
||||
constexpr uint32_t APP_FW_VERSION_PATCH = 15U; /* 1.5.15:EXMC 通断测试新增 MODE0/MODE1 strap 驱动测试(action 0x0A-0x0D, PB8/PB9) */
|
||||
constexpr uint32_t APP_FW_VERSION_PATCH = 32U; /* 1.5.32:AL 通路诊断打印(排查主站连接后 AL 卡 INIT)——ECAT_Main 收到主站 AL 控制请求打 [ECAT][AL] evt/req、SetALStatus 写回打 [ECAT][AL] -> 状态+错误码、task_runner 5s 心跳 [ECAT][HB] al/code/evt(仅同步 ISR 激活时,心跳停=主循环卡死);dc_app.c 导出 EcatDbg_ReadAlDiag(0x0130+0x0220 快照)。1.5.31:DC 通路观测同步到网页——SSC OD 新增只读记录 0x2000 SYNC Diag(sub1=sync0/sub2=sync1/sub3=escirq/sub4=uptime 秒,UINT32),APPL_InputMapping 每 PDO 周期从 EthercatSync 拉取快照(ethercat_sync.cpp 导出 EcatDbg_Get* extern C);配套 tools/ecat:/api/sync_diag 经 SDO 读 0x2000 并以主站时间戳算 sync0 速率,ecat.html 运行期 2s 轮询显示,串口 5s 低频打印保留为旁证。1.5.30:DC SYNC0 通路可观测性——EthercatSync 增加 s_sync0Count/s_sync1Count/s_escIrqCount 三个 volatile 计数器(ISR 内自增,SYNC0 路径实测仅 LDR/ADDS/STR 三条指令,仍在 .ramfunc);task_runner 新增 ethercatSyncCounterPrint(),5s 诊断按增量打印 sync0 速率(主站设 1ms 周期时应 ≈1000/s),无脉冲时静默。配套 tools/ecat 支持分布时钟:ecat_tool.py send/monitor 新增 --dc-cycle-us/--dc-shift-us(0=free-run 默认),enter_op() 在 config_map 后、进 SAFEOP 前调 config_dc()+dc_sync();ecat_server.py /api/op/start 接受 dc_cycle_us/dc_shift_us;ecat.html 加 DC SYNC0 周期下拉。此前工具从不调用 config_dc,PDO 恒为 free-run,SYNC0 引脚无脉冲、Sync0_Isr 从未触发,1.5.28 配的 word1=0xCC/word2=5000 一直未被实测覆盖。1.5.29:删除 GD32 侧 EEPROM 烧录路径(EscEeprom/esc_eeprom.h/.cpp、eeprom_image.h 及 task_runner 主循环烧录块)——ESI 镜像改由 EtherCAT 主站侧刷写(tools/ecat/ecat_flash_eeprom.py 经 ESC 0x0500 EEPROM 接口,2026-08-31 实测 162 字全校验通过),固件不再负责烧录;gen_eeprom.py 生成器保留。HBI 复查修复:maskEnter/Exit 补屏蔽 EXTI2(SYNC1)——Sync1_Isr 的 PDO_InputMapping→HW_EscWriteIsr 两步事务(preempt=1)可打断主循环 HW_EscRead/Write 致错址;通断测试模式进入/退出改经 maskEnter/Exit 屏蔽三类 EXTI,防测试期 ISR 访问 GPIO 态总线读垃圾。1.5.28:对照 LAN9252 数据手册/AN1907/官方 EVB XML 修正——EEPROM word1 高字节 0x00→0xCC(Sync/Latch 0151h:SYNC0/1 Map+Output、推挽低有效配 EXTI 下降沿;原 0x00 时 SYNC0 引脚留在 LATCH 输入态,DC 模式过程数据死);word2 0→5000(Sync 脉宽 0982h,x10ns=50us,0=确认模式不可用);eeprom_image.h 尾部补真实 0xFF 字节(原注释行致 C 零填充,烧写全阵列);EXMC ADDSET 2→4 HCLK(DS 表9-4 trdrd/twrwr/twrrd≥13ns);XML ConfigData 扩到 14B 并加 CycleTimeSync1。1.5.27:LAN9252 使用修正——EXMC 16-bit 地址左移 1 位(HBI DATA 偏移 0x20000/0x30000→0x40000/0x60000);gen_eeprom.py 按 ETG.1000.6 重写(CRC-8/word7、身份字 word8-15、CoE=0x0004、分类区 byte128);DCU_ESI.xml ConfigData PDI 0x05→0x8D、RxPdo/TxPdo 互换并改 4×64bit 对齐 dc_app.c;HBI 两步事务互斥守卫(主循环 mask EXTI0/1 + PDI/Sync1 ISR 屏蔽 EXTI0);删除死代码 EthercatSlave::periodicIsr 三缓冲;20s HBI 诊断在同步 ISR 激活后自动停跑 */
|
||||
constexpr uint32_t APP_FW_VERSION_BUILD = 0U;
|
||||
|
||||
|
||||
@@ -178,7 +178,8 @@ constexpr uint8_t CONFIG_CANFD_ID_SNAPSHOT_MAX = 16U;
|
||||
/** @brief 快照条目最大数据字节数(CAN FD DLC 上限)。 */
|
||||
constexpr uint8_t CONFIG_CANFD_ID_SNAPSHOT_DATA_MAX = 64U;
|
||||
|
||||
/** @brief ESP32 硬件复位控制端口(PC9 接 ESP32 EN/RESET)。 */
|
||||
/** @brief ESP32 硬件复位控制端口(PC9 接 ESP32 EN/RESET,对应原理图 WLESS_EN)。
|
||||
* @note 复位后 PC9 浮空,ESP_CHIP_EN 会被 C41/U11 拉低;BOOT 也必须主动拉高。 */
|
||||
constexpr uint32_t CONFIG_ESP32_RESET_PORT = GPIOC;
|
||||
/** @brief ESP32 硬件复位控制引脚。 */
|
||||
constexpr uint32_t CONFIG_ESP32_RESET_PIN = GPIO_PIN_9;
|
||||
@@ -296,11 +297,9 @@ constexpr uint32_t CONFIG_IRQ_PREEMPT_SPI_USART = 3U;
|
||||
/* ============================================================
|
||||
* EtherCAT LAN9252 配置(开关见顶部 CONFIG_MAIN_LOOP_WFI / ETHERCAT_SYNC_MODE)
|
||||
* ============================================================ */
|
||||
constexpr uint32_t CONFIG_ETHERCAT_ISR_MAX_US_THRESHOLD = 5U;
|
||||
constexpr uint32_t CONFIG_ESC_PDI_INPUT_OFFSET = 0x1000U;
|
||||
constexpr uint32_t CONFIG_ESC_PDI_OUTPUT_OFFSET = 0x1200U;
|
||||
/** @brief 默认 PDO 缓冲大小(字节),应用/ISR 三缓冲共用。 */
|
||||
constexpr size_t CONFIG_ETHERCAT_PDO_SIZE = 32U;
|
||||
/* 1.5.27:CONFIG_ETHERCAT_ISR_MAX_US_THRESHOLD / CONFIG_ESC_PDI_INPUT_OFFSET /
|
||||
* CONFIG_ESC_PDI_OUTPUT_OFFSET / CONFIG_ETHERCAT_PDO_SIZE 随死代码 EthercatSlave::
|
||||
* periodicIsr 三缓冲一并删除;PDO 交换由 SSC(dc_app.c APPL_*, 4×64bit=32B) 完成。 */
|
||||
|
||||
/* ---------- EXMC 引脚配置(GD32E508 EXMC 标准映射) ---------- */
|
||||
constexpr uint32_t CONFIG_ESC_EXMC_BASE_ADDR = 0x60000000U;
|
||||
@@ -337,6 +336,17 @@ constexpr IRQn_Type CONFIG_ESC_SYNC1_IRQn = EXTI2_IRQn;
|
||||
constexpr uint32_t CONFIG_ESC_SYNC1_EXTI_SOURCE_PORT = GPIO_PORT_SOURCE_GPIOB;
|
||||
constexpr uint32_t CONFIG_ESC_SYNC1_EXTI_SOURCE_PIN = GPIO_PIN_SOURCE_2;
|
||||
|
||||
/* ---------- ESC 硬件复位(PB10 → LAN9252 Pin11 RST#,SCH 第1页 Pin47) ---------- */
|
||||
/** @note 2026-08-22(1.5.25) 前固件从未初始化 PB10,RST# 仅靠 R44 10k 上拉自然释放,
|
||||
* 不满足 LAN9252 手册 18.6.3 tpurstd(电源稳定→RST#释放)≥25ms、trstia(低脉宽)≥200µs。
|
||||
* 由 EscLan9252::hardwareReset() 执行受控复位序列;释放后保持推挽输出高,
|
||||
* GD32 自身复位期间引脚回浮空,由 R44 上拉维持高,不会误复位 ESC。 */
|
||||
constexpr uint32_t CONFIG_ESC_NRST_PORT = GPIOB;
|
||||
constexpr uint32_t CONFIG_ESC_NRST_PIN = GPIO_PIN_10;
|
||||
constexpr uint32_t CONFIG_ESC_NRST_ASSERT_MS = 30U; /* 低脉宽,同时覆盖 tpurstd 25ms 与 trstia 200µs */
|
||||
constexpr uint32_t CONFIG_ESC_HBI_POLL_TIMEOUT_MS = 300U; /* RST# 释放后等待 ESC 启动/EEPROM 加载 */
|
||||
constexpr uint32_t CONFIG_ESC_HBI_POLL_STEP_MS = 5U;
|
||||
|
||||
/* ---------- EXMC 数据/控制线引脚(GD32E508 标准映射) ---------- */
|
||||
constexpr uint32_t CONFIG_EXMC_D0_PORT = GPIOD; constexpr uint32_t CONFIG_EXMC_D0_PIN = GPIO_PIN_14;
|
||||
constexpr uint32_t CONFIG_EXMC_D1_PORT = GPIOD; constexpr uint32_t CONFIG_EXMC_D1_PIN = GPIO_PIN_15;
|
||||
@@ -357,10 +367,13 @@ constexpr uint32_t CONFIG_EXMC_D15_PORT = GPIOD; constexpr uint32_t CONFIG_EXMC
|
||||
constexpr uint32_t CONFIG_EXMC_A16_PORT = GPIOD; constexpr uint32_t CONFIG_EXMC_A16_PIN = GPIO_PIN_11;
|
||||
/* A17 (PD12) 用于 HBI Indexed Mode 访问 HBI_DATA_0 寄存器,已将 LAN9252 A2 (Pin 29) 接到 PD12。
|
||||
* 注意 LAN9252 引脚顺序:Pin25=A1, Pin26=A3, Pin27=A4, Pin28=CS, Pin29=A2。
|
||||
* A2 是 IDX/DATA 选择位,必须接到 PD12 才能访问 DATA 寄存器,EEPROM 烧录功能可用。 */
|
||||
* A2 是 IDX/DATA 选择位,必须接到 PD12 才能访问 DATA 寄存器,EEPROM 烧录功能可用。
|
||||
* 注意 EXMC 16-bit 模式地址左移 1 位(用户手册 p869):A16(PD11)=CPU 偏移 0x20000、
|
||||
* A17(PD12)=CPU 偏移 0x40000,HBI_DATA_0 访问偏移见 esc_lan9252.cpp。 */
|
||||
constexpr uint32_t CONFIG_EXMC_A17_PORT = GPIOD; constexpr uint32_t CONFIG_EXMC_A17_PIN = GPIO_PIN_12;
|
||||
constexpr uint32_t CONFIG_EXMC_NOE_PORT = GPIOD; constexpr uint32_t CONFIG_EXMC_NOE_PIN = GPIO_PIN_4;
|
||||
constexpr uint32_t CONFIG_EXMC_NWE_PORT = GPIOD; constexpr uint32_t CONFIG_EXMC_NWE_PIN = GPIO_PIN_5;
|
||||
constexpr uint32_t CONFIG_EXMC_NE1_PORT = GPIOD; constexpr uint32_t CONFIG_EXMC_NE1_PIN = GPIO_PIN_7;
|
||||
/* LQFP100 只有 NE0(PD7) 一个 NOR/SRAM 片选,归 Region0(0x60000000);命名沿用硬件真值 NE0。 */
|
||||
constexpr uint32_t CONFIG_EXMC_NE0_PORT = GPIOD; constexpr uint32_t CONFIG_EXMC_NE0_PIN = GPIO_PIN_7;
|
||||
|
||||
#endif /* SYS_CONFIG_H */
|
||||
|
||||
@@ -116,6 +116,9 @@ void AppInitializer::init()
|
||||
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));
|
||||
|
||||
Usart0::configurePins();
|
||||
Usart0::init();
|
||||
@@ -144,8 +147,13 @@ void AppInitializer::init()
|
||||
mark(kScheduler);
|
||||
|
||||
/* ---- 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 必须照常启动并等待主站配置。
|
||||
* EEPROM 校验/烧录延迟到主循环执行,避免阻塞 init(烧录需 ~20s)。 */
|
||||
if (EscLan9252::init()) {
|
||||
EthercatSlave::init();
|
||||
if (EscLan9252::isBusReady()) {
|
||||
const uint16_t sscResult = MainInit();
|
||||
if (sscResult == 0U) {
|
||||
mark(kEthercat);
|
||||
|
||||
@@ -16,9 +16,8 @@
|
||||
#include "sys/task_manager.h"
|
||||
#include "sys/task_runner.h"
|
||||
#include "comm/ethercat_slave.h"
|
||||
#include "comm/esc_eeprom.h"
|
||||
#include "comm/ethercat_sync.h"
|
||||
#include "comm/esc_exmc_bus.h"
|
||||
#include "comm/ssc/port/eeprom_image.h"
|
||||
|
||||
extern "C" {
|
||||
void MainLoop(void);
|
||||
@@ -272,6 +271,68 @@ bool TaskRunner::copyAdcSample(Adc::Sample *out)
|
||||
return true;
|
||||
}
|
||||
|
||||
/* SSC 侧 AL 诊断快照(dc_app.c): [19:16]=AL 状态 [31:24]=AL 错误码 [15:0]=AL 事件寄存器 */
|
||||
extern "C" uint32_t EcatDbg_ReadAlDiag(void);
|
||||
|
||||
/**
|
||||
* @brief 1.5.32 ECAT 心跳:主循环存活证明 + ESC AL 状态/事件寄存器快照。
|
||||
* @note 仅在同步 ISR 激活(SSC 在跑)时打印。心跳停 = 主循环卡死;
|
||||
* al=0x1 且 evt bit0 长期置位 = SSC 没处理 AL 请求;
|
||||
* al 与主站读到的不一致 = ESC 写回路径问题。
|
||||
*/
|
||||
static void ethercatAlHeartbeat()
|
||||
{
|
||||
if (!EthercatSync::isActive()) {
|
||||
return;
|
||||
}
|
||||
const uint32_t alDiag = EcatDbg_ReadAlDiag();
|
||||
printf("[ECAT][HB] al=0x%lX code=0x%02lX evt=0x%04lX (uptime=%lus)\r\n",
|
||||
(unsigned long)((alDiag >> 16) & 0x0FU),
|
||||
(unsigned long)((alDiag >> 24) & 0xFFU),
|
||||
(unsigned long)(alDiag & 0xFFFFU),
|
||||
(unsigned long)g_app_uptime_sec);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 打印 EtherCAT 同步中断计数与速率(DC 通路验证)。
|
||||
* @note 主站启用 DC(SYNC0 周期 T)后,sync0 速率应稳定 ≈ 1e6/T_us 次/秒;
|
||||
* free-run 模式下 SYNC0 引脚无脉冲,三个计数器恒为 0(本函数不打印)。
|
||||
* 仅在有增量时输出,避免未接主站时刷屏。
|
||||
*/
|
||||
static void ethercatSyncCounterPrint()
|
||||
{
|
||||
static uint32_t s_prevSync0 = 0U;
|
||||
static uint32_t s_prevSync1 = 0U;
|
||||
static uint32_t s_prevEscIrq = 0U;
|
||||
static uint32_t s_prevSec = 0U;
|
||||
|
||||
const uint32_t sync0 = EthercatSync::getSync0Count();
|
||||
const uint32_t sync1 = EthercatSync::getSync1Count();
|
||||
const uint32_t escIrq = EthercatSync::getEscIrqCount();
|
||||
|
||||
const uint32_t d0 = sync0 - s_prevSync0; /* 无符号回绕自然成立 */
|
||||
const uint32_t d1 = sync1 - s_prevSync1;
|
||||
const uint32_t di = escIrq - s_prevEscIrq;
|
||||
|
||||
const uint32_t now = g_app_uptime_sec;
|
||||
const uint32_t elapsed = (now > s_prevSec) ? (now - s_prevSec) : 1U;
|
||||
|
||||
s_prevSync0 = sync0;
|
||||
s_prevSync1 = sync1;
|
||||
s_prevEscIrq = escIrq;
|
||||
s_prevSec = now;
|
||||
|
||||
if (d0 == 0U && d1 == 0U && di == 0U) {
|
||||
return; /* 未接主站或 free-run:无脉冲,静默 */
|
||||
}
|
||||
|
||||
printf("[ECAT][SYNC] sync0=%lu(+%lu, %lu/s) sync1=%lu(+%lu) escirq=%lu(+%lu) dt=%lus\r\n",
|
||||
(unsigned long)sync0, (unsigned long)d0, (unsigned long)(d0 / elapsed),
|
||||
(unsigned long)sync1, (unsigned long)d1,
|
||||
(unsigned long)escIrq, (unsigned long)di,
|
||||
(unsigned long)elapsed);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 5s 诊断任务:默认仅状态变化/错误增量;可选全量 UART 摘要。
|
||||
* @note CONFIG_UART_TEMP_PERIODIC_PRINT=1 时打印 ADC/CAN/ECAT 全量行(调试用)。
|
||||
@@ -326,19 +387,16 @@ void TaskRunner::taskDiagnosticPrint()
|
||||
#endif
|
||||
|
||||
if (EthercatSlave::isEscReady()) {
|
||||
uint32_t maxCyc = 0U, avgCyc = 0U, cnt = 0U;
|
||||
EthercatSlave::getIsrStats(&maxCyc, &avgCyc, &cnt);
|
||||
/* 180MHz: cycles / 180 = µs;用 *1000/180 获得亚微秒精度避免浮点 */
|
||||
const uint32_t maxUs_x1000 = maxCyc * 1000U / 180U;
|
||||
const uint32_t avgUs_x1000 = avgCyc * 1000U / 180U;
|
||||
printf("[ECAT] periodicIsr cnt=%lu max=%lu.%03luus avg=%lu.%03luus\r\n",
|
||||
(unsigned long)cnt,
|
||||
(unsigned long)(maxUs_x1000 / 1000U), (unsigned long)(maxUs_x1000 % 1000U),
|
||||
(unsigned long)(avgUs_x1000 / 1000U), (unsigned long)(avgUs_x1000 % 1000U));
|
||||
/* 1.5.27:periodicIsr 已删除(死代码,无调用点),PDO 交换由 SSC
|
||||
* Sync0_Isr → ECAT_Application → dc_app.c APPL_* 完成。 */
|
||||
printf("[ECAT] ESC ready (chip id=0x%08lX), PDO via SSC Sync0_Isr\r\n",
|
||||
(unsigned long)EthercatSlave::getChipId());
|
||||
} else {
|
||||
printf("[ECAT] ESC not detected (chip id=0x%08lX), EtherCAT disabled\r\n",
|
||||
(unsigned long)EthercatSlave::getChipId());
|
||||
}
|
||||
ethercatSyncCounterPrint();
|
||||
ethercatAlHeartbeat();
|
||||
}
|
||||
#else
|
||||
/* ---- 默认静默路径:仅错误增量 / 状态变化,无 uptime 心跳 ---- */
|
||||
@@ -351,7 +409,6 @@ void TaskRunner::taskDiagnosticPrint()
|
||||
static uint8_t s_prev_adc_valid = 0xFFU; /* 0xFF=尚未采样 */
|
||||
static bool s_prev_esc_ready = false;
|
||||
static bool s_esc_ready_inited = false;
|
||||
static uint32_t s_prev_ecat_max_cyc = 0U;
|
||||
|
||||
const uint32_t rx_drop = diag->rx_drop_count;
|
||||
const uint32_t rx_full = diag->rx_fifo_full_count;
|
||||
@@ -403,39 +460,14 @@ void TaskRunner::taskDiagnosticPrint()
|
||||
(unsigned long)g_app_uptime_sec);
|
||||
s_prev_esc_ready = esc_ready;
|
||||
}
|
||||
|
||||
if (esc_ready) {
|
||||
uint32_t maxCyc = 0U, avgCyc = 0U, cnt = 0U;
|
||||
EthercatSlave::getIsrStats(&maxCyc, &avgCyc, &cnt);
|
||||
/* 阈值单位 µs → CPU 周期(180 MHz) */
|
||||
const uint32_t threshCyc = CONFIG_ETHERCAT_ISR_MAX_US_THRESHOLD * 180U;
|
||||
if (maxCyc > threshCyc && maxCyc > s_prev_ecat_max_cyc) {
|
||||
const uint32_t maxUs_x1000 = maxCyc * 1000U / 180U;
|
||||
printf("[ECAT] ISR max over threshold: max=%lu.%03luus thr=%luus cnt=%lu (uptime=%lus)\r\n",
|
||||
(unsigned long)(maxUs_x1000 / 1000U),
|
||||
(unsigned long)(maxUs_x1000 % 1000U),
|
||||
(unsigned long)CONFIG_ETHERCAT_ISR_MAX_US_THRESHOLD,
|
||||
(unsigned long)cnt,
|
||||
(unsigned long)g_app_uptime_sec);
|
||||
s_prev_ecat_max_cyc = maxCyc;
|
||||
}
|
||||
}
|
||||
/* 1.5.27:periodicIsr ISR 统计随死代码一并删除 */
|
||||
/* DC SYNC0 通路验证:仅在有脉冲增量时打印(未接主站时静默)。 */
|
||||
ethercatSyncCounterPrint();
|
||||
ethercatAlHeartbeat();
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 执行 EtherCAT 周期/事件处理(非实时应用层 PDO 映射)。
|
||||
* @param[in] 无。
|
||||
* @param[out] 无。
|
||||
* @retval 无返回值。
|
||||
* @note 由周期 ISR(EXTI0 SYNC0)或 ESC 事件 ISR(EXTI1 IRQ)置位 TASK_ETHERCAT_PROCESS。
|
||||
*/
|
||||
void TaskRunner::taskEthercatProcess()
|
||||
{
|
||||
EthercatSlave::process();
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 初始化任务执行器运行时上下文。
|
||||
* @param[in] 无。
|
||||
@@ -468,41 +500,21 @@ void TaskRunner::runPending()
|
||||
{
|
||||
Watchdog::feed();
|
||||
|
||||
/* === HBI 周期诊断:每 20 秒重新跑一次 EXMC/HBI 探活,方便热插拔/焊接后观察 === */
|
||||
/* === HBI 周期诊断:每 20 秒重新跑一次 EXMC/HBI 探活,方便热插拔/焊接后观察 ===
|
||||
* 1.5.27:EtherCAT 同步 ISR 激活后停止(diagDump 会把 EXMC 引脚切成 GPIO,
|
||||
* 拆毁运行中的总线;诊断的 bit-bang 也与 HBI 事务互斥冲突)。 */
|
||||
static uint32_t s_lastDiagSec = 0xFFFFFFFFU;
|
||||
if (g_app_uptime_sec >= s_lastDiagSec + 20U || s_lastDiagSec == 0xFFFFFFFFU) {
|
||||
if (!EthercatSync::isActive()
|
||||
&& (g_app_uptime_sec >= s_lastDiagSec + 20U || s_lastDiagSec == 0xFFFFFFFFU)) {
|
||||
s_lastDiagSec = g_app_uptime_sec;
|
||||
printf("[ECAT][DIAG] === 20s periodic HBI probe (uptime=%lus) ===\r\n",
|
||||
(unsigned long)g_app_uptime_sec);
|
||||
EscExmcBus::diagDump();
|
||||
}
|
||||
|
||||
/* 延迟 EEPROM 烧录:init 不阻塞,主循环第一轮执行一次。
|
||||
* 校验编译期镜像与 AT24C32 内容,不匹配则全量烧录(~20s,期间看门狗正常喂)。 */
|
||||
static bool s_eepromDone = false;
|
||||
if (!s_eepromDone) {
|
||||
s_eepromDone = true;
|
||||
if (EscEeprom::init()) {
|
||||
if (EscEeprom::verifyEsi(ESI_EEPROM_IMAGE, ESI_EEPROM_SIZE)) {
|
||||
printf("[ECAT][EEPROM] content OK, skip burn\r\n");
|
||||
} else {
|
||||
printf("[ECAT][EEPROM] content mismatch, burning ESI (%u bytes)...\r\n",
|
||||
(unsigned)ESI_EEPROM_SIZE);
|
||||
if (EscEeprom::writeEsi(ESI_EEPROM_IMAGE, ESI_EEPROM_SIZE)) {
|
||||
if (EscEeprom::verifyEsi(ESI_EEPROM_IMAGE, ESI_EEPROM_SIZE)) {
|
||||
EscEeprom::reload();
|
||||
printf("[ECAT][EEPROM] burn+verify OK, reload issued\r\n");
|
||||
} else {
|
||||
printf("[ECAT][EEPROM] post-burn verify FAIL\r\n");
|
||||
}
|
||||
} else {
|
||||
printf("[ECAT][EEPROM] burn FAIL\r\n");
|
||||
}
|
||||
}
|
||||
} else {
|
||||
printf("[ECAT][EEPROM] init FAIL (PDI control not acquired)\r\n");
|
||||
}
|
||||
}
|
||||
/* 延迟 EEPROM 烧录已删除(1.5.29):ESI 镜像改由 EtherCAT 主站侧刷写
|
||||
* (tools/ecat/ecat_flash_eeprom.py 经 ESC 0x0500 EEPROM 接口),
|
||||
* 固件不再承担烧录/校验职责。 */
|
||||
|
||||
/* poll() 含按铃超时看门狗(cancelRing),必须每轮无条件执行。
|
||||
* 主动推送已随 2026-08 请求驱动改造移除,此处仅需消费请求响应的按铃。 */
|
||||
|
||||
@@ -52,8 +52,6 @@ private:
|
||||
static void taskHeartbeat();
|
||||
/** @brief 诊断打印任务。 */
|
||||
static void taskDiagnosticPrint();
|
||||
/** @brief EtherCAT 周期/事件处理(非实时应用层 PDO 映射)。 */
|
||||
static void taskEthercatProcess();
|
||||
};
|
||||
|
||||
#endif /* SYS_TASK_RUNNER_H */
|
||||
|
||||
Reference in New Issue
Block a user