从中断返回后的轻微延迟

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 个读数的中值过滤器非常容易实现,即使对于许多安全关键型应用程序也足够了。

【讨论】:

以上是关于从中断返回后的轻微延迟的主要内容,如果未能解决你的问题,请参考以下文章

Lua 中断之后怎么从中断的位置继续执行

实战技能分享,减小开关中断对系统实时性的影响,提升系统响应速度

实战技能分享,减小开关中断对系统实时性的影响,提升系统响应速度

STM32之中断

嵌入式开发基础之中断管理

嵌入式开发基础之中断管理