Search code examples
cembeddedavratmega

AVR C Programming two functions on button press with delay


I am really new to AVR Programming. I have an ATMEGA8 and want to make something like this:

If you push a Button a LED should turn on and off 10 times. WORKS. But as long as you push the button a piezo should make a sound. The problem is that I am not able to do this two functions at the same time.

Blink LED function

 int i;
 void led(void) 
 {
     for (i = 0; i < 10; i++)
     {
         PORTB |= (1 << PB0);  //LED on
         _delay_ms(250);       //wait 250ms

         PORTB &= ~(1 << PB0); //LED off
         _delay_ms(250);       //wait 250ms
     }
 }

Main Code:

while (1)
{
    if (!(PINB & (1<<PB7)) )
    {
        PORTB |= (1 << PB1); // Piezo on
        led();
    }
    else
    {
        PORTB &= ~(1 << PB1); // Piezo off
    }
}

The piezo stays on until the blink led function is over even if I just push the button for a short time.

Sorry for the bad English skills. I hope you understand my problem and maybe you can help me.


Solution

  • The simplest method of achieving your aim is perhaps to poll the button in the LED loop as you already are in the button loop. However as a general solution it has poor cohesion and is not extensible to more complex applications.

    A general purpose method of achieving concurrency, without resorting to interrupts or a multi-tasking scheduler is to use a state-machine for the LED control.

    Rather than calling the led() and requiring it to complete the entire flash sequence before returning it, the state-machine would simply determine whether the button was pressed and whether it was time to change teh LED state and then return immediately. It keeps track of time and state, but does not perform a complete LED sequence in one call.

    statemachine

    This is implemented below, however note that the timing is crude and implemented using the delay function you have already used - because I cannot tell what other services are available to you. If any processing were to take any significant time, this may affect the flash timing. It relies on calling the LED state-machine every 10ms, and it simply counts calls. It would be better for the state-machine to use an independent clock source (the standard library clock() function for example), then it would not matter if it was called aperiodically - rather than counting the number of calls, it would switch state on the actual time passed.

    Note the use of static variables to maintain state. A static maintains its value between calls to the function.

    #define TICK 10            // milliseconds 
    #define FLASH_ON_TICKS 25  // 250ms
    #define FLASH_OFF_TICKS 25 // 250ms
    #define FLASH_COUNT 10
    
    static void ledUpdate( int start_flashing ) ;
    
    int main( void )
    {
      for(;;)
      {
        // Perform loop on each tick (10ms)
        // Assumes loop processing time is not significant! 
        _delay_ms( TICK ) ;
    
        if (!(PINB & (1<<PB7)) )
        {
          PORTB |= (1 << PB1); // Piezo on
          ledUpdate( 1 ) ;  
        }
        else
        {
          PORTB &= ~(1 << PB1); // Piezo off
          ledUpdate( 0 ) ;  
        }
      }
    
      return 0 ;
    }
    
    void ledUpdate( int start_flashing )
    {
      static enum
      {
        LED_IDLE,
        LED_FLASH_ON,
        LED_FLASH_OFF
    
      } led_state = LED_IDLE ;
    
      static int led_tick = 0 ;
      static int flash_count = 0 ;
    
      switch( led_state )
      {
        case LED_IDLE :
        {
          if( start_flashing )
          {
            led_state = LED_FLASH_ON ;
            PORTB |= (1 << PB0);  //LED on
          }
        }
        break ;
    
        case LED_FLASH_ON :
        {
          led_tick++ ;
          flash_count++ ;
          if( led_tick >= FLASH_ON_TICKS )
          {
            led_state = LED_FLASH_OFF ;
            led_tick = 0 ;
            PORTB &= ~(1 << PB0); //LED off
          }
        }
        break ;
    
        case LED_FLASH_OFF :
        {
          if( flash_count >= FLASH_COUNT )
          {
            led_state = LED_IDLE ;
          }
          else
          {
            led_tick++ ;
            if( led_tick >= FLASH_ON_TICKS )
            {
              led_state = LED_FLASH_ON ;
              led_tick = 0 ;
              PORTB |= (1 << PB0);  //LED on
            }
        }
        break ;
      }
    }
    

    Note that the button state only affects the LED if it is not already flashing, and the ten cycles complete even if the button is released. If you want the flashing to stop when the button is released, then start_flashing must be tested in the LED_FLASH_OFF and possibly the LED_FLASH_ON state and cause an early return to LED IDLE. For example:

    statemachine2

    The method could easily be adapted to running the LED state-machine on a timer interrupt. Instead of ledUpdate() taking the button state as a parameter, this could be communicated to an interrupt handler via a shared variable. The rest of the state machine code would remain the same. The main loop would then simply set the shared variable while the button was down.

    Personally I'd advocate separating and encapsulating the piezo control, button polling, and LED control such that the main loop looked like:

    int main()
    {
        for(;;)
        {
            int button_state = getButtonState() ;
            upodatePiezo( button_state ) ;
            updateLed( button_state ) ;
        }
    }
    

    That would have stringer cohesion and looser coupling. Two useful aims in software design to achieve maintainability and reusability of code.