从中断返回后的轻微延迟
Posted
技术标签:
【中文标题】从中断返回后的轻微延迟【英文标题】:Slight Delay After Returning from Interrupt 【发布时间】:2015-12-15 06:02:50 【问题描述】:我编写了一个小程序,它使用 STM32 Discovery 板上的按钮在二进制/十进制/十六进制模式下充当计数器(屏幕循环显示 3 个选项,一旦按下,每次按下最多计数 16在重置为循环选项之前)。
我遇到了一个让我有点困惑的小“错误”(阅读,不是真的)。如果我以十进制/十六进制计数,它会立即返回循环选项,但如果我以二进制计数,则需要大约 1 秒左右才能这样做(明显延迟)。
int main(void)
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM6, ENABLE);
lcd_init();
button_init();
while (1)
while (!counting)
standard_output();
void standard_output(void)
state = 0;
lcd_command(0x01);
delay_microsec(2000);
lcd_putstring("Binary");
for (i=0; i<40; i++) delay_microsec(50000); // keep display for 2 secs
if (counting) return; // if we have pressed the button, want to exit this loop
state = 1;
lcd_command(0x01);
delay_microsec(2000);
lcd_putstring("Decimal");
for (i=0; i<40; i++) delay_microsec(50000); // keep display for 2 secs
if (counting) return; // if we have pressed the button, want to exit this loop
state = 2;
lcd_command(0x01);
delay_microsec(2000);
lcd_putstring("Hexadecimal");
for (i=0; i<40; i++) delay_microsec(50000); // keep display for 2 secs
if (counting) return; // if we have pressed the button, want to exit this loop
void EXTI0_IRQHandler(void)
if (EXTI_GetITStatus(EXTI_Line0) != RESET)
if (!stillBouncing) // a button press is only registered if stillBouncing == 0
if (!counting) // if we weren't already counting, a valid button press means we are now
counting = 1;
count = 0; // starting count from 0
else
count++;
if (count < 16)
lcd_command(0x01);
delay_microsec(2000);
format_int(count);
else
counting = 0; // we are no longer counting if count >= 16
stillBouncing = 10; // every time a button press is registered, we set this to 10
while (stillBouncing > 0) // and check that it hasn't been pressed for 10 consecutive 1000microsec intervals
if (!delay_millisec_or_user_pushed(1000))
stillBouncing--;
EXTI_ClearITPendingBit(EXTI_Line0);
void format_int(unsigned int n)
if (state == 0) // if we selected binary
for (i=0;i<4;++i)
num[i] = (n >> i) & 1; // generate array of bit values for the 4 least significant bits
i = 4;
while (i>0)
i--;
lcd_putint(num[i]); // put ints from array to lcd in reverse order to display correctly
else if (state == 1) // if we selected decimal
lcd_putint(n); // lcd_putint is enough for decimal
else // if we selected hex
snprintf(hex, 4, "%x", n); // format string such that integer is represented as hex in string
lcd_putstring(hex); // put string to lcd
int delay_millisec_or_user_pushed(unsigned int n)
delay_microsec(n);
if (!GPIO_ReadInputDataBit(GPIOA, GPIO_Pin_0))
return 0;
return 1;
我真的不知道它为什么会这样做,现在已经玩过了,但仍然无法弄清楚。这很好,但我想知道为什么它会这样做。
【问题讨论】:
中断处理程序内部的延迟是一个非常糟糕的主意。中断处理程序不应阻塞。不知道这是否是您的问题的原因,但它的设计真的很糟糕,所以我想我会指出它。 这是一个我们被告知我们需要处理开关弹跳的任务。到目前为止,我的经验是在 Java 和 Python 方面——应该如何处理? 可能 lcd_putint 需要很长时间来刷新显示。format_int()
在二进制情况下,它循环 4 次,然后是 Hex 和 Dec 情况的 4 倍。
使用定时器加中断。 @unwind 是对的:中断处理程序应尽可能短,因此永远不要阻塞忙等待(这通常是个坏主意,即使在线程模式下也是如此)。
【参考方案1】:
可能lcd_putint
需要很长时间才能刷新显示。它可能会将每个数字转换为字符串,然后将其放到屏幕上。
format_int()
在二进制情况下,它循环 4 次,然后是 Hex 和 Dec 情况的 4 倍。
如果你把代码改成下面这样,我猜会更快:
char bin[5];
sprintf(bin, "%d%d%d%d", ((n&0x08)>>3), ((n&0x04)>>2), ((n&0x02)>>1), (n&0x01));
lcd_putstring(bin);
我知道将数字转换为二进制字符串有很多解决方案,但关键是使用lcd_putstring
,这肯定比调用4次lcd_putint
更快
【讨论】:
【参考方案2】:首先,您必须确保连接到按钮的输入引脚具有拉电阻,无论是在 PCB 上还是在微控制器 I/O 端口内部启用。如果没有,当按钮处于非活动状态时,输入将处于未定义状态,并且您将获得垃圾中断。电阻器应拉向非活动状态。
正如 cmets 中所指出的,在中断服务例程中绝不应该有任何延迟。 ISR 应尽可能小且快。
请务必注意,如果您将中断触发引脚连接到按钮,则意味着您将在引脚上出现的每次反弹或其他 EMI 噪声时都会收到中断。这些虚假的、虚假的中断将停止主程序,并且整体实时性能将受到影响。这是一个典型的初学者错误,它存在于您的程序中。
您可以为按钮使用中断触发引脚,但是您必须知道自己在做什么。您必须在获得第一个边沿触发后立即从 ISR 内部禁用中断本身,方式如下:
确保按钮中断设置为在上升沿和下降沿触发。收到中断后,从 ISR 内部禁用中断。从 ISR 内部,启动其中一个片上硬件定时器,并在 x 毫秒后通过定时器中断触发它。
具有虚构寄存器名称的通用虚构 MCU 的此类 ISR 的伪代码:
void button_isr (void)
BUTTON_ISR_ENABLE = CLEAR; // some hw register that disables the ISR
BUTTON_ISR_FLAG = CLEAR; // some hw register that clears the interrupt flag
TIMER_COUNTER = CURRENT_TIME + DEBOUNCE_TIME; // some hw timer register
TIMER_ISR_ENABLE = SET; // some hw timer register
典型的去抖动时间在 5ms 到 20ms 之间。您可以使用示波器测量特定开关上的弹跳。
当定时器用完时,定时器 ISR 被触发,你再次读取输入。如果它们的读数相等(均高),则设置一个标志“按下按钮”。如果没有,您的线路上有一些噪音,应该忽略。禁用定时器但再次启用按钮 I/O 中断。
定时器 ISR 的伪代码,用于通用的虚构 MCU:
static bool button_pressed = false;
void timer_isr (void)
TIMER_ISR_FLAG = CLEAR;
TIMER_ISR_ENABLE = CLEAR;
if(BUTTON_PIN == ACTIVE) // whatever you have here, active high/active low
button_pressed = true;
else
button_pressed = false;
BUTTON_ISR_ENABLE = SET;
在实际代码中,寄存器名称会更加神秘,如何设置/清除标志因 MCU 而异。有时你写 1,有时你写 0。
上面的代码应该适用于标准应用程序。对于具有更严格实时要求的应用程序,您将有一个计时器/任务连续运行,以均匀的时间间隔轮询按钮。要求两个后续读取给出相同的按下/未按下值,以便接受它作为按钮状态的变化。
更高级的算法涉及进行多次读取的中值过滤器。具有 3 个读数的中值过滤器非常容易实现,即使对于许多安全关键型应用程序也足够了。
【讨论】:
以上是关于从中断返回后的轻微延迟的主要内容,如果未能解决你的问题,请参考以下文章
实战技能分享,减小开关中断对系统实时性的影响,提升系统响应速度