feat(project): 更新项目配置并增加ESC EEPROM功能

- 添加新的SPI命令文件:app_spi_cmd_can_burst.cpp 和 app_spi_cmd_exmc_diag.cpp
- 移除旧的SPI命令文件:app_spi_cmd_ota_wait.cpp, app_spi_cmd_ping.cpp,
  app_spi_cmd_can_bridge.cpp, app_spi_cmd_can_feedback.cpp,
  app_spi_cmd_id_snapshot.cpp, app_spi_cmd_adc_feedback.cpp,
  app_spi_cmd_motor_state.cpp, app_spi_cmd_motor_version.cpp,
  app_spi_cmd_motor_ota.cpp
- 新增SSC EtherCAT从站协议栈相关文件组,包含头文件和源文件
- 添加ESC EEPROM操作相关的头文件和源文件
- 修正HBI寄存器EXMC偏移地址映射错误
- 实现8位、16位、32位ESC寄存器读写函数
- 改进EEPROM初始化逻辑,添加重试机制和诊断功能
This commit is contained in:
cbb
2026-08-09 03:27:21 +08:00
parent 8e5da18100
commit a2d0b28b3d
49 changed files with 13258 additions and 554 deletions
+108 -42
View File
@@ -8,8 +8,8 @@
*
* 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 偏移 0x40000
* HBI_DATA_0 高 word: A2=1, A1=1 → EXMC 偏移 0x60000
* 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 地址。
@@ -24,13 +24,13 @@ 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 → 偏移 0x20000 → HBI_IDX_0 高 word(A1=EXMC_A16=PD11)
* A2=1, A1=0 → word 2 → 偏移 0x40000 → HBI_DATA_0 低 word(A2=EXMC_A17=PD12)
* A2=1, A1=1 → word 3 → 偏移 0x60000 → HBI_DATA_0 高 word
* 注:EXMC A16 对应 byte 偏移 0x20000,A17 对应 0x40000。 */
* 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 = 0x40000U; /* A2=1, A1=0 */
constexpr uint32_t HBI_DATA0_HIGH_OFF = 0x60000U; /* A2=1, A1=1 */
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 */
@@ -76,14 +76,36 @@ bool waitIdle(uint32_t timeout_loops = 500000U)
uint8_t readEsc8(uint16_t esc_addr)
{
EscExmcBus::write16(HBI_IDX0_OFFSET, esc_addr);
return (uint8_t)(EscExmcBus::read16(HBI_DATA0_LOW_OFF) & 0x00FFU);
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)
{
EscExmcBus::write16(HBI_IDX0_OFFSET, esc_addr);
EscExmcBus::write16(HBI_DATA0_LOW_OFF, (uint16_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)
@@ -102,21 +124,30 @@ 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_LOW_OFF); /* 计数器自增,读高 word */
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_LOW_OFF, (uint16_t)((val >> 16) & 0xFFFFU));/* 高 word */
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;
@@ -126,37 +157,72 @@ bool EscEeprom::init()
return false;
}
/* 步骤 1:置 PDI EEPROM Control = 1,把 EEPROM 接口交给 PDI(GD32) */
writeEsc8(ESC_EEP_CFG, EEP_CFG_PDI_CTRL);
/* 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);
}
/* 步骤 2:置 Access to EEPROM = 1,获取访问权 */
writeEsc8(ESC_EEP_PDI_ACC, EEP_PDI_ACC_ACCESS);
/* 步骤 1:置 PDI EEPROM Control = 1,把 EEPROM 接口交给 PDI(GD32) */
writeEsc8(ESC_EEP_CFG, EEP_CFG_PDI_CTRL);
/* 步骤 3:等待接口空闲,读回状态确认 */
if (!waitIdle()) {
printf("[EEPROM] init: FAIL, EEPROM interface busy timeout\r\n");
return false;
/* 步骤 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();
}
/* 读回 Configuration 寄存器确认 PDI 控制已生效 */
uint8_t cfg = readEsc8(ESC_EEP_CFG);
if ((cfg & EEP_CFG_PDI_CTRL) == 0U) {
printf("[EEPROM] init: FAIL, PDI EEPROM Control not set (cfg=0x%02X)\r\n", (unsigned)cfg);
return false;
}
/* 确认 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), AT24C32 requires it\r\n",
(unsigned)ctrl);
/* 不直接 fail,允许继续尝试;可能由 strap 自动配置 */
}
s_inited = true;
printf("[EEPROM] init: OK, PDI has EEPROM control (cfg=0x%02X, ctrl=0x%04X)\r\n",
(unsigned)cfg, (unsigned)ctrl);
return true;
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)