Files
DCU_APP/GD32E508_DCU_APP/comm/ethercat_sync.cpp
T
cbb 6136c2363f feat(ethercat): 添加LAN9252睡眠挂起机制防止HBI访问冲突
添加了s_pmSuspended状态标志来管理LAN9252的电源管理模式,在芯片进入D1-D3
睡眠状态时阻止HBI访问以避免唤醒问题。同步修改了ethercat_sync中断处理程序,
在睡眠挂起期间跳过HBI相关操作确保芯片能够正常维持睡眠状态。

fix(spi): 提升SPI CS EXTI抢占优先级避免帧丢失

将SPI CS EXTI中断优先级从1提升至0,使其能够抢占PDI/SYNC1等关键中断,确
保SPI帧释放处理能够在微秒级完成,避免因延时导致的ESP32 CS间隔超时和帧
丢失问题。

feat(ssc): 实现OP输入新鲜度不变量监控机制

添加了输入映射序列号监控机制,当PDI路径中断时能够检测到输入数据冻结并
自动触发强制刷新,解决结构洞问题导致的输入数据停止更新现象。

chore(pdo): 更新PDO输入字节注释说明布局变更历史
2026-09-13 18:59:57 +08:00

222 lines
9.1 KiB
C++
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/**
* @file ethercat_sync.cpp
* @brief EtherCAT SYNC0/IRQ/SYNC1 中断入口 — 转发到 SSC 协议栈。
*
* SYNC0 (EXTI0, preempt=0) → Sync0_Isr() : DC 周期同步, PDO 交换
* ESC IRQ (EXTI1, preempt=1) → PDI_Isr() : AL Event (邮箱/SM 事件)
* SYNC1 (EXTI2, preempt=1) → Sync1_Isr() : 预留
*/
#include "comm/ethercat_sync.h"
#include "comm/esc_lan9252.h" /* pmSuspended:ESC 睡眠挂起期 ISR 拒碰 HBI(1.6.27) */
#include "sys/config.h"
#include "main.h" /* g_app_uptime_sec */
#include "sys/cpu_load.h"
#include <cstdio>
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
/* SSC 协议栈 ISR 入口 (ecatappl.c) */
void PDI_Isr(void);
void Sync0_Isr(void);
void Sync1_Isr(void);
}
bool EthercatSync::s_active = false;
volatile uint32_t EthercatSync::s_sync0Count = 0U;
volatile uint32_t EthercatSync::s_sync1Count = 0U;
volatile uint32_t EthercatSync::s_escIrqCount = 0U;
void EthercatSync::init()
{
#if (CONFIG_ETHERCAT_SYNC_MODE == 0)
printf("[ECAT][SYNC] init: mode=EXTI (hardware SYNC0, CONFIG_ETHERCAT_SYNC_MODE=0)\r\n");
#else
printf("[ECAT][SYNC] init: mode=TIMER0 (degraded, CONFIG_ETHERCAT_SYNC_MODE=1)\r\n");
#endif
/* 使能 GPIOB 时钟(SYNC0=PB0 / IRQ=PB1 / SYNC1=PB2) */
rcu_periph_clock_enable(RCU_GPIOB);
rcu_periph_clock_enable(RCU_AF);
/* 配置三个引脚为上拉输入(EXTI 下降沿触发:LAN9252 输出低有效脉冲) */
gpio_init(GPIOB, GPIO_MODE_IPU, GPIO_OSPEED_50MHZ,
CONFIG_ESC_SYNC0_PIN | CONFIG_ESC_IRQ_PIN | CONFIG_ESC_SYNC1_PIN);
printf("[ECAT][SYNC] init: GPIOB pins configured (SYNC0=PB%u IRQ=PB%u SYNC1=PB%u)\r\n",
(unsigned)CONFIG_ESC_SYNC0_EXTI_SOURCE_PIN,
(unsigned)CONFIG_ESC_IRQ_EXTI_SOURCE_PIN,
(unsigned)CONFIG_ESC_SYNC1_EXTI_SOURCE_PIN);
#if (CONFIG_ETHERCAT_SYNC_MODE == 0)
/* ---- 默认:EXTI 外部触发模式 ---- */
/* 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);
printf("[ECAT][SYNC] init: SYNC0 EXTI%u enabled (falling, preempt=%u)\r\n",
(unsigned)CONFIG_ESC_SYNC0_EXTI_SOURCE_PIN,
(unsigned)CONFIG_IRQ_PREEMPT_ETHERCAT_SYNC);
#else
/* ---- 降级模式:TIMER0 内部定时 ---- */
rcu_periph_clock_enable(RCU_TIMER0);
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_UP_IRQn, CONFIG_IRQ_PREEMPT_ETHERCAT_SYNC, 0U);
timer_enable(TIMER0);
printf("[ECAT][SYNC] init: TIMER0 enabled (100us, preempt=%u)\r\n",
(unsigned)CONFIG_IRQ_PREEMPT_ETHERCAT_SYNC);
#endif
/* 公共:ESC IRQ (PB1->EXTI1) + SYNC1 (PB2->EXTI2) */
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);
printf("[ECAT][SYNC] init: ESC IRQ EXTI%u enabled (falling, preempt=%u)\r\n",
(unsigned)CONFIG_ESC_IRQ_EXTI_SOURCE_PIN,
(unsigned)CONFIG_IRQ_PREEMPT_ETHERCAT_ESC);
gpio_exti_source_select(CONFIG_ESC_SYNC1_EXTI_SOURCE_PORT, CONFIG_ESC_SYNC1_EXTI_SOURCE_PIN);
exti_init(CONFIG_ESC_SYNC1_EXTI, EXTI_INTERRUPT, EXTI_TRIG_FALLING);
exti_interrupt_enable(CONFIG_ESC_SYNC1_EXTI);
exti_interrupt_flag_clear(CONFIG_ESC_SYNC1_EXTI);
nvic_irq_enable(CONFIG_ESC_SYNC1_IRQn, CONFIG_IRQ_PREEMPT_ETHERCAT_SYNC1, 0U);
printf("[ECAT][SYNC] init: SYNC1 EXTI%u enabled (falling, preempt=%u)\r\n",
(unsigned)CONFIG_ESC_SYNC1_EXTI_SOURCE_PIN,
(unsigned)CONFIG_IRQ_PREEMPT_ETHERCAT_SYNC1);
printf("[ECAT][SYNC] init: OK\r\n");
/* 全部 EXTI/NVIC 使能完成,HBI 出现并发用户(主循环 vs SYNC0/IRQ ISR),
* 之后 EXMC 诊断必须让位(diagDump 内部据此跳过)。 */
s_active = true;
}
__attribute__((section(".ramfunc"), noinline))
void EthercatSync::onSync0Exti()
{
/* 直接寄存器访问,绕过 GD32 库函数以消除可变抖动 */
if ((EXTI_PD & (uint32_t)CONFIG_ESC_SYNC0_EXTI) != RESET) {
EXTI_PD = (uint32_t)CONFIG_ESC_SYNC0_EXTI;
/* 1.6.27:ESC 睡眠挂起期,清了 pending 就走——Sync0_Isr 内的 PRAM/CSR
* 访问全是 HBI,读即唤醒。睡眠中 SYNC0 引脚本应无沿,此分支只兜
* 进入睡眠瞬间的残留 pending。 */
if (EscLan9252::pmSuspended()) {
return;
}
++s_sync0Count;
Sync0_Isr();
}
}
void EthercatSync::onEscIrqExti()
{
if (exti_interrupt_flag_get(CONFIG_ESC_IRQ_EXTI) != RESET) {
exti_interrupt_flag_clear(CONFIG_ESC_IRQ_EXTI);
/* 1.6.27:ESC 睡眠挂起期直接返回——PDI_Isr 会读 0x220/PRAM(HBI),读即唤醒 */
if (EscLan9252::pmSuspended()) {
return;
}
++s_escIrqCount;
/* PDI_Isr(preempt=1) 可被 Sync0_Isr(preempt=0) 抢占;HBI Indexed 是两步
* 事务(IDX 写 + DATA 读),被抢占即错址。此处屏蔽 EXTI0 实现互斥:
* SYNC0 边沿在屏蔽期到来会锁存在 pending,退出后立即补偿执行,
* 代价是 PDI_Isr 剩余时长(µs 级)计入 SYNC0 抖动。 */
const uint32_t prevSync0 = NVIC_GetEnableIRQ(CONFIG_ESC_SYNC0_IRQn);
NVIC_DisableIRQ(CONFIG_ESC_SYNC0_IRQn);
/* 1.6.12 曾在此预抽 CAN FIFO(pumpAllChannels),实测溢出大降但拼接照旧
* —— 病理在 357µs 阻塞窗口本身,1.6.13 直接把 CAN RX 提到 preempt=0
* 根治(见 sys/config.h),预抽失去意义且引入 PDI/ISR 双消费者风险,撤除。 */
PDI_Isr();
if (prevSync0 != 0U) {
NVIC_EnableIRQ(CONFIG_ESC_SYNC0_IRQn);
}
}
}
void EthercatSync::onSync1Exti()
{
if (exti_interrupt_flag_get(CONFIG_ESC_SYNC1_EXTI) != RESET) {
exti_interrupt_flag_clear(CONFIG_ESC_SYNC1_EXTI);
/* 1.6.27:ESC 睡眠挂起期直接返回——Sync1_Isr 内为 HBI 访问 */
if (EscLan9252::pmSuspended()) {
return;
}
++s_sync1Count;
/* 同 onEscIrqExti:Sync1_Isr(preempt=1) 内的 HBI 访问需与 SYNC0 互斥 */
const uint32_t prevSync0 = NVIC_GetEnableIRQ(CONFIG_ESC_SYNC0_IRQn);
NVIC_DisableIRQ(CONFIG_ESC_SYNC0_IRQn);
Sync1_Isr();
if (prevSync0 != 0U) {
NVIC_EnableIRQ(CONFIG_ESC_SYNC0_IRQn);
}
}
}
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);
/* 1.6.27:ESC 睡眠挂起期跳过——Sync0_Isr 内为 HBI 访问 */
if (!EscLan9252::pmSuspended()) {
Sync0_Isr();
}
}
#else
/* EXTI 模式下 TIMER0 不使用,本函数为占位;CONFIG_ETHERCAT_SYNC_MODE=1 时激活。 */
#endif
}
/* ---- 中断入口 C 包装 ---- */
extern "C" {
#if (CONFIG_ETHERCAT_SYNC_MODE == 0)
__attribute__((section(".ramfunc"), noinline))
void EXTI0_IRQHandler(void)
{
CpuLoad::isrEnter();
EthercatSync::onSync0Exti();
CpuLoad::isrExit();
}
void EXTI1_IRQHandler(void) { CpuLoad::isrEnter(); EthercatSync::onEscIrqExti(); CpuLoad::isrExit(); }
void EXTI2_IRQHandler(void) { CpuLoad::isrEnter(); EthercatSync::onSync1Exti(); CpuLoad::isrExit(); }
#else
void TIMER0_UP_TIMER9_IRQHandler(void)
{
CpuLoad::isrEnter();
EthercatSync::onTimerIrq();
CpuLoad::isrExit();
}
void EXTI1_IRQHandler(void) { CpuLoad::isrEnter(); EthercatSync::onEscIrqExti(); CpuLoad::isrExit(); }
void EXTI2_IRQHandler(void) { CpuLoad::isrEnter(); EthercatSync::onSync1Exti(); CpuLoad::isrExit(); }
#endif
/* ---- SSC 诊断对象 0x2000 的 C 侧访问 (dc_app.c APPL_InputMapping 每周期拉取) ---- */
uint32_t EcatDbg_GetSync0(void) { return EthercatSync::getSync0Count(); }
uint32_t EcatDbg_GetSync1(void) { return EthercatSync::getSync1Count(); }
uint32_t EcatDbg_GetEscIrq(void) { return EthercatSync::getEscIrqCount(); }
uint32_t EcatDbg_GetUptimeSec(void){ return g_app_uptime_sec; }
/* 1.6.0 剖分埋点:供 ecatappl.c (C) 读取 DWT 周期计数 */
uint32_t EcatDbg_Dwt(void) { return DWT->CYCCNT; }
} /* extern "C" */