<center> <h4> EEE 158 1st Sem AY 2022-2023 </h4> <h1> STM32 Interrupts and Timers</h1> </center> *Notes before proceeding:* * The lengths of the required videos are indicated inside the parentheses for your reference. * The given sample codes are for reference only and are not meant to be run as they are unless otherwise stated. * The definitions of SFRs here are summarized; check the datasheet for more information. # Introduction For this week, we will explore 2 more peripherals that are essential for most MCU applications. The first is the interrupt controller which effectively allows multitasking by allowing asynchronous events trigger special subroutines and change MCU operation momentarily. Timers, on the other hand, provide further utility to the MCU as a precise source of delay or source of a periodic interrupt. # Learning Outcomes * Configure the STM32 interrupt controller. * Use external interrupts for asynchronous inputs. * Configure STM32 timer modules for precise delays and periodic subroutines. * Implement interrupts with the STM32 timers. # Summary of Activities <center> <table> <tr> <th width=20%>Topic</th> <th width=30%>Activity</th> <th width=30%>Assessment</th> <th width=20%>Estimated Time (hours)</th> </tr> <tr> <td rowspan=3>STM32 Interrupts</td> <td><a href="#STM32-Interrupts">Video (Overview)</a></td> <td></td> <td>0.50</td> </tr> <tr> <td><a href="#Example-External-Interrupt">Video (External Interrupt)</a></td> <td></td> <td>0.33</td> </tr> <tr> <td></td> <td><a href="#Exercise-1-Pushy-Pushy">Coding Exercise</a></td> <td>0.75</td> </tr> <tr> <td rowspan=3>STM32 Timer Peripheral</td> <td><a href="#STM32-Timers">Overview Handout</a></td> <td></td> <td>0.25</td> </tr> <tr> <td><a href="#Example-Delays-with-Timers">Example Code</a></td> <td></td> <td>0.42</td> </tr> <tr> <td></td> <td><a href="#Exercise-1-Pushy-Pushy">Coding Exercise</a></td> <td>0.75</td> </tr> <tr> <td></td> <td>Optional Synchronous Discussion/Consultation</td> <td> </td> <td>0.50</td> </tr> <tr> <th width=20%>Total</th> <th width=30%> </th> <th width=30%> </th> <th width=20%> 3.50 hours </th> </tr> </table> </center> # STM32 Interrupts ***Interrupts*** are asynchronous (i.e. can happen anytime) events that disrupt the normal flow of your program. This allows the microcontroller to focus on a key task and attend to these events (e.g. pressing a button) as they come without needing to wait for them. Professor Zhu provides us with an overview of interrupts in STM32 MCUs in [this video (20:42)](https://youtu.be/uFBNf7F3l60). ## Configuring an Interrupt Properly harnessing the power of interrupts may sound like a daunting task. However, any interrupt can be usually configured in 5 steps: 1. **Configure the peripheral** that would be generating the interrupt . 2. **Take note of the `position` and `acronym` of the interrupt** in Table 37 of Chapter 10 of the [STM32F411 reference manual](https://uvle.upd.edu.ph/mod/resource/view.php?id=345803). The `position` is also referred to as the *Interrupt Request Number* (`IRQn`) and the `acronym` will be useful to use predefined pointers/macros. 3. **Set the interrupt priority by writing on the interrupt priority register `NVIC->IP[IRQn]`**. Each interrupt is assigned one byte or 8 bits but only the 4 upper bits are used for the STM32F411 series. This means there are a total of 16 priority levels. 4. **Enable the interrupt itself by setting the appropriate `NVIC->ISER` bit**. Since the STM32F411 supports up to 85 different interrupt sources, `ISER` is actually an array of 3 registers. The following is the general *formula* to set the correct bit: `NVIC->ISER[IRQn >> 5] |= (1 << (IRQn % 32)`. 5. **Define your interrupt handler or also known as the Interrupt Service Routine (ISR)**. The name of the ISR is defined in the header files but is usually `acronym_IRQHandler`. The ISR is the function that is called whenever the interrupt is triggered and usually needs to acknowledge the triggering of the interrupt in some way. To learn more about the Nested Vector Interrupt Controller (NVIC) and its registers, you can watch [this video (optional)](https://youtu.be/K0vmH2YGbOY). The video also contains some CMSIS functions you can use to help with configuring the NVIC. ## Example: External Interrupt Of course, the best way to understand configuration is doing it yourself. To help you out, below is the sample code to configure the *external interrupt* for `PC13` or our onboard pushbutton: ```c= void Interrupt_Init(void){ EXTI15_10_Init(); // Step 1, this is defined elsewhere // Step 2: // EXTI15_10 is the acronym for the external interrupt for PC13 // EXTI15_10_IRQn = 40 since it is position 40 in Table 37 NVIC->IP[EXTI15_10_IRQn] = (1 << 4); // Step 3: Set priority to 1 NVIC->ISER[40 >> 5] |= (1 << (40 % 32)); // Step 4: Enable interrupt } // Step 5: Define EXTI15_10_IRQHandler void EXTI15_10_IRQHandler(void){ // Do something return; } ``` [This video (12:49)](https://youtu.be/uKwD3JuRWeA) goes over the details in configuring external interrupts and below is an example for the configuration for `PC13` based on the `SYSCFG` registers in Chapter 7 of [STM32F411 reference manual](https://uvle.upd.edu.ph/mod/resource/view.php?id=345803): ```c= void PB_Init(void){ RCC->AHB1ENR |= (1<<2); // Enables GPIOC peripheral GPIOC->MODER &= ~(3<<26); // PC13 as Input GPIOC->PUPDR &= ~(3<<26); // PC13 with NO pull-up, NO pulldown } void EXTI15_10_Init(void){ PB_Init(); // Initialize PA13 as input. RCC->APB2ENR |= (1<<14); // Enable System configuration controller SYSCFG->EXTICR[3] &= ~(1<<7); // Select Port C as source, EXTIx = 0b0010 SYSCFG->EXTICR[3] &= ~(1<<6); // Select Port C as source, EXTIx = 0b0010 SYSCFG->EXTICR[3] |= (1<<5); // Select Port C as source, EXTIx = 0b0010 SYSCFG->EXTICR[3] &= ~(1<<4); // Select Port C as source, EXTIx = 0b0010 EXTI->IMR |= (1<<13); // Disable interrupt request mask on EXTI line 13 EXTI->FTSR |= (1<<13); // Enable EXTI on Falling edge EXTI->RTSR &= ~(1<<13); // Disable EXTI on Rising edge RCC->APB2ENR &= ~(1<<14); // Disable System configuration controller } // Requirement for the IRQHandler void EXTI15_10_IRQHandler(void){ // Do something // ... // ... EXTI->PR |= (1<<13); // Clear PR to re-enable EXTI interrupt } ``` Note that for our configuration, we are only enabling the interrupt generation on the falling edge of the input. Recall that our input switch comes with a pull-up resistor and that pressing the switch causes a falling edge. Still, we only have the instructions to configure the interrupt. In order to actually use the interrupt, let us make the LED toggle states whenever the interrupt is called with the following extra code and modification of the ISR: ```c= void LD_Init(void){ RCC->AHB1ENR |= (1<<0); // Enables GPIOA peripheral GPIOA->MODER &= ~(1<<11); // PA5 as Output, MODER = 0b01 GPIOA->MODER |= (1<<10); // PA5 as Output, MODER = 0b01 GPIOA->OTYPER &= ~(1<<5); // Sets GPIOA, PIN 5 as push-pull GPIOA->ODR |= (1<<5); // PA5 initially HIGH } int main(void){ LD_Init(); Interrupt_Init(); /* Loop forever */ while(1); } // Final Definition void EXTI15_10_IRQHandler(void){ GPIOA->ODR ^= (1<<5); // Toggle LED EXTI->PR |= (1<<13); // Clear PR to re-enable EXTI interrupt } ``` By combining all of the sample code given above you should find that the LED toggles from being `ON` and being `OFF` on each button press like so: <center> <img src="https://drive.google.com/uc?export=view&id=1eBoiiPSb7NxnJbPO6EAznPg8fIeRdoLv"/> <br /> </center> On TINA cloud, you can also upload your code and make use of the interactive transient mode. Alternatively, you may load up the [example circuit uploaded on UVLe](https://uvle.upd.edu.ph/mod/resource/view.php?id=349584) with the push button replaced by an electronically controlled switch. After a 5-second transient simulation, you should get the following result: <center> <img src="https://drive.google.com/uc?export=view&id=1eva4LUFJNdYBEdqK75ABv935WX0Qy0mh"/> <br /> </center> `VG` is the control signal of the switch (i.e. when `VG` is *HIGH*, the button is pressed). `OB_B1` is the signal going into `PC13` and `OB_LD1` is the output at `PA5` which powers up the LED. Notice that when the button is pressed, a falling edge can be seen on `OB_B1`. *What do you think would we need to change if we had a switch with a pull-down resistor instead?* ## Semaphores Semaphores, in the context of MCU interrupts, are global variables that are modified by the interrupt handlers and read in the main code. An example would be `pressed` in the code below: ```c= volatile unsigned int pressed; int main(void){ pressed = 0; LD_Init(); Interrupt_Init(); /* Loop forever */ while(1) { if (pressed) { GPIOA->ODR ^= (1<<5); // Toggle LED pressed = 0; } } } // Definition with Semaphore void EXTI15_10_IRQHandler(void){ pressed = 1; EXTI->PR |= (1<<13); // Clear PR to re-enable EXTI interrupt } ``` In this example, we have exactly the same behavior as our code above but with a different code. Use of semaphores greatly reduces the overhead for interrupt handlers. Remember that the MCU can only really do one thing at once. If it is busy servicing an interrupt, then it is *not* doing its main thing. It also becomes more easier for us programmers to take note of multiple interrupts that could occur in our program especially if you would like to wait for a combination of interrupts to occur. # Exercise 1: Pushy Pushy (4 points) Either modify the code above or start from scratch, accomplish the following functionality: * On the *first button press*, the LED turns `ON` on the *moment of pressing* the button and *stays* `ON` when the button is released. * On the *second button press*, the LED turns OFF when the button is released. * Subsequent button presses will alternate between these (i.e. `ON` on the third button press, `OFF` on the fourth button release, and so on...). That looks like this on the board: <center> <img src="https://drive.google.com/uc?export=view&id=18qO-TGDS6jozPo7CZw17QStPSlKsL1rv"/> <br /> </center> <br /> Or like this on TINA: <center> <img src="https://drive.google.com/uc?export=view&id=1GmVlZLbj87GCHaZrjW7t6JRPg3XsTns4"/> <br /> </center> <br /> Some things that may or may not help: 1. Interrupts and peripherals can be configured and *re*-configured at *any* time during the program but be careful since interrupts can also get triggered during configuring of interrupts. 2. When using TINA, you may find the LED in a unexpected state or the pressing the switch does nothing during the start of your program and that's okay. 3. You can consider the MCU as a state machine. # STM32 Timers Timers, as the name suggests, allow the microcontroller to measure periods of time. It does this by counting the number of clock cycles that have elapsed since the timer was activated. Alternatively, timers are also used to generate interrupts after a programmed time period has elapsed. To learn how timers work, in general, you can watch [this video from the previous offering for EEE 153 (17:16)](https://youtu.be/KfWFrxRhHEY). STM32F411RE devices contain up to a total of 7 general purpose 16-bit timers (`TIM2` to `TIM5`, and `TIM9` to `TIM11`) plus one advanced purpose 16-bit timer (`TIM1`). For this module we will be focusing on the general purpose timers and use them to generate precise delays and the following are the essential registers to take note of: 1. **Timer Clock Enable** (`RCC->APBxENR[y]`): In order for a timer to time correctly, it needs a clock input. By default, our timers use the peripheral bus clock and to enable it for our timers, the specific `APBxENR` bit must be set. To know the specific bit to set, we can always consult the [STM32F411 reference manual](https://uvle.upd.edu.ph/mod/resource/view.php?id=345803) 2. **Timer Enable** (`TIMx->CR1[0]`): As with any other peripheral, our timer modules need to be enabled. Do note that timers can be enabled and disabled at any time. 3. **Timer Count Register** (`TIMx->CNT`): We can read the current counter value of the timer from this register. It is good practice to set this register to `0` before we enable the counter to ensure that we get our expected delay. 4. **Timer Auto-Reset Register** (`TIMx->ARR`): When the timer value reaches the value set on the period register, the timer value is reset to `0` and an interrupt is generated, if enabled. This register is also referred to as the timer *period register*. 5. **Update Generation Bit**(`TIM2->EGR[0]`): Set this bit to `1` in order to automatically reset all necessary registers to restart counting. 6. **Timer Prescaler Register** (`TIMx->PSC`): The prescaler *slows down* the input clock in order to generate delays that are several times higher than the input clock frequency. For the STM32, the effective input clock frequency ($F_{eff}$) to the timer can be calculated as: $$F_{eff} = \frac{F_{in}}{PSC + 1}$$ where $F_{in}$ is the input clock frequency, usually $16MHz$, and $PSC$ is the prescaler value written on the `TIMx->PSC`. ## Example: Delays with Timers Consider the configuration of `TIM2` below: ```c= void Tim2_Init(void){ RCC->APB1ENR |= (1<<0); // Enable clock for TIM2 TIM2->PSC = 16000-1; // Set PSC+1 = 160000 TIM2->ARR = 0xFFFF; // Reset at maximum CNT value TIM2->EGR |= (1<<0); // Reset timer counter registers TIM2->CNT = 0; // Manually reset CNT (needed in TINA) TIM2->CR1 |= (1<<0); // Enable timer, CR1[0] = 1 } ``` At the given `PSC` value and assuming a peripheral clock of $16 MHz$, we get the following effective frequency: $$F_{eff} = \frac{16 \times 10^{6}}{16 \times 10^{3}} = 1000 Hz$$ This means that each `CNT` corresponds to $1ms$ elapsing. So, if we modify our [main code earlier](https://hackmd.io/@hrbenitez/158_2s2223_Interrupts#Semaphores) like so: ```c= volatile unsigned int pressed; int main(void){ pressed = 0; LD_Init(); Interrupt_Init(); Tim2_Init(); /* Loop forever */ while(1) { if (pressed) { TIM2->EGR |= (1<<0); // Reset timer counter registers TIM2->CNT = 0; // Manually reset CNT (needed in TINA) TIM2->CR1 |= (1<<0); // Enable timer while (TIM2->CNT < 100) { // While CNT is below 100 GPIOA->ODR |= (1<<5); // LED ON } GPIOA->ODR &= ~(1<<5); // LED ON pressed = 0; // Clear Semaphore } } } ``` We can get the following behavior when we push the button: <center> <img src="https://drive.google.com/uc?export=view&id=18fL9pIIsFR_pnus6m7ns1IW9cH7PGSeM"/> <br /> </center> <br /> *Blink and you'll miss it*. We can also run it on TINA and get the following waveform: <center> <img src="https://drive.google.com/uc?export=view&id=19-PvuYHUD2BPdamM5SNwVdiKZELMyTMh"/> <br /> </center> <br /> Notice that the LED lights up for about $100ms$ at each button press. Try and modify the condition of the loop in line `14` above and observe what happens. Because of the prescaler value that we set, when `TIM2->CNT` reaches `100`, $100ms$ have elapsed and that is precisely the amount of time `LD1` is `ON`. So some might think why would we need to use timers if the `delay()` function we used previously works fine? Well that's because timers are generally more flexible and our main program can do other things while it waits for the timer to finish counting like this modification of the infinite loop above: ```c= while(1) { if (pressed) { TIM2->EGR |= (1<<0); // Reset timer value TIM2->CR1 |= (1<<0); // Enable timer while (TIM2->CNT < 1000) { // While CNT is below 1000 if (TIM2->CNT % 100 > 50) { GPIOA->ODR |= (1<<5); // LED ON } else { GPIOA->ODR &= ~(1<<5); // LED OFF } } GPIOA->ODR &= ~(1<<5); // LED ON pressed = 0; // Clear Semaphore } } ``` What this does is left as an exercise to the reader :) ## Example: Timers as Interrupts Perhaps more importantly, timers can be used as interrupt sources. By default, general purpose timers generate an interrupt when the `CNT` and `ARR` registers become equal. In addition to the usual interrupt registers and timer registers above, the following bits need to be taken note of: 1. **Interrupt Generation Enable Bit** (`TIM2->DIER[0]`): Setting this bit to `1` enables the timer to generate interrupts. 2. **Interrupt Pending Flag** (`TIM2->SR[0]`): MCU hardware sets this bit to `1` if an interrupt has been triggered. We have to set this bit to `0` in order to receive new timer interrupts. There are several others that configure the behavior of the timer interrupt but these are the only ones needed for our example. Including NVIC configuration, our `Tim2_Init()` will now look like this: ```c= void Tim2_Init(void){ RCC->APB1ENR |= (1<<0); // Enable clock for TIM2 TIM2->PSC = 16000-1; // Set PSC+1 = 160000 TIM2->ARR = 100; // Set timer to reset after CNT = 100 TIM2->DIER |= (1<<0); // Enable timer interrupt generation NVIC->IP[TIM2_IRQn] = (2 << 4); // Set priority to 2 NVIC->ISER[TIM2_IRQn >> 5] |= (1 << (TIM2_IRQn % 32)); // Enable interrupt TIM2->SR &= ~(1<<0); TIM2->EGR |= (1<<0); TIM2->CR1 &= ~(1<<0); // Disable timer, for now } ``` We can also use another semaphore so that our main code and interrupt handlers look like this: ```c= volatile unsigned int pressed; volatile unsigned int waiting; int main(void){ pressed = 0; waiting = 0; Interrupt_Init(); Tim2_Init(); LD_Init(); /* Loop forever */ while(1) { if (pressed) { TIM2->EGR |= (1<<0); // Reset timer TIM2->CR1 |= (1<<0); // Enable timer waiting = 1; while (waiting) { GPIOA->ODR |= (1<<5); // LED ON } GPIOA->ODR &= ~(1<<5); // LED OFF pressed = 0; } } } void TIM2_IRQHandler(void){ waiting = 0; TIM2->SR &= ~(1<<0); // Clear UIF update interrupt flag } void EXTI15_10_IRQHandler(void){ pressed = 1; EXTI->PR |= (1<<13); // Clear PR to re-enable EXTI interrupt } ``` This should have the same result with the [previous example](#Example-External-Interrupt). It would be important to note that at line`5` of `Tim2_Config` we set `ARR` to `1000` which was the `CNT` value we were using for the delay loop earlier. ## Example: Periodic Interrupts Alternatively, we can use interrupts generated by timers to run a periodic subroutine through the timer interrupt handler. The usual usage for this is to generate a square wave with a fixed frequency as follows: ```c= int main(void){ LD_Init(); Tim2_Init(); TIM2->CR1 |= (1<<0); // Enable timer /* Loop forever */ while(1) { } } void TIM2_IRQHandler(void){ GPIOA->ODR ^= (1<<5); // LED ON TIM2->SR &= ~(1<<0); // Clear UIF update interrupt flag } ``` Note that `Tim2_Init` is the same as the one used in the [previous example](#Example-Timers-as-Interrupts). Our development board or TINA simulation should look like something similar to our example code in the [last module](https://hackmd.io/@hrbenitez/158_2s2223_GPIO#Register-Level-Programming). # Exercise 2: Pushy Blinky (7 points) Either modify the example code given or start from scratch, accomplish the following functionality: * The on-board LED is `OFF` at system reset and the system clock is set to $16MHz$. * When the on-board switch is pressed, the LED will toggle between `ON` and `OFF` states. * The LED will continue to toggle as long as the switch remains pressed. * The toggling of the LED should form a square wave when viewed on TINA, the frequency of the square wave is $(2+X) Hz$ where $X$ is the last digit of your student number. * When the on-board switch is released, the LED will retain its last state and remain in that state until the switch is pressed again. At the minimum square wave frequency of $2 Hz$ it will look like this on the development board: <center> <img src="https://drive.google.com/uc?export=view&id=1eiXmibmKsRP5VsBF9p1gxQ0gVhyPTGeN"/> <br /> </center> <br /> At the maximum square wave frequency of $11 Hz$ the output waveforms will look like this: <center> <img src="https://drive.google.com/uc?export=view&id=18faAgnUzHDYmJF8hEAxlSdXyud9SvzET"/> <br /> </center> <br /> Some things that may or may not help: * You need to use interrupts to handle inputs because of the limitations of TINA. * You are not required to use interrupts for the timers but it is recommended. * Both `ARR` and `PSC` are 16-bit registers so make sure the values written on these can be represented by 16 bits. * Semaphores become more useful if more interrupts are involved. * A $5Hz$ square wave means that the LED blinks 5 times in 1 second. # Submission Requirements Submit one `.zip` file containing the following: 1. The main `.c` file for [Exercise 1](Exercise-1-Pushy-Pushy). Name this file `<section>_<surname>_Week4_Ex1.c`. 2. The main `.c` file for [Exercise 2](Exercise-2-Pushy-Blinky-Pushy). Name this file `<section>_<surname>_Week4_Ex2.c`. Include your name, section, and student number as a comment in your code. [Link to submission bin](https://uvle.upd.edu.ph/mod/assign/view.php?id=350306)