Files
DCU_APP/GD32E508_DCU_APP/comm/esc_eeprom.cpp
T
cbb a2d0b28b3d 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初始化逻辑,添加重试机制和诊断功能
2026-08-09 03:27:21 +08:00

397 lines
14 KiB
C++
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/**
* @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);
}