Skip to main content

STM32 USART Pt. 2 : Interrupts

In the previous post I showed you guys how to setup the USART in its most basic mode which is UART with no interrupts. In this second part I will explain the register bits and relevant code to configure the UART in interrupt mode. Interrupt allows for non-blocking reception and transmission of our data. Our program is free to do other task and not concern itself with the constant polling of the status register. 





Control Register 1:

From the previous post you will recall the Control Register 1. Whip out your datasheet and take a look or just follow along here. Below you see a screenshot of CR1 and I have highlighted the relevant bit that we must set to enable desired interrupts.
  • TXEIE   : TX Empty Interrupt Enable: By enabling this bit we will get an interrupts when the TX buffer, in other words the data register, is empty.
  • TCIE     : Transfer Complete Interrupt Enable: This will generate an interrupt once the data transmission is complete.
  • RXNEIE  : This will enable an interrupt to be generated when we have received data.

The NVIC:

Below is an snapshot of the NVIC table found on page 199 of RM0008 . The left most value is the position which is equivalent to the IRQ number. The next  value is the priority. The "settable" means that the priority is settable. next is the peripheral acronym, followed by the description and ultimately on the far right is the address where the microcontroller will jump to expecting to find an interrupt handler. One neat trick to remember is that the name of the interrupt handling function is just like the acronym plus "_IRQHnadler". So the interrupt handling function for I2C2_ER is nothing more than

void  I2C2_ER_IRQHandler(void)

Remember that interrupt handling functions, or interrupt servicing routines (ISR) should always be of void return type and take void arguments. If you can comment below and tell me why this is so, you will get a total of 1 cookie. 



Also notice  in the image above that the USART interrupts are described as global. This means we do not get an interrupt handler for specifically for each event like RX not empty or TX empty or Transmit complete. We have to use the same exact interrupt handler for all of them and we will not know which one of the 3 generated the interrupt unless we check the status register in our interurpt routine. So what I am saying here is that you HAVE to check your status register in the ISR to find out which interrupt was generated and why you have landed in the ISR, once you know which flag is set in the status register you handle it accordingly. Check out the code below. Skim through it and I will explain the new lines afterwards. This code is the same code for the basic UART code in the last post. I will only explain the new lines. 




 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
/* Includes */

#include "stm32f10x.h"

int main(void) {

 //-----------------------| UART CODE |------------------------------------------
 //USART1 / GPIOA clock enable
 RCC->APB2ENR |= RCC_APB2ENR_USART1EN | RCC_APB2ENR_IOPAEN;

 //remaping if needed
 //AFIO->MAPR |= AFIO_MAPR_USART1_REMAP ; //remap RX TX to PB7 PB6

 //pin configurations: PA9- TX is set to ALternate-push-pull and 50MHz
 GPIOA->CRH |= GPIO_CRH_MODE9 | GPIO_CRH_CNF9_1;
 GPIOA->CRH &= ~(GPIO_CRH_CNF9_0);

 /*
  * PIN PA10 is the RX pin and it has to be set to input and FLOATING
  * this is the rest value of the pin so we really dont have to do anything to it.
  *
  * */

 //USART DIV value
 USART1->BRR = 0x1D4C; //for 72MHZ on APB1 bus

 //enable RXNE and TXE interrupts on USART SIDE
 USART1->CR1 |= USART_CR1_RXNEIE | USART_CR1_TXEIE;

 //----------|  RX enable        TX enable     UART enable
 USART1->CR1 |= USART_CR1_RE | USART_CR1_TE | USART_CR1_UE;

 //ENABLE interrupt for USART1 on NVIC side
 NVIC_EnableIRQ(USART1_IRQn);

 while (1) {

 }

}

void USART1_IRQHandler(void) {

 //check if we are here because of RXNE interrupt
 if (USART1->SR & USART_SR_RXNE) //if RX is not empty
 {
  char temp = USART1->DR; //fetch the data received
  USART1->DR = temp;  //send it back out
  while (!(USART1->SR & USART_SR_TC))
   ;

 }

 //check if we are here because of TXEIE interrupt
 if (USART1->SR & USART_SR_TXE) //if RX is not empty
 {
  //handle transmit completion here

 }

}


Line 28 Enables the inteerupts on the UART. ANd line 34 enables the interrupt on the NVIC block. Lines 42 through 61 are my ISR. As you can see in line 45 I am checking the STATS register to see if RXNE is set, because then that would have generated the interrupt and explain why I am in the ISR and I handle the code accordingly by inserting the exact same cod I had for the echo program in the last post. If you recall the echo program was all in my while loop, but now it is not. My while loop is free to do other things.
Line 55 I am checking to see if I am in the ISR because my Transmit register is empty... I chose not to implement nothing in that case but you surely can if you need to. 
Remember to keep your ISR routines short and sweet. You are halting the main program. Also your ISR routines should not call other routines, its best to use flags and state or any kind of variable and then have you main loop check for those flags and call the appropriate routines.
Dont forget to check out the video version of these tutorials on my YouTube channel
Well kiddos that about does it for this. simple UART  post. Peace!

Comments

  1. hi bro.
    i wathed your USART DMA education on youtube.it was very good.
    next i come to your site to visiting it code...
    but i could not find it...
    please put your code here or give me your **github link** or other thing.
    thank you

    ReplyDelete

Post a Comment

Popular posts from this blog

Interface a Rotary Encoder the right way.

So whilst reading my favorite odyssey found here I stumbled onto something I never spotted before. It turns out that the general purpose timers support hardware interfacing with an incremental encoder. This means no more interrupts and no need to increment whatever variable you had. Now all you have to do is get the value from the Count register in the timer. Did I also mention it takes care of figuring out which direction you are turning it? Amazing! Lets talk about it.






Encoder Interface Mode Verbatim from the reference manual of the STM32F103 page 392 or Section 15.3.12  "Encoder interface mode acts simply as an external clock with direction selection. This 
means that the counter just counts continuously between 0 and the auto-reload value in the 
TIMx_ARR register (0 to ARR or ARR down to 0 depending on the direction). So the user 
must configure TIMx_ARR before starting. In the same way, the capture, compare, 
prescaler, trigger output features continue to work as normal."
W…

STM32L0/F0/F3 I2C : Part 3 RX TX via DMA

Most modern microcontroller come with a peripheral called DMA which allows for an even more hands-off approach. The Direct Memory Access controller will get a tutorial of it's own in the future. However, it is so simple to use that I can easily explain the required bits in this tutorial without feeling like I will overwhelm the reader. In this iteration of the I2C series I will cover how to TX and RX date on the peripheral in conjunction with the DMA controller. 

First let me briefly explain in a high level what the DMA controller does. It allows you to transfer data in 3 ways: Peripheral to memoryMemory to peripheralMemory to memory. What does this mean? For example, when you receive data on I2C in your RXDR register, you can have the DMA controller move that data received into an array. At the end of the day your variables are just memory locations, so if you have declared an array it has an address associated with it, this is address is what the DMA considers  "Memory"…

STM32 I2C Does it suck?

Recently I have been annoyed with  bugs in the I2C implementation in ST's F1/L1/F40/F2 series, and  any microcontroller made by ST before 2012. There appears to be, what I can only describe as, a race condition when attempting to receive data in I2C interrupt(low priority)  or polling mode.





Just to be clear a race condition is defined as , verbatim from wiki: "the behavior of an electronics, software, or other system where the system's substantive behavior is dependent on the sequence or timing of other uncontrollable events. It becomes a bug when one or more of the possible behaviors is undesirable."
I was humming along trying to make my YouTube tutorials when I arrived to the I2C protocol implementation. I have used I2C with the F1 series plenty of times, however I have never had the need to receive data from a slave. However for the sake of completeness I decided to implement an RXing routine in I2C for my tutorial. Only to find statements like this in the referen…