Interrupts on the STM8S
Sunday, September 2nd, 2012A while ago I wrote about using interrupts on the STM8S (see External Interrupts on the STM8S and hinted there that I would come back to the topic and here we are. In this article we will cover the following:
- List of interrupts available and the interrupt table
- Writing your own Interrupt Service Routine (ISR)
- Setting/Changing interrupt priorities
It is probably a good time to remind you that the STM8S Reference Manual (document number RM0016), available from ST’s web site is handy, to keep available for reference. This post is really meant for the application developer who wants to know which interrupts are available and how to use them.
Interrupt Table
The STM8S holds a list of interrupt vectors in a table in memory. As a programmer you are able to add your own ISRs to your application. These are really just conventional methods with a slightly different entry and exit mechanism. You don’t have to worry about how this mechanism works as you can make the compiler do all of the work for you. We shall see how later. The compiler is also be good enough to ensure that the table of interrupt vectors is also updated to point to your code as part of the application start up.
The following table lists the interrupts which are available on the STM8S103F3 microcontroller:
Vector Number | Abbreviation | Description |
1 (0x01) | TRAP | TRAP |
2 (0x02) | TLI | Top Level Interrupt |
3 (0x03) | AWU | Auto Wake Up |
4 (0x04) | CLK | Clock |
5 (0x05) | EXTI_PORTA | External Interrupts for Port A (Pins 2 through 6 inclusive) |
6 (0x06) | EXTI_PORTB | External Interrupts for Port B (All pins) |
7 (0x07) | EXTI_PORTC | External Interrupts for Port C (All pins) |
8 (0x08) | EXTI_PORTD | External Interrupts for Port D (Pins 0 through 6 inclusive) |
9 (0x09) | N/A | Not used |
10 (0x0a) | N/A | Not used |
11 (0x0b) | N/A | Not used |
12 (0x0c) | SPI | SPI |
13 (0x0d) | TIM1_UPD_OVF_TRG_BRK | Timer 1 Update/Overflow/Trigger/Break |
14 (0x0e) | TIM1_CAP_COM | Timer 1 Capture/Compare |
15 (0x0f) | TIM2_UPD_OVF_BRK | Timer 2 Update/Overflow/Break |
16 (0x10) | TIM2_CAP_COM | Timer 2 Capture/Compare |
17 (0x11) | TIM3_UPD_OVF_BRK | Timer 5 Update/Overflow/Break |
18 (0x12) | TIM3_CAP_COM | Timer 3 Capture/Compare |
19 (0x13) | UART_RX | UART Rx |
20 (0x14) | UART_TX | UART Tx |
21 (0x15) | I2C | I2C |
22 (0x16) | UART2_RX | UART 2 Rx |
23 (0x17) | UART2_TX | UART 2 Tx |
24 (0x18) | ADC1 | ADC1 |
25 (0x19) | TIM4_UPD_OVF | Timer 1 Update/Overflow |
26 (0x1a) | EEPROM_EEC | EEPROM EEC |
One thing to remember is that while the interrupt vector numbers remain unchanged the availability of the interrupt vectors will change depending upon the chip you are using. For instance, vector 10 is not used here but on the STM8S208 this is available to process one of the CAN interrupts. The simplest way to find out if an interrupt is available is to look at the header file for your chip. So let’s
So how do you know which interrupts are available for your microcontroller?
The first thing to note is that the vector numbers for interrupts 1-9 are usually the same as these features are available on most of the controllers. Note that whilst interrupt 9 is not available on my chip it is the EXTI_PORTE vector. I have not been able to locate any standard definitions (i.e. #define’s etc) for these interrupt vectors in any of the header files supplied with the compiler.
For the rest of the vectors we will have to start to look through the header files for the microcontroller. Opening up <iostm8s103f3.h> and going to the very end of the file we find a section with the comment Interrupt vector numbers. There should be one or more definitions for each of the features which allow the use of interrupts and which are available on the microcontroller.
One of the things to note about the list of available interrupts is that there are more than one #define statements for each feature. Consider the following extract:
/*-------------------------------------------------------------------------
* Interrupt vector numbers
*-----------------------------------------------------------------------*/
#define SPI_TXE_vector 0x0C
#define SPI_RXNE_vector 0x0C
#define SPI_WKUP_vector 0x0C
#define SPI_MODF_vector 0x0C
#define SPI_CRCERR_vector 0x0C
#define SPI_OVR_vector 0x0C
#define TIM1_CAPCOM_TIF_vector 0x0D
#define TIM1_CAPCOM_BIF_vector 0x0D
#define TIM1_OVR_UIF_vector 0x0D
If we look at the SPI definitions we can see that all of the vectors map to the same interrupt number. This is because there is only one ISR for this (SPI) feature. So the important point to take away is that your ISR must work out which condition it is working with.
Consider the example from the earlier post External Interrupts on the STM8S. This used the following ISR:
#pragma vector = 8
__interrupt void EXTI_PORTD_IRQHandler(void)
{
PD_ODR_ODR3 = !PD_ODR_ODR3; // Toggle Port D, pin 3.
}
This method performed the same action every time this ISR was called. Now this did not matter for the example as we only had one switch attached to Port D. If we had another switch and LED attached to the same port then we would have had to work out which switch had been pressed in order to work out which action to take. Whilst this is simple in the case of switches the same principle applies to other features like SPI. In the case of SPI, the application should interrogate the status registers in order to work out why the ISR has been called.
Writing your own Interrupt Service Routine (ISR)
Writing your own ISR is really no different from writing any other method in C. There are a few extra rules which need to be followed but the majority of the techniques are the same. So let us return to the external interrupt code example:
#pragma vector = 8
__interrupt void EXTI_PORTD_IRQHandler(void)
{
PD_ODR_ODR3 = !PD_ODR_ODR3; // Toggle Port D, pin 3.
}
The first thing you notice is the addition of the #pragma vector = 8 statement. This tells the compiler which interrupt vector we are going to be writing. In this case it is vector 8 which is the EXTI_PORTD interrupt vector (see the table above). You can also use the values found in the header file for you microcontroller. So you could write the following:
#pragma vector = SPI_TXE_vector
instead of:
#pragma vector = 0x0c
If you are using the same method for multiple vectors then you can provide the list of vector numbers as a comma separated list thus:
#pragma vector = SPI_TXE_vector, TIM1_OVR_UIF_vector
Something which is not obvious from the above code is the fact the an ISR cannot take any parameters nor can it return a value. Hence the method definition takes a void parameter list and returns void.
You should also note that it is possible to write an interrupt method (i.e. decorate a method with __interrupt) without providing a #pragma vector statement. In this case the compiler will not generate an entry in the interrupt vector table.
You should also consider how fast the ISR needs to be. In this case, a single button press, we do not need any real efficiency as the microcontroller is not really doing anything overly complex. This may not be the case in non-trivial applications. In which case you will need to analyse your system to determine how fast the ISR needs to be. You also need to take into account the fact that one ISR may be interrupted by a higher priority ISR.
Setting/Changing interrupt priorities
Interrupts can be assigned a software priority from 0 to 3. This allows the interrupts to be nested ensuring that the most important interrupts receive attention over less important interrupts.
Interrupt priority is given by the following:
Priority | Bit Value |
0 | 10 |
1 | 01 |
2 | 00 |
3 | 11 |
Where the priority increases from the top of the table towards the bottom.
By default, all of the interrupts are assigned the same software priority, 3. This effectively means that the priority is disabled and all interrupts are treated as equal by the software. When the software priority is disabled or when two or more interrupts have the same priority then the interrupts are queued for processing
The software interrupt priority for the interrupt vectors (and hence your ISR) is stored in a group of registers. Each register (ITC_SPR1 through ITC_SPR8) holds the priority for four interrupt vectors. At reset all of these priorities are reset to 11 – software priority disabled. The register mapping is given by the following table:
Register | Bits 7:6 | Bits 5:4 | Bits 3:2 | Bits 1:0 |
ITC_SPR1 | VECT3SPR | VECT2SPR | VECT1SPR | VECT0SPR |
ITC_SPR2 | VECT7SPR | VECT6SPR | VECT5SPR | VECT4SPR |
ITC_SPR3 | VECT11SPR | VECT10SPR | VECT9SPR | VECT8SPR |
ITC_SPR4 | VECT15SPR | VECT14SPR | VECT13SPR | VECT12SPR |
ITC_SPR5 | VECT19SPR | VECT18SPR | VECT17SPR | VECT16SPR |
ITC_SPR6 | VECT23SPR | VECT22SPR | VECT21SPR | VECT20SPR |
ITC_SPR7 | VECT27SPR | VECT26SPR | VECT25SPR | VECT24SPR |
ITC_SPR8 | – | – | VECT29SPR | VECT28SPR |
Changing the priority is a simple matter of identifying the vector and changing the appropriate register (ITC_SPR1_VECT1SPR for vector 1, ITC_SPR1_VECT2SPR for vector 2 etc.).
Conclusion
Much of the information we have covered here is adequate for the average user intending to develop a module for the Netduino GO!. For more advanced topics such as what happens in low power modes, how interrupts are handled, nested interrupt handling etc. then you should refer to Chapter 6 in RM0016.