Interrupts and Timers#
Interrupts and timers are critical features in embedded systems that enable the microcontroller to respond quickly to external and internal events without continuously polling. Interrupts allow the MCU to react to changes in hardware, such as a button press, while timers enable precise timing and periodic execution of tasks. In this chapter, we’ll cover the fundamentals of configuring and using interrupts and timers, as well as best practices for writing efficient interrupt service routines.
Understanding Interrupts and Their Role#
n interrupt is a signal that tells the microcontroller to temporarily pause its current task and execute a specific function, called an Interrupt Service Routine (ISR). This mechanism enables the MCU to respond quickly to critical events without waiting for the main program loop to check for them.
Types of Interrupts:
External Interrupts: Triggered by external events, such as a button press or signal from a sensor.
Internal (Peripheral) Interrupts: Triggered by internal events, such as a timer overflow or completion of an ADC conversion.
How Interrupts Work: When an interrupt occurs, the MCU saves its current execution state, jumps to the ISR, executes the ISR code, and then resumes the main program from where it left off. This sequence is known as interrupt handling.
Setting Up and Using Timer Interrupts#
A timer is a peripheral in the microcontroller that counts up or down at a specific frequency. Timers are useful for creating delays, measuring time intervals, and triggering periodic tasks.
Configuring a Timer:
Set the Timer Mode: Most MCUs offer several timer modes, including up-counter, down-counter, and PWM generation modes.
Set the Prescaler: The prescaler divides the system clock, allowing the timer to count at a slower frequency. For example, if the system clock is 16 MHz and the prescaler is set to 16, the timer will count at 1 MHz.
Define the Timer Overflow Value: Set a value at which the timer overflows or resets, triggering an interrupt if enabled.
Example Code for Timer Interrupt (C):
void setupTimer() {
TIMER_SetPrescaler(16); // Set prescaler
TIMER_SetOverflow(1000); // Set overflow value for 1 ms
TIMER_EnableInterrupt(); // Enable interrupt on overflow
}
void timerISR() {
// Code to execute every 1 ms
}
Timer Interrupts for Periodic Tasks: Timer interrupts are commonly used to run tasks at regular intervals. For example, a timer interrupt can be used to sample a sensor every 10 milliseconds or update a display every 100 milliseconds.
External and Internal Interrupt Sources#
Interrupt sources can come from either external pins or internal peripherals. Understanding and configuring these sources effectively is crucial for responsive and efficient embedded applications.
External Interrupts: External interrupts are triggered by changes on GPIO pins, such as a button press or a sensor signal. You can configure an external interrupt to trigger on specific events:
Rising Edge: Triggered when the signal changes from low to high.
Falling Edge: Triggered when the signal changes from high to low.
Both Edges: Triggered on any signal change.
Example Code for External Interrupt on Button Press:
void setupButtonInterrupt() {
GPIO_SetMode(GPIO_PIN_2, GPIO_MODE_INPUT); // Set pin as input
GPIO_EnableInterrupt(GPIO_PIN_2, EDGE_RISING); // Trigger on rising edge
}
void buttonPressISR() {
// Code to execute on button press
}
Internal Interrupts: These are triggered by internal peripherals, such as:
Timer Overflow: Triggered when a timer reaches its set overflow value.
ADC Conversion Complete: Triggered when an analog-to-digital conversion is complete.
USART Transmit/Receive Complete: Triggered when data transmission or reception is completed on a UART interface.
Each interrupt source typically has its own ISR, allowing you to handle various events independently.
Designing an Effective Interrupt Service Routine (ISR)#
The ISR is a function that executes in response to an interrupt. Writing efficient ISRs is critical, as they temporarily halt the main program, potentially delaying other operations.
Best Practices for Writing ISRs:
Keep It Short and Fast: Avoid complex operations in the ISR. Perform only the essential actions and defer more complex processing to the main program.
Avoid Blocking Calls: Blocking functions (like delays or loops) can cause missed interrupts and latency. Avoid them in ISRs.
Use Volatile Variables: Variables shared between the ISR and the main program should be declared as volatile to ensure the compiler does not optimize them away.
Avoid Global Variables if Possible: Minimize the use of global variables in ISRs, as they can lead to conflicts with the main code. Use flags or counters to signal the main loop instead.
Example of Using a Flag in an ISR:
volatile int buttonPressed = 0;
void buttonPressISR() {
buttonPressed = 1; // Set flag to indicate button press
}
void main() {
while (1) {
if (buttonPressed) {
// Handle button press in main code
buttonPressed = 0; // Reset flag
}
}
}
Using flags in this way minimizes ISR workload, as the main loop can handle non-critical processing.
Priority and Latency Considerations#
Some microcontrollers allow you to assign priorities to different interrupts, which determines the order in which they are handled if multiple interrupts occur simultaneously.
Interrupt Priority: Higher-priority interrupts can interrupt lower-priority ISRs, which can be useful in real-time applications where certain tasks (like safety functions) must take precedence.
Latency: The time delay between an interrupt trigger and ISR execution is called interrupt latency. Minimizing latency is essential in time-critical applications, and it can be achieved by keeping ISRs short and optimizing overall code execution.
When designing your system, carefully consider which interrupts require higher priority based on their time sensitivity.
Common Timer Applications#
Timers are versatile peripherals that can be used for a variety of functions beyond simply generating delays. Here are some common applications:
Pulse Width Modulation (PWM): Many timers can generate PWM signals, which are useful for controlling motor speed, LED brightness, and servo position.
Event Timing and Duration Measurement: You can use a timer to measure how long an event lasts, which is useful for applications like frequency measurement or sensor pulse timing.
Periodic Tasks: As discussed, timers can trigger periodic interrupts for regularly occurring tasks, such as sensor sampling, data logging, or display updates.
Watchdog Timers: A watchdog timer is a special timer that resets the system if it isn’t periodically reset by the program. This feature helps prevent system hang-ups by automatically rebooting the MCU if it becomes unresponsive.
Example Code for PWM Signal Generation:
void setupPWM() {
TIMER_SetMode(TIMER_PWM_MODE); // Set timer to PWM mode
TIMER_SetDutyCycle(50); // Set duty cycle to 50%
}
PWM is particularly useful for analog-like control in embedded systems, allowing for finer control of devices like motors and LEDs.
Using Timers for Real-Time Systems#
Timers are often used in real-time systems, where tasks need to execute precisely within defined intervals. In such systems, timing accuracy is crucial, and timers provide the foundation for task scheduling.
Tick Timer: A timer that generates regular interrupts (ticks) at a fixed interval can serve as the basis for a real-time operating system (RTOS) scheduler or a timebase for periodic tasks.
Task Scheduling: By using timers to trigger periodic tasks, you can implement a basic scheduling mechanism. For instance, tasks like sensor polling, data processing, and communication can each be scheduled at different intervals based on the timer ticks.
Timers in real-time systems allow you to develop precise, predictable, and time-sensitive applications, even without a full RTOS.
Summary#
This chapter provided an in-depth look at interrupts and timers in embedded systems, covering everything from setting up external and internal interrupts to configuring timers for various applications. You learned how to write efficient ISRs, set interrupt priorities, and use timers for periodic tasks, PWM generation, and real-time applications.
With a solid understanding of interrupts and timers, you’re now ready to explore communication protocols and more advanced timing techniques, as these foundational skills are key to handling real-world events in embedded systems.