Files
DCU_APP/GD32E508_DCU_APP/comm/esc_eeprom.cpp
T
cbb 934836618a feat(spi): 添加ADC反馈命令处理
新增SPI命令类型0xC4用于ADC调试反馈,实现在GD32上采集
温度/VDDA/VIN等模拟量数据的反馈功能,并支持主动推送和被动查询模式。

feat(spi): 扩展PING命令响应格式

修改PING命令响应格式,增加APP身份信息16字节载荷,包含版本号、
启动时间、功能开关位图等信息,用于主机获取从设备详细状态。

refactor(can_feedback): 优化CAN反馈推送逻辑

改进CAN反馈推送机制,跳过编译期关闭的通道,只遍历启用的通道,
确保推送效率并避免无效操作。

feat(comm): 添加EXMC总线初始化和诊断功能

添加了EXMC总线的初始化功能,包括GPIO配置、NOR/SRAM区域参数设置,
以及总线探活机制。实现了总线可见性检测,当检测到全0或全1的挂死状态时
会返回false并输出错误信息。同时添加了printf日志输出用于调试。

refactor(esc_lan9252): 重构ESC初始化返回值

将EscLan9252::init()方法的返回类型从void改为bool,
使其能够返回EXMC总线准备状态,便于上层进行状态检查。

docs(app_spi_cmd_ping): 添加代码注释文档

为SPI命令处理相关类和方法添加详细的Doxygen风格注释文档,
提高代码可读性和维护性。
2026-07-20 02:36:56 +08:00

331 lines
11 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 偏移 0x40000
* HBI_DATA_0 高 word: A2=1, A1=1 → EXMC 偏移 0x60000
*
* 注意: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 → 偏移 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。 */
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 */
/* 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)
{
EscExmcBus::write16(HBI_IDX0_OFFSET, esc_addr);
return (uint8_t)(EscExmcBus::read16(HBI_DATA0_LOW_OFF) & 0x00FFU);
}
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);
}
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_LOW_OFF); /* 计数器自增,读高 word */
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 */
}
} /* anonymous namespace */
bool EscEeprom::s_inited = false;
bool EscEeprom::init()
{
s_inited = false;
if (!EscExmcBus::isOk()) {
printf("[EEPROM] init: FAIL, EXMC bus not ready\r\n");
return false;
}
/* 步骤 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()) {
printf("[EEPROM] init: FAIL, EEPROM interface busy timeout\r\n");
return false;
}
/* 读回 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;
}
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);
}