从中断返回后的轻微延迟

Slight Delay After Returning from Interrupt

我写了一个小程序,它使用 STM32 Discovery 开发板上的一个按钮作为任一 Binary/Decimal/Hexadecimal 模式下的计数器(屏幕循环显示 3 个选项,一旦按下,计数为 16在重置为循环选项之前每次按下。

我遇到了一个小 "bug"(阅读,不是真的)让我有点困惑。如果我在 Decimal/Hexadecimal 中计数,它 returns 会立即循环通过选项,但如果我在二进制中计数,则需要 ~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;
}

我真的不知道它为什么要这样做,现在已经尝试过但仍然无法弄清楚。它很好,但我想知道为什么它这样做。

可能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

更快

首先,必须确保连接到按钮的输入引脚有拉电阻,要么在PCB上,要么在微控制器i/o端口内部启用。如果没有,当按钮处于非活动状态时,输入将处于未定义状态,并且您将获得垃圾中断。电阻器应拉向非活动状态。

如评论中所述,您永远不应在中断服务程序中有任何延迟。 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
    }
    
  • 典型的去抖时间在 5 毫秒到 20 毫秒之间。您可以使用示波器测量特定开关上的反弹。

  • 当计时器用完时,触发计时器ISR,您再次读取输入。如果它们读数相等(均高),则设置标志 "button pressed"。如果不是,则线路上有一些噪音,应该忽略。禁用计时器但再次启用按钮 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;
    }
    

在实际代码中,寄存器名称会更加神秘,set/clear 标志的方式因 MCU 而异。有时写 1 清除,有时写 0。

以上代码应该适用于标准应用程序。对于具有更严格实时要求的应用程序,您将有一个 timer/task 运行 连续,以均匀的时间间隔轮询按钮。要求两个后续读取给出相同的值 pressed/not pressed,以便接受它作为按钮状态的变化。

更高级的算法涉及进行多次读取的中值过滤器。具有 3 个读取的中值过滤器非常容易实现,甚至对于许多安全关键应用程序也足够了。