feat(cproject): 重构项目文件结构并添加EtherCAT通信支持

- 将设备相关文件重新组织为device/spi和comm分组
- 添加EtherCAT从站通信模块(esc_exmc_bus、esc_lan9252、ethercat_slave等)
- 添加EXMC外设组件支持
- 统一文件命名规范为小写

refactor(canfd): 优化CANFD模块中断安全性和配置方式

- 替换自定义临界区函数为RT_CRITICAL_SAVE/RESTORE宏
- 使用配置常量CONFIG_IRQ_PREEMPT_CAN替代硬编码值
- 简化邮箱满时的处理逻辑,移除不必要的SysTick等待
- 移除废弃的中断保存头文件引用

refactor(timer): 更新定时器配置以使用配置常量

- 修复头文件包含路径大小写问题
- 使用CONFIG_NVIC_PRIORITY_GROUP配置项替代硬编码值
This commit is contained in:
cbb
2026-06-26 00:57:59 +08:00
parent 86c0ec730b
commit 1612e326ee
32 changed files with 1378 additions and 147 deletions
+29 -16
View File
@@ -1,4 +1,4 @@
# A project translates into one executable or library.
# A project translates into one executable or library.
project:
groups:
@@ -22,26 +22,38 @@ project:
- file: ./sys/config.h
- group: device
files:
- file: ./device/adc.h
- file: ./device/adc.cpp
- file: ./device/canfd.h
- file: ./device/canfd.cpp
- file: ./device/timer.h
- file: ./device/timer.cpp
- file: ./device/usart0.h
- file: ./device/usart0.cpp
- file: ./device/adc.h
- file: ./device/ADC.cpp
- file: ./device/canfd.h
- file: ./device/CANFD.cpp
- file: ./device/app_spi_command.h
- file: ./device/app_spi_command.cpp
- file: ./device/spi_command_handler.h
- file: ./device/app_spi_command_defs.h
- file: ./device/app_spi_cmd_boot_request.cpp
- file: ./device/app_spi_cmd_ota_wait.cpp
- file: ./device/app_spi_cmd_ping.cpp
- file: ./device/app_spi_cmd_can_bridge.cpp
- file: ./device/Timer.h
- file: ./device/Timer.cpp
- file: ./device/temp_sensor.h
- file: ./device/watchdog.h
- file: ./device/temp_sensor.cpp
- file: ./device/watchdog.h
- file: ./device/watchdog.cpp
- group: device/spi
files:
- file: ./device/spi/app_spi_command.h
- file: ./device/spi/app_spi_command.cpp
- file: ./device/spi/spi_command_handler.h
- file: ./device/spi/app_spi_command_defs.h
- file: ./device/spi/app_spi_cmd_boot_request.cpp
- file: ./device/spi/app_spi_cmd_ota_wait.cpp
- file: ./device/spi/app_spi_cmd_ping.cpp
- file: ./device/spi/app_spi_cmd_can_bridge.cpp
- group: comm
files:
- file: ./comm/esc_exmc_bus.h
- file: ./comm/esc_exmc_bus.cpp
- file: ./comm/esc_lan9252.h
- file: ./comm/esc_lan9252.cpp
- file: ./comm/ethercat_slave.h
- file: ./comm/ethercat_slave.cpp
- file: ./comm/ethercat_sync.h
- file: ./comm/ethercat_sync.cpp
# List components to use for your application.
# A software component is a re-usable unit that may be configurable.
@@ -53,6 +65,7 @@ project:
- component: Device:GD32E50x_StdPeripherals:CRC
- component: Device:GD32E50x_StdPeripherals:DBG
- component: Device:GD32E50x_StdPeripherals:DMA
- component: Device:GD32E50x_StdPeripherals:EXMC
- component: Device:GD32E50x_StdPeripherals:EXTI
- component: Device:GD32E50x_StdPeripherals:FMC
- component: Device:GD32E50x_StdPeripherals:GPIO
+99
View File
@@ -0,0 +1,99 @@
/**
* @file esc_exmc_bus.cpp
* @brief LAN9252 ESC 的 EXMC 16-bit 并行总线驱动实现。
*/
#include "comm/esc_exmc_bus.h"
#include "sys/config.h"
#if CONFIG_ETHERCAT_ENABLE
extern "C" {
#include "gd32e50x_rcu.h"
#include "gd32e50x_gpio.h"
#ifdef EXMC_NORSRAM_REGION1
#include "gd32e50x_exmc.h"
#endif
}
/* 内存映射访问宏:基址 + 偏移(按 16-bit 半字对齐,LAN9252 HBI 地址左移 1 位)。
* 使用 volatile uint16_t* 转换避免依赖 CMSIS 的 __IO 宏。 */
#define ESC_EXMC_REG16(offset) (*(volatile uint16_t*)(CONFIG_ESC_EXMC_BASE_ADDR + (offset)))
void EscExmcBus::init()
{
/* 使能 GPIO 与 EXMC 时钟 */
rcu_periph_clock_enable(RCU_GPIOD);
rcu_periph_clock_enable(RCU_GPIOE);
rcu_periph_clock_enable(RCU_AF);
#ifdef EXMC_NORSRAM_REGION1
rcu_periph_clock_enable(RCU_EXMC);
/* 配置 16-bit 数据线 D0-D15 为 AF 推挽 */
gpio_init(CONFIG_EXMC_D0_PORT, GPIO_MODE_AF_PP, GPIO_OSPEED_50MHZ, CONFIG_EXMC_D0_PIN);
gpio_init(CONFIG_EXMC_D1_PORT, GPIO_MODE_AF_PP, GPIO_OSPEED_50MHZ, CONFIG_EXMC_D1_PIN);
gpio_init(CONFIG_EXMC_D2_PORT, GPIO_MODE_AF_PP, GPIO_OSPEED_50MHZ, CONFIG_EXMC_D2_PIN);
gpio_init(CONFIG_EXMC_D3_PORT, GPIO_MODE_AF_PP, GPIO_OSPEED_50MHZ, CONFIG_EXMC_D3_PIN);
gpio_init(CONFIG_EXMC_D4_PORT, GPIO_MODE_AF_PP, GPIO_OSPEED_50MHZ, CONFIG_EXMC_D4_PIN);
gpio_init(CONFIG_EXMC_D5_PORT, GPIO_MODE_AF_PP, GPIO_OSPEED_50MHZ, CONFIG_EXMC_D5_PIN);
gpio_init(CONFIG_EXMC_D6_PORT, GPIO_MODE_AF_PP, GPIO_OSPEED_50MHZ, CONFIG_EXMC_D6_PIN);
gpio_init(CONFIG_EXMC_D7_PORT, GPIO_MODE_AF_PP, GPIO_OSPEED_50MHZ, CONFIG_EXMC_D7_PIN);
gpio_init(CONFIG_EXMC_D8_PORT, GPIO_MODE_AF_PP, GPIO_OSPEED_50MHZ, CONFIG_EXMC_D8_PIN);
gpio_init(CONFIG_EXMC_D9_PORT, GPIO_MODE_AF_PP, GPIO_OSPEED_50MHZ, CONFIG_EXMC_D9_PIN);
gpio_init(CONFIG_EXMC_D10_PORT, GPIO_MODE_AF_PP, GPIO_OSPEED_50MHZ, CONFIG_EXMC_D10_PIN);
gpio_init(CONFIG_EXMC_D11_PORT, GPIO_MODE_AF_PP, GPIO_OSPEED_50MHZ, CONFIG_EXMC_D11_PIN);
gpio_init(CONFIG_EXMC_D12_PORT, GPIO_MODE_AF_PP, GPIO_OSPEED_50MHZ, CONFIG_EXMC_D12_PIN);
gpio_init(CONFIG_EXMC_D13_PORT, GPIO_MODE_AF_PP, GPIO_OSPEED_50MHZ, CONFIG_EXMC_D13_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);
/* 控制线:A16 / NOE / NWE / NE1 */
gpio_init(CONFIG_EXMC_A16_PORT, GPIO_MODE_AF_PP, GPIO_OSPEED_50MHZ, CONFIG_EXMC_A16_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);
/* EXMC NOR/SRAM Region1 时序配置(16-bit,非复用,异步) */
exmc_norsram_parameter_struct norsram_init;
exmc_norsram_struct_para_init(&norsram_init);
norsram_init.norsram_region = CONFIG_ESC_EXMC_BANK_NOR_SRAM_REGION1;
norsram_init.write_mode = EXMC_ASYN_WRITE;
norsram_init.read_mode = EXMC_ASYN_READ;
norsram_init.nwait_mode = EXMC_NWAIT_DISABLE;
norsram_init.asyn_wait = DISABLE;
norsram_init.nwait_polarity = EXMC_NWAIT_POLARITY_LOW;
norsram_init.databus_width = EXMC_NOR_DATABUS_WIDTH_16B;
/* 时序:按 LAN9252 HBI 要求配置 read/write wait state */
norsram_init.read_latency = EXMC_DEN_ALE_TR_6_HCLK; // 需按 LAN9252 HBI 时序实测调整
norsram_init.write_latency = EXMC_DEN_ALE_TR_6_HCLK; // 需按 LAN9252 HBI 时序实测调整
exmc_norsram_init(&norsram_init);
exmc_norsram_enable(CONFIG_ESC_EXMC_BANK_NOR_SRAM_REGION1);
#else
/* EXMC 外设库未安装:仅占位,CONFIG_ETHERCAT_ENABLE=1 时需先安装 EXMC 组件。 */
#endif
}
uint16_t EscExmcBus::read16(uint32_t offset)
{
return ESC_EXMC_REG16(offset);
}
void EscExmcBus::write16(uint32_t offset, uint16_t data)
{
ESC_EXMC_REG16(offset) = data;
}
void EscExmcBus::readBlock(uint32_t offset, uint16_t *buf, size_t count)
{
for (size_t i = 0U; i < count; ++i) {
buf[i] = ESC_EXMC_REG16(offset + (uint32_t)(i * 2U));
}
}
void EscExmcBus::writeBlock(uint32_t offset, const uint16_t *buf, size_t count)
{
for (size_t i = 0U; i < count; ++i) {
ESC_EXMC_REG16(offset + (uint32_t)(i * 2U)) = buf[i];
}
}
#endif /* CONFIG_ETHERCAT_ENABLE */
+53
View File
@@ -0,0 +1,53 @@
/**
* @file esc_exmc_bus.h
* @brief LAN9252 ESC 的 EXMC 16-bit 并行总线抽象接口。
* @note 通过 EXMC NOR/SRAM Region1(NE1,基址 0x60000000)以内存映射方式访问 LAN9252。
* 16-bit 数据总线 + A16 地址线 + NOE/NWE/NE1 控制线。
*/
#ifndef COMM_ESC_EXMC_BUS_H
#define COMM_ESC_EXMC_BUS_H
#include <cstdint>
#include <cstddef>
/**
* @brief LAN9252 ESC 的 EXMC 并行总线驱动。
* @note 提供 16-bit 读写与块读写接口,内部通过内存映射地址直接访问 ESC 寄存器空间。
*/
class EscExmcBus {
public:
/** @brief 初始化 EXMC NOR/SRAM Region1 与全部 GPIO(AF 复用)。 */
static void init();
/**
* @brief 从指定偏移读取 16-bit 数据。
* @param[in] offset 地址偏移(字节)。
* @return uint16_t 读取的 16-bit 数据。
*/
static uint16_t read16(uint32_t offset);
/**
* @brief 向指定偏移写入 16-bit 数据。
* @param[in] offset 地址偏移(字节)。
* @param[in] data 待写入的 16-bit 数据。
*/
static void write16(uint32_t offset, uint16_t data);
/**
* @brief 从指定偏移连续读取一块 16-bit 数据。
* @param[in] offset 地址偏移(字节)。
* @param[out] buf 输出缓冲(以 uint16_t 为单位)。
* @param[in] count 读取的 16-bit 半字数量。
*/
static void readBlock(uint32_t offset, uint16_t *buf, size_t count);
/**
* @brief 向指定偏移连续写入一块 16-bit 数据。
* @param[in] offset 地址偏移(字节)。
* @param[in] buf 数据缓冲(以 uint16_t 为单位)。
* @param[in] count 写入的 16-bit 半字数量。
*/
static void writeBlock(uint32_t offset, const uint16_t *buf, size_t count);
};
#endif /* COMM_ESC_EXMC_BUS_H */
+82
View File
@@ -0,0 +1,82 @@
/**
* @file esc_lan9252.cpp
* @brief LAN9252 EtherCAT ESC 驱动实现。
*/
#include "comm/esc_lan9252.h"
#include "comm/esc_exmc_bus.h"
#include "sys/config.h"
#if CONFIG_ETHERCAT_ENABLE
namespace {
/* LAN9252 ESC 关键寄存器地址(字节偏移,EXMC 访问) */
constexpr uint16_t LAN9252_REG_CHIP_ID_L = 0x0000U; /**< 芯片 ID 低 16 位(=0x4321)。 */
constexpr uint16_t LAN9252_REG_CHIP_ID_H = 0x0002U; /**< 芯片 ID 高 16 位(=0x8765)。 */
constexpr uint32_t LAN9252_CHIP_ID_VALUE = 0x87654321U; /**< 期望的 32-bit 芯片 ID。 */
/* PDI 内存基址偏移(实际取决于 LAN9252 PDI 配置,这里为示意值) */
constexpr uint16_t LAN9252_PDI_BASE_OFFSET = 0x1000U;
}
void EscLan9252::init()
{
EscExmcBus::init();
/* TODO: 配置 ESC 中断使能、AL 控制等基本寄存器(留待协议栈集成阶段实现) */
}
bool EscLan9252::selfTest()
{
const uint16_t id_low = readReg(LAN9252_REG_CHIP_ID_L);
const uint16_t id_high = readReg(LAN9252_REG_CHIP_ID_H);
const uint32_t chip_id = ((uint32_t)id_high << 16) | id_low;
return (chip_id == LAN9252_CHIP_ID_VALUE);
}
uint16_t EscLan9252::readReg(uint16_t addr)
{
/* ESC 寄存器地址按字节寻址,EXMC 16-bit 访问需要地址左移 1 位 */
return EscExmcBus::read16((uint32_t)addr << 1U);
}
void EscLan9252::writeReg(uint16_t addr, uint16_t val)
{
EscExmcBus::write16((uint32_t)addr << 1U, val);
}
void EscLan9252::readPdi(uint16_t offset, uint8_t *buf, size_t len)
{
if (buf == nullptr || len == 0U) {
return;
}
/* PDI 按 16-bit 读取,非对齐字节需特殊处理(此处简化为按半字读取) */
const uint32_t exmc_offset = (uint32_t)(LAN9252_PDI_BASE_OFFSET + offset) << 1U;
const size_t half_count = len / 2U;
for (size_t i = 0U; i < half_count; ++i) {
uint16_t w = EscExmcBus::read16(exmc_offset + (uint32_t)(i * 2U));
buf[i * 2U] = (uint8_t)(w & 0xFFU);
buf[i * 2U + 1] = (uint8_t)((w >> 8) & 0xFFU);
}
/* 处理奇数字节 */
if ((len & 1U) != 0U) {
uint16_t w = EscExmcBus::read16(exmc_offset + (uint32_t)(half_count * 2U));
buf[half_count * 2U] = (uint8_t)(w & 0xFFU);
}
}
void EscLan9252::writePdi(uint16_t offset, const uint8_t *buf, size_t len)
{
if (buf == nullptr || len == 0U) {
return;
}
const uint32_t exmc_offset = (uint32_t)(LAN9252_PDI_BASE_OFFSET + offset) << 1U;
const size_t half_count = len / 2U;
for (size_t i = 0U; i < half_count; ++i) {
uint16_t w = (uint16_t)buf[i * 2U] | ((uint16_t)buf[i * 2U + 1] << 8);
EscExmcBus::write16(exmc_offset + (uint32_t)(i * 2U), w);
}
if ((len & 1U) != 0U) {
EscExmcBus::write16(exmc_offset + (uint32_t)(half_count * 2U), (uint16_t)buf[half_count * 2U]);
}
}
#endif /* CONFIG_ETHERCAT_ENABLE */
+58
View File
@@ -0,0 +1,58 @@
/**
* @file esc_lan9252.h
* @brief LAN9252 EtherCAT 从站控制器(ESC)驱动接口。
* @note 通过 EscExmcBus 16-bit 并行总线访问 LAN9252 寄存器与 PDI 内存。
*/
#ifndef COMM_ESC_LAN9252_H
#define COMM_ESC_LAN9252_H
#include <cstdint>
#include <cstddef>
/**
* @brief LAN9252 ESC 驱动。
* @note 封装 ESC 寄存器读写与 PDI(Process Data Interface)内存块访问。
*/
class EscLan9252 {
public:
/** @brief 初始化 EXMC 总线与 ESC 基本寄存器。 */
static void init();
/**
* @brief ESC 自检:读取芯片 ID 寄存器校验。
* @return bool true 表示 ID 匹配(0x87654321),false 表示失败。
*/
static bool selfTest();
/**
* @brief 读取 ESC 寄存器(16-bit)。
* @param[in] addr 寄存器地址(ESC 寄存器空间偏移)。
* @return uint16_t 寄存器值。
*/
static uint16_t readReg(uint16_t addr);
/**
* @brief 写入 ESC 寄存器(16-bit)。
* @param[in] addr 寄存器地址。
* @param[in] val 待写入值。
*/
static void writeReg(uint16_t addr, uint16_t val);
/**
* @brief 从 PDI 内存读取一块数据。
* @param[in] offset PDI 偏移(字节)。
* @param[out] buf 输出缓冲。
* @param[in] len 读取字节数。
*/
static void readPdi(uint16_t offset, uint8_t *buf, size_t len);
/**
* @brief 向 PDI 内存写入一块数据。
* @param[in] offset PDI 偏移(字节)。
* @param[in] buf 数据缓冲。
* @param[in] len 写入字节数。
*/
static void writePdi(uint16_t offset, const uint8_t *buf, size_t len);
};
#endif /* COMM_ESC_LAN9252_H */
+70
View File
@@ -0,0 +1,70 @@
/**
* @file ethercat_slave.cpp
* @brief EtherCAT 从站周期处理实现。
*/
#include "comm/ethercat_slave.h"
#include "comm/esc_lan9252.h"
#include "sys/config.h"
#include "sys/task_manager.h"
#include "main.h"
#if CONFIG_ETHERCAT_ENABLE
PdoBuffer EthercatSlave::s_inputs[2] = {};
PdoBuffer EthercatSlave::s_outputs[2] = {};
volatile uint8_t EthercatSlave::s_inputReadIndex = 0U;
volatile uint8_t EthercatSlave::s_outputWriteIndex = 0U;
void EthercatSlave::init()
{
EscLan9252::init();
if (EscLan9252::selfTest()) {
/* ESC 自检通过:TODO 启用周期同步 */
}
s_inputReadIndex = 0U;
s_outputWriteIndex = 0U;
}
__attribute__((section(".ramfunc")))
void EthercatSlave::periodicIsr()
{
/* 1. 读取输入 PDO 到另一块缓冲(当前可读缓冲的配对) */
const uint8_t write_idx = s_inputReadIndex ^ 1U;
EscLan9252::readPdi(0x0000U, s_inputs[write_idx].data, CONFIG_ETHERCAT_PDO_SIZE);
s_inputs[write_idx].len = CONFIG_ETHERCAT_PDO_SIZE;
/* 2. 写入输出 PDO(从应用层未写入的那个缓冲读取) */
const uint8_t out_read_idx = s_outputWriteIndex ^ 1U;
EscLan9252::writePdi(0x0000U, s_outputs[out_read_idx].data, s_outputs[out_read_idx].len);
/* 3. 翻转输入缓冲指针,使新完成的缓冲成为可读缓冲 */
s_inputReadIndex = write_idx;
/* 4. 置位非实时任务,通知主循环处理应用层 PDO 映射 */
TaskManager::set(1UL << TASK_ETHERCAT_PROCESS);
}
const PdoBuffer* EthercatSlave::getLatestInputs()
{
/* s_inputReadIndex 始终指向最新完成的输入缓冲 */
return &s_inputs[s_inputReadIndex];
}
void EthercatSlave::setOutputs(const PdoBuffer *outputs)
{
if (outputs == nullptr) {
return;
}
/* 写入应用层缓冲(ISR 当前不读取的那个) */
const uint8_t write_idx = s_outputWriteIndex ^ 1U;
s_outputs[write_idx] = *outputs;
s_outputWriteIndex = write_idx;
}
void EthercatSlave::process()
{
/* 应用层 PDO 映射入口(非实时,由主循环 TASK_ETHERCAT_PROCESS 调用)。
* TODO: 将 getLatestInputs() 数据映射到应用对象字典;将应用输出填入 setOutputs()。 */
}
#endif /* CONFIG_ETHERCAT_ENABLE */
+67
View File
@@ -0,0 +1,67 @@
/**
* @file ethercat_slave.h
* @brief EtherCAT 从站周期处理接口与 PDO 双缓冲(ping-pong)结构。
* @note 周期 ISR 调用 periodicIsr() 完成输入 PDO 读取 + 输出 PDO 写入;
* 应用层通过 getLatestInputs()/setOutputs() 与周期路径无锁交换数据。
*/
#ifndef COMM_ETHERCAT_SLAVE_H
#define COMM_ETHERCAT_SLAVE_H
#include <cstdint>
#include <cstddef>
/** @brief 默认 PDO 大小(可按需调整)。 */
constexpr size_t CONFIG_ETHERCAT_PDO_SIZE = 32U;
/**
* @brief PDO 数据缓冲(输入/输出共用结构)。
* @note 周期 ISR 与应用层通过双缓冲无锁交换。
*/
struct PdoBuffer {
uint8_t data[CONFIG_ETHERCAT_PDO_SIZE] = {0}; /**< 数据载荷。 */
size_t len = 0U; /**< 有效数据长度。 */
};
/**
* @brief EtherCAT 从站周期处理。
* @note 周期路径(periodicIsr)必须极简:读输入→写输出→翻转指针→置位任务。
*/
class EthercatSlave {
public:
/** @brief 初始化 ESC 与双缓冲。 */
static void init();
/**
* @brief 周期中断处理(由 SYNC0 EXTI 触发)。
* @note 放入 RAM 执行段(.ramfunc)以降低 Flash 等待带来的抖动。
* 流程:读输入 PDO → 写输出 PDO → 翻转输入缓冲指针 → 置位 TASK_ETHERCAT_PROCESS。
*/
static void periodicIsr();
/**
* @brief 获取最新输入 PDO(主循环调用,无锁)。
* @return const PdoBuffer* 最新输入缓冲只读指针;周期 ISR 不会修改该缓冲。
*/
static const PdoBuffer* getLatestInputs();
/**
* @brief 设置下次周期要发送的输出 PDO(主循环调用)。
* @param[in] outputs 输出数据指针(内容会被拷贝到内部输出缓冲)。
* @note 调用方在 ISR 不持有期间调用即可,内部使用乒乓缓冲避免竞争。
*/
static void setOutputs(const PdoBuffer *outputs);
/**
* @brief 应用层 PDO 映射入口(非实时,由 TASK_ETHERCAT_PROCESS 调用)。
* @note 留接口,具体映射逻辑由协议栈后续实现。
*/
static void process();
private:
static PdoBuffer s_inputs[2]; /**< 输入双缓冲(pong-pong)。 */
static PdoBuffer s_outputs[2]; /**< 输出双缓冲(ping-pong)。 */
static volatile uint8_t s_inputReadIndex; /**< ISR 当前写入索引;getLatestInputs 读另一个。 */
static volatile uint8_t s_outputWriteIndex; /**< 应用层写入索引;ISR 读另一个。 */
};
#endif /* COMM_ETHERCAT_SLAVE_H */
+122
View File
@@ -0,0 +1,122 @@
/**
* @file ethercat_sync.cpp
* @brief EtherCAT SYNC0 周期同步实现。
*/
#include "comm/ethercat_sync.h"
#include "comm/ethercat_slave.h"
#include "sys/config.h"
#include "sys/task_manager.h"
#include "main.h"
#if CONFIG_ETHERCAT_ENABLE
extern "C" {
#include "gd32e50x_rcu.h"
#include "gd32e50x_gpio.h"
#include "gd32e50x_exti.h"
#include "gd32e50x_misc.h"
#if (CONFIG_ETHERCAT_SYNC_MODE == 1)
#include "gd32e50x_timer.h"
#endif
}
void EthercatSync::init()
{
/* 使能 GPIOB 时钟(SYNC0=PB0 / IRQ=PB1 / SYNC1=PB2) */
rcu_periph_clock_enable(RCU_GPIOB);
rcu_periph_clock_enable(RCU_AF);
/* 配置三个引脚为上拉输入(EXTI 下降沿触发:LAN9252 输出低有效脉冲) */
gpio_init(GPIOB, GPIO_MODE_IPU, GPIO_OSPEED_50MHZ,
CONFIG_ESC_SYNC0_PIN | CONFIG_ESC_IRQ_PIN | CONFIG_ESC_SYNC1_PIN);
#if (CONFIG_ETHERCAT_SYNC_MODE == 0)
/* ---- 默认:EXTI 外部触发模式 ---- */
/* SYNC0:PB0 -> EXTI0,下降沿,preempt=0 */
gpio_exti_source_select(CONFIG_ESC_SYNC0_EXTI_SOURCE_PORT, CONFIG_ESC_SYNC0_EXTI_SOURCE_PIN);
exti_init(CONFIG_ESC_SYNC0_EXTI, EXTI_INTERRUPT, EXTI_TRIG_FALLING);
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);
/* ESC IRQ:PB1 -> EXTI1,下降沿,preempt=1 */
gpio_exti_source_select(CONFIG_ESC_IRQ_EXTI_SOURCE_PORT, CONFIG_ESC_IRQ_EXTI_SOURCE_PIN);
exti_init(CONFIG_ESC_IRQ_EXTI, EXTI_INTERRUPT, EXTI_TRIG_FALLING);
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);
/* SYNC1:PB2 -> EXTI2,下降沿,preempt=1(备用) */
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);
#else
/* ---- 降级模式:TIMER0 内部定时 ---- */
rcu_periph_clock_enable(RCU_TIMER0);
timer_parameter_struct timer_initpara;
timer_struct_para_init(&timer_initpara);
/* 100us 周期:180MHz / (179+1) / (99+1) = 10kHz = 100us */
timer_initpara.prescaler = 179U;
timer_initpara.alignedmode = TIMER_COUNTER_EDGE;
timer_initpara.counterdirection = TIMER_COUNTER_UP;
timer_initpara.period = 99U;
timer_initpara.clockdivision = TIMER_CKDIV_DIV1;
timer_init(TIMER0, &timer_initpara);
timer_interrupt_flag_clear(TIMER0, TIMER_INT_FLAG_UP);
timer_interrupt_enable(TIMER0, TIMER_INT_UP);
nvic_irq_enable(TIMER0_IRQn, CONFIG_IRQ_PREEMPT_ETHERCAT_SYNC, 0U);
timer_enable(TIMER0);
#endif
}
void EthercatSync::onSync0Exti()
{
if (exti_interrupt_flag_get(CONFIG_ESC_SYNC0_EXTI) != RESET) {
exti_interrupt_flag_clear(CONFIG_ESC_SYNC0_EXTI);
EthercatSlave::periodicIsr();
}
}
void EthercatSync::onEscIrqExti()
{
if (exti_interrupt_flag_get(CONFIG_ESC_IRQ_EXTI) != RESET) {
exti_interrupt_flag_clear(CONFIG_ESC_IRQ_EXTI);
/* 极简:仅置位任务,事件处理移主循环 */
TaskManager::set(1UL << TASK_ETHERCAT_PROCESS);
}
}
void EthercatSync::onSync1Exti()
{
if (exti_interrupt_flag_get(CONFIG_ESC_SYNC1_EXTI) != RESET) {
exti_interrupt_flag_clear(CONFIG_ESC_SYNC1_EXTI);
/* SYNC1 备用,暂不处理 */
}
}
void EthercatSync::onTimerIrq()
{
#if (CONFIG_ETHERCAT_SYNC_MODE == 1)
if (timer_interrupt_flag_get(TIMER0, TIMER_INT_FLAG_UP) != RESET) {
timer_interrupt_flag_clear(TIMER0, TIMER_INT_FLAG_UP);
EthercatSlave::periodicIsr();
}
#endif
}
/* ---- 中断入口 C 包装 ---- */
extern "C" {
#if (CONFIG_ETHERCAT_SYNC_MODE == 0)
void EXTI0_IRQHandler(void) { EthercatSync::onSync0Exti(); }
void EXTI1_IRQHandler(void) { EthercatSync::onEscIrqExti(); }
void EXTI2_IRQHandler(void) { EthercatSync::onSync1Exti(); }
#else
void TIMER0_IRQHandler(void) { EthercatSync::onTimerIrq(); }
#endif
} /* extern "C" */
#endif /* CONFIG_ETHERCAT_ENABLE */
+26
View File
@@ -0,0 +1,26 @@
/**
* @file ethercat_sync.h
* @brief EtherCAT SYNC0 周期同步接口(EXTI 独立向量 / TIMER0 降级)。
* @note 默认由 LAN9252 SYNC0 引脚(PB0/EXTI0)硬件触发周期中断,
* preempt=0 独占向量保证抖动 <1us;CONFIG_ETHERCAT_SYNC_MODE=1 时用 TIMER0 降级。
* ESC IRQ(PB1/EXTI1,preempt=1)与 SYNC1(PB2/EXTI2,preempt=1)独立向量。
*/
#ifndef COMM_ETHERCAT_SYNC_H
#define COMM_ETHERCAT_SYNC_H
class EthercatSync {
public:
/** @brief 初始化 SYNC0/IRQ/SYNC1 EXTI 引脚与 NVIC(或降级模式 TIMER0)。 */
static void init();
/** @brief SYNC0 EXTI 中断处理(EXTI0)。 */
static void onSync0Exti();
/** @brief ESC IRQ EXTI 中断处理(EXTI1)。 */
static void onEscIrqExti();
/** @brief SYNC1 EXTI 中断处理(EXTI2,备用)。 */
static void onSync1Exti();
/** @brief TIMER0 降级模式中断处理。 */
static void onTimerIrq();
};
#endif /* COMM_ETHERCAT_SYNC_H */
+1 -1
View File
@@ -1,5 +1,5 @@
/**
* @file ADC.cpp
* @file adc.cpp
* @brief ADC 采样模块实现。
*/
+10 -53
View File
@@ -1,13 +1,13 @@
/**
* @file CANFD.cpp
* @file canfd.cpp
* @brief CANFD 通信模块实现。
*/
#include "main.h"
#include "device/canfd.h"
#include "sys/config.h"
#include "sys/critical.h"
#include "sys/task_manager.h"
#include "sys/systick.h"
namespace {
struct TxFrame {
@@ -44,30 +44,6 @@ constexpr CanChannelConfig kChannels[] = {
constexpr size_t kChannelCount = sizeof(kChannels) / sizeof(kChannels[0]);
constexpr uint8_t kTxQueueDepth = 4U;
constexpr uint32_t kCanRxParseTaskMask = (1UL << TASK_CAN_RX_PARSE);
/**
* @brief Enter a critical section and preserve the previous interrupt mask.
* @param[in] 无。
* @param[out] 无。
* @retval uint32_t Previous PRIMASK value.
*/
static uint32_t enterCriticalSection()
{
const uint32_t primask = __get_PRIMASK();
__disable_irq();
return primask;
}
/**
* @brief Exit a critical section by restoring the saved interrupt mask.
* @param[in] primask Previous PRIMASK value returned by `enterCriticalSection()`.
* @param[out] 无。
* @retval 无返回值。
*/
static void exitCriticalSection(uint32_t primask)
{
__set_PRIMASK(primask);
}
} // namespace
CanFd::Diag CanFd::s_diag{};
@@ -281,7 +257,7 @@ void CanFd::init()
initFdMode(ch.canPeriph);
configureRxFilter(ch.canPeriph, ch.filterNumber);
can_interrupt_enable(ch.canPeriph, CAN_INT_RFNE0 | CAN_INT_RFF0 | CAN_INT_RFO0);
nvic_irq_enable(ch.rx0Irq, 1U, static_cast<uint8_t>(i));
nvic_irq_enable(ch.rx0Irq, CONFIG_IRQ_PREEMPT_CAN, static_cast<uint8_t>(i));
s_rxQueues[i].head = 0U;
s_rxQueues[i].tail = 0U;
@@ -375,14 +351,14 @@ void CanFd::sendPrepared()
bool CanFd::hasRxFrame()
{
bool found = false;
const uint32_t primask = enterCriticalSection();
uint32_t save = RT_CRITICAL_SAVE();
for(size_t i = 0; i < kChannelCount; ++i) {
if(s_rxQueues[i].count != 0U) {
found = true;
break;
}
}
exitCriticalSection(primask);
RT_CRITICAL_RESTORE(save);
return found;
}
@@ -401,7 +377,7 @@ bool CanFd::consumeRxFrame(uint32_t *id, uint8_t *data, uint8_t *length, uint8_t
RxFrame localFrame{};
bool found = false;
const uint32_t primask = enterCriticalSection();
uint32_t save = RT_CRITICAL_SAVE();
for(size_t n = 0; n < kChannelCount; ++n) {
const uint8_t idx = static_cast<uint8_t>((s_nextConsumeChannel + n) % kChannelCount);
auto &queue = s_rxQueues[idx];
@@ -416,7 +392,7 @@ bool CanFd::consumeRxFrame(uint32_t *id, uint8_t *data, uint8_t *length, uint8_t
found = true;
break;
}
exitCriticalSection(primask);
RT_CRITICAL_RESTORE(save);
if(!found) {
return false;
@@ -467,29 +443,10 @@ bool CanFd::transmitFrame(uint8_t channelIndex, uint32_t ext_id,
const uint32_t canPeriph = kChannels[channelIndex].canPeriph;
uint8_t mailbox = can_message_transmit(canPeriph, &tx_msg);
/* 邮箱全占时的恢复处理:
* 当目标控制器初始化时 auto_retrans = ENABLE,且总线上没有其它节点回 ACK,
* 已入邮箱的帧会无限自动重传,TME 位永不置回,导致后续帧一律 CAN_NOMAILBOX。
*
* 改良策略(两段式,尽量不丢帧):
* 1) 先短自旋 ~2 ms 轮询邮箱状态。健康总线上 1 Mbps + 8 字节帧的实际发送
* 仅约 130 µs,正常重传几百 µs 内即可 drain,绝大多数“偶发 NOMAILBOX”
* 场景到这一步就已经恢复,不会触发 abort,也就不会丢帧。
* 2) 若 2 ms 后仍全部 PENDING(典型:物理层不通、TCAN1057A 没焊或 CAN_L/H
* 断线导致 ACK Error 死循环),只 abort 第一个 PENDING 邮箱腾一个位置,
* 最坏丢 1 帧而不是 3 帧,并通过 s_diag.tx_abort_count 暴露给串口 diag,
* 上层(含 SPI 桥)仍能把后续帧正常下发。 */
/* 邮箱全占时保持非阻塞:本函数可能处于 SPI CS 中断链路,不能等待
* SysTick。若 auto_retrans 因总线无 ACK 卡住全部邮箱,只立即 abort
* 一个 PENDING 邮箱并重投一次,把中断占用时间控制在寄存器访问级别。 */
if (mailbox == CAN_NOMAILBOX) {
constexpr uint8_t kDrainPolls = 2U;
for (uint8_t poll = 0U; poll < kDrainPolls && mailbox == CAN_NOMAILBOX; ++poll) {
SysTickTimer::delayMs(1U);
mailbox = can_message_transmit(canPeriph, &tx_msg);
}
}
if (mailbox == CAN_NOMAILBOX) {
/* 只 abort 一个 PENDING 邮箱:找到首个 stop 成功的就停手,
* 不再扫描后续邮箱,把丢帧上限收敛到 1。stop 失败的邮箱
* (硬件正在物理发送)继续尝试下一个,避免一次 abort 都没成功。 */
for (uint8_t mb = CAN_MAILBOX0; mb <= CAN_MAILBOX2; ++mb) {
if (can_transmit_states(canPeriph, mb) != CAN_TRANSMIT_PENDING) {
continue;
+3 -3
View File
@@ -1,10 +1,10 @@
/**
* @file Timer.cpp
* @file timer.cpp
* @brief 用户调度定时器模块实现。
*/
#include "main.h"
#include "device/Timer.h"
#include "device/timer.h"
#include "sys/task_manager.h"
#include "sys/config.h"
@@ -44,7 +44,7 @@ void SchedulerTimer::configureTimer()
*/
void SchedulerTimer::configureNvic()
{
nvic_priority_group_set(NVIC_PRIGROUP_PRE2_SUB2);
nvic_priority_group_set(CONFIG_NVIC_PRIORITY_GROUP);
nvic_irq_enable(CONFIG_SCHED_TIMER_IRQn, CONFIG_SCHED_TIMER_IRQ_PREEMPT_PRIO, CONFIG_SCHED_TIMER_IRQ_SUB_PRIO);
}
@@ -1,6 +1,6 @@
#include "device/spi_command_handler.h"
#include "device/app_spi_command.h"
#include "device/app_spi_command_defs.h"
#include "device/spi/spi_command_handler.h"
#include "device/spi/app_spi_command.h"
#include "device/spi/app_spi_command_defs.h"
#include "sys/ota_boot_request.h"
#include "sys/config.h"
#include "sys/task_manager.h"
@@ -1,17 +1,21 @@
#include "device/spi_command_handler.h"
#include "device/app_spi_command.h"
#include "device/app_spi_command_defs.h"
#include "device/spi/spi_command_handler.h"
#include "device/spi/app_spi_command.h"
#include "device/spi/app_spi_command_defs.h"
#include "device/canfd.h"
#include "sys/config.h"
#include <cstdio>
namespace {
#if (CONFIG_SPI_CAN_SEND_VERBOSE_LOG != 0)
constexpr uint8_t kCanLogMaxDlc = 64U;
#endif
void postCanSendLog(AppSpiCommand &bus, const char *result,
uint8_t can_ch, uint32_t can_id,
const uint8_t *can_data, uint8_t can_dlc)
{
#if (CONFIG_SPI_CAN_SEND_VERBOSE_LOG != 0)
const uint8_t logged_dlc = (can_dlc <= kCanLogMaxDlc) ? can_dlc : kCanLogMaxDlc;
char data_hex[(kCanLogMaxDlc * 2U) + 1U] = {};
for (uint8_t i = 0U; i < logged_dlc; ++i) {
@@ -27,6 +31,14 @@ void postCanSendLog(AppSpiCommand &bus, const char *result,
(logged_dlc > 0U) ? data_hex : "-",
result ? result : "UNKNOWN");
bus.postLog(log_msg);
#else
(void)bus;
(void)result;
(void)can_ch;
(void)can_id;
(void)can_data;
(void)can_dlc;
#endif
}
class SpiCanBridgeHandler final : public ISpiCommandHandler {
@@ -39,11 +51,13 @@ public:
void handle(AppSpiCommand &bus, const SpiCommandContext &ctx) override
{
if (ctx.payload_len < 6U) {
#if (CONFIG_SPI_APP_LOG_ENABLE != 0)
char log_msg[AppSpiCommand::kLogBufSize] = {};
(void)snprintf(log_msg, sizeof(log_msg),
"CAN_SEND payload too short: len=%u -> ERROR",
static_cast<unsigned>(ctx.payload_len));
bus.postLog(log_msg);
#endif
uint8_t err = 0xE0U;
bus.buildResponse(ctx.tx_buf, ctx.tx_buf_size, ctx.tx_len,
SpiDefs::kRspError, &err, 1U);
@@ -1,6 +1,6 @@
#include "device/spi_command_handler.h"
#include "device/app_spi_command.h"
#include "device/app_spi_command_defs.h"
#include "device/spi/spi_command_handler.h"
#include "device/spi/app_spi_command.h"
#include "device/spi/app_spi_command_defs.h"
namespace {
@@ -1,6 +1,6 @@
#include "device/spi_command_handler.h"
#include "device/app_spi_command.h"
#include "device/app_spi_command_defs.h"
#include "device/spi/spi_command_handler.h"
#include "device/spi/app_spi_command.h"
#include "device/spi/app_spi_command_defs.h"
#include "sys/config.h"
#include "main.h"
#include <cstring>
@@ -1,4 +1,4 @@
/**
/**
* @file app_spi_command.cpp
* @brief APP 运行期 SPI 链路与命令路由实现。
*
@@ -13,9 +13,9 @@
* 只需要新增命令处理器,不再继续膨胀本文件。
*/
#include "device/app_spi_command.h"
#include "device/spi_command_handler.h"
#include "device/app_spi_command_defs.h"
#include "device/spi/app_spi_command.h"
#include "device/spi/spi_command_handler.h"
#include "device/spi/app_spi_command_defs.h"
extern "C" {
#include "gd32e50x_exti.h"
@@ -26,6 +26,7 @@ extern "C" {
#include "gd32e50x_dma.h"
#include "gd32e50x_spi.h"
#include "sys/config.h"
#include "sys/critical.h"
#include "sys/task_manager.h"
#include "sys/ota_boot_request.h"
#include "sys/systick.h"
@@ -180,16 +181,25 @@ static bool spi_try_parse_frame(const uint8_t *frame, uint16_t frame_len,
void AppSpiCommand::postLog(const char *msg)
{
#if (CONFIG_SPI_APP_LOG_ENABLE != 0)
if (!msg) return;
snprintf(m_logBuf, kLogBufSize, "[SPI-APP] %s", msg);
m_logPending = true;
#else
(void)msg;
#endif
}
void AppSpiCommand::postLogFmt(const char *fmt, uint8_t val)
{
#if (CONFIG_SPI_APP_LOG_ENABLE != 0)
if (!fmt) return;
snprintf(m_logBuf, kLogBufSize, fmt, val);
m_logPending = true;
#else
(void)fmt;
(void)val;
#endif
}
void AppSpiCommand::buildResponse(uint8_t *tx_buf, uint16_t tx_buf_size,
@@ -251,7 +261,7 @@ void AppSpiCommand::configureSpi0()
spi_i2s_interrupt_enable(SPI0, SPI_I2S_INT_RBNE);
spi_i2s_interrupt_enable(SPI0, SPI_I2S_INT_ERR);
spi_i2s_interrupt_disable(SPI0, SPI_I2S_INT_TBE);
nvic_irq_enable(SPI0_IRQn, 2U, 0U);
nvic_irq_enable(SPI0_IRQn, CONFIG_IRQ_PREEMPT_SPI_USART, 0U);
spi_enable(SPI0);
}
@@ -282,7 +292,7 @@ void AppSpiCommand::configureExti()
exti_init(EXTI_15, EXTI_INTERRUPT, EXTI_TRIG_BOTH);
exti_interrupt_enable(EXTI_15);
exti_interrupt_flag_clear(EXTI_15);
nvic_irq_enable(EXTI10_15_IRQn, 1U, 1U);
nvic_irq_enable(EXTI10_15_IRQn, CONFIG_IRQ_PREEMPT_SPI_USART, 1U);
}
@@ -341,7 +351,7 @@ void AppSpiCommand::processCommand()
uint16_t payload_len = 0U;
if (spi_try_parse_frame(m_rxBuf, m_rxLen, &frame_type, &payload, &payload_len)) {
if (!isFlashRelatedFrame(frame_type)) {
if (CONFIG_SPI_APP_LOG_ENABLE != 0 && !isFlashRelatedFrame(frame_type)) {
postLogFmt("[SPI-APP] 收到非刷写 SPI type=0x%02X", frame_type);
}
@@ -432,8 +442,10 @@ void AppSpiCommand::processCommand()
}
}
/* 置位 TASK_SPI_LOG,唤醒主循环立刻打印日志 */
TaskManager::set(1UL << TASK_SPI_LOG);
if (m_logPending) {
/* 置位 TASK_SPI_LOG,唤醒主循环立刻打印日志 */
TaskManager::set(1UL << TASK_SPI_LOG);
}
}
/* ============================================================
@@ -445,19 +457,28 @@ void AppSpiCommand::poll()
char log_copy[kLogBufSize] = {};
bool has_log = false;
__disable_irq();
uint32_t save = RT_CRITICAL_SAVE();
if (m_logPending) {
memcpy(log_copy, m_logBuf, sizeof(log_copy));
m_logPending = false;
has_log = true;
}
__enable_irq();
RT_CRITICAL_RESTORE(save);
if (has_log) {
printf("%s\r\n", log_copy);
}
}
void AppSpiCommand::processPendingCommand()
{
if (!m_cmdPending) {
return;
}
m_cmdPending = false;
processCommand();
}
/* ============================================================
* CS 边沿处理
* ============================================================ */
@@ -522,7 +543,8 @@ void AppSpiCommand::onCsRelease()
m_rxLen = 0U;
return;
}
processCommand();
m_cmdPending = true;
TaskManager::set(1UL << TASK_SPI_CMD_PROCESS);
}
/* ============================================================
@@ -1,4 +1,4 @@
#ifndef DEVICE_APP_SPI_COMMAND_H
#ifndef DEVICE_APP_SPI_COMMAND_H
#define DEVICE_APP_SPI_COMMAND_H
#include <cstdint>
@@ -12,7 +12,7 @@ class ISpiCommandHandler;
* - 维护 SPI0 从机的硬件链路(GPIO/SPI/DMA/EXTI)
* - 将接收到的合法帧按 TYPE 注册式分发给 ISpiCommandHandler
*
* 具体命令处理逻辑不再放在这里,而是拆到 `device/app_spi_cmd_*.cpp`,
* 具体命令处理逻辑不再放在这里,而是拆到 `device/spi/app_spi_cmd_*.cpp`,
* 通过 `registerHandler()` 注册进来,保持本模块可扩展。
*/
class AppSpiCommand {
@@ -23,6 +23,9 @@ public:
/** @brief 主循环轮询:打印延迟日志。 */
void poll();
/** @brief 主循环调用:执行 ISR 中推迟的 SPI 命令解析(从 onCsRelease 移出)。 */
void processPendingCommand();
/** @brief SPI0 中断处理入口。 */
void onInterrupt();
@@ -66,6 +69,7 @@ private:
uint8_t m_txLen = 0U;
volatile uint8_t m_txPos = 0U;
volatile bool m_logPending = false;
volatile bool m_cmdPending = false; /**< ISR 置位,标记有待主循环解析的 SPI 命令。 */
uint8_t m_rxBuf[kRxBufSize] = {};
uint8_t m_txBuf[kTxBufSize] = {};
char m_logBuf[kLogBufSize] = {};
+5 -4
View File
@@ -6,6 +6,7 @@
#include "main.h"
#include "device/usart0.h"
#include "sys/config.h"
#include "sys/critical.h"
uint8_t Usart0::s_txBuf[Usart0::kTxBufSize];
volatile uint16_t Usart0::s_txHead = 0U;
@@ -54,10 +55,10 @@ bool Usart0::isBufferEmpty()
*/
bool Usart0::putCharNonBlocking(int ch)
{
__disable_irq();
uint32_t save = RT_CRITICAL_SAVE();
const uint16_t next = nextIndex(s_txHead);
if(next == s_txTail) {
__enable_irq();
RT_CRITICAL_RESTORE(save);
s_dropCount++;
return false;
}
@@ -65,7 +66,7 @@ bool Usart0::putCharNonBlocking(int ch)
s_txBuf[s_txHead] = static_cast<uint8_t>(ch);
s_txHead = next;
usart_interrupt_enable(USART0, USART_INT_TBE);
__enable_irq();
RT_CRITICAL_RESTORE(save);
return true;
}
@@ -136,7 +137,7 @@ void Usart0::init()
usart_receive_config(USART0, USART_RECEIVE_ENABLE);
usart_transmit_config(USART0, USART_TRANSMIT_ENABLE);
usart_enable(USART0);
nvic_irq_enable(USART0_IRQn, 2U, 0U);
nvic_irq_enable(USART0_IRQn, CONFIG_IRQ_PREEMPT_SPI_USART, 0U);
}
/* ----- 中断入口 / printf 重定向的 C 包装 --------------------------- */
+8 -3
View File
@@ -8,6 +8,7 @@
#include "sys/task_manager.h"
#include "sys/task_runner.h"
#include "sys/config.h"
#include "sys/critical.h"
/**
* @brief 打印应用启动横幅与系统时钟信息。
@@ -46,13 +47,17 @@ int App::run()
while(1) {
TaskRunner::runPending();
const uint32_t primask = __get_PRIMASK();
__disable_irq();
#if (CONFIG_MAIN_LOOP_WFI_ENABLE != 0)
uint32_t save = RT_CRITICAL_SAVE();
if(TaskManager::state().task_flags == 0U) {
__DSB();
__WFI();
}
__set_PRIMASK(primask);
RT_CRITICAL_RESTORE(save);
#else
/* EtherCAT 实时模式:关闭 WFI 改用忙等,保证周期中断立即响应。 */
__NOP();
#endif
}
return 0;
+3 -1
View File
@@ -1,4 +1,4 @@
#ifndef MAIN_H
#ifndef MAIN_H
#define MAIN_H
#include "RTE_Components.h"
@@ -23,6 +23,8 @@ enum system_task_t {
TASK_HEARTBEAT, /**< 系统心跳维护任务。 */
TASK_DIAGNOSTIC_PRINT, /**< 诊断信息打印任务。 */
TASK_SPI_LOG, /**< SPI 延迟日志打印任务。 */
TASK_ETHERCAT_PROCESS, /**< EtherCAT 周期/事件处理任务(非实时应用层 PDO 映射)。 */
TASK_SPI_CMD_PROCESS, /**< SPI CS release 后的命令解析任务(从 ISR 移出)。 */
TASK_COUNT /**< 任务总数,用于边界或统计场景。 */
};
+120 -4
View File
@@ -102,9 +102,13 @@
#define CONFIG_TASK_PERIOD_HEARTBEAT_TICKS 10000U
/** @brief 周期温度/电压 UART 打印开关:0=关闭,1=开启。 */
#define CONFIG_UART_TEMP_PERIODIC_PRINT 1
#define CONFIG_UART_TEMP_PERIODIC_PRINT 0
/** @brief UART 诊断打印级别:0=仅温度/电压,1=温度/电压+CAN 统计。 */
#define CONFIG_UART_DIAG_LEVEL 1
/** @brief SPI 命令逐帧日志开关:0=关闭,1=开启。高速 CAN 桥接时应关闭。 */
#define CONFIG_SPI_APP_LOG_ENABLE 0
/** @brief CAN_SEND 成功帧详细日志开关:0=关闭,1=开启。开启会显著影响高速传输。 */
#define CONFIG_SPI_CAN_SEND_VERBOSE_LOG 0
/** @brief FWDGT 独立看门狗预分频值。 */
@@ -123,11 +127,11 @@
/** @brief APP 主版本号。 */
#define APP_FW_VERSION_MAJOR 1U
/** @brief APP 次版本号。 */
#define APP_FW_VERSION_MINOR 0U
#define APP_FW_VERSION_MINOR 1U
/** @brief APP 补丁版本号。 */
#define APP_FW_VERSION_PATCH 1U
#define APP_FW_VERSION_PATCH 0U
/** @brief APP 构建编号。 */
#define APP_FW_VERSION_BUILD 2U
#define APP_FW_VERSION_BUILD 0U
/* ============================================================
* BOOT 版本在 OTA 配置页中的 Flash 存储地址
@@ -142,4 +146,116 @@
/** @brief OTA 配置页总 word 数(6 个 OTA 标志 + 2 个 BOOT 版本)。 */
#define CONFIG_OTA_CONFIG_WORD_COUNT 8U
/* ============================================================
* NVIC 优先级分组与中断优先级分层
* (EtherCAT 实时架构改造:PRE4_SUB0,4 位抢占优先级)
* ============================================================ */
/** @brief NVIC 优先级分组:4 位抢占 + 0 位子优先级。 */
#define CONFIG_NVIC_PRIORITY_GROUP NVIC_PRIGROUP_PRE4_SUB0
/** @brief EtherCAT SYNC0 周期中断抢占优先级(最高,永不被阻塞)。 */
#define CONFIG_IRQ_PREEMPT_ETHERCAT_SYNC 0U
/** @brief 调度器定时器抢占优先级。 */
#define CONFIG_IRQ_PREEMPT_SCHED 1U
/** @brief CAN RX0 抢占优先级。 */
#define CONFIG_IRQ_PREEMPT_CAN 2U
/** @brief EtherCAT ESC 事件中断抢占优先级(可被 SYNC0 抢占)。 */
#define CONFIG_IRQ_PREEMPT_ETHERCAT_ESC 1U
/** @brief EtherCAT SYNC1 备用抢占优先级。 */
#define CONFIG_IRQ_PREEMPT_ETHERCAT_SYNC1 1U
/** @brief SPI / EXTI / USART 抢占优先级(最低)。 */
#define CONFIG_IRQ_PREEMPT_SPI_USART 3U
/* ============================================================
* 主循环与实时策略
* ============================================================ */
/** @brief 主循环 WFI 开关:0=关闭(忙等,适配 EtherCAT),1=开启(省电)。 */
#define CONFIG_MAIN_LOOP_WFI_ENABLE 0
/* ============================================================
* EtherCAT LAN9252 使能与配置
* ============================================================ */
/** @brief EtherCAT 使能开关:0=禁用(未焊接 LAN9252),1=启用(焊接后)。
* 禁用时 comm/ 全部代码不编译,初始化不调用 EtherCAT,EXMC/EXTI0-2 不配置。 */
#define CONFIG_ETHERCAT_ENABLE 0
/** @brief SYNC0 同步模式:0=EXTI 外部触发(ESC 硬件同步,默认),1=TIMER0 降级(DC 未同步时)。 */
#define CONFIG_ETHERCAT_SYNC_MODE 0
/* ---------- EXMC 引脚配置(GD32E508 EXMC 标准映射) ---------- */
/** @brief EXMC NOR/SRAM Region1 基址(NE1 片选)。 */
#define CONFIG_ESC_EXMC_BASE_ADDR 0x60000000U
/** @brief EXMC BANK 选择(BANK0 Region1)。
* @note EXMC 库未纳入编译时回退为 0U;启用 EtherCAT 时 EXMC 组件将被引入。 */
#ifdef EXMC_BANK0_NORSRAM_REGION1
#define CONFIG_ESC_EXMC_BANK_NOR_SRAM_REGION1 EXMC_BANK0_NORSRAM_REGION1
#else
#define CONFIG_ESC_EXMC_BANK_NOR_SRAM_REGION1 0U
#endif
/* ---------- ESC 中断引脚(PB0/PB1/PB2,三个独立 EXTI 向量) ---------- */
/** @note 保留 PA0 给未来 STANDBY 模式唤醒(WKUP_PIN0)。 */
#define CONFIG_ESC_SYNC0_PORT GPIOB
#define CONFIG_ESC_SYNC0_PIN GPIO_PIN_0
#define CONFIG_ESC_SYNC0_EXTI EXTI_0
#define CONFIG_ESC_SYNC0_IRQn EXTI0_IRQn
#define CONFIG_ESC_SYNC0_EXTI_SOURCE_PORT GPIO_PORT_SOURCE_GPIOB
#define CONFIG_ESC_SYNC0_EXTI_SOURCE_PIN GPIO_PIN_SOURCE_0
#define CONFIG_ESC_IRQ_PORT GPIOB
#define CONFIG_ESC_IRQ_PIN GPIO_PIN_1
#define CONFIG_ESC_IRQ_EXTI EXTI_1
#define CONFIG_ESC_IRQ_IRQn EXTI1_IRQn
#define CONFIG_ESC_IRQ_EXTI_SOURCE_PORT GPIO_PORT_SOURCE_GPIOB
#define CONFIG_ESC_IRQ_EXTI_SOURCE_PIN GPIO_PIN_SOURCE_1
#define CONFIG_ESC_SYNC1_PORT GPIOB
#define CONFIG_ESC_SYNC1_PIN GPIO_PIN_2
#define CONFIG_ESC_SYNC1_EXTI EXTI_2
#define CONFIG_ESC_SYNC1_IRQn EXTI2_IRQn
#define CONFIG_ESC_SYNC1_EXTI_SOURCE_PORT GPIO_PORT_SOURCE_GPIOB
#define CONFIG_ESC_SYNC1_EXTI_SOURCE_PIN GPIO_PIN_SOURCE_2
/* ---------- EXMC 数据/控制线引脚(GD32E508 标准映射) ---------- */
#define CONFIG_EXMC_D0_PORT GPIOD
#define CONFIG_EXMC_D0_PIN GPIO_PIN_14
#define CONFIG_EXMC_D1_PORT GPIOD
#define CONFIG_EXMC_D1_PIN GPIO_PIN_15
#define CONFIG_EXMC_D2_PORT GPIOD
#define CONFIG_EXMC_D2_PIN GPIO_PIN_0
#define CONFIG_EXMC_D3_PORT GPIOD
#define CONFIG_EXMC_D3_PIN GPIO_PIN_1
#define CONFIG_EXMC_D4_PORT GPIOE
#define CONFIG_EXMC_D4_PIN GPIO_PIN_7
#define CONFIG_EXMC_D5_PORT GPIOE
#define CONFIG_EXMC_D5_PIN GPIO_PIN_8
#define CONFIG_EXMC_D6_PORT GPIOE
#define CONFIG_EXMC_D6_PIN GPIO_PIN_9
#define CONFIG_EXMC_D7_PORT GPIOE
#define CONFIG_EXMC_D7_PIN GPIO_PIN_10
#define CONFIG_EXMC_D8_PORT GPIOE
#define CONFIG_EXMC_D8_PIN GPIO_PIN_11
#define CONFIG_EXMC_D9_PORT GPIOE
#define CONFIG_EXMC_D9_PIN GPIO_PIN_12
#define CONFIG_EXMC_D10_PORT GPIOE
#define CONFIG_EXMC_D10_PIN GPIO_PIN_13
#define CONFIG_EXMC_D11_PORT GPIOE
#define CONFIG_EXMC_D11_PIN GPIO_PIN_14
#define CONFIG_EXMC_D12_PORT GPIOE
#define CONFIG_EXMC_D12_PIN GPIO_PIN_15
#define CONFIG_EXMC_D13_PORT GPIOD
#define CONFIG_EXMC_D13_PIN GPIO_PIN_8
#define CONFIG_EXMC_D14_PORT GPIOD
#define CONFIG_EXMC_D14_PIN GPIO_PIN_9
#define CONFIG_EXMC_D15_PORT GPIOD
#define CONFIG_EXMC_D15_PIN GPIO_PIN_10
#define CONFIG_EXMC_A16_PORT GPIOD
#define CONFIG_EXMC_A16_PIN GPIO_PIN_11
#define CONFIG_EXMC_NOE_PORT GPIOD
#define CONFIG_EXMC_NOE_PIN GPIO_PIN_4
#define CONFIG_EXMC_NWE_PORT GPIOD
#define CONFIG_EXMC_NWE_PIN GPIO_PIN_5
#define CONFIG_EXMC_NE1_PORT GPIOD
#define CONFIG_EXMC_NE1_PIN GPIO_PIN_7
#endif /* SYS_CONFIG_H */
+62
View File
@@ -0,0 +1,62 @@
/**
* @file critical.h
* @brief 实时临界区基础设施:基于 BASEPRI 寄存器的中断屏蔽。
* @note EtherCAT 实时架构改造专用。GD32E508 为 Cortex-M4F,支持 BASEPRI 寄存器。
*
* @details 设计说明:
* - BASEPRI 阈值屏蔽抢占优先级 >= 1 的中断,仅保留 preempt=0(EtherCAT SYNC0
* 周期中断)可立即响应,保证 EtherCAT 周期抖动 <1us。
* - 取代原 PRIMASK 方案(`__disable_irq` / `__set_PRIMASK`),后者会屏蔽全部中断
* (含 preempt=0),无法满足 EtherCAT 实时性要求。
* - NVIC 优先级分组为 `NVIC_PRIGROUP_PRE4_SUB0`(4 位抢占 + 0 位子优先级),
* 因此 `__NVIC_PRIO_BITS = 4`,阈值 `(1U << (8U - 4U)) = 0x10`,即屏蔽优先级
* 数值 0x1x 及以上(对应 preempt 1..15),保留 preempt=0(优先级 0x0x)。
* - 阈值定义依赖 `__NVIC_PRIO_BITS`,该宏由 CMSIS 设备头文件提供(GD32E508 = 4)。
* - 项目使用 AC6(armclang) gnu++17,支持 GCC 语句表达式(statement expression),
* `RT_CRITICAL_SAVE()` 利用该特性返回保存的 BASEPRI 值,便于嵌套恢复。
*/
#ifndef SYS_CRITICAL_H
#define SYS_CRITICAL_H
#include "RTE_Components.h"
#include CMSIS_device_header
/** @brief BASEPRI 屏蔽阈值:屏蔽 preempt>=1,保留 preempt=0(EtherCAT SYNC0)。
* 对于 4 位抢占优先级(__NVIC_PRIO_BITS=4),该值为 0x10。 */
#define RT_CRITICAL_BASEPRI_VALUE (1U << (8U - __NVIC_PRIO_BITS))
/**
* @brief 进入临界区:屏蔽抢占优先级 >= 1 的中断。
* @note preempt=0 的 EtherCAT SYNC0 周期中断仍可立即抢占执行。
*/
#define RT_CRITICAL_ENTER() __set_BASEPRI(RT_CRITICAL_BASEPRI_VALUE)
/**
* @brief 退出临界区:恢复全部中断响应(BASEPRI 清零)。
*/
#define RT_CRITICAL_EXIT() __set_BASEPRI(0U)
/**
* @brief 保存当前 BASEPRI 并进入临界区,返回进入前的 BASEPRI 值。
* @return 进入临界区前的 BASEPRI 寄存器值(供 RT_CRITICAL_RESTORE 恢复)。
* @note 利用 GCC 语句表达式返回值,用于嵌套临界区场景:
* @code
* uint32_t save = RT_CRITICAL_SAVE();
* // ... 临界区代码 ...
* RT_CRITICAL_RESTORE(save);
* @endcode
*/
#define RT_CRITICAL_SAVE() \
({ \
uint32_t __basepri_save = __get_BASEPRI(); \
__set_BASEPRI(RT_CRITICAL_BASEPRI_VALUE); \
__basepri_save; \
})
/**
* @brief 恢复 BASEPRI 到指定值(用于 RT_CRITICAL_SAVE() 配套的嵌套恢复)。
* @param[in] val RT_CRITICAL_SAVE() 返回的 BASEPRI 值。
*/
#define RT_CRITICAL_RESTORE(val) __set_BASEPRI(val)
#endif /* SYS_CRITICAL_H */
+14 -3
View File
@@ -1,4 +1,4 @@
/**
/**
* @file initial.cpp
* @brief 应用启动初始化实现。
*/
@@ -8,13 +8,18 @@
#include "device/canfd.h"
#include "device/temp_sensor.h"
#include "device/usart0.h"
#include "device/Timer.h"
#include "device/timer.h"
#include "device/adc.h"
#include "device/watchdog.h"
#include "device/app_spi_command.h"
#include "device/spi/app_spi_command.h"
#include "sys/systick.h"
#include "sys/ota_boot_request.h"
#include "sys/task_manager.h"
#include "sys/config.h"
#if CONFIG_ETHERCAT_ENABLE
#include "comm/ethercat_slave.h"
#include "comm/ethercat_sync.h"
#endif
/**
* @brief 执行应用级外设与调度初始化。
@@ -42,6 +47,12 @@ void AppInitializer::init()
TaskManager::init();
printf("[APP] init: SchedulerTimer...\r\n");
SchedulerTimer::init();
#if CONFIG_ETHERCAT_ENABLE
printf("[APP] init: EthercatSlave...\r\n");
EthercatSlave::init();
printf("[APP] init: EthercatSync...\r\n");
EthercatSync::init();
#endif
printf("[APP] init: Watchdog...\r\n");
Watchdog::init();
printf("[APP] init: all done\r\n");
+6 -31
View File
@@ -1,4 +1,4 @@
/**
/**
* @file task_manager.cpp
* @brief 任务调度管理器实现。
*/
@@ -6,32 +6,7 @@
#include "main.h"
#include "sys/task_manager.h"
#include "sys/config.h"
namespace {
/**
* @brief Enter a critical section and preserve the previous interrupt mask.
* @param[in] 无。
* @param[out] 无。
* @retval uint32_t Previous PRIMASK value.
*/
static uint32_t enterCriticalSection()
{
const uint32_t primask = __get_PRIMASK();
__disable_irq();
return primask;
}
/**
* @brief Exit a critical section by restoring the saved interrupt mask.
* @param[in] primask Previous PRIMASK value returned by `enterCriticalSection()`.
* @param[out] 无。
* @retval 无返回值。
*/
static void exitCriticalSection(uint32_t primask)
{
__set_PRIMASK(primask);
}
} // namespace
#include "sys/critical.h"
task_scheduler_t g_scheduler = {0};
@@ -68,9 +43,9 @@ void TaskManager::init()
*/
void TaskManager::set(uint32_t taskMask)
{
const uint32_t primask = enterCriticalSection();
uint32_t save = RT_CRITICAL_SAVE();
g_scheduler.task_flags |= taskMask;
exitCriticalSection(primask);
RT_CRITICAL_RESTORE(save);
}
/**
@@ -83,10 +58,10 @@ void TaskManager::set(uint32_t taskMask)
uint32_t TaskManager::takePending()
{
uint32_t pending;
const uint32_t primask = enterCriticalSection();
uint32_t save = RT_CRITICAL_SAVE();
pending = g_scheduler.task_flags;
g_scheduler.task_flags &= ~pending;
exitCriticalSection(primask);
RT_CRITICAL_RESTORE(save);
return pending;
}
+35 -2
View File
@@ -1,4 +1,4 @@
/**
/**
* @file task_runner.cpp
* @brief 任务执行器实现。
*/
@@ -6,13 +6,16 @@
#include "main.h"
#include "device/adc.h"
#include "device/watchdog.h"
#include "device/app_spi_command.h"
#include "device/spi/app_spi_command.h"
#include "device/canfd.h"
#include "device/temp_sensor.h"
#include "sys/config.h"
#include "sys/ota_boot_request.h"
#include "sys/task_manager.h"
#include "sys/task_runner.h"
#if CONFIG_ETHERCAT_ENABLE
#include "comm/ethercat_slave.h"
#endif
namespace {
constexpr uint32_t kAdcFilterWindow = CONFIG_ADC_FILTER_SIZE + 1U;
@@ -265,6 +268,34 @@ void TaskRunner::taskDiagnosticPrint()
#endif
}
/**
* @brief 执行 SPI CS release 后推迟的命令解析(从 ISR 移出)。
* @param[in] 无。
* @param[out] 无。
* @retval 无返回值。
* @note ISR 瘦身:onCsRelease() 仅置位 TASK_SPI_CMD_PROCESS,实际解析在此执行。
*/
void TaskRunner::taskSpiCmdProcess()
{
AppSpiCommandBus.processPendingCommand();
}
/**
* @brief 执行 EtherCAT 周期/事件处理(非实时应用层 PDO 映射)。
* @param[in] 无。
* @param[out] 无。
* @retval 无返回值。
* @note 由周期 ISR(EXTI0 SYNC0)或 ESC 事件 ISR(EXTI1 IRQ)置位 TASK_ETHERCAT_PROCESS。
*/
void TaskRunner::taskEthercatProcess()
{
#if CONFIG_ETHERCAT_ENABLE
EthercatSlave::process();
#else
/* 未使能 EtherCAT 时此分支不应被置位;保留空操作以避免链接错误。 */
#endif
}
/**
* @brief 初始化任务执行器运行时上下文。
* @param[in] 无。
@@ -309,4 +340,6 @@ void TaskRunner::runPending()
if(pending & (1UL << TASK_HEARTBEAT)) taskHeartbeat();
if(pending & (1UL << TASK_DIAGNOSTIC_PRINT)) taskDiagnosticPrint();
if(pending & (1UL << TASK_SPI_LOG)) AppSpiCommandBus.poll();
if(pending & (1UL << TASK_SPI_CMD_PROCESS)) taskSpiCmdProcess();
if(pending & (1UL << TASK_ETHERCAT_PROCESS)) taskEthercatProcess();
}
+4
View File
@@ -49,6 +49,10 @@ private:
static void taskHeartbeat();
/** @brief 诊断打印任务。 */
static void taskDiagnosticPrint();
/** @brief SPI CS release 后推迟的命令解析(从 ISR 移出)。 */
static void taskSpiCmdProcess();
/** @brief EtherCAT 周期/事件处理(非实时应用层 PDO 映射)。 */
static void taskEthercatProcess();
};
#endif /* SYS_TASK_RUNNER_H */