在STM32 Nucleo上多次触发上升沿中断
Posted
技术标签:
【中文标题】在STM32 Nucleo上多次触发上升沿中断【英文标题】:Rising edge interrupt triggering multiple times on STM32 Nucleo 【发布时间】:2021-02-22 19:08:31 【问题描述】:我正在使用 STM32 NUCLEO-F401RE 微控制器板。
我有一个扬声器,可以在向上/向下推动操纵杆时按设定的量改变频率。我的问题是,有时(通常情况下)当向上/向下推动操纵杆时,频率会多次增加/减少,这意味着 ISR 正在执行多次。此外,InterruptIn 对象设置为在上升沿触发,但有时它也会在下降沿执行(当向上/向下推动操纵杆返回中性时)。有什么帮助可以克服这个问题吗?
void upISR()
if (greenLED.getStatus())
myTicker.detach();
frequency+=200;
myTicker.attach(callback(&spkr, &Speaker::toggle), 0.5/frequency);
'
int main()
InterruptIn up(A2);
InterruptIn down(A3);
InterruptIn fire(D4);
up.rise(&upISR);
down.rise(&downISR);
fire.rise(&toggleISR);
redLED.on();
while (1)
【问题讨论】:
这是一个经典的问题,请阅读“去抖动”(开关去抖动,按钮去抖动) @BenVoigt 我读过的很多内容都很好地描述了这个问题,然后只提出了一些令人震惊的糟糕的软件解决方案。很少有人处理直接触发中断的开关,解决方案不适合在中断上下文中使用。 OP 应该如何将好的建议与坏的建议(或可能适用于或不适用于这种情况的建议)进行分类?最好在这里发布一个答案并让社区来衡量其质量 - 当然只计算合法投票 ;-)。 @Clifford:因为虽然“非常糟糕”对任何情况都不利,但在一种情况下的“好”解决方案也不能解决所有情况。仅根据 OP 在问题中提供的信息,我无法给出完整的答案……还有一些示波器轨迹和开关与 MCU 之间的连接示意图会有所帮助。 您通常不希望在开关状态更改时中断,这很少奏效。一种选择是基于定时器的中断并定期对开关进行采样并获取您获得的值并使用它。另一个是一个过滤器,它基本上以某种形式对开关状态进行过采样,并在一个级别连续查找 X 或对其应用数学以消除故障,16 个中有 15 个处于同一级别,等等。每次重新开始采样时设置在中间,如果它达到阈值有足够的 1 或 0 等... 它很简单,因为你有一个正常(未推送)状态和一个推送状态,所以你正在寻找它处于推送状态但不是所有的故障,所以你必须决定我强制进入正常状态还是我有按住功能,如果有,那么你如何定义/采样... 【参考方案1】:我们清楚地认为这是正常和预期的开关弹跳。从机械上讲,开关是一块金属,当被作用时,该金属会从一个极移动到另一个极,即使它们不像雨刷和两个极。移动的金属会碰撞和反弹,电气连接会显示这一点。弹跳通常足够慢,足以让处理器获得多个中断,尽管这可能是对所有可能在电气上看到的弹跳的欠采样。如果您尝试在示波器上查看它,则示波器本身可能不会有意过滤其中的一部分(但您的芯片也会过滤)。
查看问题的一种方法是与任何事情一样,先研究,然后再编写应用程序。这不是解决方案,而是为您的系统描述问题的一种方式
switch_isr ( void )
...
some_global_variable <<= 1;
some_global_variable |= (pin_state_register>>pin_number)&1;
...
main ( void )
...
some_local_variable = 0;
while(1)
if(some_local_variable != some_global_variable)
some_local_variable = some_global_variable;
primitive_hex_print(some_local_variable);
没有理由期望看到 shift 变量中的每个状态变化,但您应该看到一些并了解问题所在。另一种方法是在每次中断时只增加一个计数器,在前台定期打印,您会看到一个按钮按下可能会导致多次计数。并且从打印输出停止大致改变人类时间到稳定时间。
过滤是关于每单位时间的状态变化,你必须有一些时间的味道,无论是在前台轮询由中断设置的一些信息(向上/向下计数器等)的循环,还是状态变化相对于计时器/时钟。
我不知道你的任务的完整规则是什么,如果你只能为每个开关设置一个中断而不是一个计时器,或者最好是一个计时器,我看不到一个真正有效的干净解决方案。您必须在前台进行过滤,但所做的只是轮询中断收集的引脚状态的副本,这与不使用中断有什么不同吗?如果您无法设置定时器中断,则不能使用克利福德的答案,如果您可以使用定时器和中断,那么您可以使用该中断或引脚状态更改中断和过滤器收集的引脚状态副本定期采样开关状态在定时器中断。与 Clifford 的不同,但在所有情况下,您都需要相对于时间的状态更改历史来查看事情何时解决。
如果没有时间参考并且状态不随时间变化(由于状态没有改变,引脚中断无法显示),您就无法过滤掉反弹。相反,要提高你的灵巧度以及上下轻弹操纵杆的方式。
【讨论】:
请注意这里的拼写,尤其是包含撇号的单词。以下是一些正确的拼写,以及您的帖子历史记录中的拼写错误数量:不要 (765)、不要 (488)、不会 (217)、不能 (306)。对于没有英语作为第一语言的人,这里有一些宽容,但文体和故意拼写错误违背了网站的目标。您的浏览器中有拼写检查器吗?【参考方案2】:不要将 EXTI 用于机械手柄和按钮。
使用常规中断(例如 systick)来轮询引脚的状态。
【讨论】:
【参考方案3】:机械开关弹跳或多或少是所有机械开关的一个特征。通常需要在软件中实现“去抖动”,尤其是在这种情况下开关直接驱动中断时。
在谷歌上快速搜索软件谴责技术会产生一些相当糟糕的技术 IMO。不幸的是,我看到它做得不好的次数比做得好的多。
我建议您在开关 ISR 中启动(或在“反弹”的情况下重新启动)一个硬件计时器,持续时间为 20 毫秒左右(长于开关反弹时间,但短于您可以设置的时间)可能真正释放开关)。然后在定时器 ISR 中,测试开关的状态并相应地改变频率:
伪代码:
void upISR()
debounceTimerRestart() ;
void downISR()
debounceTimerRestart() ;
void debounceTimerISR()
debounceTimerStop() ;
tDirection dir = getJoystickDir() ;
swithc( dir )
case UP :
increaseFrquency() ;
break ;
case DN :
decreaseFrquency() ;
break ;
这样做是在开关停止弹跳后不久触发定时器中断(“去抖时间”)。请注意,计时器是“单次”而不是周期性的。
下面我根据@BenVoigt 的建议(在 cmets 中)提出了一项改进。我把它分开来表明这是他的作品。上面的方法通常可以工作,但如果你的开关特别差,下面的方法可以解决问题,而且成本很低,所以你也可以:
void debounceTimerISR()
debounceTimerStop() ;
static tDirection previous_dir = CENTRE ;
tDirection dir = getJoystickDir() ;
// If the state changed...
if( previous_dir != dir )
previous_dir = dir ;
switch( dir )
case UP :
increaseFrquency() ;
break ;
case DN :
decreaseFrquency() ;
break ;
【讨论】:
一个简单的改变也将提供故障抑制,也就是说,将getJoystickDir()
的值与getJoystickDir()
的先前值进行比较,并且仅当摇杆当前位于活跃的位置,以前不是。
@BenVoigt 可能,但在大多数情况下是不必要的,但仍然是免费的增强功能。如果在计时器到期后,摇杆既没有上升也没有下降,则什么也不会发生。任何持续时间小于去抖时间的“正毛刺”都会被自动拒绝。确实,开关在关闭时暂时打开的“负毛刺”将被视为您的建议将解决的过渡,并且可能需要使用特别差、脏或磨损的开关,但硬件的寿命可能会受到限制。
@BenVoigt:经过反思,添加了您的建议。谢谢。
@Clifford 非常感谢您的帮助和解释。我在我的代码中使用一个标志来实现去抖动,该标志在调用任何 ISR 时设置,并在短计时器后清除,如果设置了该标志,ISR 将无法执行。你认为这是一种实现它的好方法吗?
@mvem_ 虽然您的解决方案可以工作,但它不是噪声/故障免疫,并且与 P_J 建议的简单轮询计时器相比没有真正的好处。他有一点。如果您想讨论您的解决方案,您应该在此处将其作为答案发布或发布新问题,SO 不是讨论论坛,这不是对 cmets 功能的适当使用以上是关于在STM32 Nucleo上多次触发上升沿中断的主要内容,如果未能解决你的问题,请参考以下文章