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风格注释文档, 提高代码可读性和维护性。
This commit is contained in:
@@ -0,0 +1,330 @@
|
||||
/**
|
||||
* @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);
|
||||
}
|
||||
@@ -0,0 +1,74 @@
|
||||
/**
|
||||
* @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 */
|
||||
@@ -8,6 +8,7 @@
|
||||
*/
|
||||
#include "comm/esc_exmc_bus.h"
|
||||
#include "sys/config.h"
|
||||
#include <stdio.h>
|
||||
|
||||
extern "C" {
|
||||
#include "gd32e50x_rcu.h"
|
||||
@@ -18,8 +19,13 @@ extern "C" {
|
||||
/* 内存映射访问宏:基址 + 偏移(按 16-bit 半字对齐,LAN9252 HBI 地址左移 1 位)。 */
|
||||
#define ESC_EXMC_REG16(offset) (*(volatile uint16_t*)(CONFIG_ESC_EXMC_BASE_ADDR + (offset)))
|
||||
|
||||
void EscExmcBus::init()
|
||||
bool EscExmcBus::s_ok = false;
|
||||
|
||||
bool EscExmcBus::init()
|
||||
{
|
||||
s_ok = false;
|
||||
|
||||
printf("[ECAT][EXMC] init: enable RCU clocks\r\n");
|
||||
rcu_periph_clock_enable(RCU_GPIOD);
|
||||
rcu_periph_clock_enable(RCU_GPIOE);
|
||||
rcu_periph_clock_enable(RCU_AF);
|
||||
@@ -43,11 +49,14 @@ void EscExmcBus::init()
|
||||
gpio_init(CONFIG_EXMC_D14_PORT, GPIO_MODE_AF_PP, GPIO_OSPEED_50MHZ, CONFIG_EXMC_D14_PIN);
|
||||
gpio_init(CONFIG_EXMC_D15_PORT, GPIO_MODE_AF_PP, GPIO_OSPEED_50MHZ, CONFIG_EXMC_D15_PIN);
|
||||
|
||||
/* 控制线:A16 / NOE / NWE / NE1 */
|
||||
/* 控制线:A16 / A17 / NOE / NWE / NE1
|
||||
* A17(PD12) 用于 HBI Indexed Mode 访问 HBI_DATA_0,已将 LAN9252 A2 (Pin 29) 接到 PD12。 */
|
||||
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, CONFIG_EXMC_A17_PIN);
|
||||
gpio_init(CONFIG_EXMC_NOE_PORT, GPIO_MODE_AF_PP, GPIO_OSPEED_50MHZ, 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);
|
||||
printf("[ECAT][EXMC] init: GPIO configured (D0-D15/A16/A17/NOE/NWE/NE1)\r\n");
|
||||
|
||||
/*
|
||||
* EXMC NOR/SRAM Region1:16-bit、非复用、异步写。
|
||||
@@ -88,8 +97,34 @@ void EscExmcBus::init()
|
||||
|
||||
wr_timing = rw_timing;
|
||||
|
||||
printf("[ECAT][EXMC] init: applying NOR/SRAM Region1 params (16b, async, t_data=%u HCLK)\r\n",
|
||||
(unsigned)rw_timing.asyn_data_setuptime);
|
||||
|
||||
exmc_norsram_init(&norsram_init);
|
||||
exmc_norsram_enable(CONFIG_ESC_EXMC_BANK_NOR_SRAM_REGION1);
|
||||
|
||||
/* 总线探活:Region1 使能后立即读取 LAN9252 芯片 ID 低半字(期望 0x4321)。
|
||||
* 0x0000/0xFFFF 通常意味着 EXMC 时序不满足、地址线接错或 ESC 未上电;
|
||||
* 此处仅做总线可见性探活,芯片 ID 完整性校验留给 EscLan9252::init()。 */
|
||||
const uint16_t probe_low = read16((uint32_t)0x0000U << 1U);
|
||||
const uint16_t probe_high = read16((uint32_t)0x0002U << 1U);
|
||||
const uint32_t probe_id = ((uint32_t)probe_high << 16) | probe_low;
|
||||
printf("[ECAT][EXMC] init: Region1 enabled, probe id_lo=0x%04X id_hi=0x%04X (id=0x%08lX)\r\n",
|
||||
(unsigned)probe_low, (unsigned)probe_high, (unsigned long)probe_id);
|
||||
|
||||
/* 总线挂死(全 0/全 1)视为 EXMC 未就绪;chip ID 完整校验在 EscLan9252::init()。 */
|
||||
if (probe_low == 0x0000U && probe_high == 0x0000U) {
|
||||
printf("[ECAT][EXMC] init: FAIL, bus reads all-zero (check wiring/NE1/A16/timing)\r\n");
|
||||
return false;
|
||||
}
|
||||
if (probe_low == 0xFFFFU && probe_high == 0xFFFFU) {
|
||||
printf("[ECAT][EXMC] init: FAIL, bus reads all-ones (check ESC power/NOE/NWE)\r\n");
|
||||
return false;
|
||||
}
|
||||
|
||||
s_ok = true;
|
||||
printf("[ECAT][EXMC] init: OK, bus visible\r\n");
|
||||
return true;
|
||||
}
|
||||
|
||||
__attribute__((section(".ramfunc"), noinline))
|
||||
|
||||
@@ -16,14 +16,21 @@
|
||||
*/
|
||||
class EscExmcBus {
|
||||
public:
|
||||
/** @brief 初始化 EXMC NOR/SRAM Region1 与全部 GPIO(AF 复用)。 */
|
||||
static void init();
|
||||
/** @brief 初始化 EXMC NOR/SRAM Region1 与全部 GPIO(AF 复用)。
|
||||
* @return bool true 表示 EXMC Region1 已成功使能且首次回读探活通过。 */
|
||||
static bool init();
|
||||
|
||||
/** @brief ISR-only:仅供 preempt=0 调用,无锁。 */
|
||||
static uint16_t read16(uint32_t offset);
|
||||
|
||||
/** @brief ISR-only:仅供 preempt=0 调用,无锁。 */
|
||||
static void write16(uint32_t offset, uint16_t data);
|
||||
|
||||
/** @brief init 后查询:EXMC Region1 是否使能且探活通过(供上层诊断汇总)。 */
|
||||
static bool isOk() { return s_ok; }
|
||||
|
||||
private:
|
||||
static bool s_ok; /**< EXMC Region1 使能 + 探活结果(init 后置位)。 */
|
||||
};
|
||||
|
||||
#endif /* COMM_ESC_EXMC_BUS_H */
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
#include "comm/esc_lan9252.h"
|
||||
#include "comm/esc_exmc_bus.h"
|
||||
#include "sys/config.h"
|
||||
#include <stdio.h>
|
||||
#include "RTE_Components.h"
|
||||
#include CMSIS_device_header
|
||||
|
||||
@@ -18,14 +19,21 @@ constexpr uint32_t LAN9252_CHIP_ID_VALUE = 0x87654321U; /**< 期望的 32-bit
|
||||
constexpr uint16_t LAN9252_PDI_BASE_OFFSET = 0x1000U;
|
||||
}
|
||||
|
||||
void EscLan9252::init()
|
||||
bool EscLan9252::s_busReady = false;
|
||||
|
||||
bool EscLan9252::init()
|
||||
{
|
||||
/* 使能 DWT 周期计数器,供 periodicIsr 执行时间测量 */
|
||||
CoreDebug->DEMCR |= CoreDebug_DEMCR_TRCENA_Msk;
|
||||
DWT->CYCCNT = 0U;
|
||||
DWT->CTRL |= DWT_CTRL_CYCCNTENA_Msk;
|
||||
|
||||
EscExmcBus::init();
|
||||
s_busReady = EscExmcBus::init();
|
||||
if (!s_busReady) {
|
||||
printf("[ECAT][ESC] init: FAIL, EXMC bus not ready (see [ECAT][EXMC] log)\r\n");
|
||||
return false;
|
||||
}
|
||||
printf("[ECAT][ESC] init: EXMC bus ready\r\n");
|
||||
|
||||
/* ============================================================
|
||||
* TODO(spec): 完整 ESC 寄存器配置(需 SSC 或等价协议栈代码,本 spec 不实现)
|
||||
@@ -56,6 +64,7 @@ void EscLan9252::init()
|
||||
* 本 spec 仅通过 EthercatSlave::isEscReady() 的 AL 状态守卫使
|
||||
* "SYNC0 不会触发"在运行期可观测,完整配置留待 SSC 集成 spec。
|
||||
* ============================================================ */
|
||||
return true;
|
||||
}
|
||||
|
||||
uint32_t EscLan9252::readChipId()
|
||||
|
||||
@@ -21,8 +21,9 @@
|
||||
*/
|
||||
class EscLan9252 {
|
||||
public:
|
||||
/** @brief 初始化 EXMC 总线与 ESC 基本寄存器。 */
|
||||
static void init();
|
||||
/** @brief 初始化 EXMC 总线与 ESC 基本寄存器。
|
||||
* @return bool true 表示 EXMC 总线探活通过(可继续读取芯片 ID)。 */
|
||||
static bool init();
|
||||
|
||||
/**
|
||||
* @brief 读取 ESC 芯片 ID(INIT-ONLY,供 init 打印诊断用)。
|
||||
@@ -41,6 +42,12 @@ public:
|
||||
|
||||
/** @brief INIT-ONLY:仅允许在 EthercatSync::init() 使能 EXTI NVIC 之前调用;运行期禁止调用,需 ESC 寄存器访问须通过 SYNC0 ISR 代访队列(本 spec 不涵盖)。 */
|
||||
static uint16_t initOnlyReadReg(uint16_t addr);
|
||||
|
||||
/** @brief EXMC 总线是否就绪(init 后可查询)。 */
|
||||
static bool isBusReady() { return s_busReady; }
|
||||
|
||||
private:
|
||||
static bool s_busReady; /**< EXMC 探活结果(init 后置位,供上层诊断汇总)。 */
|
||||
};
|
||||
|
||||
#endif /* COMM_ESC_LAN9252_H */
|
||||
#endif /* COMM_ESC_LAN9252_H */
|
||||
@@ -32,36 +32,72 @@ uint8_t EthercatSlave::s_alState = 0U;
|
||||
|
||||
void EthercatSlave::init()
|
||||
{
|
||||
EscLan9252::init();
|
||||
printf("[ECAT][SLAVE] init: begin\r\n");
|
||||
|
||||
if (!EscLan9252::init()) {
|
||||
/* EXMC 总线未就绪:无法继续读芯片 ID 与 AL 状态,统一上报 FAIL。 */
|
||||
s_chipId = 0U;
|
||||
s_alState = 0U;
|
||||
s_escReady = false;
|
||||
printf("[ECAT][SLAVE] init: FAIL, ESC bus not ready\r\n");
|
||||
s_inputWriteIdx = 0U;
|
||||
s_outputReadIdx = 0U;
|
||||
s_periodicSeq = 0U;
|
||||
s_processedSeq = 0U;
|
||||
return;
|
||||
}
|
||||
|
||||
/* 第 1 层调试可观测性:读取芯片 ID 并打印 selfTest 结果 */
|
||||
s_chipId = EscLan9252::readChipId();
|
||||
const bool chipIdOk = (s_chipId == 0x87654321U);
|
||||
printf("[ECAT][SLAVE] chip id=0x%08lX expected=0x%08lX selfTest=%s\r\n",
|
||||
(unsigned long)s_chipId, (unsigned long)0x87654321UL,
|
||||
chipIdOk ? "PASS" : "FAIL");
|
||||
|
||||
/* AL 状态守卫:仅在芯片 ID 匹配时读取 AL 状态(ID 不匹配时 EXMC 可能读回 0/0xFFFF,无意义)。
|
||||
* AL ≥ SAFEOP(0x04) 才认为 SYNC0 可触发;完整 ESC 配置(SM/FMMU/AL Control/SYNC0)需 SSC 集成。 */
|
||||
s_alState = 0U;
|
||||
uint16_t alStatusReg = 0U; /* 0x0130:AL Status(bit0-3=state,bit8-15=AL Status Code) */
|
||||
if (chipIdOk) {
|
||||
s_alState = (uint8_t)(EscLan9252::initOnlyReadReg(0x0130U) & 0x000FU);
|
||||
alStatusReg = EscLan9252::initOnlyReadReg(0x0130U);
|
||||
s_alState = (uint8_t)(alStatusReg & 0x000FU);
|
||||
}
|
||||
const uint8_t alCode = (uint8_t)((alStatusReg >> 8U) & 0xFFU);
|
||||
s_escReady = chipIdOk && (s_alState >= AL_STATE_SAFEOP);
|
||||
|
||||
/* AL 状态码 → 可读字符串(EtherCAT 状态机定义,见 ETG.1000.6)。
|
||||
* 0x01=INIT, 0x02=PREOP, 0x03=BOOT, 0x04=SAFEOP, 0x08=OP。
|
||||
* 本工程 AL_STATE_SAFEOP=0x04 与官方定义一致;守卫 s_alState>=0x04,
|
||||
* 因此 OP(0x08) 也会通过,SAFEOP(0x04) 刚好通过,BOOT/PREOP/INIT 不通过。 */
|
||||
const char *alStateName = "UNKNOWN";
|
||||
switch (s_alState) {
|
||||
case 0x00U: alStateName = "NONE"; break;
|
||||
case 0x01U: alStateName = "INIT"; break;
|
||||
case 0x02U: alStateName = "PREOP"; break;
|
||||
case 0x03U: alStateName = "BOOT"; break;
|
||||
case 0x04U: alStateName = "SAFEOP"; break;
|
||||
case 0x08U: alStateName = "OP"; break;
|
||||
default: break;
|
||||
}
|
||||
/* AL Status Code(寄存器 0x0130 高 8 位):非 0 表示从站进入错误状态
|
||||
* (如 SM Watchdog=0x1B、FMMU 配置错、邮箱协议错),需主站介入。 */
|
||||
|
||||
if (!chipIdOk) {
|
||||
printf("[ECAT] ESC chip id=0x%08lX selfTest=FAIL\r\n", (unsigned long)s_chipId);
|
||||
printf("[ECAT] ESC NOT ready, sync ISR disabled (check EXMC wiring/timing)\r\n");
|
||||
printf("[ECAT][SLAVE] ESC NOT ready, sync ISR disabled (check EXMC wiring/timing)\r\n");
|
||||
} else if (s_alState < AL_STATE_SAFEOP) {
|
||||
printf("[ECAT] ESC chip id=0x%08lX selfTest=PASS, but AL state=0x%02X < SAFEOP(0x%02X)\r\n",
|
||||
(unsigned long)s_chipId, (unsigned)s_alState, (unsigned)AL_STATE_SAFEOP);
|
||||
printf("[ECAT] SYNC0 will not fire, full ESC config requires SSC integration\r\n");
|
||||
printf("[ECAT][SLAVE] chip id PASS, but AL state=0x%02X(%s) < SAFEOP(0x%02X), code=0x%02X\r\n",
|
||||
(unsigned)s_alState, alStateName, (unsigned)AL_STATE_SAFEOP, (unsigned)alCode);
|
||||
printf("[ECAT][SLAVE] SYNC0 will not fire, full ESC config requires SSC integration\r\n");
|
||||
} else {
|
||||
printf("[ECAT] ESC ready, chip id=0x%08lX, AL state=0x%02X\r\n",
|
||||
(unsigned long)s_chipId, (unsigned)s_alState);
|
||||
printf("[ECAT][SLAVE] ESC ready, chip id=0x%08lX, AL state=0x%02X(%s), code=0x%02X\r\n",
|
||||
(unsigned long)s_chipId, (unsigned)s_alState, alStateName, (unsigned)alCode);
|
||||
}
|
||||
|
||||
s_inputWriteIdx = 0U;
|
||||
s_outputReadIdx = 0U;
|
||||
s_periodicSeq = 0U;
|
||||
s_processedSeq = 0U;
|
||||
printf("[ECAT][SLAVE] init: %s\r\n", s_escReady ? "OK" : "FAIL(partial)");
|
||||
}
|
||||
|
||||
__attribute__((section(".ramfunc"), noinline))
|
||||
|
||||
@@ -20,6 +20,12 @@ extern "C" {
|
||||
|
||||
void EthercatSync::init()
|
||||
{
|
||||
#if (CONFIG_ETHERCAT_SYNC_MODE == 0)
|
||||
printf("[ECAT][SYNC] init: mode=EXTI (hardware SYNC0, CONFIG_ETHERCAT_SYNC_MODE=0)\r\n");
|
||||
#else
|
||||
printf("[ECAT][SYNC] init: mode=TIMER0 (degraded, CONFIG_ETHERCAT_SYNC_MODE=1)\r\n");
|
||||
#endif
|
||||
|
||||
/* 使能 GPIOB 时钟(SYNC0=PB0 / IRQ=PB1 / SYNC1=PB2) */
|
||||
rcu_periph_clock_enable(RCU_GPIOB);
|
||||
rcu_periph_clock_enable(RCU_AF);
|
||||
@@ -27,6 +33,10 @@ void EthercatSync::init()
|
||||
/* 配置三个引脚为上拉输入(EXTI 下降沿触发:LAN9252 输出低有效脉冲) */
|
||||
gpio_init(GPIOB, GPIO_MODE_IPU, GPIO_OSPEED_50MHZ,
|
||||
CONFIG_ESC_SYNC0_PIN | CONFIG_ESC_IRQ_PIN | CONFIG_ESC_SYNC1_PIN);
|
||||
printf("[ECAT][SYNC] init: GPIOB pins configured (SYNC0=PB%u IRQ=PB%u SYNC1=PB%u)\r\n",
|
||||
(unsigned)CONFIG_ESC_SYNC0_EXTI_SOURCE_PIN,
|
||||
(unsigned)CONFIG_ESC_IRQ_EXTI_SOURCE_PIN,
|
||||
(unsigned)CONFIG_ESC_SYNC1_EXTI_SOURCE_PIN);
|
||||
|
||||
#if (CONFIG_ETHERCAT_SYNC_MODE == 0)
|
||||
/* ---- 默认:EXTI 外部触发模式 ---- */
|
||||
@@ -36,6 +46,9 @@ void EthercatSync::init()
|
||||
exti_interrupt_enable(CONFIG_ESC_SYNC0_EXTI);
|
||||
exti_interrupt_flag_clear(CONFIG_ESC_SYNC0_EXTI);
|
||||
nvic_irq_enable(CONFIG_ESC_SYNC0_IRQn, CONFIG_IRQ_PREEMPT_ETHERCAT_SYNC, 0U);
|
||||
printf("[ECAT][SYNC] init: SYNC0 EXTI%u enabled (falling, preempt=%u)\r\n",
|
||||
(unsigned)CONFIG_ESC_SYNC0_EXTI_SOURCE_PIN,
|
||||
(unsigned)CONFIG_IRQ_PREEMPT_ETHERCAT_SYNC);
|
||||
#else
|
||||
/* ---- 降级模式:TIMER0 内部定时 ---- */
|
||||
rcu_periph_clock_enable(RCU_TIMER0);
|
||||
@@ -52,6 +65,8 @@ void EthercatSync::init()
|
||||
timer_interrupt_enable(TIMER0, TIMER_INT_UP);
|
||||
nvic_irq_enable(TIMER0_IRQn, CONFIG_IRQ_PREEMPT_ETHERCAT_SYNC, 0U);
|
||||
timer_enable(TIMER0);
|
||||
printf("[ECAT][SYNC] init: TIMER0 enabled (100us, preempt=%u)\r\n",
|
||||
(unsigned)CONFIG_IRQ_PREEMPT_ETHERCAT_SYNC);
|
||||
#endif
|
||||
|
||||
/* 公共:ESC IRQ (PB1->EXTI1) + SYNC1 (PB2->EXTI2) */
|
||||
@@ -60,12 +75,20 @@ void EthercatSync::init()
|
||||
exti_interrupt_enable(CONFIG_ESC_IRQ_EXTI);
|
||||
exti_interrupt_flag_clear(CONFIG_ESC_IRQ_EXTI);
|
||||
nvic_irq_enable(CONFIG_ESC_IRQ_IRQn, CONFIG_IRQ_PREEMPT_ETHERCAT_ESC, 0U);
|
||||
printf("[ECAT][SYNC] init: ESC IRQ EXTI%u enabled (falling, preempt=%u)\r\n",
|
||||
(unsigned)CONFIG_ESC_IRQ_EXTI_SOURCE_PIN,
|
||||
(unsigned)CONFIG_IRQ_PREEMPT_ETHERCAT_ESC);
|
||||
|
||||
gpio_exti_source_select(CONFIG_ESC_SYNC1_EXTI_SOURCE_PORT, CONFIG_ESC_SYNC1_EXTI_SOURCE_PIN);
|
||||
exti_init(CONFIG_ESC_SYNC1_EXTI, EXTI_INTERRUPT, EXTI_TRIG_FALLING);
|
||||
exti_interrupt_enable(CONFIG_ESC_SYNC1_EXTI);
|
||||
exti_interrupt_flag_clear(CONFIG_ESC_SYNC1_EXTI);
|
||||
nvic_irq_enable(CONFIG_ESC_SYNC1_IRQn, CONFIG_IRQ_PREEMPT_ETHERCAT_SYNC1, 0U);
|
||||
printf("[ECAT][SYNC] init: SYNC1 EXTI%u enabled (falling, preempt=%u)\r\n",
|
||||
(unsigned)CONFIG_ESC_SYNC1_EXTI_SOURCE_PIN,
|
||||
(unsigned)CONFIG_IRQ_PREEMPT_ETHERCAT_SYNC1);
|
||||
|
||||
printf("[ECAT][SYNC] init: OK\r\n");
|
||||
}
|
||||
|
||||
__attribute__((section(".ramfunc"), noinline))
|
||||
|
||||
Reference in New Issue
Block a user