按下按钮 3 秒以及如何使用 Atmega8 1MHz 测量其时间?

Pressing button for 3 seconds and how to measure its time with Atmega8 1MHz?

目前我有 Atmega8 1MHz 微控制器。我正在使用 C 语言。 所以基本上我的程序在 7 段显示器上显示数字。 每次按下按钮,它都会增加数字 +1

现在我想测量时间,如果用户按下按钮 3 秒。我唯一需要的是如何测量中断是否达到 3 秒。 我需要它背后的逻辑,我应该延迟使用 if 语句吗?或者我不知道也许还有其他事情

这是我的代码:

#define F_CPU 1000000UL
#define IRQ1 INT0_vect
#define IRQ2 INT1_vect

#include <avr/io.h>
#include <util/delay.h>
#include <avr/interrupt.h>

volatile int the_count = 0; 
volatile int i;
volatile int k;

ISR(IRQ1){
    if (the_count < 100){
        the_count++;
        i = the_count % 10;
        k = the_count / 10;
    }
    else{
         the_count = 0;
         i = the_count % 10;
         k = the_count / 10;
    }
}

ISR(IRQ2){
    if (the_count < 100){
        the_count = the_count;
        i = the_count % 10;
        k = the_count / 10;
    }
    else{
        the_count = 0;
        i = the_count % 10;
        k = the_count / 10;
    }
}

void init()
{
    DDRD = 0b00000111;
    DDRB = 0b11111111;
    PORTB = 255;
    _delay_ms(2000);
    PORTB = 0;
    GICR = 0xc0;
    MCUCR = 0x08;
    IRQ1;
    IRQ2;
}



int main(void){
    init();
    GICR = 0xc0;   
    MCUCR = 0x08; 
    sei();
    
    int digit[] = {0b000000101, 0b10111101, 0b00100110, 0b10100100, 0b10011100, 0b11000100, 0b01000100, 0b10101101, 0b00000100, 0b10001100};
    
    
    int dig1 = 0b00000110;  //first digit area on display
    int dig2 = 0b00000101;  //second digit area on display
    
    
    while (1) 
    {
        if (k < 1){                                             
            PORTB = digit[0];
            PORTD = dig1;
            _delay_ms(1);
            PORTD = dig2;
            PORTB = digit[i];
            _delay_ms(1);
        }
        
        else if (k>=1 && i==0 && the_count<100){                
            PORTB = digit[k];
            PORTD = dig1;
            _delay_ms(1);
            PORTD = dig2;
            PORTB = digit[0];
            _delay_ms(1);
        }
        else if (k>=1 && i!=0 && the_count<100){                   
            PORTB = digit[k];
            PORTD = dig1;
            _delay_ms(1);
            PORTD = dig2;
            PORTB = digit[i];
            _delay_ms(1);
        }
        else {                                                       
            the_count = 0;
        }
    }
}

我不会费力地浏览所有代码来回答您的问题 - 尤其是因为您的命名约定、缺少注释或 I/O 抽象使得很难确定 I/O 是什么为了什么或什么代码做什么。但“逻辑”如下(将时间和 I/O 调用替换为可用的或由您酌情实施的调用):

bool buttonPressHoldDetect( uint32_t hold_time_millisec )
{
   static bool release_pending = false ;
   bool button_hold_time_event = false ;

   static bool previous_button_pressed_state = isButtonPressed() ;
   bool button_pressed = isButtonPressed() ;

   // If button down...
   if( button_pressed )
   {
       // If it was previously up...
       if( !previous_button_pressed_state )
       {
           // Timestamp the button down event
           uint32_t button_down_start = getTickMillisec() ;
       }

       // If button held down for  hold time...
       if( !release_pending && 
           (getTickMillisec() - button_down_start) > hold_time_millisec )
       {
           button_hold_time_event = true ;
           release_pending  = false ;
       }
   }
   else
   {
       // button released, allow a subsequent press to be timed
       release_pending = false ;
   }
   
   // Retain previous state for event detection
   previous_button_pressed_state = button_pressed ;

   // Return true when the button has been 
   // held for hold time
   return button_hold_time_event;
}

那么您将有一个以下形式的轮询循环:

for(;;)
{
    // If button held for three seconds...
    if( buttonPressHoldDetect( 3000 ) ;
    {
        // Do button held for three second stuff
    }

    // Do other stuff
}

zero-delay / no busy-wait 轮询允许您在循环中执行其他工作。当然,其他工作也应该避免延迟和 busy-waits - 你的 loop-time(因此按钮轮询率)是循环中完成的所有工作(包括延迟)的总和,所以对于大多数响应式系统,您需要尽量减少完成的工作并使循环尽可能具有确定性(恒定循环时间)。

具体来说:

  • 您不需要使用中断(除了用于定时的滴答中断之外)。你可以,并且中断可能只是设置一个由 isButtonPressed() 返回的标志,但它只会增加复杂性(尤其是 w.r.t.switch de-bounce),我建议简单地轮询它,

  • 你不应该使用延迟 - 在延迟期间你不能做其他有用的工作。 (除非您使用 multi-thread 调度程序)。请改用时间戳和经过时间,并避免使用任何类型的“busy-waits”。

您确实需要考虑开关弹跳,这可以类似地处理:

bool isButtonPressed()
{
    static const uint32_t DEBOUNCE_MILLISEC = 20 ;
    static bool button_pressed = readInput( BUTTON ) != 0 ;
    static uint32_t button_event_time = 0 ;

    // Get the current time and button state
    uint32_t now = getTickMillisec() ;
    bool current_button_state = readInput( BUTTON ) != 0 ;

    // If button changes state after the debounce period...
    if( (now - button_event_time) > DEBOUNCE_MILLISEC
        && current_button_state != button_pressed )
    {
         // Change the button state and timestamp the event
         button_pressed = current_button_state ;
         button_event_time = now ;
    }

    return button_pressed ; 
}

此外,如果在循环中完成的工作很少,但您想要一个比主体执行时间更长的特定且确定性的循环时间(例如在 PID 控制循环中这将是必不可少的),则:

static const int LOOP_TIME_MILLISEC = 20 ; // loop 50 times per second
int loop_start_time = 0 ;

for(;;)
{
    uint32_t now = getTickMillisec() ;

    // if time to start loop iteration...
    if( (now - loop_start_time) >= LOOP_TIME_MILLISEC )
    {
        loop_start_time = now ;

        // If button held for three seconds...
        if( buttonPressHoldDetect( 3000 ) ;
        {
            // Do button held for three second stuff
        }

        // Do other stuff
    }   
}

我已经很长时间没有使用 AVR 了,但是如果您还没有合适的计时功能,getTickMillisec()(连同初始化和 ISR)可以通过以下方式实现:

#include <avr/io.h> ;
#include <avr/interrupt.h> ;

// Timer reload value for 1ms
#define SYSTICK_RELOAD (1000000UL / 1000)

// Millisecond counter
volatile uint32_t tick_millisec = 0 ;

ISR (TIMER1_COMPA_vect)
{
    tick_millisec ++;
}

void sysTickInit()
{
    // CTC mode, Clock/1
    TCCR1B |= (1 << WGM12) | (1 << CS10);

    // Load the output compare
    OCR1AH = (SYSTICK_RELOAD >> 8);
    OCR1AL = SYSTICK_RELOAD ;

    // Enable the compare match interrupt
    TIMSK1 |= (1 << OCIE1A);

    // Enable interrupts
    sei();
}

uint32_t getTickMillisec()
{
    uint32_t now = 0 ;

    // Read tick count and re-read if it is not consistent
    // (due interrupt pre-emption and update during non-atomic access)
    do
    {
        now = tick_millisec ;
    } while( now != tick_millisec ) ;

    return now ;
}
    

注意 getTickMillisec() 中的循环 - 在 8 位设备上访问 tick_millisec 是 non-atomic,因此 ISR 可能会在读取它的过程中更新。循环 re-reads 直到它一致(即相同的值读取两次)。或者你可以简单地禁用中断:

uint32_t getTickMillisec()
{
    // Read tick count with interrupts disabled to ensure consistency
    cli() ;
    uint32_t now = tick_millisec ;
    sei() ;

    return now ;
}
    

但这通常会影响中断处理程序的时序,因此最好避免。

有关 16 位 TIMER1 的详细信息,请参阅第 75 页的 ATmega8 datasheet