从中断返回后的轻微延迟

时间:2015-09-18 07:18:58

标签: c embedded stm32 lcd

我编写了一个小程序,它使用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;
}

我真的不知道为什么会这样做并且现在已经玩过它但仍然无法弄明白。它很好,但我想知道为什么它正在这样做。

2 个答案:

答案 0 :(得分:3)

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

答案 1 :(得分:1)

首先,您必须确保连接到按钮的输入引脚具有拉电阻,可以在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
    }
    
  • 典型的去抖时间在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个读取的中值滤波器非常容易实现,即使对于许多安全关键应用也是如此。