标签:
嵌入式抗干扰设计MCUFPGA输入捕获卫星电子摘要:芯片手册"有效沿至少维持3个GPIO时钟周期"常被误读成硬件抗毛刺阈值。基于低轨卫星 AS32X601 与 FPGA 脉冲 IO 通信的真实场景,给出多层抗干扰架构、RC 与定时器参数选型、输入捕获高频踩坑点和可直接落地的测试清单。
场景
FPGA 输出 ms 级电平脉冲作为事件(过顶触发、相机就绪、时序同步),MCU 检测边沿执行逻辑。低轨 LEO 轨道下,外部 IO 可能受到耦合干扰;芯片内部状态触发器也可能发生单粒子瞬态 SET。外部干扰脉冲常见为几 ns 到几百 ns,但是否被 GPIO 采样并形成中断,取决于 GPIO 控制器时钟、采样相位、输入电平和内部时序。内部寄存器 SET 与外部 RC、输入捕获滤波是不同层面的问题,不能混为一谈。
1 一个被误读的参数
手册写"有效沿高电平至少维持 3 个 GPIO 控制器时钟周期",这里的准确含义是:GPIO 控制器按自身时钟采样输入,只有被连续采样到的电平变化才可能形成中断沿;3 个周期是识别下限,不是接收端的抗毛刺能力,也不是对所有 ns 级毛刺都必然触发的保证。毛刺是否被采到,取决于采样相位、脉冲宽度、输入波形和控制器时钟。因此,不能写成"没有最小脉宽门限",也不能把 3 个周期当作抗干扰指标。
以 48 MHz GPIO 控制器时钟、1 ms 高电平脉冲为例(仅作换算示例;GPIO 控制器时钟取决于其所在总线,GPIOE 位于 AxiLite4 Bus2,实际值应通过 SMU_GetClocksFreq() 读取 AXILite4Bus2_Frequency 确认):
| 项目 | 数值 |
|---|---|
| 3 个 GPIO 时钟周期 | 62.5 ns |
| 示例高电平宽度 | 1 ms |
| 宽度 / 采样识别下限 | 约 1.6×10⁴ |
| 事件速率 | 稀疏事件;若最小间隔约 1 ms,则连续满速时不超过约 500 Hz |
事件速率低、相对采样下限有较大余量,适合加入 RC 低通、定时器捕获和软件脉宽判据。但"1 ms 高 + 1 ms 低"的连续波形只有约 500 Hz,不能写成 1 kHz。若系统规定事件最小间隔为 2 ms,才会落到约 500 Hz 以下;具体速率必须按协议定义,不能只看高电平宽度。
2 边沿中断 vs 定时器输入捕获
两种方案都应先做基本电气防护:ESD/过压钳位、限流、确定空闲电平,以及按线缆和边沿速率评估的 RC/外部施密特。是否额外加外部施密特,要看 FPGA 输出边沿、线缆长度和噪声频谱;AS32X601 的数字输入和复用输入默认带有内部施密特,模拟模式才关闭。外部 RC/施密特也不能替代芯片内部状态触发器的 TMR、CRC、看门狗等系统级防护。
| 环节 | 普通边沿中断(GPIO 中断) | 定时器输入捕获 |
|---|---|---|
| 第一级毛刺抑制 | 无用户可配置数字滤波;输入按 GPIO 控制器时钟采样,约 3 个周期为识别下限 | 有同步采样;可配置 TIM_ICFilter |
| 第二级数字滤波 | 无,只能进中断后软件补救 | 有,TIM_ICFilter 以 f_DTS 分频和连续同电平采样实现 |
| 时间戳/测宽 | 软件计时,受中断延迟抖动影响 | 硬件捕获,定时器时钟级精度 |
| 毛刺升高表现 | CPU 占用线性上涨,极端看门狗复位 | 毛刺落入捕获窗口时可能置位 CC1OF/CC2OF、使捕获序列错位;可计数并可上报 |
注意:ICFilter 只对经过定时器输入同步器的信号起作用;如果 SET 发生在采样之后的芯片内部状态位,外部 RC 和 ICFilter 都不能被当作保护手段。
边沿中断的致命点在顺序:一旦毛刺被 GPIO 采样并触发中断,软件才"有机会"判断它是毛刺,而在判断之前 CPU 已经被打断。毛刺密集时"中断风暴 → CPU 饥饿 → 看门狗复位",且地面难复现、日志只有看门狗超时,极难定位。
边沿中断的风险来自"高概率毛刺 + 采样后直接进入业务主入口 + 可能的灾难性故障"三者组合。满足以下条件可合规用于告警、唤醒类信号:不作为业务脉冲主入口、外部电气防护充分、软件做齐清标志 + 冷却窗口 + 速率熔断三件套、故障后果可降级、用单边沿触发。
c
/* AS32X601:GPIOE Pin10 下降沿中断 + 冷却窗 + 速率熔断 + 受控恢复 */
#include <stdint.h>
#define EDGE_COOLDOWN_TICKS 20u /* 200 µs @10 µs/tick */
#define EDGE_RATE_WINDOW_TICKS 1000u /* 10 ms @10 µs/tick */
#define EDGE_RATE_LIMIT 8u /* 每统计窗允许的原始边沿数 */
#define EDGE_RECOVER_TICKS 1000u /* 10 ms 后尝试恢复 */
#define FAULT_GLITCH_STORM 0x0001u
extern uint32_t Bsp_GetMonotonic10us(void);
extern void App_EdgeEvent(void);
extern void App_EdgeTelemetry(uint32_t fault_code);
static volatile uint32_t s_last_edge;
static volatile uint32_t s_win_start;
static volatile uint32_t s_edge_cnt;
static volatile uint32_t s_edge_veto;
static volatile uint32_t s_disable_tick;
static volatile uint8_t s_edge_disabled;
void Edge_Input_Init(void)
{
GPIO_InitTypeDef g;
PLIC_InitTypeDef p;
GPIOE_CLK_ENABLE();
PLIC_CLK_ENABLE();
GPIO_StructInit(&g);
g.GPIO_Pin = GPIO_Pin_10;
g.GPIO_Mode = GPIO_Mode_IN;
g.GPIO_IType = GPIO_IPU; /* 下降沿触发,空闲应为高;数据手册 RPU 典型值 48 kΩ */
GPIO_Init(GPIOE, &g);
PLIC_StructInit(&p);
p.PLIC_IRQChannel = GPIOE_IRQn; /* 端口级中断,一个端口一个入口 */
p.PLIC_IRQPriority = 1;
p.PLIC_IRQChannelCmd = ENABLE;
PLIC_Init(&p);
GPIO_ClearITPendingBit(GPIOE, GPIO_Pin_10);
GPIO_ITConfig(GPIOE, GPIO_Pin_10, GPIO_ITType_EDGEDOWN, ENABLE);
}
void GPIOE_IRQ_Handler(void) /* 本例只使能了 PE10,直接判它即可 */
{
uint32_t now;
if (GPIO_GetITStatus(GPIOE, GPIO_Pin_10) != SET) {
return;
}
GPIO_ClearITPendingBit(GPIOE, GPIO_Pin_10);
if (s_edge_disabled != 0u) {
return;
}
now = Bsp_GetMonotonic10us();
if ((uint32_t)(now - s_win_start) >= EDGE_RATE_WINDOW_TICKS) {
s_win_start = now;
s_edge_cnt = 0u;
}
/*
* 先累计原始边沿和冷却否决,再判断冷却窗。
* 否则高频毛刺一直被冷却窗拦截,速率熔断可能永远达不到。
*/
++s_edge_cnt;
if (s_edge_cnt > EDGE_RATE_LIMIT) {
s_edge_disabled = 1u;
s_disable_tick = now;
GPIO_ITMaskConfig(GPIOE, GPIO_Pin_10, DISABLE); /* DISABLE = 屏蔽 */
GPIO_ClearITPendingBit(GPIOE, GPIO_Pin_10);
App_EdgeTelemetry(FAULT_GLITCH_STORM);
return;
}
if ((uint32_t)(now - s_last_edge) < EDGE_COOLDOWN_TICKS) {
++s_edge_veto;
return;
}
s_last_edge = now;
App_EdgeEvent();
}
void Edge_Input_TryRecover(uint32_t now)
{
if ((s_edge_disabled == 0u) ||
((uint32_t)(now - s_disable_tick) < EDGE_RECOVER_TICKS)) {
return;
}
/* 空闲必须回到确定的无效高电平,再清残留标志和解除屏蔽。 */
if (GPIO_ReadInputDataBit(GPIOE, GPIO_Pin_10) != GPIO_IRead_Vih) {
return;
}
GPIO_ClearITPendingBit(GPIOE, GPIO_Pin_10);
s_last_edge = now - EDGE_COOLDOWN_TICKS;
s_win_start = now;
s_edge_cnt = 0u;
s_edge_veto = 0u;
s_edge_disabled = 0u;
GPIO_ITMaskConfig(GPIOE, GPIO_Pin_10, ENABLE); /* ENABLE = 解除屏蔽 */
}
/* 由周期任务读取后上报遥测;ISR 内不做格式化或打印。 */
uint32_t Edge_Input_GetVetoCount(void)
{
return s_edge_veto;
}
601 上还要注意三点:中断是端口级 的(GPIOE_IRQn 只有一个 GPIOE_IRQ_Handler 入口);本例只在 PE10 上使能了中断,所以入口里判 PE10 一个脚就够,但若以后同口再使能其它脚,必须在这里补上对应的脚位判断 ,漏判会丢中断;GPIO_ITMaskConfig 的 DISABLE 才是屏蔽 (源码里 DISABLE → IMR |= Pin);PLIC 的 claim/complete 已由 SDK 的 PLIC_SwitchMEXTI() 处理,ISR 内不要再重复 claim/complete。
上电初始化后必须在周期任务或主循环中调用 Edge_Input_TryRecover(now);否则熔断屏蔽后不会自动恢复,只能等复位。App_EdgeTelemetry() 在中断里调用,同样只应做入队或置标志,串口打印、Flash 写日志这类操作必须放到任务上下文。
冷却窗口挡不住"间隔刚好大于冷却窗"的毛刺,必须冷却窗 + 速率熔断一起上。
选型参考:业务事件脉冲 → 输入捕获;电源/过温告警、外部唤醒 → 边沿中断(配三件套);定时器资源耗尽 → 边沿中断 + 更强外围硬件 + 严格协议。
3 多层架构与各级滤除能力
- 电气防护(ESD/限流/钳位、确定空闲电平)→ 2. 外围 RC/施密特(按边沿和噪声条件选用)→ 3. 定时器同步采样 + TIM_ICFilter(硬件粗滤)→ 4. 软件脉宽/周期判据 → 5. 帧级 CRC/序号/重传(最终兜底)。把被否决的边沿数上报遥测,在轨可间接评估干扰和采样异常。
以 1ms 脉冲为例,各层能滤掉多宽扰动:
| 层级 | 机制 | 可滤除宽度 |
|---|---|---|
| 电气防护 | 串阻 + 钳位/ESD 器件 + 确定空闲电平 | 限流、抑制振铃、ESD 泄放;不负责数字表决 |
| RC 低通 | τ=100µs | <50µs |
| 施密特 | 3.3V 下保证 VIL≤1.2V、VIH≥1.5V;内部数字输入默认使能 |
抑制缓边沿和阈值附近抖动;不能等同于外部可调滤波 |
| TIM_ICFilter 硬件滤波 | f_DTS 分频 + 连续同电平采样 | 示例 ICF=0x9、45MHz、DIV1 约 1.4µs |
| 软件表决 | 100kHz×16 拍 | <170µs(16×10µs=160µs) |
| 脉宽判据 | 0.7~1.5ms 窗口 | 非标称脉宽全丢 |
| 帧协议 | CRC+序号+重传 | 单脉冲构不成有效帧 |
对于外部耦合到输入线的 ns~几百 ns 干扰,RC 和 TIM_ICFilter 可以显著降低误采样概率,但不能写成"一定被消掉"。是否通过还取决于干扰幅度、发生相位、GPIO 控制器时钟和输入路径;芯片内部状态触发器发生 SET 时,更不受这两层控制。因此应通过注入测试和遥测确认,而不能用"概率可忽略"替代设计保证。
4 毫秒脉冲参数选型
约束:τ ≤ T/10 ,阈值穿越延迟 t≈0.69τ(VT+≈0.5VDD)。
| 最小脉宽 T | R | C | τ=R×C | 50%阈值穿越延迟 | 可滤扰动(保守值) |
|---|---|---|---|---|---|
| 0.5ms | 4.7kΩ | 10nF | 47µs | 32µs(6.5%) | <20µs |
| 1ms | 10kΩ | 10nF | 100µs | 69µs(6.9%) | <50µs |
| 2ms | 10kΩ | 15nF | 150µs | 104µs(5.2%) | <70µs |
| 5ms | 10kΩ | 47nF | 470µs | 324µs(6.5%) | <230µs |
表中的"可滤扰动"不是精确截止频率,而是按输入从稳定无效电平起跳、阈值约 0.5VDD 做的保守估算。实际穿越点要按 VIH/VIL、施密特特性、源阻抗和实际波形标定;如果干扰从相反电平起跳,穿越时间还会不同。
两条设计约束:R 不能只按"越大越稳"选 ,要核算输入漏电流、VIH/VIL 和源端驱动能力;若按输入漏电流 ±10µA 估算,10kΩ 会引入 0.1V 偏置,47kΩ 可达 0.47V,具体极限必须以 AS32X601 数据手册为准。电容优先 C0G/NP0;X7R 的电压系数和温度漂移会让 RC 时间常数变化,是否可用于辐照环境还要按器件等级和试验数据确认。
定时器配置(100kHz tick=10µs):判决窗 ≤T/3,采样周期远小于判决窗。
| 最小脉宽 T | 表决拍数 | 判决窗 | 脉宽判据 |
|---|---|---|---|
| 0.5ms | 8 | 80µs | 0.35~0.75ms |
| 1ms | 16 | 160µs | 0.7~1.5ms |
| 2ms | 32 | 320µs | 1.4~3.0ms |
| 5ms | 100 | 1.0ms | 3.5~7.5ms |
tick 不能直接拿 CPU 主频。TIM0 的 CK_INT 来自 AxiLite4 Bus1,应在运行时用 SMU_GetClocksFreq() 读取 AXILite4Bus1_Frequency;其他 TIM 也要先确认时钟来源。16 位定时器(TIM3/4/6/7)在 100kHz、10µs/tick 下约 655ms 回绕,测更长的稀疏事件间隔时必须做软件扩展或改用 32 位定时器;TIM0 在同样 tick 下约 11.9 小时回绕,超过该时长仍需软件扩展。
TIM_ICFilter 的时间窗不是固定的 5µs。以 45MHz、TIM_CKD_DIV1、IC1F=0x9 为例,滤波器约为 1.4µs(再叠加同步延迟),实际值按 CK_INT/CKD 和 ICF 编码计算并示波器确认。延迟预算示例:RC 69µs + 软件表决 160µs ≈ 0.23ms,占 1ms 脉宽约 23%;理论值只作初选,实测标定后再定判据上下限。
5 输入捕获两个高频坑
- 双沿捕获错位 :脉冲外多个上升沿会让捕获序列永久错位。用 PWM 输入模式(从模式复位),上升沿复位 CNT,两个捕获值同源于同一个复位点,硬件自动对齐、可自恢复。对于重复的 PWM 波形,
CCR1表示相邻上升沿间隔,CCR2表示高电平宽度;对于单次、稀疏事件,只有CCR2的高电平宽度可直接用于事件判据,CCR1不能强行解释成协议周期。 - CC1OF/CC2OF 捕获溢出 :捕获值未读又有新边沿会置对应溢出标志。检测到必须作废当前配对状态重新同步,不能只清标志,否则可能用旧值拼出虚假测量。
c
/* AS32X601:TIM0 CH1(PB11)PWM 输入模式 + 从模式复位 + 下降沿即时结算 */
#include <stdint.h>
#define CAP_TICK_HZ 100000u /* 10 µs/tick,按实际 TIM0 时钟整除 */
#define CAP_IC_FILTER 0x9u /* f_DTS/8,N=8;45MHz、DIV1 约 1.4µs */
#define CAP_MIN_HIGH_TICKS 70u /* 0.7ms,必须按波形和RC标定 */
#define CAP_MAX_HIGH_TICKS 150u /* 1.5ms,必须按波形和RC标定 */
#define CAP_FAULT_CLOCK 0x0101u
#define CAP_FAULT_OVERFLOW 0x0102u
extern void App_CapturePulse(uint32_t high_ticks, uint32_t interval_ticks);
extern void App_CaptureTelemetry(uint32_t fault_code);
static volatile uint32_t s_interval_ticks; /* 相邻上升沿间隔;单次事件可能为0 */
static volatile uint32_t s_capture_overflow;
static volatile uint32_t s_capture_veto;
static volatile uint8_t s_interval_valid;
static volatile uint8_t s_pulse_seen;
static volatile uint8_t s_frame_active;
static void Tim0_Capture_Init(void)
{
GPIO_InitTypeDef g;
TIM_TimeBaseInitTypeDef tb;
TIM_ICInitTypeDef ic;
PLIC_InitTypeDef p;
SMU_ClocksTypeDef clks;
uint32_t tim_clk_hz;
uint32_t prescaler;
GPIOB_CLK_ENABLE();
PLIC_CLK_ENABLE();
HTIM0_CLK_ENABLE();
if (SMU_GetClocksFreq(&clks) != SUCCESS) {
App_CaptureTelemetry(CAP_FAULT_CLOCK);
return;
}
tim_clk_hz = clks.AXILite4Bus1_Frequency; /* TIM0 的 CK_INT,不是 CPU 主频 */
if ((tim_clk_hz < CAP_TICK_HZ) || ((tim_clk_hz % CAP_TICK_HZ) != 0u)) {
App_CaptureTelemetry(CAP_FAULT_CLOCK);
return;
}
prescaler = tim_clk_hz / CAP_TICK_HZ;
GPIO_PinAFConfig(GPIOB, GPIO_PinSource11, GPIO_AF_HTIM0_M2); /* PB11 -> TIM0_CH1 */
GPIO_StructInit(&g);
g.GPIO_Pin = GPIO_Pin_11;
g.GPIO_Mode = GPIO_Mode_IN;
g.GPIO_IType = GPIO_IPD; /* 空闲为低,避免 FPGA 未配置时悬空;上升沿开始计宽 */
GPIO_Init(GPIOB, &g);
TIM_TimeBaseStructInit(&tb);
tb.TIM_CounterMode = TIM_CounterMode_Up;
tb.TIM_ClockDivision = TIM_CKD_DIV1;
tb.TIM_Period = 0xFFFFFFFFU; /* TIM0 是 32 位定时器 */
tb.TIM_Prescaler = prescaler - 1u; /* 100kHz -> 10µs/tick */
TIM_TimeBaseInit(TIM0, &tb);
TIM_ICStructInit(&ic);
ic.TIM_Channel = TIM_Channel_1;
ic.TIM_ICPolarity = TIM_ICPolarity_Rising;
ic.TIM_ICSelection = TIM_ICSelection_DirectTI;
ic.TIM_ICFilter = CAP_IC_FILTER;
TIM_PWMIConfig(TIM0, &ic); /* IC1=上升沿,IC2自动配成下降沿 */
TIM_SelectInputTrigger(TIM0, TIM_TS_TI1FP1); /* 上升沿复位 CNT */
TIM_SelectSlaveMode(TIM0, TIM_SlaveMode_Reset);/* PWMIConfig 不会自动配置从模式 */
/*
* TimeBaseInit 会写 EGR.UG,可能留下 Update 标志。
* 本例不开启 Update 中断,只清理捕获和更新标志。
*/
TIM_ClearFlag(TIM0, TIM_FLAG_Update);
TIM_ClearITPendingBit(TIM0, TIM_IT_CC1);
TIM_ClearITPendingBit(TIM0, TIM_IT_CC2);
TIM_ClearFlag(TIM0, TIM_FLAG_CC1OF);
TIM_ClearFlag(TIM0, TIM_FLAG_CC2OF);
s_interval_ticks = 0u;
s_capture_overflow = 0u;
s_capture_veto = 0u;
s_interval_valid = 0u;
s_pulse_seen = 0u;
s_frame_active = 0u;
TIM_ITConfig(TIM0, TIM_IT_CC1 | TIM_IT_CC2, ENABLE);
PLIC_StructInit(&p);
p.PLIC_IRQChannel = TIM0_IRQn;
p.PLIC_IRQPriority = 1;
p.PLIC_IRQChannelCmd = ENABLE;
PLIC_Init(&p);
TIM_Cmd(TIM0, ENABLE);
}
void TIM0_IRQ_Handler(void) /* 注意:不是 TIM0_IRQHandler */
{
uint32_t hi;
uint32_t interval;
/*
* 溢出时先读捕获寄存器,再清溢出和CCxIF,避免旧值进入业务层。
* 这里不使用 Update 中断;从模式复位产生的 UIF 不能用于本业务。
*/
if ((TIM_GetFlagStatus(TIM0, TIM_FLAG_CC1OF) == SET) ||
(TIM_GetFlagStatus(TIM0, TIM_FLAG_CC2OF) == SET)) {
(void)TIM_GetCapture1(TIM0);
(void)TIM_GetCapture2(TIM0);
if (TIM_GetFlagStatus(TIM0, TIM_FLAG_CC1OF) == SET) {
TIM_ClearFlag(TIM0, TIM_FLAG_CC1OF);
}
if (TIM_GetFlagStatus(TIM0, TIM_FLAG_CC2OF) == SET) {
TIM_ClearFlag(TIM0, TIM_FLAG_CC2OF);
}
TIM_ClearITPendingBit(TIM0, TIM_IT_CC1);
TIM_ClearITPendingBit(TIM0, TIM_IT_CC2);
++s_capture_overflow;
s_interval_valid = 0u;
s_pulse_seen = 0u;
s_frame_active = 0u;
App_CaptureTelemetry(CAP_FAULT_OVERFLOW);
return;
}
/*
* 上升沿:记录一帧开始,并保存"上一次上升沿到本次上升沿"的间隔。
* 第一个孤立脉冲没有前一个上升沿,因此 interval_valid 保持为 0。
*/
if (TIM_GetITStatus(TIM0, TIM_IT_CC1) == SET) {
uint32_t cc1 = TIM_GetCapture1(TIM0);
if ((s_pulse_seen != 0u) && (cc1 != 0u)) {
s_interval_ticks = cc1;
s_interval_valid = 1u;
} else {
s_interval_valid = 0u;
}
s_pulse_seen = 1u;
s_frame_active = 1u;
TIM_ClearITPendingBit(TIM0, TIM_IT_CC1);
}
/* 下降沿:读取高电平宽度,在本沿完成业务结算,不等待下一次上升沿。 */
if (TIM_GetITStatus(TIM0, TIM_IT_CC2) == SET) {
hi = TIM_GetCapture2(TIM0);
interval = (s_interval_valid != 0u) ? s_interval_ticks : 0u;
TIM_ClearITPendingBit(TIM0, TIM_IT_CC2);
if ((s_frame_active == 0u) ||
(hi < CAP_MIN_HIGH_TICKS) ||
(hi > CAP_MAX_HIGH_TICKS)) {
++s_capture_veto;
s_frame_active = 0u;
/*
* 脉宽越界说明本次上升沿本身就是毛刺,不能再作为
* 下一次"相邻上升沿间隔"的基准。
*/
if ((hi < CAP_MIN_HIGH_TICKS) || (hi > CAP_MAX_HIGH_TICKS)) {
s_pulse_seen = 0u;
}
return;
}
App_CapturePulse(hi, interval);
s_frame_active = 0u;
}
}
/* 由周期任务读取后上报遥测。 */
uint32_t Tim0_Capture_GetVetoCount(void) { return s_capture_veto; }
uint32_t Tim0_Capture_GetOverflowCount(void) { return s_capture_overflow; }
移植前先查手册:在本芯片这组配置下,PWM 输入模式是 CCR1 = 相邻上升沿间隔、CCR2 = 高电平宽度。不同 MCU 的 PWM 输入例程不一定使用相同的极性、触发源和寄存器语义。对单次、稀疏的 LEO 事件,应以 CC2 下降沿和高电平宽度做主判据;只有协议明确要求测周期时,才把 CCR1 的间隔纳入判决。
App_CapturePulse() 运行在中断上下文:这里只应做入队、置标志或通知任务,脉宽换算、协议解析、日志和遥测等重负载必须放到任务上下文。否则捕获中断被拉长,下一次边沿到来前寄存器未及时读走,反而会推高 CCxOF 风险。
6 协议兜底 + 踩坑清单
输入捕获治不了"脉宽恰好合法"的巧合毛刺,协议层必须兜底:帧起始符 + 数据序列 + 序号 + CRC + ACK 重传,把"单脉冲=事件"改成"整帧=事件"。简单编码如 1ms 高+1ms 低+1ms 高,单个毛刺凑不出完整事件。
易踩坑:
- RC 不是越大越好,τ> T/10 会削窄有效脉冲,表现为"偶发丢事件",比误触发更难排查;
- 别指望 TIM_ICFilter 主责,窗口就几 µs;
- 上电复位期 IO 门控,等 FPGA 就绪握手后再开接收;FPGA PLL 锁定和配置完成时间按具体器件手册确认,不要把某个固定值当成通用保证;
- MCU 引脚作为输入时,
GPIO_OStrength不控制外部信号边沿。是否降低驱动、减缓边沿,应在 FPGA 输出侧按信号完整性、串扰和 EMI 评估;不要用 MCU 的 4.5mA 档作为边沿控制手段; - 空间环境:关键逻辑 TMR、未用 IO 不悬空、看门狗兜底、毛刺否决计数上遥测(关注 SET/SEU/SAA)。
7 测试清单(只看"不误触发"不够)
| 测试 | 注入条件 | 预期 |
|---|---|---|
| 窄毛刺抑制 | 1ns/100ns/1µs/10µs | 否决计数涨,无事件输出 |
| 脉宽下边界 | 0.69T / 0.71T | 前者丢后者收 |
| 脉宽上边界 | 1.4T / 1.6T | 前者收后者丢 |
| 输入电平边界 | VILmax/VIHmin 附近及带裕量电平 |
验证不落入不确定区;3.3V 下 0.6VDD=1.98V 不能当作无效高电平 |
| 密集毛刺 | 100kHz 持续 10s | CPU 正常,不看门狗复位 |
| 上电抖动 | 握手前制造 IO 抖动 | 接收保持关闭 |
| 温压拉偏 | 高低温+电压复测边界 | 判据正常 |
| GPIO 边沿中断熔断 | 高频连续边沿 | 冷却计数涨,超限关中断上报 |
关键:注入干扰后否决计数不动,说明某层防护没生效。
结语
IO 抗毛刺是多层防护责任划分:电气层、RC/施密特、硬件滤波、软件判据、协议层层层接力,没有哪一层能独立解决全部干扰。外部耦合瞬态和芯片内部 SET 必须分开评估;外部滤波能降低干扰进入采样链的概率,但不能保证内部状态位不被翻转。真正要把握的是"过滤毛刺"和"别把有效脉冲滤掉"的平衡。所有结论都带适用前提,属于工程权衡------同一个边沿中断,放业务脉冲入口是隐患,做告警输入就是标准方案。读结论,更要看前提。
有帮助欢迎点赞收藏,星载硬件、抗辐照问题评论区交流。