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:
cbb
2026-07-20 02:36:56 +08:00
parent fedd39b902
commit 934836618a
18 changed files with 948 additions and 93 deletions
@@ -50,6 +50,7 @@ project:
- file: ./device/spi/app_spi_cmd_can_bridge.cpp - file: ./device/spi/app_spi_cmd_can_bridge.cpp
- file: ./device/spi/app_spi_cmd_can_feedback.cpp - file: ./device/spi/app_spi_cmd_can_feedback.cpp
- file: ./device/spi/app_spi_cmd_id_snapshot.cpp - file: ./device/spi/app_spi_cmd_id_snapshot.cpp
- file: ./device/spi/app_spi_cmd_adc_feedback.cpp
- group: spi_link - group: spi_link
files: files:
- file: ../../spi_link/spi_link_v2.cpp - file: ../../spi_link/spi_link_v2.cpp
+330
View File
@@ -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);
}
+74
View File
@@ -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 */
+37 -2
View File
@@ -8,6 +8,7 @@
*/ */
#include "comm/esc_exmc_bus.h" #include "comm/esc_exmc_bus.h"
#include "sys/config.h" #include "sys/config.h"
#include <stdio.h>
extern "C" { extern "C" {
#include "gd32e50x_rcu.h" #include "gd32e50x_rcu.h"
@@ -18,8 +19,13 @@ extern "C" {
/* 内存映射访问宏:基址 + 偏移(按 16-bit 半字对齐,LAN9252 HBI 地址左移 1 位)。 */ /* 内存映射访问宏:基址 + 偏移(按 16-bit 半字对齐,LAN9252 HBI 地址左移 1 位)。 */
#define ESC_EXMC_REG16(offset) (*(volatile uint16_t*)(CONFIG_ESC_EXMC_BASE_ADDR + (offset))) #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_GPIOD);
rcu_periph_clock_enable(RCU_GPIOE); rcu_periph_clock_enable(RCU_GPIOE);
rcu_periph_clock_enable(RCU_AF); 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_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); 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_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_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_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); 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、非复用、异步写。 * EXMC NOR/SRAM Region1:16-bit、非复用、异步写。
@@ -88,8 +97,34 @@ void EscExmcBus::init()
wr_timing = rw_timing; 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_init(&norsram_init);
exmc_norsram_enable(CONFIG_ESC_EXMC_BANK_NOR_SRAM_REGION1); 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)) __attribute__((section(".ramfunc"), noinline))
+9 -2
View File
@@ -16,14 +16,21 @@
*/ */
class EscExmcBus { class EscExmcBus {
public: public:
/** @brief 初始化 EXMC NOR/SRAM Region1 与全部 GPIO(AF 复用)。 */ /** @brief 初始化 EXMC NOR/SRAM Region1 与全部 GPIO(AF 复用)。
static void init(); * @return bool true 表示 EXMC Region1 已成功使能且首次回读探活通过。 */
static bool init();
/** @brief ISR-only:仅供 preempt=0 调用,无锁。 */ /** @brief ISR-only:仅供 preempt=0 调用,无锁。 */
static uint16_t read16(uint32_t offset); static uint16_t read16(uint32_t offset);
/** @brief ISR-only:仅供 preempt=0 调用,无锁。 */ /** @brief ISR-only:仅供 preempt=0 调用,无锁。 */
static void write16(uint32_t offset, uint16_t data); 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 */ #endif /* COMM_ESC_EXMC_BUS_H */
+11 -2
View File
@@ -5,6 +5,7 @@
#include "comm/esc_lan9252.h" #include "comm/esc_lan9252.h"
#include "comm/esc_exmc_bus.h" #include "comm/esc_exmc_bus.h"
#include "sys/config.h" #include "sys/config.h"
#include <stdio.h>
#include "RTE_Components.h" #include "RTE_Components.h"
#include CMSIS_device_header #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; constexpr uint16_t LAN9252_PDI_BASE_OFFSET = 0x1000U;
} }
void EscLan9252::init() bool EscLan9252::s_busReady = false;
bool EscLan9252::init()
{ {
/* 使能 DWT 周期计数器,供 periodicIsr 执行时间测量 */ /* 使能 DWT 周期计数器,供 periodicIsr 执行时间测量 */
CoreDebug->DEMCR |= CoreDebug_DEMCR_TRCENA_Msk; CoreDebug->DEMCR |= CoreDebug_DEMCR_TRCENA_Msk;
DWT->CYCCNT = 0U; DWT->CYCCNT = 0U;
DWT->CTRL |= DWT_CTRL_CYCCNTENA_Msk; 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 不实现) * TODO(spec): 完整 ESC 寄存器配置(需 SSC 或等价协议栈代码,本 spec 不实现)
@@ -56,6 +64,7 @@ void EscLan9252::init()
* 本 spec 仅通过 EthercatSlave::isEscReady() 的 AL 状态守卫使 * 本 spec 仅通过 EthercatSlave::isEscReady() 的 AL 状态守卫使
* "SYNC0 不会触发"在运行期可观测,完整配置留待 SSC 集成 spec。 * "SYNC0 不会触发"在运行期可观测,完整配置留待 SSC 集成 spec。
* ============================================================ */ * ============================================================ */
return true;
} }
uint32_t EscLan9252::readChipId() uint32_t EscLan9252::readChipId()
+9 -2
View File
@@ -21,8 +21,9 @@
*/ */
class EscLan9252 { class EscLan9252 {
public: public:
/** @brief 初始化 EXMC 总线与 ESC 基本寄存器。 */ /** @brief 初始化 EXMC 总线与 ESC 基本寄存器。
static void init(); * @return bool true 表示 EXMC 总线探活通过(可继续读取芯片 ID)。 */
static bool init();
/** /**
* @brief 读取 ESC 芯片 ID(INIT-ONLY,供 init 打印诊断用)。 * @brief 读取 ESC 芯片 ID(INIT-ONLY,供 init 打印诊断用)。
@@ -41,6 +42,12 @@ public:
/** @brief INIT-ONLY:仅允许在 EthercatSync::init() 使能 EXTI NVIC 之前调用;运行期禁止调用,需 ESC 寄存器访问须通过 SYNC0 ISR 代访队列(本 spec 不涵盖)。 */ /** @brief INIT-ONLY:仅允许在 EthercatSync::init() 使能 EXTI NVIC 之前调用;运行期禁止调用,需 ESC 寄存器访问须通过 SYNC0 ISR 代访队列(本 spec 不涵盖)。 */
static uint16_t initOnlyReadReg(uint16_t addr); 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 */
+45 -9
View File
@@ -32,36 +32,72 @@ uint8_t EthercatSlave::s_alState = 0U;
void EthercatSlave::init() 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 结果 */ /* 第 1 层调试可观测性:读取芯片 ID 并打印 selfTest 结果 */
s_chipId = EscLan9252::readChipId(); s_chipId = EscLan9252::readChipId();
const bool chipIdOk = (s_chipId == 0x87654321U); 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 状态守卫:仅在芯片 ID 匹配时读取 AL 状态(ID 不匹配时 EXMC 可能读回 0/0xFFFF,无意义)。
* AL ≥ SAFEOP(0x04) 才认为 SYNC0 可触发;完整 ESC 配置(SM/FMMU/AL Control/SYNC0)需 SSC 集成。 */ * AL ≥ SAFEOP(0x04) 才认为 SYNC0 可触发;完整 ESC 配置(SM/FMMU/AL Control/SYNC0)需 SSC 集成。 */
s_alState = 0U; s_alState = 0U;
uint16_t alStatusReg = 0U; /* 0x0130:AL Status(bit0-3=state,bit8-15=AL Status Code) */
if (chipIdOk) { 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); 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) { if (!chipIdOk) {
printf("[ECAT] ESC chip id=0x%08lX selfTest=FAIL\r\n", (unsigned long)s_chipId); printf("[ECAT][SLAVE] ESC NOT ready, sync ISR disabled (check EXMC wiring/timing)\r\n");
printf("[ECAT] ESC NOT ready, sync ISR disabled (check EXMC wiring/timing)\r\n");
} else if (s_alState < AL_STATE_SAFEOP) { } 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", printf("[ECAT][SLAVE] chip id PASS, but AL state=0x%02X(%s) < SAFEOP(0x%02X), code=0x%02X\r\n",
(unsigned long)s_chipId, (unsigned)s_alState, (unsigned)AL_STATE_SAFEOP); (unsigned)s_alState, alStateName, (unsigned)AL_STATE_SAFEOP, (unsigned)alCode);
printf("[ECAT] SYNC0 will not fire, full ESC config requires SSC integration\r\n"); printf("[ECAT][SLAVE] SYNC0 will not fire, full ESC config requires SSC integration\r\n");
} else { } else {
printf("[ECAT] ESC ready, chip id=0x%08lX, AL state=0x%02X\r\n", 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); (unsigned long)s_chipId, (unsigned)s_alState, alStateName, (unsigned)alCode);
} }
s_inputWriteIdx = 0U; s_inputWriteIdx = 0U;
s_outputReadIdx = 0U; s_outputReadIdx = 0U;
s_periodicSeq = 0U; s_periodicSeq = 0U;
s_processedSeq = 0U; s_processedSeq = 0U;
printf("[ECAT][SLAVE] init: %s\r\n", s_escReady ? "OK" : "FAIL(partial)");
} }
__attribute__((section(".ramfunc"), noinline)) __attribute__((section(".ramfunc"), noinline))
+23
View File
@@ -20,6 +20,12 @@ extern "C" {
void EthercatSync::init() 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) */ /* 使能 GPIOB 时钟(SYNC0=PB0 / IRQ=PB1 / SYNC1=PB2) */
rcu_periph_clock_enable(RCU_GPIOB); rcu_periph_clock_enable(RCU_GPIOB);
rcu_periph_clock_enable(RCU_AF); rcu_periph_clock_enable(RCU_AF);
@@ -27,6 +33,10 @@ void EthercatSync::init()
/* 配置三个引脚为上拉输入(EXTI 下降沿触发:LAN9252 输出低有效脉冲) */ /* 配置三个引脚为上拉输入(EXTI 下降沿触发:LAN9252 输出低有效脉冲) */
gpio_init(GPIOB, GPIO_MODE_IPU, GPIO_OSPEED_50MHZ, gpio_init(GPIOB, GPIO_MODE_IPU, GPIO_OSPEED_50MHZ,
CONFIG_ESC_SYNC0_PIN | CONFIG_ESC_IRQ_PIN | CONFIG_ESC_SYNC1_PIN); 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) #if (CONFIG_ETHERCAT_SYNC_MODE == 0)
/* ---- 默认:EXTI 外部触发模式 ---- */ /* ---- 默认:EXTI 外部触发模式 ---- */
@@ -36,6 +46,9 @@ void EthercatSync::init()
exti_interrupt_enable(CONFIG_ESC_SYNC0_EXTI); exti_interrupt_enable(CONFIG_ESC_SYNC0_EXTI);
exti_interrupt_flag_clear(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); 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 #else
/* ---- 降级模式:TIMER0 内部定时 ---- */ /* ---- 降级模式:TIMER0 内部定时 ---- */
rcu_periph_clock_enable(RCU_TIMER0); rcu_periph_clock_enable(RCU_TIMER0);
@@ -52,6 +65,8 @@ void EthercatSync::init()
timer_interrupt_enable(TIMER0, TIMER_INT_UP); timer_interrupt_enable(TIMER0, TIMER_INT_UP);
nvic_irq_enable(TIMER0_IRQn, CONFIG_IRQ_PREEMPT_ETHERCAT_SYNC, 0U); nvic_irq_enable(TIMER0_IRQn, CONFIG_IRQ_PREEMPT_ETHERCAT_SYNC, 0U);
timer_enable(TIMER0); timer_enable(TIMER0);
printf("[ECAT][SYNC] init: TIMER0 enabled (100us, preempt=%u)\r\n",
(unsigned)CONFIG_IRQ_PREEMPT_ETHERCAT_SYNC);
#endif #endif
/* 公共:ESC IRQ (PB1->EXTI1) + SYNC1 (PB2->EXTI2) */ /* 公共:ESC IRQ (PB1->EXTI1) + SYNC1 (PB2->EXTI2) */
@@ -60,12 +75,20 @@ void EthercatSync::init()
exti_interrupt_enable(CONFIG_ESC_IRQ_EXTI); exti_interrupt_enable(CONFIG_ESC_IRQ_EXTI);
exti_interrupt_flag_clear(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); 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); 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_init(CONFIG_ESC_SYNC1_EXTI, EXTI_INTERRUPT, EXTI_TRIG_FALLING);
exti_interrupt_enable(CONFIG_ESC_SYNC1_EXTI); exti_interrupt_enable(CONFIG_ESC_SYNC1_EXTI);
exti_interrupt_flag_clear(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); 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)) __attribute__((section(".ramfunc"), noinline))
@@ -0,0 +1,168 @@
/**
* @file app_spi_cmd_adc_feedback.cpp
* @brief GD32 ADC 调试反馈(温度 / VDDA / VIN)SPI 命令与 1Hz 主动推送。
*
* TYPE = SpiDefs::kTypeAdcFeedback (0xC4)
*
* 总开关:sys/config.h 中 CONFIG_SPI_ADC_FEEDBACK_ENABLE
* - 0:不注册 handler、不推送(本文件仍参与链接,函数空实现)
* - 1:1Hz 主动 requestEsp32Read(0xC4) + 支持 ESP32 被动查询
*
* 与 0xC1 CAN 反馈同款模型:主循环 pushStep 非阻塞;ring 未读走则跳过。
* 优先级低于 CAN 反馈(runPending 里先 canFeedbackPushStep 再本函数)。
*
* payload v1 固定 20 字节(小端):
* [0] ver=1
* [1] flags bit0=sample_valid
* [2..3] temp_x100 int16 百分之一摄氏度
* [4..5] vdda_mV uint16
* [6..7] vin_adc_mV uint16 引脚分压后电压 mV
* [8..9] temp_raw uint16
* [10..11] vref_raw uint16
* [12..15] seq uint32 推送序号
* [16..19] uptime_sec uint32
*/
#include "device/spi/spi_command_handler.h"
#include "device/spi/app_spi_command.h"
#include "device/spi/app_spi_command_defs.h"
#include "device/adc.h"
#include "sys/config.h"
#include "sys/ota_boot_request.h"
#include "sys/task_runner.h"
#include "main.h"
#include <cstring>
#if (CONFIG_SPI_ADC_FEEDBACK_ENABLE != 0)
namespace {
static constexpr uint8_t kPayloadVer = 1U;
static constexpr uint16_t kPayloadLen = 20U;
static uint32_t s_pushSeq = 0U;
static volatile bool s_needPush = false;
/**
* @brief 构建 0xC4 ADC 反馈 payload(20B)。
* @param[out] out 输出缓冲
* @param max_len 缓冲容量
* @return 写入长度;失败 0
*/
static uint16_t buildAdcFeedbackPayload(uint8_t *out, uint16_t max_len)
{
if (out == nullptr || max_len < kPayloadLen) {
return 0U;
}
Adc::Sample s{};
const bool have = TaskRunner::copyAdcSample(&s);
const int32_t temp_x100 = have
? static_cast<int32_t>(s.temperature * 100.0f)
: 0;
const uint16_t vdda_mV = have
? static_cast<uint16_t>(s.vdda * 1000.0f)
: 0U;
const uint16_t vin_mV = have
? static_cast<uint16_t>(s.vin_adc_v * 1000.0f)
: 0U;
std::memset(out, 0, kPayloadLen);
out[0] = kPayloadVer;
out[1] = (have && s.valid) ? 0x01U : 0x00U;
/* int16 百分之一℃,按两字节小端原样写入 */
writeU16Le(&out[2], static_cast<uint16_t>(static_cast<int16_t>(temp_x100)));
writeU16Le(&out[4], vdda_mV);
writeU16Le(&out[6], vin_mV);
writeU16Le(&out[8], have ? s.temp_raw : 0U);
writeU16Le(&out[10], have ? s.vref_raw : 0U);
writeU32Le(&out[12], s_pushSeq);
writeU32Le(&out[16], g_app_uptime_sec);
return kPayloadLen;
}
/** @brief SPI 0xC4 ADC 反馈:被动查询应答。 */
class SpiAdcFeedbackHandler final : public ISpiCommandHandler {
public:
/** @brief 是否处理 0xC4。 */
bool canHandle(uint8_t frame_type) const override
{
return frame_type == SpiDefs::kTypeAdcFeedback;
}
/**
* @brief 被动查询:回当前 ADC 快照。
* @param bus 命令总线
* @param ctx 请求上下文
*/
void handle(AppSpiCommand &bus, const SpiCommandContext &ctx) override
{
uint8_t payload[kPayloadLen] = {};
const uint16_t n = buildAdcFeedbackPayload(payload, sizeof(payload));
bus.buildResponse(ctx.tx_buf, ctx.tx_buf_size, ctx.tx_len,
SpiDefs::kTypeAdcFeedback, payload, n);
}
};
static SpiAdcFeedbackHandler s_handler;
} // namespace
/**
* @brief 标记需要推送一帧 0xC4(由 1Hz taskHeartbeat 调用)。
*/
void adcFeedbackStartRound()
{
s_needPush = true;
}
/**
* @brief 非阻塞推送一步:ring 空闲则 requestEsp32Read(0xC4)。
* @note 主循环调用;与 CAN 反馈共用 ring,忙则跳过。
*/
void adcFeedbackPushStep()
{
if (!s_needPush) {
return;
}
if (OtaBootRequest::isRebootPending()) {
s_needPush = false;
return;
}
/* 与 CAN 反馈共用 ring:未读走则等下一主循环,不抢占 */
if (AppSpiCommandBus.isRingPending()) {
return;
}
uint8_t payload[kPayloadLen] = {};
const uint16_t n = buildAdcFeedbackPayload(payload, sizeof(payload));
if (n == 0U) {
s_needPush = false;
return;
}
++s_pushSeq;
writeU32Le(&payload[12], s_pushSeq);
if (AppSpiCommandBus.requestEsp32Read(SpiDefs::kTypeAdcFeedback, payload, n)) {
s_needPush = false;
}
}
/** @brief 注册 0xC4 处理器。 */
void registerSpiAdcFeedbackCommand(AppSpiCommand &bus)
{
bus.registerHandler(&s_handler);
}
#else /* CONFIG_SPI_ADC_FEEDBACK_ENABLE == 0 */
/** @brief 开关关闭时的空实现。 */
void adcFeedbackStartRound() {}
/** @brief 开关关闭时的空实现。 */
void adcFeedbackPushStep() {}
/** @brief 开关关闭时不注册 handler。 */
void registerSpiAdcFeedbackCommand(AppSpiCommand & /*bus*/) {}
#endif
@@ -137,14 +137,21 @@ static uint16_t buildFeedbackPayload(uint8_t channel, uint8_t page,
namespace { namespace {
/** @brief SPI 0xC1 反馈查询 + 0xC2 模式切换处理器。 */
class SpiCanFeedbackHandler final : public ISpiCommandHandler { class SpiCanFeedbackHandler final : public ISpiCommandHandler {
public: public:
/** @brief 是否处理 0xC1 / 0xC2。 */
bool canHandle(uint8_t frame_type) const override bool canHandle(uint8_t frame_type) const override
{ {
return (frame_type == SpiDefs::kTypeCanFeedback) return (frame_type == SpiDefs::kTypeCanFeedback)
|| (frame_type == SpiDefs::kTypeCanSetMode); || (frame_type == SpiDefs::kTypeCanSetMode);
} }
/**
* @brief 分发到 handleFeedback / handleSetMode。
* @param bus 命令总线
* @param ctx 请求上下文
*/
void handle(AppSpiCommand &bus, const SpiCommandContext &ctx) override void handle(AppSpiCommand &bus, const SpiCommandContext &ctx) override
{ {
if (ctx.frame_type == SpiDefs::kTypeCanFeedback) { if (ctx.frame_type == SpiDefs::kTypeCanFeedback) {
@@ -155,6 +162,11 @@ public:
} }
private: private:
/**
* @brief 被动 0xC1:按 channel/page 回一页反馈。
* @param bus 命令总线
* @param ctx 请求上下文(payload[0]=ch, [1]=page)
*/
void handleFeedback(AppSpiCommand &bus, const SpiCommandContext &ctx) void handleFeedback(AppSpiCommand &bus, const SpiCommandContext &ctx)
{ {
if (ctx.payload_len < 2U) { if (ctx.payload_len < 2U) {
@@ -180,6 +192,11 @@ private:
SpiDefs::kRspAck, payload, payload_len); SpiDefs::kRspAck, payload, payload_len);
} }
/**
* @brief 0xC2:切换三路 CAN 接收过滤模式。
* @param bus 命令总线
* @param ctx 请求上下文(payload[0]=mode)
*/
void handleSetMode(AppSpiCommand &bus, const SpiCommandContext &ctx) void handleSetMode(AppSpiCommand &bus, const SpiCommandContext &ctx)
{ {
if (ctx.payload_len < 1U) { if (ctx.payload_len < 1U) {
@@ -240,11 +257,18 @@ static SpiCanFeedbackHandler s_handler;
*/ */
static volatile bool s_roundActive = true; /* 首次允许启动,无需等 1s */ static volatile bool s_roundActive = true; /* 首次允许启动,无需等 1s */
/**
* @brief 激活新一轮 1Hz CAN 反馈推送(taskHeartbeat 调用)。
*/
void canFeedbackStartRound() void canFeedbackStartRound()
{ {
s_roundActive = true; s_roundActive = true;
} }
/**
* @brief 非阻塞推送一步 0xC1(单页);跳过未使能通道。
* @note 主循环调用;ring 忙或本轮未激活则立即返回。
*/
void canFeedbackPushStep() void canFeedbackPushStep()
{ {
static uint8_t s_channel = 0U; static uint8_t s_channel = 0U;
@@ -267,6 +291,22 @@ void canFeedbackPushStep()
return; return;
} }
/* 跳过编译期关闭的通道(与 APP PING V2_FEAT_CAN_CHx / CONFIG_CAN_CHx_ENABLE 一致) */
if (!CanFd::isChannelEnabled(s_channel)) {
s_page = 0U;
for (;;) {
++s_channel;
if (s_channel >= kFeedbackChannelCount) {
s_channel = 0U;
s_roundActive = false;
return;
}
if (CanFd::isChannelEnabled(s_channel)) {
break;
}
}
}
uint8_t payload[kFeedbackPayloadLen] = {}; uint8_t payload[kFeedbackPayloadLen] = {};
uint16_t payload_len = buildFeedbackPayload(s_channel, s_page, uint16_t payload_len = buildFeedbackPayload(s_channel, s_page,
payload, sizeof(payload)); payload, sizeof(payload));
@@ -275,17 +315,27 @@ void canFeedbackPushStep()
payload, payload_len); payload, payload_len);
} }
/* 推进到下一页/下一通道,循环遍历 3 通道 × 2 页 */ /* 推进:每通道 2 页,仅遍历使能通道;到末通道后结束本秒轮次 */
if (++s_page >= 2U) { if (++s_page >= 2U) {
s_page = 0U; s_page = 0U;
if (++s_channel >= kFeedbackChannelCount) { for (;;) {
++s_channel;
if (s_channel >= kFeedbackChannelCount) {
s_channel = 0U; s_channel = 0U;
/* 回到 ch=0,page=0:一轮推完,等待下一秒 taskHeartbeat 激活 */
s_roundActive = false; s_roundActive = false;
break;
}
if (CanFd::isChannelEnabled(s_channel)) {
break;
}
} }
} }
} }
/**
* @brief 注册 0xC1/0xC2 CAN 反馈与模式切换处理器。
* @param bus 命令总线
*/
void registerSpiCanFeedbackCommand(AppSpiCommand &bus) void registerSpiCanFeedbackCommand(AppSpiCommand &bus)
{ {
bus.registerHandler(&s_handler); bus.registerHandler(&s_handler);
@@ -5,47 +5,93 @@
#include "sys/ota_boot_request.h" #include "sys/ota_boot_request.h"
#include "sys/systick.h" #include "sys/systick.h"
#include "main.h" #include "main.h"
#include "spi_link_v2.h"
#include <cstring> #include <cstring>
namespace { namespace {
/**
* @brief 汇总 GD32 APP 编译期功能开关 → V2_FEAT_* 位图。
*/
static uint16_t buildAppFeatureFlags()
{
uint16_t feat = 0U;
#if (CONFIG_SPI_ADC_FEEDBACK_ENABLE != 0)
feat = static_cast<uint16_t>(feat | V2_FEAT_ADC_FEEDBACK);
#endif
#if (CONFIG_UART_TEMP_PERIODIC_PRINT != 0)
feat = static_cast<uint16_t>(feat | V2_FEAT_UART_TEMP_PRINT);
#endif
#if (CONFIG_SPI_APP_LOG_ENABLE != 0)
feat = static_cast<uint16_t>(feat | V2_FEAT_SPI_APP_LOG);
#endif
#if (CONFIG_CAN_FD_BRS_ENABLE != 0)
feat = static_cast<uint16_t>(feat | V2_FEAT_CAN_BRS);
#endif
if (CONFIG_CAN_CH0_ENABLE) {
feat = static_cast<uint16_t>(feat | V2_FEAT_CAN_CH0);
}
if (CONFIG_CAN_CH1_ENABLE) {
feat = static_cast<uint16_t>(feat | V2_FEAT_CAN_CH1);
}
if (CONFIG_CAN_CH2_ENABLE) {
feat = static_cast<uint16_t>(feat | V2_FEAT_CAN_CH2);
}
return feat;
}
} // namespace
/**
* @brief 填充 APP 身份 16B(版本 + feat),供 HELLO_ACK 附加块与 PING 共用。
* @param[out] out 至少 V2_APP_IDENTITY_LEN 字节
*/
void fillGd32AppIdentityPayload(uint8_t *out)
{
if (out == nullptr) {
return;
}
std::memset(out, 0, V2_APP_IDENTITY_LEN);
out[0] = (uint8_t)APP_FW_VERSION_MAJOR;
out[1] = (uint8_t)APP_FW_VERSION_MINOR;
out[2] = (uint8_t)APP_FW_VERSION_PATCH;
out[3] = (uint8_t)APP_FW_VERSION_BUILD;
const BootVersion boot_ver = OtaBootRequest::readBootVersion();
out[4] = boot_ver.major;
out[5] = boot_ver.minor;
out[6] = boot_ver.patch;
out[7] = boot_ver.build;
writeU32Le(&out[8], SysTickTimer::getMs());
out[12] = V2_FEAT_MAP_VER;
writeU16Le(&out[13], buildAppFeatureFlags());
/* [15] reserved = 0 */
}
namespace {
/** @brief SPI 0xA1 APP PING 处理器:应答身份 16B。 */
class SpiAppPingHandler final : public ISpiCommandHandler { class SpiAppPingHandler final : public ISpiCommandHandler {
public: public:
/** @brief 是否处理该帧类型。 */
bool canHandle(uint8_t frame_type) const override bool canHandle(uint8_t frame_type) const override
{ {
return frame_type == SpiDefs::kTypeAppPing; return frame_type == SpiDefs::kTypeAppPing;
} }
/**
* @brief 处理 PING:填充与 HELLO 相同的身份块并回 0x80。
* @param bus 命令总线
* @param ctx 请求上下文
*/
void handle(AppSpiCommand &bus, const SpiCommandContext &ctx) override void handle(AppSpiCommand &bus, const SpiCommandContext &ctx) override
{ {
/* v2 PING 响应 payload(16B): /* 与 HELLO_ACK 附加块同一布局,用于存活/uptime 刷新 */
* [0..3] APP version (major/minor/patch/build) uint8_t ping_payload[V2_APP_PING_LEN];
* [4..7] BOOT version (major/minor/patch/build) fillGd32AppIdentityPayload(ping_payload);
* [8..11] uptime_ms (4B LE)
* [12..15] build_hash (4B, 暂用 0 占位) */
uint8_t ping_payload[16];
std::memset(ping_payload, 0, sizeof(ping_payload));
ping_payload[0] = (uint8_t)APP_FW_VERSION_MAJOR;
ping_payload[1] = (uint8_t)APP_FW_VERSION_MINOR;
ping_payload[2] = (uint8_t)APP_FW_VERSION_PATCH;
ping_payload[3] = (uint8_t)APP_FW_VERSION_BUILD;
/* BOOT 版本号从 OTA 配置页 Flash 读取(封装函数,避免裸指针散落) */
const BootVersion boot_ver = OtaBootRequest::readBootVersion();
ping_payload[4] = boot_ver.major;
ping_payload[5] = boot_ver.minor;
ping_payload[6] = boot_ver.patch;
ping_payload[7] = boot_ver.build;
const uint32_t uptime_ms = SysTickTimer::getMs();
ping_payload[8] = (uint8_t)(uptime_ms & 0xFFU);
ping_payload[9] = (uint8_t)((uptime_ms >> 8) & 0xFFU);
ping_payload[10] = (uint8_t)((uptime_ms >> 16) & 0xFFU);
ping_payload[11] = (uint8_t)((uptime_ms >> 24) & 0xFFU);
/* build_hash 占位 0(ping_payload[12..15] 已由 memset 清零) */
bus.buildResponse(ctx.tx_buf, ctx.tx_buf_size, ctx.tx_len, bus.buildResponse(ctx.tx_buf, ctx.tx_buf_size, ctx.tx_len,
SpiDefs::kRspAck, ping_payload, sizeof(ping_payload)); SpiDefs::kRspAck, ping_payload, sizeof(ping_payload));
} }
@@ -55,6 +101,7 @@ static SpiAppPingHandler s_handler;
} // namespace } // namespace
/** @brief 注册 0xA1 PING 命令处理器。 */
void registerSpiAppPingCommand(AppSpiCommand &bus) void registerSpiAppPingCommand(AppSpiCommand &bus)
{ {
bus.registerHandler(&s_handler); bus.registerHandler(&s_handler);
@@ -147,8 +147,9 @@ static bool isFlashRelatedFrame(uint8_t frame_type)
} }
} }
/* GD32 APP 侧同步状态机(ISR 与主循环只读/原子写,避免临界区开销)。 */ /* GD32 APP 侧 CREDIT:运行时单响应缓冲 0/1(有待发=0,空闲=1)。
static volatile uint8_t g_localCredit = 8U; * 初值必须为 1,禁止用 BOOT 的 8 槽语义;否则 HELLO 前被读到会误导主机 remoteCredit。 */
static volatile uint8_t g_localCredit = 1U;
/* SPI Link v2 会话/序号状态机(ISR 写、主循环读写,均原子,无需临界区)。 */ /* SPI Link v2 会话/序号状态机(ISR 写、主循环读写,均原子,无需临界区)。 */
static volatile uint32_t g_sessionId = 0; /* v2 会话 ID(0=未建立) */ static volatile uint32_t g_sessionId = 0; /* v2 会话 ID(0=未建立) */
@@ -384,6 +385,7 @@ extern void registerSpiAppPingCommand(AppSpiCommand &bus);
extern void registerSpiCanBridgeCommand(AppSpiCommand &bus); extern void registerSpiCanBridgeCommand(AppSpiCommand &bus);
extern void registerSpiCanFeedbackCommand(AppSpiCommand &bus); extern void registerSpiCanFeedbackCommand(AppSpiCommand &bus);
extern void registerSpiIdSnapshotCommand(AppSpiCommand &bus); extern void registerSpiIdSnapshotCommand(AppSpiCommand &bus);
extern void registerSpiAdcFeedbackCommand(AppSpiCommand &bus);
AppSpiCommand AppSpiCommandBus; AppSpiCommand AppSpiCommandBus;
void AppSpiCommand::init() void AppSpiCommand::init()
{ {
@@ -404,6 +406,7 @@ void AppSpiCommand::init()
registerSpiCanBridgeCommand(AppSpiCommandBus); registerSpiCanBridgeCommand(AppSpiCommandBus);
registerSpiCanFeedbackCommand(AppSpiCommandBus); registerSpiCanFeedbackCommand(AppSpiCommandBus);
registerSpiIdSnapshotCommand(AppSpiCommandBus); registerSpiIdSnapshotCommand(AppSpiCommandBus);
registerSpiAdcFeedbackCommand(AppSpiCommandBus); /* CONFIG 关时为空注册 */
configureGpio(); configureGpio();
configureDma(); configureDma();
@@ -525,9 +528,13 @@ void AppSpiCommand::processCommand()
/* 与 requestEsp32Read() 一致(L5 8bc01dd):建帧前 credit=1, /* 与 requestEsp32Read() 一致(L5 8bc01dd):建帧前 credit=1,
* 避免 TX 占用时 g_localCredit=0 写入 HELLO_ACK,导致主机 remoteCredit=0 拒发 PING。 */ * 避免 TX 占用时 g_localCredit=0 写入 HELLO_ACK,导致主机 remoteCredit=0 拒发 PING。 */
g_localCredit = 1U; g_localCredit = 1U;
/* 建连 + 开机身份一次完成:HELLO_ACK = 6B 链路 + 16B 身份(版本/开关) */
uint8_t identity[V2_APP_IDENTITY_LEN];
fillGd32AppIdentityPayload(identity);
size_t built_len = 0U; size_t built_len = 0U;
v2_build_hello_ack_frame(g_sessionId, V2_ROLE_APP, (uint8_t)g_localCredit, v2_build_hello_ack_frame(g_sessionId, V2_ROLE_APP, (uint8_t)g_localCredit,
0, hdr.tx_seq, m_txBuf, &built_len); 0, hdr.tx_seq, identity, sizeof(identity),
m_txBuf, &built_len);
m_txLen = (uint16_t)built_len; m_txLen = (uint16_t)built_len;
s_lastReqSeq = hdr.tx_seq; s_lastReqSeq = hdr.tx_seq;
@@ -164,7 +164,22 @@ void canFeedbackPushStep();
/** @brief 激活新一轮 CAN 反馈推送(由 1Hz taskHeartbeat 调用)。 */ /** @brief 激活新一轮 CAN 反馈推送(由 1Hz taskHeartbeat 调用)。 */
void canFeedbackStartRound(); void canFeedbackStartRound();
/**
* @brief ADC 调试反馈(0xC4)主动推送一步。
* @note CONFIG_SPI_ADC_FEEDBACK_ENABLE=0 时为空操作。
*/
void adcFeedbackPushStep();
/** @brief 激活一次 ADC 调试反馈推送(1Hz taskHeartbeat)。 */
void adcFeedbackStartRound();
/** @brief 全局 SPI 命令总线实例。 */ /** @brief 全局 SPI 命令总线实例。 */
extern AppSpiCommand AppSpiCommandBus; extern AppSpiCommand AppSpiCommandBus;
/**
* @brief 填充 APP 身份 16B(版本 + 功能开关),供 HELLO_ACK 与 PING 共用。
* @param out 至少 V2_APP_IDENTITY_LEN 字节。
*/
void fillGd32AppIdentityPayload(uint8_t *out);
#endif /* DEVICE_APP_SPI_COMMAND_H */ #endif /* DEVICE_APP_SPI_COMMAND_H */
@@ -24,6 +24,9 @@ static constexpr uint8_t kTypeCanSend = 0xC0U;
static constexpr uint8_t kTypeCanFeedback = 0xC1U; /**< ESP32 -> GD32 轮询 CAN 反馈快照 */ static constexpr uint8_t kTypeCanFeedback = 0xC1U; /**< ESP32 -> GD32 轮询 CAN 反馈快照 */
static constexpr uint8_t kTypeCanSetMode = 0xC2U; /**< ESP32 -> GD32 切换 CAN 接收过滤模式 */ static constexpr uint8_t kTypeCanSetMode = 0xC2U; /**< ESP32 -> GD32 切换 CAN 接收过滤模式 */
static constexpr uint8_t kTypeIdSnapshot = 0xC3U; /**< ESP32 -> GD32 拉取不重复 ID 快照表 */ static constexpr uint8_t kTypeIdSnapshot = 0xC3U; /**< ESP32 -> GD32 拉取不重复 ID 快照表 */
/** @brief GD32 ADC 调试反馈(温度/VDDA/VIN);1Hz 主动 EVENT 或被动查询。
* 由 CONFIG_SPI_ADC_FEEDBACK_ENABLE 门控,调试用可随时关。 */
static constexpr uint8_t kTypeAdcFeedback = 0xC4U;
/* 响应类型 */ /* 响应类型 */
static constexpr uint8_t kRspAck = 0x80U; static constexpr uint8_t kRspAck = 0x80U;
@@ -32,6 +35,12 @@ static constexpr uint8_t kRspError = 0x82U;
} // namespace SpiDefs } // namespace SpiDefs
/** @brief 将 16 位值按小端写入 2 字节缓冲。 */
inline void writeU16Le(uint8_t *dst, uint16_t val) {
dst[0] = static_cast<uint8_t>(val & 0xFFU);
dst[1] = static_cast<uint8_t>((val >> 8) & 0xFFU);
}
/** @brief 将 32 位值按小端写入 4 字节缓冲(供 CAN 反馈/ID 快照等 payload 复用)。 */ /** @brief 将 32 位值按小端写入 4 字节缓冲(供 CAN 反馈/ID 快照等 payload 复用)。 */
inline void writeU32Le(uint8_t *dst, uint32_t val) { inline void writeU32Le(uint8_t *dst, uint32_t val) {
dst[0] = static_cast<uint8_t>(val & 0xFFU); dst[0] = static_cast<uint8_t>(val & 0xFFU);
+42 -37
View File
@@ -1,4 +1,4 @@
/** /**
* @file config.h * @file config.h
* @brief 应用级集中配置项定义。 * @brief 应用级集中配置项定义。
* @note 用于统一管理当前项目中可调的外设、时序、通信与算法常量。 * @note 用于统一管理当前项目中可调的外设、时序、通信与算法常量。
@@ -10,6 +10,40 @@
#include <stddef.h> #include <stddef.h>
#include "gd32e50x.h" #include "gd32e50x.h"
/* ============================================================
* 功能开关(调试/特性总闸 — 改这里即可开关,改完重编)
* 0/false=关,1/true=开。需要 #if 的保留 #define。
* ============================================================ */
/** @brief 周期温度/电压 UART 打印(5s,task_manager #if)。 */
#define CONFIG_UART_TEMP_PERIODIC_PRINT 1
/** @brief UART 诊断详细级别(task_runner #if;0=精简 1=含 CAN 统计)。 */
#define CONFIG_UART_DIAG_LEVEL 1
/**
* @brief SPI 调试反馈 0xC4:GD32 ADC 温度/VDDA/VIN 1Hz 主动推送。
* @note 调试用;0=不注册 handler、不推送。与 ESP32 GD32_ADC_FEEDBACK_ENABLE 配对。
*/
#define CONFIG_SPI_ADC_FEEDBACK_ENABLE 1
/** @brief SPI 命令逐帧日志(#if)。 */
#define CONFIG_SPI_APP_LOG_ENABLE 0
/** @brief CAN_SEND 成功帧详细日志(#if)。 */
#define CONFIG_SPI_CAN_SEND_VERBOSE_LOG 0
/** @brief CAN-FD BRS 使能(canfd.cpp #if)。 */
#define CONFIG_CAN_FD_BRS_ENABLE 1U
/** @brief 启动时总线探测帧;正常固件关。 */
#define CONFIG_CAN_STARTUP_BUS_TEST_ENABLE 0
/** @brief 启动时内部回环自检;正常固件关。 */
#define CONFIG_CAN_STARTUP_LOOPBACK_TEST_ENABLE 0
/** @brief CAN 启动详细诊断 dump;默认关。 */
#define CONFIG_CAN_STARTUP_DIAG_VERBOSE 0
/** @brief CAN0/1/2 通道使能。 */
constexpr bool CONFIG_CAN_CH0_ENABLE = true;
constexpr bool CONFIG_CAN_CH1_ENABLE = true;
constexpr bool CONFIG_CAN_CH2_ENABLE = true;
/** @brief 主循环 WFI(main.cpp #if)。 */
#define CONFIG_MAIN_LOOP_WFI_ENABLE 0
/** @brief EtherCAT SYNC0 同步模式(#if)。 */
#define CONFIG_ETHERCAT_SYNC_MODE 0
/* ============================================================ /* ============================================================
* APP 固件版本号 * APP 固件版本号
* ============================================================ */ * ============================================================ */
@@ -109,23 +143,7 @@ constexpr uint32_t CONFIG_CAN_DATA_BS1 = CAN_BT_BS1_3TQ;
/** @brief 数据段时间段 2。 */ /** @brief 数据段时间段 2。 */
constexpr uint32_t CONFIG_CAN_DATA_BS2 = CAN_BT_BS2_1TQ; constexpr uint32_t CONFIG_CAN_DATA_BS2 = CAN_BT_BS2_1TQ;
/** @brief BRS(比特率切换)使能开关。 /* BRS / 启动探测 / 通道使能:见文件顶部「功能开关」 */
* @note 保留 #define:canfd.cpp 中用 #if 做编译期分支。 */
#define CONFIG_CAN_FD_BRS_ENABLE 1U
/** @brief 启动时向物理总线发送 classic/FD/BRS 探测帧;正常固件默认关闭。 */
#define CONFIG_CAN_STARTUP_BUS_TEST_ENABLE 0
/** @brief 启动时切换控制器到内部回环执行自检;正常固件默认关闭。 */
#define CONFIG_CAN_STARTUP_LOOPBACK_TEST_ENABLE 0
/** @brief CAN 启动详细诊断(时钟/init 汇总/ERR 快照/寄存器 dump);默认关闭。 */
#define CONFIG_CAN_STARTUP_DIAG_VERBOSE 0
/** @brief CAN0 通道使能开关:true=启用初始化/收发/自测,false=完全禁用。 */
constexpr bool CONFIG_CAN_CH0_ENABLE = true;
/** @brief CAN1 通道使能开关。 */
constexpr bool CONFIG_CAN_CH1_ENABLE = true;
/** @brief CAN2 通道使能开关。 */
constexpr bool CONFIG_CAN_CH2_ENABLE = true;
/** @brief CAN 接收过滤器目标标准帧 ID。 */ /** @brief CAN 接收过滤器目标标准帧 ID。 */
constexpr uint32_t CONFIG_CAN_RX_FILTER_ID = CONFIG_CAN_STATUS_ID; constexpr uint32_t CONFIG_CAN_RX_FILTER_ID = CONFIG_CAN_STATUS_ID;
@@ -165,15 +183,7 @@ constexpr uint32_t CONFIG_TASK_PERIOD_HEARTBEAT_TICKS = 10000U;
/** @brief 调度基准节拍:APP 状态/诊断打印周期,50000 tick = 5s。 */ /** @brief 调度基准节拍:APP 状态/诊断打印周期,50000 tick = 5s。 */
constexpr uint32_t CONFIG_TASK_PERIOD_DIAG_PRINT_TICKS = 50000U; constexpr uint32_t CONFIG_TASK_PERIOD_DIAG_PRINT_TICKS = 50000U;
/** @brief 周期温度/电压 UART 打印开关。@note 保留 #define:task_manager.cpp #if 使用。 */ /* UART/SPI 调试开关:见文件顶部「功能开关」 */
#define CONFIG_UART_TEMP_PERIODIC_PRINT 1
/** @brief UART 诊断打印级别。@note 保留 #define:task_runner.cpp #if 使用。 */
#define CONFIG_UART_DIAG_LEVEL 1
/** @brief SPI 命令逐帧日志开关。@note 保留 #define:#if 使用。 */
#define CONFIG_SPI_APP_LOG_ENABLE 0
/** @brief CAN_SEND 成功帧详细日志开关。@note 保留 #define:#if 使用。 */
#define CONFIG_SPI_CAN_SEND_VERBOSE_LOG 0
/** @brief FWDGT 独立看门狗预分频值。 */ /** @brief FWDGT 独立看门狗预分频值。 */
constexpr uint32_t CONFIG_FWDGT_PSC = FWDGT_PSC_DIV64; constexpr uint32_t CONFIG_FWDGT_PSC = FWDGT_PSC_DIV64;
@@ -206,17 +216,8 @@ constexpr uint32_t CONFIG_IRQ_PREEMPT_ETHERCAT_ESC = 1U;
constexpr uint32_t CONFIG_IRQ_PREEMPT_ETHERCAT_SYNC1 = 1U; constexpr uint32_t CONFIG_IRQ_PREEMPT_ETHERCAT_SYNC1 = 1U;
constexpr uint32_t CONFIG_IRQ_PREEMPT_SPI_USART = 3U; constexpr uint32_t CONFIG_IRQ_PREEMPT_SPI_USART = 3U;
/* ============================================================ /* ============================================================
* 主循环与实时策略 * EtherCAT LAN9252 配置(开关见顶部 CONFIG_MAIN_LOOP_WFI / ETHERCAT_SYNC_MODE)
* ============================================================ */ * ============================================================ */
/** @brief 主循环 WFI 开关。@note 保留 #define:main.cpp #if 使用。 */
#define CONFIG_MAIN_LOOP_WFI_ENABLE 0
/* ============================================================
* EtherCAT LAN9252 使能与配置
* ============================================================ */
/** @brief SYNC0 同步模式。@note 保留 #define:#if 使用。 */
#define CONFIG_ETHERCAT_SYNC_MODE 0
constexpr uint32_t CONFIG_ETHERCAT_ISR_MAX_US_THRESHOLD = 5U; constexpr uint32_t CONFIG_ETHERCAT_ISR_MAX_US_THRESHOLD = 5U;
constexpr uint32_t CONFIG_ESC_PDI_INPUT_OFFSET = 0x1000U; constexpr uint32_t CONFIG_ESC_PDI_INPUT_OFFSET = 0x1000U;
constexpr uint32_t CONFIG_ESC_PDI_OUTPUT_OFFSET = 0x1200U; constexpr uint32_t CONFIG_ESC_PDI_OUTPUT_OFFSET = 0x1200U;
@@ -272,6 +273,10 @@ constexpr uint32_t CONFIG_EXMC_D13_PORT = GPIOD; constexpr uint32_t CONFIG_EXMC
constexpr uint32_t CONFIG_EXMC_D14_PORT = GPIOD; constexpr uint32_t CONFIG_EXMC_D14_PIN = GPIO_PIN_9; constexpr uint32_t CONFIG_EXMC_D14_PORT = GPIOD; constexpr uint32_t CONFIG_EXMC_D14_PIN = GPIO_PIN_9;
constexpr uint32_t CONFIG_EXMC_D15_PORT = GPIOD; constexpr uint32_t CONFIG_EXMC_D15_PIN = GPIO_PIN_10; constexpr uint32_t CONFIG_EXMC_D15_PORT = GPIOD; constexpr uint32_t CONFIG_EXMC_D15_PIN = GPIO_PIN_10;
constexpr uint32_t CONFIG_EXMC_A16_PORT = GPIOD; constexpr uint32_t CONFIG_EXMC_A16_PIN = GPIO_PIN_11; constexpr uint32_t CONFIG_EXMC_A16_PORT = GPIOD; constexpr uint32_t CONFIG_EXMC_A16_PIN = GPIO_PIN_11;
/* A17 (PD12) 用于 HBI Indexed Mode 访问 HBI_DATA_0 寄存器,已将 LAN9252 A2 (Pin 29) 接到 PD12。
* 注意 LAN9252 引脚顺序:Pin25=A1, Pin26=A3, Pin27=A4, Pin28=CS, Pin29=A2。
* A2 是 IDX/DATA 选择位,必须接到 PD12 才能访问 DATA 寄存器,EEPROM 烧录功能可用。 */
constexpr uint32_t CONFIG_EXMC_A17_PORT = GPIOD; constexpr uint32_t CONFIG_EXMC_A17_PIN = GPIO_PIN_12;
constexpr uint32_t CONFIG_EXMC_NOE_PORT = GPIOD; constexpr uint32_t CONFIG_EXMC_NOE_PIN = GPIO_PIN_4; constexpr uint32_t CONFIG_EXMC_NOE_PORT = GPIOD; constexpr uint32_t CONFIG_EXMC_NOE_PIN = GPIO_PIN_4;
constexpr uint32_t CONFIG_EXMC_NWE_PORT = GPIOD; constexpr uint32_t CONFIG_EXMC_NWE_PIN = GPIO_PIN_5; constexpr uint32_t CONFIG_EXMC_NWE_PORT = GPIOD; constexpr uint32_t CONFIG_EXMC_NWE_PIN = GPIO_PIN_5;
constexpr uint32_t CONFIG_EXMC_NE1_PORT = GPIOD; constexpr uint32_t CONFIG_EXMC_NE1_PIN = GPIO_PIN_7; constexpr uint32_t CONFIG_EXMC_NE1_PORT = GPIOD; constexpr uint32_t CONFIG_EXMC_NE1_PIN = GPIO_PIN_7;
+25 -2
View File
@@ -212,9 +212,30 @@ void TaskRunner::taskCanRxParse()
* @param[in] 无。 * @param[in] 无。
* @param[out] 无。 * @param[out] 无。
* @retval 无返回值。 * @retval 无返回值。
* @note 当前任务:推进 OTA Boot 请求、刷新 CAN 反馈快照、启动反馈推送轮次。 * @note 推进 OTA Boot 请求、刷新 CAN 反馈快照、启动 CAN/ADC 1Hz 推送轮次。
*/ */
void TaskRunner::taskHeartbeat() { OtaBootRequest::process(); CanFd::refreshFeedbackSnapshot(); canFeedbackStartRound(); } void TaskRunner::taskHeartbeat()
{
OtaBootRequest::process();
CanFd::refreshFeedbackSnapshot();
canFeedbackStartRound();
/* ADC 调试反馈 1Hz:CONFIG_SPI_ADC_FEEDBACK_ENABLE=0 时为空操作 */
adcFeedbackStartRound();
}
/**
* @brief 拷贝当前 ADC 工程量快照(供 SPI 0xC4 等只读路径)。
* @param[out] out 非空时写入最新 Sample
* @return true 已写入(不保证 sample.valid==1)
*/
bool TaskRunner::copyAdcSample(Adc::Sample *out)
{
if (out == nullptr) {
return false;
}
*out = s_runtime.sample;
return true;
}
/** /**
* @brief 输出当前温度、电压与 CAN 诊断信息。 * @brief 输出当前温度、电压与 CAN 诊断信息。
@@ -342,6 +363,8 @@ void TaskRunner::runPending()
* 替代 ESP32 主动轮询。非阻塞:上一帧未读走时立即返回。 * 替代 ESP32 主动轮询。非阻塞:上一帧未读走时立即返回。
* 每次主循环迭代调用一次,ESP32 读走一帧后 CS 释放中断唤醒主循环继续推送。 */ * 每次主循环迭代调用一次,ESP32 读走一帧后 CS 释放中断唤醒主循环继续推送。 */
canFeedbackPushStep(); canFeedbackPushStep();
/* ADC 调试反馈(0xC4):优先级低于 CAN;ring 忙则跳过。可 config 关闭。 */
adcFeedbackPushStep();
/* 周期路径( SYNC0 ISR preempt=0 )用 s_periodicSeq 序列号通知主循环, /* 周期路径( SYNC0 ISR preempt=0 )用 s_periodicSeq 序列号通知主循环,
* 不再走 task_flags,避免 BASEPRI 临界区与 preempt=0 的 RMW 竞态。 * 不再走 task_flags,避免 BASEPRI 临界区与 preempt=0 的 RMW 竞态。
+9
View File
@@ -1,6 +1,8 @@
#ifndef SYS_TASK_RUNNER_H #ifndef SYS_TASK_RUNNER_H
#define SYS_TASK_RUNNER_H #define SYS_TASK_RUNNER_H
#include "device/adc.h"
/** /**
* @brief 任务执行器。 * @brief 任务执行器。
* @note 负责消费 TaskManager 置位的任务,并驱动各模块完成实际工作。 * @note 负责消费 TaskManager 置位的任务,并驱动各模块完成实际工作。
@@ -25,6 +27,13 @@ public:
*/ */
static void runPending(); static void runPending();
/**
* @brief 拷贝当前 ADC 工程量快照(供 SPI 0xC4 调试反馈等只读路径)。
* @param[out] out 非空时写入最新 Sample。
* @return true 表示 out 已写入(不保证 valid==1,调用方看 sample.valid)。
*/
static bool copyAdcSample(Adc::Sample *out);
private: private:
struct Runtime; struct Runtime;
static Runtime s_runtime; static Runtime s_runtime;