331 lines
11 KiB
C++
331 lines
11 KiB
C++
/**
|
||||
|
|
* @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);
|
|||
|
|
}
|