Messing With Stm32 Discovery Interrupts

Note:

This is a bunch of old blog posts i recovered from old website. Some of the information in there might not be longer correct.

Sooo.... like I promised last time, we will talk about interrupts. We will check out two, very, VERY useful types of interrupts that you can use on the stm32. This will be only a tiny fraction of the gazillions different interrupts available on the chip. I'm not gonna go into too much detail what interrupts are but the basic principle is that a process handled by the microcontroller is interrupted and the event that generated the interrupt is handled first. If you think about it you will see the benefit of this mechanism because what is the alternative to this? The alternative is to wait for an event, however waiting it is doing nothing until something happens :) So with interrupts you can do something productive and when interrupts happens...you deal with it then. The interrupts in stm32 are handled by NVIC (Nested Vectored Interrupt Controller) system. When I was at school and playing with PIC or 8051 microcontrollers whenever interrupt occurred you had to save the context of your program yourself. So you had to store the PC and the register values in a variable, handle the interrupt and then restore your context. If you lost it then your code would crash and the teacher would laugh at you and shout out hurtful things when he would pass you by in the hall or lock you in the cleaning closet for the night and give sixpack of beer to a janitor so he would get drunk and ignore the noises coming from the school toilet. So that taught us to always remember about the context. However mistakes could still happen But in stm32 NVIC handles all context saving and restoring upon entering and leaving the interrupt handler. So that your code can focus on the event handling instead. Ok, when I mentioned gazzylions of interrupts on stm32 I might have exaggerated it by a tiny bit. There is 90 interrupt vectors available on the stm32f401 chip that i'm using (I recommend you to read the RM0368 Reference manual for the chip, I found the chapter about the interrupts very informative). Interrupt vector is a way of mapping a interrupt event to a interrupt handler (a function). This is how you tell your microcontroller what to do if interrupt happens. You remember the .s assembly file in our project directory? The one we stole from STM32_Cube library. It contains something like below:

g_pfnVectors:
.word  _estack
.word  Reset_Handler
.word  NMI_Handler
.word  HardFault_Handler
.word  MemManage_Handler
.word  BusFault_Handler
.word  UsageFault_Handler
.word  0
.word  0
.word  0
.word  0
.word  SVC_Handler
.word  DebugMon_Handler
.word  0
.word  PendSV_Handler
.word  SysTick_Handler
/* External Interrupts */
.word     WWDG_IRQHandler
/* Window WatchDog              */
.word     PVD_IRQHandler
/* PVD through EXTI Line detection */
.word     TAMP_STAMP_IRQHandler
/* Tamper and TimeStamps EXTI line */
.word     RTC_WKUP_IRQHandler
/* RTC Wakeup through the EXTI line */
.word     FLASH_IRQHandler
/* FLASH                        */
.word     RCC_IRQHandler
/* RCC                          */
.word     EXTI0_IRQHandler 
/* EXTI Line0                   */
.word     EXTI1_IRQHandler
/* EXTI Line1                   */
.word     EXTI2_IRQHandler
/* EXTI Line2                   */

Its just the part of it. You can have a look at the one in your directory. Each of the .word keyword is at a memory address to where the Program Counter will jump when the corresponding interrupt occurs. And then it will execute the code in the assigned interrupt routine. The very first interrupt, Reset_Handler, is the Mother of All Interrupts. It triggers every time the microcontroller starts and executes the code that initializes the whole system and then redirects the program flow to main function. After that you have a bunch of handlers that trigger when something bad happens to perform some recovery or error handling. Very often in the default code those handlers contain a while(1){}. Which is ok for the initial development but in actual operation you want to reset the device and get it up and running as quickly as possible. After that you have vectors to the handlers you need to explicitly enable: SysTick_Handler, EXTI, RCC, etc. Something to note is that even though you will have only one vector for i.e USART module you might in fact have more then 1 events that will cause the interrupt. So for USART an interrupt can be caused by: Transmit Data Register Empty, CTS flag , Transmission Complete, Parity Error etc... Its up to you to check in your interrupt handler what EXACTLY caused the interrupt. So when i mentioned gazzylion interrupt at the start of the post i wasn''t that far off after all because even though we have 90 interrupt vectors we have in fact probably about ~250 interrupts. That almost gazyllion... One more think that has to be mentioned about interrupts is their priority. Obviously the higher the priority the "more important" the interrupt. But be careful, the lower the value the higher the priority so priority of 2 is higher that the priority of 7. Ok, lets move to the examples now. We will set two interrupts. Remember in previous post how we used the button to read the state? We were polling the button right? Now we will configure the button to generate the interrupt whenever it is pressed. And we will also generate a SysTick_Handler so that we can start using some precise delays in our code. So lets start with the SysTick, lets create the systick.c with the following:

#include <stm32f401xc.h>
#include "systick.h"

/* volatile cause used in interrupt */
static volatile uint32_t the_tick = 0;

void kg_systick_init(){
	/* 1 processor tick with AHB/8 occurs
	every 100ns but we want 1us ticks
	so load it with 10-1
	*/
	SysTick->LOAD |= (9 << 0);
	SysTick->VAL = 0;
	/* AHB/8 int enable */
	SysTick->CTRL &= 0;
	SysTick->CTRL = (3 << 0);
	/* set NVIC priority */
	NVIC_SetPriority(SysTick_IRQn, 0);
	/* enable */
	NVIC_EnableIRQ(SysTick_IRQn);
}

void kg_systick_increment(){
	the_tick++;
}

uint32_t kg_systick_get_tick(){
	return the_tick;
}

Now lets enable interrupts for EXTI which will handle the pin interrupt. In gpio.c add this to init funtion:

/* now the button interrupt settings */
SYSCFG->EXTICR[0] &= SYSCFG_EXTICR1_EXTI0_PA; 
/* select GPIOA pin0 */
EXTI->RTSR |= EXTI_RTSR_TR0; 
/* rising edge enable */
EXTI->IMR |= EXTI_IMR_MR0; 
/* enable interrupt */
/* set NVIC priority to 1 */
NVIC_SetPriority(EXTI0_IRQn, 1);
/* enable */
NVIC_EnableIRQ(EXTI0_IRQn);

The NVIC_SetPriority() and NVIC_EnableIRQ() are macros provided by CMSIS library. You can have a look at how they are implemented in the stm32f401xc.h header file. So now we need the file where we can implement our interrupt handlers. We can stick them in in to the system_it.c file.

#include <stm32f401xc.h>
#include "system_it.h"
#include "systick.h"
#include "gpio.h"
#include "utils.h"

void SysTick_Handler(void){
	kg_systick_increment();
}

void EXTI0_IRQHandler(void){
	if(EXTI->PR & EXTI_PR_PR0){
		kg_delay(5000);
		while(kg_gpio_pin_get(GPIOA, GPIO_PIN_0));
		flashMode ^= 1;
		/* clear it by setting it to 1 */
		EXTI->PR |= EXTI_PR_PR0;
	}
}

All whats left now to do is to call the init functions in the main.

#include "systick.h"
#include "utils.h"
#include "gpio.h"

/* led flashing mode 
	toggled in the 
	EXTI interrupt routine
*/

int flashMode = 1; 
/*start in flash mode*/
int main(){
	kg_systick_init();
	kg_gpio_init();
	while(1){
		/* flash slow */
		kg_gpio_basic_flash(SET_ms(250));
		while(flashMode){
			/* flash fast */
			kg_gpio_basic_flash(SET_ms(50));
		}
	}
	return 0;
}

Now just run the make and upload the code into the stm32 board and behold this beautiful manifestation of the engineering effort ;) This magnificent software will change the flashing speed of the 4 LED''s with nothing but a press of a button.