5 Wake-Up On Button Press
This example demonstrates the usage of interrupts and sleep modes. In this use case, the microcontroller exits sleep on button press, turns on an LED, and goes back to sleep. On button release, it exits sleep, turns off the LED, and goes back to sleep. The LED is ON while the button is pressed, but the microcontroller can be in sleep mode.
Pins can detect transitions from ‘0
’ to ‘1
’ (rising
edge) and from ‘1
’ to ‘0
’ (falling edge). An interrupt
can be triggered on one or both transitions. This is configured using the ISC bit field
in the PORTx.PINnCTRL register.
These interrupts wake the device from all sleep modes. In this example, on button press/release, the microcontroller will wake from sleep and turn an LED on/off and then go back to sleep. In this way, the LED will stay on only while the button is pressed.
First, the B2 pin, where the button is attached, is configured as input, and its interrupt is activated.
PORTB.DIR &= ~ PIN2_bm; PORTB.PIN2CTRL |= PORT_PULLUPEN_bm | PORT_ISC_BOTHEDGES_gc;
0
’. Therefore, whenever the
button is not pressed, it must be connected to VDD using a pull-up resistor.
The pull-up resistor is activated using the PULLUPEN bit field in the PORTx.PINnCTRL
register.Second, the sleep mode is selected using the macros defined in the
<avr/sleep.h>
header. The modes of sleep are Idle, Standby
and Power-Down. In this example, the deepest sleep mode is used.
set_sleep_mode(sleep_MODE_PWR_DOWN);
This only selects which sleep mode to be used. The following macro makes the microcontroller go to sleep.
sleep_mode();
sleep_mode()
macro
also enables sleep before sending the CPU sleep
instruction and
disables sleep when the microcontroller wakes up.When going to sleep, the global interrupts must be enabled. Otherwise, there is no way to
wake up. Using the sei()
macro defined in the
<avr/interrupt.h>
header enables global interrupts. In this
example, this is done in the initialization code.
Sometimes, there are blocks of instructions that need to execute without
interruption. They are called atomic blocks. This is achieved by disabling interrupts at
the beginning of the block and re-enabling them at the end of the block. For this, the
ATOMIC_BLOCK
macro in <avr/atomic.h>
is
used.
Finally, inside the Interrupt Service Routine (ISR), a flag is set to indicate a transition (rising or falling edge) happened. Because it is a good practice to keep the ISR short, the transition type check and LED toggling are handled in the main-line code.
ISR(PORTB_PORT_vect)
{
if(PB2_INTERRUPT)
{
pb2Ioc = 1;
PB2_CLEAR_INTERRUPT_FLAG;
}
}
Because the same interrupt is used for all the pins in a port, the ISR code must check what pin triggered the interrupt. This is done by checking its flag in PORTx.INTFLAGS. In this example, to check whether the pin B2 triggered the interrupt, the following macro is used.
#define PB2_INTERRUPT PORTB.INTFLAGS & PIN2_bm
Clearing the interrupt flag is done by writing a ‘1
’ to its
location in PORTn.INTFLAGS.
#define PB2_CLEAR_INTERRUPT_FLAG PORTB.INTFLAGS &= PIN2_bm
The main-line code checks the pin value ten milliseconds after the interrupt
to decide whether it was a falling edge or a rising edge. If the pin is low (value
‘0
’), then it must have been a falling edge, otherwise it was a
rising edge. The ten milliseconds delay is a denouncing method.
#define PB2_LOW !(PORTB.IN & PIN2_bm)
An MCC generated code example for AVR128DA48 with the same functionality as the one described in this section can be found here: