0% found this document useful (0 votes)
176 views83 pages

Diving Into HAL

yy
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF or read online on Scribd
0% found this document useful (0 votes)
176 views83 pages

Diving Into HAL

yy
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF or read online on Scribd
You are on page 1/ 83
Il Diving into the HAL 6. GPIO Management With the advent of the STCube initiative, ST has decided to completely revamp the Hardware Abstraction Layer (HAL) for its STM32 microcontrollers. Prior to the release of the STCube HAL, the official library to develop STM32 applications was for a long time the Standard Peripheral Library. Despite of the fact it is still widespread between STM32 developers, and you can find a Jot of examples on the web using this library, the STCube HAL is a great improvement respect of the old Standard Peripheral Library. In fact, being the first library developed by ST, not all of its parts were consistent between different STM32 families and a lot of bugs were present in the early versions of the library. This caused the emergence of different alternatives to the Standard Peripheral Library, and the official software from ST is still considered poor by many people. So, ST has completely redesigned the HAL and, even if it still needs a little bit of tuning, itis what ST will officially support in the future. Moreover, the new LAL simplifies a lot the porting of code between the STM32 sub-families (Fo, F1, etc.) reducing the effort needed to adapt your application to a different MCU (without a good abstraction layer, the pin-to-pin compatibility is just an advantage from the marketing point of view), For this and several other reasons, this book is based exclusively on the STCube HAL. ‘This chapter starts our journey inside the HAL looking to one of its simplest modules: HAL_GP10. We have already used many functions from this module in the early examples in this book, but now it is the right time to understand all possibilities offered by a so simple and commonly used peripheral However, before we can start describing HAL features, it is best to give a quick look to how the ‘STM32 peripherals are mapped to logical addresses and how they are represented inside the HAL library. 6.1 STM32 Peripherals Mapping and HAL Handlers Every STM32 peripheral is interconnected to the MCU core by several orders of buses, as shown in igure 1' “Tere, to simplify this topie, we are considering the bus organization of one of the simplest STMS2 microcontrollers, the 'STM32F030, STMS2Fé and STMS2F7, for example, have a mate advanced bus interconnection system, which is outside the scope this book. Please, always refer to the reference manual of your MCU. GPIO Management 178 SYSCFG, Reset and ADC, clock ‘TMs, Tims, controler TIM6, TIM7, (RCC) TIM14 to TIMI7, IWDG, Wwo8, RTC, PWR, 12Ct, 1202, USART1 to USART6, SPI, SPI2, DeGMcU DMA requests Figure 1: Bus architecture of an STM32F030 microcontroller + The System bus connects the system bus of the Cortex-M core to a BusMatrix, which manages the arbitration between the core and the DMA. Both the core and the the DMA act as masters. + The DMA bus connects the Advanced High-performance Bus(AHB) master interface of the DMA to the BusMatrix, which manages the access of CPU and DMA to SRAM, flash memory and peripherals. + The BusMatrix manages the access arbitration between the core system bus and the DMA master bus. The arbitration uses a Round Robin algorithm. The BusMatrix is composed of two masters (CPU, DMA) and four slaves (flash memory interface, SRAM, AHB1 with AHB to Advanced Peripheral Bus(APB) bridge and AHB2). AHB peripherals are connected on system bus through a BusMatrix to allow DMA access. + The AHB to APB bridge provides full synchronous connections between the AHB and the APB bus, where the most of peripherals are connected. ‘As we will see in a later chapter, each of these buses is connected to different clock sources, which determine the maximum speed for the peripheral connected to that bus” In Chapter 1 we have learned that peripherals are mapped to a specific region of the 4GB address space, starting from @x4000 0000 and lasting up to OxSFFF FFFF. This region is further divided in several sub-regions, each one mapped to a specific peripheral, as shown in Figure 2. *For some of you the above description may be unclear and too complex to understand, Don’t worry and keep reading the next content inthis chapter. They will become clear once you reach the chapter dedicated to the DMA. GPIO Management —~— | : el SRAM. Figure 2: Memory map of peripheral regions for an STM32F030 microcontroller 179 ‘The way this space is organized, and hence how peripherals are mapped, is specific of a given STM32 ‘microcontroller. For example, in an STM32F030 microcontroller the AHB2 bus is mapped to the region ranging from 0x4800 0000 to @x4800 17FF. This means that the region is 6144 bytes wide. “This region is further divided in several sub-regions, each one corresponding to a specific peripheral. Following the previous example, the GPIOA peripheral (which manages all pins connected to the PORTA) is mapped from 0x4800 0000 to @x4800 03FF, which means that it occupies IKB of aliased peripheral memory. How this memory-mapped space is in tum organized depends on the specific peripheral. Table 1° shows the memory layout of a GPIO peripheral. Ee ee ee ee ee a om 4 » 8 0 7 6 ODERIGI‘ | MODERT«| | MODERTAI-0}| MODERTA‘O] | MODERTHI-O| | MODERIOT‘O} | MODEREI-O| | MODERETTO] 4 8 a 88 76 6 4 3-2 10 ‘movER7'9) | MODERES} nee a ne wooeRatta) | moDeRsi1@) | MoDERO) Bits 2y+1:2y MODERyf1: |: Port x configuration bits (y = 0..15) ‘These bits are written by software to configure the VO mode, (00: input mode (reset state) 01: General purpose output mode 40: Altemate function mode 11: Analog mode Figure 5: GPIO MODER register memory layout ‘Both Tablet and Figure 1 are taken from the ST STMS2P0s0 Reference Manual (http//bitly/IGIS3iC). 180 GPIO Management = le[ aioe le i [ouai)= [ouco fo [osa[ooonl= |e Ie = [ouale a fo:sou300" F fo: oNaG0N 10. lolowaaadso [2 foxlowauna = +°8S* jouoo JS Lose Je foots |e ee oe i iS Sf uis fe Sao seiner ed 5 Pe fe se z = le | aio fe le] i [zwai)= [zoo [> [esa [> [orl |” ole” efawsl= = eanoon 2 wanacon E22 osnsoaiso 2foanonse] 22 moo [eseleloale |g” BE” elaele v lel le| no |e lel ie [vuail<[ruao le lesa lel mmle|2 jel lelrwale J [!osiesa00n (2 tewsaon 2} "422 iostzsaa34s0 [= losizwaana [PHO PCOS rSa TS wAoT|S Becher nae moon 2d epee, 121 emaanal® ose!) [suo | [ss [> [oxos]> |” fo/2 fo [ewe lo Z_[lvee300n Je fT 110 fo | PFHOR#«SO 5 |lOleMadNA TST ail [wool [usa lelainrfole [ele le[ rele ato, lel le [ao |e 121s syaoan 2 levar) [ecole [ese le [mole |e ole fo|eusl= 8 To smuacon > = [a olor hea facel [as foals, 1» elu i ee S140 7° jp uaazdso |e lorlswaana {=e Hwa TS Josey owe ee Ee ouuate a le lef tuo fe le fo fusaf*|ursools usalefion=[& fe [uve] zh le le [zis0 [> le [> [ersai)= [zraaole [zisale fewole | ole le lesle Et osimacon = ET eis [seems 5» inna Es ee [ost enove Eg Blea vb lel [>| ro |e le] [2 [ruall= [rruaole |visale roe |?” le |e [> |rvwale sr_|!ses300n fe [siu0 fo | ™tMsos3Hs0 5 |loslasoand sal fensaolelaisalesooe|& lef |e leusle oF lo le le lo le bone |e jelF le FF totewaoon fo} — vuoazas0 [SY lovlsuaane {= 5 # bese 8b le le le le ie 3? lolz fo Sts suacon [= E bvsuc33280 (2 vieuoana = < e EE oz i i le le S ep eee 92H sruacon|=eor4soon|= s.waases0|e fostoruoane= < e EEE we. le le le. le. a 3? lols? fo 22s wusaonl© festaaconle estazav00[2 fostwucunde - e Eee ve I i - le le = eels #2 Ho nenssaonfefouienss00nf= loslerwoaassof lo sien oanef— < e EEE 3 ie i ie = = ais B21 snaoon| ene wacom louniuaaseso|efosienuaane|= e @ EEE a ie i i ie = eek BEL. saconl evr wacom snwaaseso|@ fosiwaane= E e eee 0 = ie le le fe 35 (ES OF ersaoon| fevensaconl= vsswaases0| fousaane= E @ cee 5 ae fs ge g « alele@l tal les £4) BB RE a) Bg l/8 (a) 83 (gIBs Es (glee Bes eB |2 3 8) g Bos 3 | 5 Celene 7 lai eles eleleslt 3 ai lei) 3 3 ge lies ge Belglss 3 g 33 a3 3 2/8: ERS R S| edge gi ge Eg BvSEEye: SESE RIES! 3 g a 3 i 8 |e] 8 |e] s |i la] s ‘Table 1: GPIO peripheral memory map for an STM32F030 microcontroller Aperipheral is controlled modifying and reading each register of these mapped regions. For example, GPIO Management 181 continuing the example of the GP10A peripheral, to enable PAS pin as output pin we have to configure the MODER register so that bits[11:10] are configured as @4 (which corresponds to General purpose output mode), as shown in Figure 3. Next, to pull the pin high, we have to set the corresponding bit(5] inside the Output Data Register(ODR), which according Table 1 is mapped to the GPIOA + x14 memory location, that is @x4800 0000 + oxt4 ‘The following minimal mapped memory in an STM3: le shows how to use pointers to access to the GPIOA peripheral 0 MCU, int main(void) { volatile uint22_t “GPIOA MODER = 0x0, *GPIOA_QDR = x0; ODER register Address of the GPIOA->0DR register GPIOANODER = (uint32_t~)ox48000000; Address of the 6PI0A GPIOA_ODR = (uint92_t+)(ox45000000 + Oxt4); / // This ensure that the peripheral is enabled and connected to the AHB1 bus HAL_RCC_OPTOA_CLK_ENABLE(); “GPIOA_MODER = *GPTOAMODER | 0x100; // Sets Ho SGPIOA_OOR = *GPIOA_ODR | 0x20; // Sets ODR| while(t); 4:40] = oxt ] = Oxt, that is pulls PAS high It is important to clarify once again that every STM32 family (Fo, Fi, ete.) and every member of the given family (STM32F030, STM32F1, etc.) provides its subset of peripherals, which are mapped to specific addresses. Moreover, the way how peripherals are implemented differs between STM32- One of the HAL roles is to abstract from the specific peripheral mapping. This is done by defining several handlers for each peripheral. A handler is nothing more then aC struct, whose references are used to point to real peripheral address. Let us see one of them, In the previous chapters, we have configured the PAS pin with the following code: /*Contigure GPIO pin = PAS +/ GP10_InitStruct.Pin = GP1O_PIN. GPIO_InLtStruct Mode = GPIO_MODE_oUTPUT_pP; HAL_GPIO_Init(GPIOA, EGPIO_Initstruct) ; Here, the GPIOA variable is a pointer of type GPIO_typeDef defined in this way: GPIO Management 182 typeder struct { volatile uint32_t MODE! volatile uints2_t OTYPER; volatile vint32t OSPEEOR; volatile vint32_t PUPOR, volatile uint32_t IDR; volatile wint32_t OR; volatile vintaz_t BSRR; volatile uintaz_t LCKR; volatile uintaz_t AFR(2]; volatile uint32_t BRR; } GP10_typebet; ‘The i (0A pointer is defined so that it points‘ to the address @x4800 0000: GPIO_Typedef *GPIOA = ex12000000; GPIOA-»MODER |= @x400; GPIOA-»008 |= @x20; 6.2 GPIOs Configuration Every STM32 microcontroller has a variable number of general programmable I/Os. The exact number depends on: + The type of package chosen (LQFP48, BGA176, and so on). + The family of microcontroller (Fo, Fi, etc). + The usage of external crystals for HSE and LSE. GPIOs are the way an MCU communicates with the extemal world, Every board uses a variable number of I/Os to drive external peripherals (e.g. an LED) or to exchange data through several types of communication peripherals (UART, USB, SPI, etc.). Every time we need to configure a peripheral that uses MCU pins, we need to configure its corresponding GPIOs using the #AL_GP10 module. As seen before, the HAL is designed so that it abstracts from the specific peripheral memory mapping. But, it also provides a general and more user-friendly way to configure the peripheral, without forcing the programmers to now how to configure its registers in detail. "To configure a GPIO we use theHAL_GPTO_Init(GPTO_TypeDef *GP1Ox, GPTO_InitTypeDef *GPIO_- Init) function. GP10_InitTypedef is the C struct used to configure the GPIO, and it is defined in the following way: “This not exactly tue, since the HAL, to save RAM space, defines GPTOR as a macro (define GPIOR ((GPI0_Typede# *) cPt0A Base) GPIO Management 183 typeder struct { wint92_t Pin; uint32_t Node; uint32_t Pull; uint32_t Speed uint32_t Alternate; } GPIO_InitTypebes; This is the role of each field of the struct: + Pin: it is the number, starting from 0, of the pins we are going to configure. For example, for PAS pin it assumes the value GP1O_PIN_S°. We can use the same GP10_Init'ypeDef instance to configure several pins at once, doing a bitwise 08 (¢.g., GP10_PINA | GPIO_PINS | GP1O_- PINs). + Mode: itis the operating mode of the pin, and it can assume one of the values in Table 2. More about this field soon. + Pull: specifies the Pull-up or Pull-Down activation for the selected pins, according Table 3. + Speed: defines the pin speed. More about this later. + Alternate: specifies which peripheral to associate to the pin. More about this later. Table 2: Available GPI0_tnittypeDes. Mode for a GPIO Pin Mode Description (GP10_NODE_INPUT Input Floating Mode * (GP10_NoDE_ouTPUT_P> Output Push Pull Mode (GP10_NODE_oUTPUT_pD Output Open Drain Mode (6P10_NODE_AF_PP Altemate Function Push Pull Mode (6P10_MODE_AF 00 Alternate Function Open Drain Mode (GPIO_HODE_ANALOS ‘Analog Mode (GP10_MODE_IT_RISING External Interrupt Mode with Rising edge trigger detection (GP10_MODE_IT_FALLING External Interrupt Mode with Falling edge trigger detection, (GPI0_HODE_IT_RISING_FALLING External Interrupt Mode with Rising/Falling edge trigger detect (GP10_HODE_EVT_RISING External Event Mode with Rising edge trigger detection (GP10_MODE_FVT_FALLING External Event Mode with Falling edge trigger detection (OP1O_MODE_FVT_RISING_FALLING External Event Mode with Rising/Falling edge trigger detection “Take note that the GP10_PIN_x isa it mask, where the i-th pin coresponds tothe Fh bit faints t datatype. For example, ‘the oP10_PIN.s has a value of 60920, which s $2 in base 10 “During and just after reset, the alternate functions are not active and all the I/O ports are configured in Input Floating mode. GPIO Management 184 ‘Table 3: Available GPI0_Inittypebef .Pul1 modes for a GPIO Pin Mode Description (GP1O_NOPULL No Pull-up or Pull-down activation P10 _PULLUP. Pull-up activation GP10_PULLOOWN Pull-down activation 6.2.1 GPIO Mode ‘STM32 MCUs provide a really flexible GPIOs management. Figure 4’ shows the hardware structure ofa single I/O of an STM32F030 microcontroller. Toonenp peroneal, ‘ed ExT Trou daa reise Lingus 10 pin ‘pd eta epiner i 1 Resdwte i i From onchip Porpsoal” Asean wut Figure 4: Basie structure of an UO port bit Depending on the GPIO GP10_InitTypeDet. Mode field, the MCU changes the way the hardware of an I/O works, Let us have a look to the main modes. ‘When the 1/0 is configured as GP10_MoDE_INPUT + The output buffer is disabled + The Schmitt trigger input is activated. + The pull-up and pull-down resistors are activated depending on the value of the Put! field. + The data present on the I/O pin are sampled into the input data register every AHB clock cyde. + Arread access to the input data register provides the I/O state. "The figure is taken from the ST STMS2R030 Reference Manual (htps/bity/1G8S8iC. (GPIO Management 185 ‘When the 1/0 port is programmed as GP10_MODE_ANALOS: + The output buffer is disabled. + The Schmitt trigger input is deactivated, providing zero consumption for every analog value of the /O pin. + The weak pull-up and pull-down resistors are disabled by hardware. + Read access to the input data register gets the value 0. When the 1/0 port is programmed as output: + The output buffer is enabled as follow: ~ iffmode is GP10_MODE_OUTPUT_0D: A in the Output register (ODR) activates the N-MOS whereas a1 leaves the port in Hi-Z (the P-MOS is never activated); ~ if mode is GP10_moDE_ourPUT_pP: A @ in the ODR activates the N-MOS whereas a4 activates the P-MOS. + ‘The Schmitt trigger input is activated. + The pull-up and pull-down resistors are activated depending on the value of the Pu field. + The data present on the /O pin are sampled into the input data register every AHB clock cycle + A read access to the input data register gets the I/O state + Aread access to the output data register gets the last written value When the 1/0 port is programmed as altemate function: + The output buffer can be configured in open-drain or push-pull mode. + The output buffer is driven by the signals coming from the peripheral (transmitter enable and data). + The Schmitt trigger input is activated. + The weak pull-up and pull-down resistors are depending on the value of the Pull field + The data present on the I/O pin are sampled into the input data register every AHB clock cycle. + Arread access to the input data register gets the I/O state. ‘The GPIO modes GP10_MoDE_£v1_* are related to sleep modes. When an I/O is configured to work in one of these modes, the CPU will be woken up (when placed in sleep mode with aWFE instruction) if the corresponding I/O is triggered, without generating the corresponding interrupt (more about this topic in a following chapter). The GPIO modes GPT0_MODE_IT_* modes are related to interrupts management, and they will be analyzed in the next chapter. GPIO Management 186 However, keep in mind that this implementation scheme can vary between the STM32-families, especially for the low-power series. Always refer to the reference manual of your MCU, which exactly describes /O modes and their impact on the MCU working and power consumption. Itis also important to remark that this flexibility represents an advantage for the hardware design too. For example, there is no need to use external pull-up resistors to drive FC devices, since the corresponding GPIOs can be configured setting GPIO_Ini typedef Node = GPYO_NODE_OUTPUT_PP and GPI0_InitTypeDef.Pull = GPIO_PULLUP. This saves space on the PCB and simplifies the BOM. Figure 5: Pin Configuration dialog can be used to configure /O mode VO mode can be eventually configured using the CubeMX tool, as shown in Figure 5. Pin Configuration dialog can be reached inside the Configuration view, clicking on the GPIO button. 6.2.2 GPIO Alternate Function Most of GPIOs have “altemate functions’, that is they can be used as I/O pin for at least one intemal peripheral. However, keep in mind that an /O can be associated to only one peripheral at a time GPIO Management 187 g 5 & Figure 6: CubeMX can be easily used to discover alternate functions of an VO To discover which peripherals can be bound to an I/O, you can refer to the MCU datasheet or simply use the CubeMX tool. Clicking on a pin in the Pin View causes a pop-up menu to appear. In this menu we can set the wanted alternate function. For example, in Figure 6 you can see that PAS can be used as USART2_RX (that is, it can be used as RX pin for USART/UART2 peripheral, and this is possible for every STM32 MCU with LOFP48 package). CubeMX will automatically generate the right initialization code for us, as shown beloy /* Configure GP10 pins : PAZ PAI +/ GP10_InitStruct Pin = GP1O_PIN_2|GP10_PIN 3; GPIO_InitStruct.Mode = GPIO_NODE_AF_PP; GPIO_InitStruct.Pull = GPIO_NOPULL; GPIO_InitStruct..Speed = GP1O_SPEED_LOW; GP1O_InitStruct Alternate ~ GPIO_AF1_USART2; HAL_GPIO_Init(GPIOA, eGPIO_Initstruct) ; Those of you working on an STM32F1 MCU will notice that the GPIO_InitTypeDef. Alternate field is missed in the CubeF1 HAL. This happens because STM32F1 MCUs have a less flexible way to define alternate functions of a pin. While other STM32 microcontrollers define the possible alternate functions at GPIO level (by configuring dedicated registers GPTOx_AFRL and GPTOx_AFRH), allowing to have up to sixteen different alternate functions associated of a pin (this only happens in packages with high pin count), GPIOs of an STM32F1 MCU have really limited remapping capabilities. For ‘example, in an STM32F103RB MCU only the USARTS can have two couple of pins that can be used as peripheral I/O alternatively. Usually, two dedicated peripheral registers, AFI0_MAPR and AFIO_MAPR2 “remap” signal I/Os of those peripherals allowing this operation. GPIO Management 188 ‘This is essentially the reason why that field is not available in CubeF1 HAL. 6.2.3 Understanding GPIO Speed One of the most misleading things of STM32 microcontrollers is the GP10_InitTypeDef Speed parameter. This field can assume the values from Table 4 and it has effect only when the GPIO is configured in output mode. Unfortunately, ST has not adopted a consistent name for those constants inside the different Cube HALs Table 4: Available Speed modes for a GPIO (CubeF0/1/3/L0/LA CubeFa/La (GP10_SPEED_LOW (GP10_SPEED_FREQ_LOW P10_SPEED_NEDIUM (GP10_SPEED_FREQ_MEDIUM GP10_SPEED_FAST GP10_SPEED_FREQ_HIGH GP10_SPEED_HIGH® Speed. A so sweet word for anybody loving performances. But what does it exactly means when it refers to a GPIO? Here a GPIO speed is not related to switching frequency, that is how many times a pin goes from ON to OFF ina unit of time. The GP10_InitTypeDef. Speed parameter, instead, defines the slew rate of a GPIO, that is how fast it goes from the OV level to VDD one, and vice versa. Figure 7: Slew rate effect on a square wave - red=desired output, green=actual output Figure 7 clearly shows this phenomenon. The red wave is the one that we will get if the response speed was maximum, and therefore there was no response delay. In practice, what we get is that shown by the green wave. But how much does this parameter impact on the slew rate of an STM32 I/O? First of all, we have to say that every STM32 family has its I/O driving characteristics. So you need to check the datasheet of your MCU inside the Absolute Maximum Ratings section, Next, we can use this simple test to ure the slew rate (the test is conducted on a Nucleo-F446RE board) "These modes are available only in somte high performance version of STMS2 MCUs, Check the reference manual for your cu. GPIO Management 189 int main(void) { GPIO_InitTypeDef GPIO_Initstruct, HAL_Init(); _HAL_ROC_GPTOG_CLK_ENABLE(); /* Configure GPIO pin > PCA +/ GPIO_Initstruct.Pin = GPIO_PINA; GPIO_InitStruct Mode = GP1O_NODE_OUTPUT_PP; GPLO_InitStruct.Pull = GPIO_NOPULL; GPIO_InitStruct Speed = GPIO_SPEED_FREQ_LOW; HAL _GP1O_Init(GP1OC, 2GPIO_InitStruct); /* Configure GPIO pin: PCB +/ GPIO_InitStruct Pin ~ GPIO_PIN_8; GPIO_InitStruct Mode = GPIO_NODE_OUTPUT_PP; GPIO_InitStruct.Pull ~ GPIO_NOPULL; PIO_InitStruct Speed = GPIO_SPEEO_FREQ_VERY HIGH; HAL _GPIO_Init(GPIOC, 8GPIO_InitStruct) ; mhite(t) { GPIOC-200R = ox:10; GP10c->008 = 0; } ‘The code is really self-explaining, We are configuring two pins as output I/Os. One of them, PCS, is configured with a GP10_SPEED_FREQ_LOW speed. The other one, PC8, with GP10_SPEED_FREQ_VERY_- HIGH. Figure & shows the difference between the two pins. As we can see, the PCé speed is about 25MHz, while the speed of PCS pin is about 50MHz’. 2 Unfortunately, my oscilloscope probes have a load capacitance two high to conduct a precise measurement. According to ‘STM32P446RE datasheet, ts maximum switching frequency is 90MIiz, when Cy, ~ 80 pF, VDD > 2.7 V and the compensation cell ‘is activated, But I was not able to obtain those results, due the poor oscilloseape and probably thanks tothe length ofthe traces ‘connecting the Nucleo morpho hesder and the MCU pins GPIO Management 190 na Figure 8: The top figure shows the slew rate of PCA pin and the one below the slew rate of PCs pin However, keep in mind that driving a pin “too hard” impacts on the overall EMI emissions of your board. Professional design is nowadays all about EMI minimizing, Unless differently required, itis strongly suggested you leave the default GPIO speed parameter to the minimum level. What about the effective switching frequency? ST claims in its datasheets that the fastest toggle speed of an output pin is every two clock cycles. The AHB1 bus, where the GPIO peripheral is connected, runs at 42MHz for an STM32F446 MCU. So a pin should toggle in about 20MHz. However, we have to add an additional overhead related to the memory transfer between the GPIO->ODR register and the value we are going to store inside it (@x11), which costs another CPU cycle, So the expected GPIO maximum switching speed is ~14MHz, The oscilloscope confirms this, as shown in Figure 9°, a=tiei wig oa Figure 9: The maximum /O switching frequency achfeved with an STMS2F4ss Curiously, driving an I/O through the bit-banding region, using the same number of assembly instructions, dramatically reduces the switching frequency down to 4MH2z, as shown in Figure 10. “Tests were conducted toggling the maximum Gi “This jus © optimization level (-08), prefetch enabled and all internal caches enabled ifes tha the detected speed isa lite bit higher then 14MILz GPIO Management 191 Figure 10: Switching frequency when toggling an V/O through bit-banding region ‘The code used to drive the test is the following (non relevant code was omitted): define BITBAND_PERI_BASE ex40000000 define ALIAS PERI_BASE @x42000000 define 81TBAND_PERI(a,b)((ALIAS_PERI_BASE+((uint32_t)a-BITBAND_PERI_BASE)*22+(b*4))) volatile uint#2_t ~GPIOC_ODR = (((((uint92_t)@x40000000) + @x00020000) + ax0800) + Oxt4); volatile uinta2_t ~GPIOC_PINS = (uint@2_t)SITBAND_PERI(GPIOC_ODR, 8); wndte(t) « “GPIOC_PINB = 0x1; ~GPOC_PINS = 0; 6.3 Driving a GPIO CubeHAL provides four manipulation routines to read, change and lock the state of an 1/0. ‘To read the status of an I/O we can use the function: GP10_PinState HAL_GP10_ReadPin(GP10_LypeDef* GPLOx, uint46_t GP10_Pin) which accepts the GPIO descriptor and the pin number. It returns GP10_PIN_RESET when the I/O is low or GP10_p1N_SET when high. Conversely, to change the I/O state, we have the function: void HAL_GPIO_WritePin(GPIO_TypeDef GPIOx, wint16t GPIO_Pin, GPIO_PinState Pinstate) which accepts the GPIO descriptor, the pin number and the desired state. If we want to simply invert the I/O state, then we can use this convenient routine: GPIO Management 192 void HAL_GPIO_TogglePin(GPI0_IypeDef* GPIOx, uint46_t GPIO_Pin) Finally, one feature of the GPIO peripheral is that we can lock the configuration of an VO. Any subsequent attempt to change its configuration will fail, until a reset occurs. To lock a pin configuration we can use this routine: HAL_StatustypeDef HAL_GPIO_LockPin(GPIO_Typedef* GP10x, uint16_t GPIO_Pin) 6.4 De-initialize a GPIO Itis possible to set a GPIO pin to its default reset status (that is in Input Floating Mode). The function: void HAL_GPIO_DeInit(GPIO_TypeDef *GPIOx, wint32_t GPIO_Pin) does this job automatically for us. This function comes in really handy if we no longer need a given peripheral, or to avoid waste of power when the CPU goes in sleep mode. GPIO Management 193 Eclipse Intermezzo It is possible to heavily customize the Eclipse interface by installing custom themes. A theme essentially allows to change the appearance of the Eclipse user interface, This may seem a non essential feature, but nowadays a lot of programmers prefer to customize colors, fonts type and size and so on of their favorite development environment, That is one of the success reasons of some minimal yet highly customizable source code editors, like TextMate and Sublime Text. ‘There are several theme packs available for Eclipse, but it is strongly suggested to install a plug-in which automatically installs several other plug-ins useful for this scope: its name is Color IDE Pack and itis available through the Eclipse Marketplace’, The most relevant plug-ins installed are: + Eclipse Color Theme, which is a “marketplace” of hundreds of Eclipse themes. + Eclipse Moonrise UI Theme, which is considered the best full-black color theme by Andrea Guarinoni, + Jeceyul’s Eclipse Themes, which contains color themes and color customization tools by Jeeeyul Lee. This author prefers a mixed approach between a full-dark theme and a full-light one: he prefers a dark theme for the source editor, and a white background for other parts of IDE, as shown below. “bitpmarketplae eclipse. org/contentjcolr-ide-pace 7. Interrupts Management Hardware management is all about dealing with asynchronous events. The most of these come from hardware peripherals. For example, a timer reaching a configured period value, or a UART that warns about the arrival of data. Others are originated by the “world outside” our board. For example, the user presses that damned switch that causes your board to hang, and you will spend a whole day understanding what's wrong All microcontrollers provide a feature called interrupts. An interrupt is an asynchronous event that causes stopping the execution of the current code on a priority basis (the more important the interrupt is, the higher its priority; this will cause that a lower-priority interrupt is suspended). The code that services the interrupt is called Interrupt Service Routine (ISR). Interrupts are a source of multiprogramming: the hardware knows about them and itis responsible of saving the current execution context (that is, the stack frame, the current Program Counter and few other things) before switching to the ISR. They are exploited by Real Time Operating Systems to introduce the notion of tasks. Without help by the hardware it is impossible to have a true preemptive system, which allows switching between several execution contexts without irreparably losing the current execution flow. Interrupts can originate both by the hardware and the software itself, ARM architecture distin- guishes between the two types: interrupts originate by the hardware, exceptions by the software (eg., an access to invalid memory location). In ARM terminology, an interrupt is a type of exception. Cortex-M processors provide a unit dedicated to exceptions management. This is called Nested Vectored Interrupt Controller (NVIC) and this chapter is about programming this really fundamental hardware component, However, here we deal only with interrupts management, Exceptions han- dling will be treated in a following chapter about advanced debugging. 7.1 NVIC Controller NVIC is a dedicated hardware unit inside the Cortex-M based microcontrollers that is responsible of the exceptions handling, Figure 1 shows the relation between the NVIC unit, the Processor Core and peripherals. Here we have to distinguish two types of peripherals: those external to the Cortex-M core, but internal to the STM32 MCU (e.g. timers, UARTS, and so on), and those peripherals external to the MCU at all. The source of the interrupts coming from the last kind of peripherals are the MCU W/O, which can be both configured as general purpose I/O (eg. a tactile switch connected to a pin configured as input) or to drive an extemal advanced peripheral (e.g. I/Os configured to exchange data with an ethernet phyther through the RMI interface). A dedicated programmable controller, named External Interrupt/Event Controller (EXTI), is responsible of the interconnection between the external I/O signals and the NVIC controller, as we will see next. Interrupts Management 195 css an - eo [eens | Cortex M core ‘STH32 Microcontroller Figure 1: the relation between the NVIC controller, the Cortex-M core and the STM32 peripherals As stated before, ARM distinguishes between system exceptions, which originate inside the CPU core, and hardware exceptions coming from external peripherals, also called Interrupt Requests (IRQ), Programmers manage exceptions through the use of specific ISRs, which are coded at higher level (most often using C language). The processor knows where to locate these routines thanks to an indirect table containing the addresses in memory of Interrupt Service Routines. This table is commonly called vector table, and every STM32 microcontrollers defines its own. Let us analyze this in depth. 7.1.1 Vector Table in STM32 All Cortex-M processors define a fixed set of exceptions (fifteen for the Cortex-M3/4/7 cores and thirteen for Cortex-M0/0+ cores) common to all Cortex-M families and hence common to all STM32- series, We already encountered them in Chapter 1. Here, you can find the same table (Table 1) for your convenience, It is a good idea to take a quick look to these exceptions (we will study fault ‘exceptions better in a following chapter dedicated to advanced debugging). + Reset: this exception is raised just after the CPU resets, Its handler is the real entry point of the running firmware, In an STM32 application all starts from this exception, The handler contains some assembly-coded functions designed to initialize the execution environment, such as the main stack, the .bss area, etc. A following chapter dedicated to the booting process will explain this deeply. + NMI: this is a special exception, which has the highest priority after the Reset one. Like the Reset exception, it cannot be masked (that is disabled), and it can be associated to critical and non-deferrable activities. In STM32 microcontrollers it is linked to the Clock Security System (CSS). CSS is a self-diagnostic peripheral that detects the failure of the HSE. If this happens, HSE is automatically disabled (this means that the internal HSI is automatically enabled) and a NMI interrupt is raised to inform the software that something is wrong with the HSE. More about this feature in Chapter 10. Interrupts Management 196 + Hard Fault: is the generic fault exception, and hence related to software interrupts. When the other fault exceptions are disabled, it acts as a collector for all types of exceptions (e.g., a memory access to an invalid location raised the Hard Fault exceptions if the Bus Fault one is not enabled). + Memory Management Fault': it occurs when executing code attempts to access an illegal location or violates a rule of the Memory Protection Unit (MPU). More about this in a following chapter. + Bus Fault’: it occurs when AHB interface receives an error response from a bus slave (also called prefetch abort if it is an instruction fetch, or data abort if it is a data access). Can also be caused by other illegal accesses (e.g. an access to a non existent SRAM memory location) + Usage Fault’: it occurs when there is a program error such as an illegal instruction, alignment problem, or attempt to access a non-existent co-processor. + SVCCall: this is not a fault condition, and it is raised when the Supervisor Call (SVC) instructions is called. This is used by Real Time Operating Systems to execute instructions in privileged state (a task needing to execute privileged operations executes the SVC instruction, and the OS performs the requested operations - this is the same behavior of a system call in other OS). + Debug Monitor": this exception is raised when a software debug event occurs while the processor core is in Monitor Debug-Mode. It is also used as exception for debug events like breakpoints and watchpoints when software based debug solution is used. + PendSV: this is another exception related to RTOS. Unlike the SVCall exception, which is executed immediately after a SVC instruction is executed, the PendSV can be delayed. This allows the RTOS to complete tasks with higher priorities + SysTick: this exception is also usually related to RTOS activities. Every RTOS needs a timer to periodically interrupt the execution of current code and to switch to another task. All STM32 microcontrollers provide a SysTick timer, internal to the Cortex-M core. Even if every other timer may be used to schedule system activities, the presence of a dedicated timer ensures portability among all STM32 families (due to optimization reasons related to the internal die of the MCU, not all timers could be available as external peripheral). Moreover, even if we aren't using an RTOS in our firmware, it is important to keep in mind that the ST CubcHAL uses the SysTick timer to perform internal time-related activities (and it also assumes that the SysTick timer is configured to generate an interrupt every 1ms). The remaining exceptions that can be defined for a given MCU are related to IRQ handling. Cortex-M0/0+ cores allows up to 32 external interrupts, while Cortex-M3/4/7 cores allows silicon manufacturers to define up to 240 interrupts. Where can we find the list of usable interrupts for a given STM32 microcontrollers? The datasheet of that MCU is certainly the main source about available interrupts. However, we can simply refer to the vector table provided by ST in its HAL. This table is defined inside the startup file for our MCU, the assembly file ending with . we have learned to import in our Eclipse project in Chapter "This exception is not avalable in Cortex-M/d« based microcontrollers. Interrupts Management 197 4 (for example, for an STM32F030R8 MCU the file name is startup_stn82f030x8.S). Opening that file we can find the whole vector table for that MCU, starting about at line 140. Number Exception type Priority" _ Function 1 Reset 4 Reset 2 NMI 2 [Non-Maskable Interrupt EI Hard Fault: 7 All classes of Fault, when the fault cannot activate because of priority or the Configurable Fault handler has been disabled, Memory MPU mismatch, including aecess violation and no match 'Ihis is Management® C888? used even ifthe MPU is disabled or not present. . Pre-fetch fault, memory access fault, and other address/memory 5 Bus Fault® Configurable Pre‘ ry y ‘Usage fault such as Undefined instruction executed or illegal state 6 Usage Fault® Configurable USS8¢| ie ranaition attempt 710 - - RESERVED n S¥Call Configurable System service call with SVC instruction, 12 Debug Monitor® Configurable Debug monitor — for software based debug. 8 . : RESERVED 4 PendSV Configurable Pending request fr system service, 15 SysTick Configurable System tick timer has fied. 16(47/240)* IRQ Configurable IRQ Input “The lower the priority number is, the higher the priority i Sie possible to change provty of exception ateiguing a diferent number. For Cortx-MaO0« processor thie number ranges from 0 to 192 in steps of 6 (that i 4 priority levele available) For Cortex MS/4/7 ranges from 080255 “These exceptions arent avaible in Cortex MOO core MO! low 32 xtra congue interrupts Corte M47 allow 240 xtra configure interop: However in practice the munber of interrupt inputs iamplemented in the real MCU i fat lees ‘Table 1: Cortex-M exception types Even if the vector table contains the addresses of the handler routines, the Cortex-M core needs a way to find the vector table inside memory. By convention, the vector table starts at the hardware address @x0000 0000 in all Cortex-M based processors. If the vector table resides in the internal flash memory (this is what usually happens), and since the flash in all STM32 MCUs is mapped from 0x0800 0000 address, itis placed starting from the @x0800 0000 address, which is aliased to @x0000 e¢¢@ when the CPU boots up*. up from different memories than the internal flash one. ‘These are advanced topics that will be covered in a following chapter about memory layout and another one dedicated to booting process ‘To avoid confusion in unexperienced readers itis best to consider the vector table position fixed and bound to the @x0000 0000 address. "Apart from the Cortex-Mo, the rest of Cortex M cores allow to relocate the position in memory ofthe vector table, Moreover, itis possible to force the MCU to boot Interrupts Management 198 Figure 2 shows how the vector table is organized in memory. Entry zero of this array is the address of the Main Stack Pointer (MSP) inside the SRAM, Usually, this address corresponds to the end of the SRAM, that is its base address + its size (more about memory layout of an STM32 application ina following chapter). Starting from the second entry of this table, we can find all exceptions and interrupts handler. This means that the vector table has a length equal to 48 for Cortex-Mo/0+ based microcontrollers and a length equal to 256 for Cortex-M3/4/7 ox08000209 [ Reset Handier() l= [ARM Exceptions : sT™IRG Main Slack fend | ox08000: 20000000 SRAM Figure 2: The minimal layout of the vector table in an STM32 MCU based on a Cartex-M3/4/7 core It is important to clarify some things about the vector table. 1. The name of the exception handlers is just a convention, and you are totally free to rename them if you like a different one. They are just symbols (as are variables and functions inside a program). However, keep in mind that the CubeMX software is designed to generate ISR with those names, which are an ST convention. So, you have to rename the ISR name too. 2. As said before, the vector table must be placed at the beginning of the flash memory, where the processor expects to find it. This is a Link Editor job that places the vector table at the beginning of the flash data during the generation of the absolute file, which is the binary file we upload to the flash. In a following chapter we will study the content of Idscripts/sections.|d file, which contains the directives to instruct GNU LD about this. Interrupts Management 199 7.2 Enabling Interrupts ‘When an STM32 MCU boots up, only Reset, NMI and Hard Fault exceptions are enabled by default. “The rest of exceptions and peripheral interrupts are disabled, and they have to be enabled on request. To enable an IRQ, the CubeHAL provides the following function: void HAL_NVIC_FhableIRQ(1RQn_Type TRON); where the 12Qn_Type is an enumeration of all exceptions and interrupts defined for that specific MCU. The TRQn_type enum is part of the ST Device HAL, and it is defined inside a header file specific for the given STM32 MCU in the Eclipse folder systen/inciude/cmsis/. These files are named stm32fsocce.h, For example, for an STM32F030R8 MCU the right filename is stn32030x8.h (the pattern name of these files is the same of start-up files) ‘The corresponding function to disable an IRQ is the void HAL_WIC_DisableTROCTRON_Type TRQn); It is important to remark that the previous two function enable/disable an interrupt at the NVIC controller level. Looking a Figure 1, you can see that an interrupt line is asserted by the peripheral connected to that line. For example, the USART2 peripheral asserts the interrupt line that corresponds to the USART2_IRQn interrupt line inside the NVIC controller. This means that the single peripheral must be properly configured to work in interrupt mode. As we will see in the remain of this book, the majority of STM32 peripherals are designed to work, among the others, in interrupt mode. By using specific HAL routines we can enable the interrupt at peripheral level, For example, using the HAL_USART_Transmit_1T() we implicitly configure the USART peripheral in interrupt mode. Clearly, itis also required to enable the corresponding interrupt at NVIC level by calling the HAL_NVIC_EnableIRQ(). Now it is a good time to start playing with interrupts 7.2.1 External es and NVIC As we have seen in Figure 1, STM32 microcontrollers provide a variable number of external interrupt sources connected to the NVIC through the EXTI controller, which in turn is capable to manage several EXTI lines. The number of interrupt sources and lines depends on the specific STM32 family. GPIO are connected to the EXT! lines, and it is possible to enable interrupts for every MCU GPIO, even if the most of them share the same interrupt line. For example, for an STM32F4 MCU, up to 114 GPIOs are connected to 16 EXTI lines. However, only 7 of these lines have an independent interrupt associated with them. Interrupts Management 200 Figure 3 shows EXTI lines 0, 10 and 15 in an STM32F4 MCU. All Px0 pins are connected to EXTIO, all Px10 pins are connected to EXTI10 and all Px15 pins are connected to EXTI15. However, EXTI lines 10 and 15 share the same IRQ inside the NVIC (and hence are serviced by the same ISR)’. ‘This means that: + Only one Px¥ pin can be a source of interrupt. For example, we cannot define both PAO and. PBO as input interrupt pins. + For EXTI lines sharing the same IRQ inside the NVIC controller, we have to code the corresponding ISR so that we must be able to discriminate which lines generated the interrupt. PAO! BOI PDO! EOI PFO Pao! Fioa >| extTlo Pato Poi — Poto! BXTT1_IRQ PO10 Ss EXTI2_IR, Peio extiio 100 Ceieee EXTI3_IRQ Pio EXTI4_IRQ PHto! BXTI9_5_IRQ HS 2xrT15_10_1KO Nvic PAIS PBI5 POIs pnisi PES PFIS Pais Figure 3: The relation between GPIO, EXTI lines and corresponding ISR in an STM32F4 MCU Sometimes, italso happens that different peripherals share the same request line, even in Cortex-Ms/4/7 based MCUs where up to 240 configurable request lines are available. For example in an STMS2P44GRE MCU, timer TIMG shares is global IRQ with ACY and DAC? under-run errarinterpts. Interrupts Management 201 ‘The following example* shows how to use interrupts to toggle the LD2 LED every time we press the user-programmable button, which is connected to the PC13 pin. First, we configure in the GPIO PC13 to fire an interrupt every time it goes from the low level to the high one (Lines 49:52). This is accomplished setting GPIO .Hode to be equal to GP10_MODE_IT_RISING (for the complete list of available interrupt related modes, refer to Table 2 in Chapter 6). Next, we enable the interrupt of the EXTI line associated with the Px13 pins, that is €x Filename: src/nain-ext.c 5_10_IRQn int wain(veid) { OPIO_InitTypeDef GPIO_InitStruct, IAL_Init(); /* GPIO Ports Clock HAL_RCC_GP10C_CLK_ENABLE(); HAL_RCC_GP1OA_CLK_ENABLE(); /Contigure GPIO cia - USER BUTTON */ GPIO_Initstruct.Pin = GP10_PIN_43; GPIO_InitStruct Mode = GPIO_NODE_IT_RISING; GPIO_InitStruct Pull ~ GPIO_PULLOOWN; HAL_GPIO_Init(GPIOC, eGPIO_InitStruct: /*Configure GPIO pin : PAS - LOZ LED */ PIO_InitStruct.Pin ~ GPTO_PINE OPIO_InitStruct Mode = GPIO_NODE_OUTPUT_PP; ;P1O_InitStruct Pull = GPIO_NOPULL; PI0_InitStruct .Speed = GPIO_SPEED_LOW; HAL _GPIO_Init(GPIOA, &6°10_In: HAL_NVIC_nableIRQ(EXTI45 40_18Qn); while(1); void EXTI15_10_18QHandler( void) HAL_GP10_EXTI_CLEAR_IT(GPIO_PIN-13); HAL_GPIO_TogglePin(GPIOK, GPIO_PIN5); } “The example is designed to work with a Nucleo-Ps01RE board Please, Nucleo board refer to other book examples if you have a different Interrupts Management 202 Finally, we need to define the function void EXT148_10_1RQHandler()°, which is the ISR routine associated to the IRQ for the EXTI15_10 line inside the vector table (lines 66:69). The content of the ISR is really simple. We toggle the PAS I/O every time the ISR fires. We also need to clear the pending bit associated to the EXTT line (more about this next). Fortunately, the ST HAL provides an abstraction mechanism that avoids us to deal with these details, unless we really need to take care of them. The previous example can be rewritten in the following PCI2 and PCI ¥7 GP1OPINA3 | GPIO_PIN-42; GPIO_InitStruct Mode = GP1O_NODE_IT_RISING; GPIO_InitStruct.Pull = GPIO_PULLOOWN; HAL_GPIO_Init(GPIOC, 2GPI0_InitStruct) /#Configure OPIO pin : PAS PIO_Initstruct Pin = GPIO_PIN.S, PIO_InitStruct Mode ~ GPTO_NODE_OUTPUT_PP; OPIO_InitStruct Pull = GPTO_NOPULL; OPIO_InitStruct .Speed = GPIO_SPEE_LOW; HAL_GPIO_Init(GPIOA, eGPI0_Initstruct) ; ED +/ HAL_NVIC.Frable1RQ{EXTI45 40_18Qn) ; mhite(t); void EXT145_10_1&QHandler (void) HAL _G°I0_EXTI_IRQMandler (GPIO_PIN_12); HAL _GPIO_EXTI_IRQHandler (GPIO_PIN_13); ) void HAL_GPIO_EXTI_Callback(uint46_t GPIO_Pin) { if(GPIO_Pin ~- OPIO_PIN43) HAL_GPIO_TogglePin(GPTOR, GPIO_PINS); else i f(GPIO_Pin — GPIO_PIN.12) HAL_SPIOLWritePin(GPIOA, GPIO_PIN.S, RESET); “Another feature ofthe ARM architectures is the ability to use conventional C functions as ISRs. When an interrupt fies, the (CPU switches from the Threaded mode that is the main execution flaw) tothe Handler mode. During tis switching proces, the ‘current execution context is saved thanks toa procedure named stacking. The CPU itslfis responsible of storing the previous saved context when the ISR terminates the execution (unstacking) The explanation of this procedure is outside from the scope of ths book, Fr more information about these aspects, refer to the Josep Yi book. Interrupts Management 203 This time we have configured as interrupt source both pin PC13 and PC12, When the Exr118_10_- 1RQHandler() ISR is called, we transfer the control to the HAL_GPLO_EXT1_1RQHandler() function inside the HAL. This will perform for us all the interrupt related activities, and it will call the HaL_- GP10_EXTI_Cal lback( ) routine passing the GPIO that has generated the IRQ. Figure 4 clearly shows the call sequence that generates from the IRQ’. HAL_GP10_EXTI_Caltbock() Figure 4: How an IRQ Is processed by the HAL This mechanism is used by almost all IRQ routines inside the HAL. Please, take note that, since EXTI12 and EXTI13 lines are connected to the same IRQ, we need to discriminate in our code which of the two pins generated the interrupt. This work is done for us by the HAL, passing the GP10_Pin parameter when the callback function is called. 7.2.2 Enabling Interrupts With CubeMX CubeMX can be used to easily enable IRQs and to automatically generate the ISR code. The first step is to enable the corresponding EXT line using the Chip view, as shown in Figure 5 crI0_pT13 RCC, 0SC32_1N RCC_OSC_IN cc_osc_our Figure 5: How a GPIO can be bound to EXT! line using CubeMX, “Don’t consider those time intervals related to the CPU eycles, they are just used to indicate “subsequent” evens Interrupts Management 204 Once we have enabled an IRQ, we need to instruct CubeMX to generate the corresponding ISR. This configuration is done through the Configuration view, clicking on the NVIC button. A list of ISRs that can be enabled appears, as shown in Figure 6. Figure 6: The NVIC configuration view allows to enable the corresponding ISR ‘CubeMX will automatically add the enabled ISRs inside the sre/stm32fxxx_it.e file, and it will take care of enabling the IRQs. Moreover, it adds for us the corresponding HAL handler routine to call, as shown below: oe + ebrief This function handles EXTI line[15:10] interrupts vy void EXTIt5_10_IRQHandler(votd) { (/# USER CODE BEGIN EXTI15 10_IRQn 0 */ /* USER CODE END EXTIS5_10_1RQn 0 ¥*/ HAL_OP10_EXTI_IRQHandLer (GPIO_PIN-A3); /* USER CODE BEGIN EXTI1S. 10_1RQn 1 */ /* USER © } DE END EXTI1S_1@_1RQn 1 */ We only need to add the corresponding callback function (for example the HAL_GPIO_EXxT1_cal1- back() routine) inside our application code. What Belongs to What When starting to deal with the ST HAL, @ lot of confusion arises from its relationship with the ARM CMSIS package. The steS2Xx_hal_cortex.c module clearly shows the interaction between the ST HAL and the CMSIS package, since it completely relies on the official ARM package to deal with the underlying Cortex-M core functionalities. Every HAL_NWIC.xxx() fiction is a wrap of the corresponding CMSISNVIC_xxx() function. This means that we may use the CMSIS API to program the NVIC controller. However, since this book is about the CubeHAL, we will use the ST API to manage interrupts. Interrupts Management 205 7.3 Interrupt Lifecycle One dealing with interrupts, itis really important to understand their lifecycle. Although the Cortex- ‘Mcore automatically performs the most of the work for us, we have to pay attention to some aspects that could be a source of confusion during the interrupt management, However, this paragraph gives a look to the interrupts lifecycle from the “HAL point of view”. If you are interested in looking deeper into this matter, the book series from Joseph Yiu’ itis again the best source. An interrupt can: 1. either be disabled (default behavior) or enabled; + we enable/disable it calling the HAL_NVIC_EnableTRQ()/HAL_NVIC_DisableIRQ() func- tion; 2. either be pending (a request is waiting to be served) or not pending: 3. either be in an active (being served) or inactive state. We have already seen the first case in the previous paragraph, Now it is important to study what happens when an interrupt occurs. When an interrupt fires, itis marked as pending until the processor can serve it. Ifno other interrupts are currently being processed, its pending state is automatically cleared by the processor, which almost immediately starts serving it. Pending state cleared [iPending Interrupt 1 2 & w tb bs Figure 7: The relation between the pending bit and the interrupt active status Figure 7 shows how this works. Interrupt A fires at the time fp and, since the CPU is not servicing another interrupt, its pending bit is cleared and its execution starts immediately* (the interrupt, becomes active). At the time (; the B interrupt fires, but here we suppose that it has a lower priority than A. So it is leaved in pending state until the A ISR concludes its operations. When this happens, the pending bit is automatically cleared and the ISR become active. “hip famen ol PSeZaeq "Here, it important to understand the with the word “immediately” we are not saying that the interrupt execution starts, ‘without delay. Ino other interrupts are running, Cortex-M3/4/? cores serve an interrupt in 12 CPU eyeles, while Cortex-MO does itn 15 eyeles and Cortex-Mo in 16 cycles, Interrupts Management 206 elnterupt fred Pencing state cleared [BPencing Interrupt BBActive Interrupt inactve interrupt Figure &: The relation between the active status and interrupts priority Figure 8 shows another important case. Here we have that the A interrupt fires, and the CPU can immediately serve it, The interrupt B fires while A is serviced, so it remains in pending state until A finishes. When this happens, the pending bit of B interrupt is cleared, and it becomes active. However, after a while, A interrupt fires again, and since it has a higher priority, B interrupt is suspended (becomes inactive) and the execution of A starts immediately. When this finishes, the B interrupt becomes active again, and it completes its job. Ae Interrupt fired Pending state cleared Pending state set oO 4 t tt & ' be Figure 9: How an interrupt can be forced to fire again setting its pending bit NVIC provides a high degree of flexibility for programmers. An interrupt can be forced to fire again during its execution, simply setting its pending bit again, as shown in Figure 9”. In the same way, the execution of an interrupt can be canceled clearing its pending bit while it is in pending state, as shown in Figure 10. "For the sake of completenes, its important to specify that Cortex-M architecture is designed so that if an interrupt fires vb processor is already servicing another interrupt, this will be serviced without restoring the previous application doing the unstacking (refer to note 3 in tis chapter for the definition of stacking/unstacking). This technique is called fail chaining and itallows to speed up interrupt management and reduce power consumption, Interrupts Management 207 {23 Interrupt fired «Pending state cleared Fel Bache rt Figure 10: IRQ servicing can be canceled clearing its pending bit before itis executed Here it is important to clarify an important aspect related to how peripherals warn the NVIC controller about the interrupt request. When an interrupt takes place, the most of STM32 peripherals, assert a specific signal connected to the NVIC, which is mapped in the peripheral memory through a dedicated bit. This peripheral Interrupt Request bit will be held high until it is manually cleared by the application code. For example, in the Example 1 we had to expressly clear the EXTI line IRQ pending bit using the macro _HAl_GPIO_EXTI_CLEAR_IT(). If we do not de-assert that bit, a new interrupt will be fired until itis cleared, Signal YO o EXT Lino 1RO © etnterapt trod SrPenang site cleares 1 SEXxTILino Ra cleared active mera Nvic [GPendng erupt Interrupt Pending Status oO 4 2 & YW b&b fs Figure 11: The relation between the peripheral IRQ and the corresponding interrupt ‘The Figure 11 clearly shows the relation between the peripheral IRQ pending state and the ISR pending state, Signal I/O is the external peripheral driving the I/O (eg. a tactile switch connected to pin), When the signal level changes, the EXTI line connected to that I/O generates an IRQ and the corresponding pending bit is asserted. As consequence, the NVIC generates the interrupt. When the processor starts servicing the ISR, the ISR pending bit is cleared automatically, but the peripheral IRQ pending bit will be held high until itis cleared by the application code. Interrupts Management 208 Signal yo. ————______________o EXT Line 1RO ‘etorap trod Penang site cleared 4 SExTI Lins Re ccaros Bactve intern ie 9 resol Interrupt Pending Status Tz Oo 4 b&b & WY b&b bs Figure 12: When an interrupt is forced setting its pending bit, the corresponding peripheral IRQ remains unset ‘The Figure 12 shows another case. Here we force the execution of the ISR setting its pending bit. ‘Since this time the external peripheral is not involved, there is no need to clear the corresponding IRQ pending bit. Since the presence of the IRQ pending bit is peripheral dependent, it is always opportune to use the ST HAL functions to manage interrupts, leaving all the underlying details to the HAL implementation (unless we want to have full control, but this is not case of this book). However, take in mind that to avoid losing important interrupts, it is a good design practice to clear peripherals IRQ pending status bit as their ISR start to be serviced. The processor core does not keep track of multiple interrupts (it does not queue interrupts), so if we clear the peripheral pending bit at the end of an ISR, we may lose important IRQs that fire in the middle. To sce if an interrupt is pending (that is, fired but not running), we can use the HAL function: uint32_t HAL_WIC_GetPendingIRO(TRQn_Type TRQn) which returns 0 if the IRQ is not pending, 4 otherwise. ‘To programmatically set the pending bit of an IRQ we can use the HAL function: void HAL_NVIC_SetPendingIRQ(IRQAType IRQN); This will cause the interrupt to fire, as it would be generated by the hardware, A distinctive feature of Cortex-M processors it that it is possible to programmatically fire an interrupt inside the ISR routine of another interrupt. Instead, to programmatically clear the pending bit of an IRQ, we can use the function: Interrupts Management 209 void liAL_NVIC_ClearPendingTRQCTRQnType TRON); Once again, it is also possible to clear the execution of a pending interrupt inside the ISR servicing another IRQ. To check if an ISR is active (IRQ being serviced), we can use the function: uinta2_t HAL_NIC_GetActive(IRQHType IRQn); which retums 1 if the IRQ is active, @ otherwise. 7.4 Interrupt Priority Levels A distinctive features of the ARM Cortex-M architecture is the ability to prioritize interrupts (except for the first three software exceptions that have a fixed priority, as shown in Table 1). Interrupt priority allows to define two things + the ISRs that will be executed first in case of concurrent interrupts; + those routines that can be optionally preempted to start executing an ISR with a higher priority. NVIC priority mechanism is substantially different between Cortex-Mo/0+ and Cortex-M3/4/7 cores. this reason we are going to explain them in two separated subparagraphs. 7.4.1 Cortex-M0/0+ Cortex-Mo/0+ based microcontrollers have a simpler interrupt priority mechanism, This means that ‘STM32F0 and STM32L0 MCUs have a different behavior from the rest of STM32 microcontrollers And you have to pay special attention if you are porting your code between the STM32 series In Cortex-Mo/0+ cores the priority of each interrupt is defined through an 8-bit register, called IPR In the ARMv6-M core architecture only 4 bits of this register are used, allowing up to 16 different priority levels. However, in practice, STM32 MCUs implementing these cores use only the two upper bits of this register, seeing all other bits equal to zero. Bit 7 6 5 4 3 2 1 oO Priority bits Not used IPR register Figure 15: The content of IPR register on an STMS2 MCU based on Cortex-M0 Figure 13 shows how the content of IPR is interpreted. This means that we have only four maximum priority levels: 0x00, 0x40, 0x80, 0xCO. The lower this number is, the higher the priority is. That is, an IRQ having a priority equal to 0x40 has a higher priority than an IRQ with a priority level equal Interrupts Management 210 to 0xCO. If two interrupts fire at the same time, the one with the higher priority will be served first, If the processor is already servicing an interrupt and a higher priority interrupt fires, then the current interrupt is suspended and the control passes to the higher priority interrupt. When this is completed, the execution goes back to the previous interrupt, if no other interrupt with higher priority occurs in the meantime. This mechanism is called interrupt preemption. "| FE MQ NE o 4 b&b & % fb Figure 14: Preemption of interrupts in case of concurrent execution Figure 14 shows an example of interrupt preemption. A is an IRQ with lower priority that fires at time to, The ISR starts the execution but the IRQ B, which has a higher priority (lower priority level), fires at time f,and the execution of A ISR is stopped. When B finishes its job, the execution of A ISR is resumed until it finishes. This “nested” mechanism induced by interrupt priorities leads to the name of the NVIC controller, which is Nested Vectored Interrupt Controller. Cortex-Mo/0+ has an important difference compared to Cortex-3/4/7 cores. The interrupt priority is static. This means that once an interrupt is enabled its priority can no longer be changed, until we disable the IRQ again. ‘The CubeHAL provides the following function to assign a priority to an IRQ: void HAL _NVIC_SetPriority(IRQnType IRQn, wint32t PreenptPriority, wint82t SubPriority); ‘The HAL_NVIC_SetPriority() function accepts the IRQ we are going to configure and the Preenpt- Priority, which is the preemption priority we are going to assign to the IRQ. The CMSIS API, and hence the CubeHAL library, is designed so that PreenptPr iority is specified with a priority level number ranging from 0 to 4, The value is internally shifted to the most significant bits automatically. This simplifies the porting of code to other MCU with a different number of priority bits (this is the reason why only the left part of IPR register is used by silicon vendors). Interrupts Management ‘As you can see, the function accepts also the additional parameter SubPriority, which is simply ignored in CubeF0 and CubeL.0 HAL since the underlying Cortex-M processor does not support interrupt sub-priority. Here ST engineers have decided to use the same APTavailable in the other HALs for Cortex-M3/4/7 based processors. Probably they decided to do so to simplify porting code between the different STM32 MCUs. Curiously, they have decided to define the corresponding function to retrieve the priority of an IRQ in the following way: Uutnta2_t HAL_KVIC_GetPriority(IRQa Type 1RQh); which is completely different from the one defined in the HALs for Cortex-M3/4/7 based processors®. ‘The following example™ shows how the interrupt priority mechanism works. Filename: sre/nain-ex3.¢ inte blink = ©; int main(votd) { GPIO_InitTypeDef GPIO_Initstruct, HAL_Init(); /* GPIO Ports Clock Enable */ _HAL_RCC_GPTOC_CLK_ENABLE() ; “_HAL_RCC_GP108_CLK_ENABLE() ; “HAL_RCC_GPTOA_CLK_ENAALE() ; Con tigh GPIO_InitStruct.Pin = GP10PINA GPIO_InitStruct Mode = GP1O_NODE_IT_RISING; GP1O_InitStruct.Pull = GPIO_PULLOOWN; HAL _GP1O_Init(GPIOC, "GP1_InitStruct) fe GPIO pin : PCI 2 GPIO Pe2 PIO_InitStruct Pin = GPIOPIN_2 ; GPIO_InitStruct Mode = GPTO_HODE_IT_FALLING; PIO_InitStruct Pull = GPIO_PULLUP; HAL _GPTO_Init(GPIOB, &GPIO_Initstruc: have opened a dedicated thread on the offical ST Forum, but thee is still no answer from ST atthe time of writing this chapter, “The ex Nucleo board lei designed to work with a Nucleo-FOSORS board, Please, refer to other book examples i you have a diferent Interrupts Management 212 /#Configure OPIO pin : PAS - LD2 LED */ GPIO_InitStruct Pin = GPIO_PIN_S, OPIO_InitStruct Mode = GPTO_NODE_OUTPUT_PP; (OPIO_InitStruct Pull ~ GPTO_NOPULL; PIO_InitStruct Speed ~ GPTO_SPEED_LOW; HAL _GPTO_Init(GPIOA, eGPIO_InitStruct) HAL_WIG_SetPriority(EXTI4_A5_IRQn, Ot, @); HAL_NVIC_Enable1RQ(EXTI4 45_IR0n) ; HAL_WWIC_SetPrior ity(EXTI2_8.180n, 0x0, 0); HAL _NVIC_EnableIRO(EXTI2_8_1RQn) while(1) ) void EXTI4_15_IRQiandler(voia) { HAL _GPIO_EXTI_IRQHandler (GPIO_PIN_13); ) void EXT12_3_IRQHandler(void) { HAL _GPTO_EXTI_IRQHandler(GPIO_PIN_2); void HAL_G?10_FXT1_Cal lback(uint46_t GPIO_Pin) { if(GPIO_Pin == GPIOPINAS) { blink = 4; while(bLink) ( HAL_GPIO_TogglePin(GP1OA, GPLO_PINS); @; 1 < 100000; 1++) for(volatile int i 7A Busy wait +/ ) ) else ( blink = 0) The code should be really easy to understand if the previous explanation is clear for you. Here wwe have two IRQs associated to EXTI lines 2 and 13. The corresponding ISRs call the HAL HAL_~ GP10_EXTI_IRQHandler() which in turn calls the HAL_GP10_EXT1_Calback() callback passing the GPIO involved in the interrupt. When the user button connected to PC13 signal is pushed, the ISR starts an infinite loop until the blink global variables is >@. This loop makes the LD2 LED blinking quickly. When the PB2 pin is asserted low (use the pinout diagram for your Nucleo from Interrupts Management 213 Appendix C to identify PB2 pin position), the ExT12_9_1RQHandier()* fires and this causes the HAL _GP1O_£XT1_1RQHandler() to set the blink variable to 0. The EXT14_45_1RgHandler() can now end. The priority of each interrupt is configured at lines 70 and 73: as you can see, since the interrupt priority is static in Cortex-Mo/0+ based MCUs, we have to set it before we enable the corresponding interrupt. Please, take note that this is a really bad way to deal with interrupts. Locking the MCU inside an interrupt is a poor programming style, and itis the root of all evil in embedded programming, Unfortunately, this is the only example that came up to the author's mind, considering that at this point the book still covers few topics. Every ISR must be designed to last as little as possible, otherwise other fundamental ISRs could be masked for a long time loosing important information coming from other peripherals. ‘As exercise, try to play with interrupt priorities, and see what happens if both interrupts have the same priority. You may notice that often the interrupt fires by simply touching the wire, even if itis not tied to the ground. Why does this happen? There are essentially two reasons that cause the interrupt to “accidentally” trigger. First of all, modern microcontrollers try to minimize the power leakages connected with the usage of internal pull-up/down resistors. So, the value of these resistors is chosen really high (something around 50k®). If you play with the voltage divider equation, you can figure out that it is really easy to pull an I/O low or high when a pull-up/down resistor has a high resistance value, Secondly, here we are not doing adequate debouncing of the input pin. Debouncing is the process of minimizing the effect of bounces produced by “unstable” sources (e.g. a mechanical switch). Usually debouncing is performed in hardware” or in software, by counting how much time is elapsed from the first variation of the input state: in our case, if the input remains low for more than a given period (usually something between 100ms and 200ms is sufficient), then we can says that the input has been effectively tied to the ground). As we will see in Chapter 11, we can also use one channel of a timer configured to work in input capture mode to detect when a GPIO changes state. This gives us the ability to automatically count how much time is elapsed from the first event. Moreover, timer channels support integrated and programmable hardware filters, which allow us to reduce the number of external components to debounce the /Os. Please, take note that for STMB2F402 MCUs the default name ofthe IRQ associated to EXT line 2 is ExT!2_3SC_IR@Handlter Refer to book examples if you are working with this MCU: Usually, a capacitor and a resistor in paallel withthe switch contacts ae sufficient in most cases. For example, ou can take a Took ta schematics ofthe Nucleo board ta see how ST engineers have debounced the USER button connected to PCI GPIO. Interrupts Management 214 7.4.2 Cortex-M3/4/7 Interrupt priority mechanism in Cortex-M3/4/7 is more advanced than the one available in Cortex- Mo/0+ based microcontrollers. Developers have a higher degree of flexibility, and this is often source of several headaches for novices. Moreover, the way interrupt priority is presented both in the ARM and ST documentation is a little bit counterintuitive In Cortex-M3/4/7 cores the priority of each interrupt is defined through the 1PR register. This is a 8bit register in the ARMv7-M core architecture that allows up to 255 different priority levels. However, in practice, STM32 MCUs implementing these cores use only the four upper bits of this register, seeing all other bits equal to zero. Bt 7 6 5 4 3 2 Priority bits Not used IPR register Figure 15: The content of IPR register on an STM32 MCU based on Cortex-M3/4/7 core Figure 15 clearly shows how the content of IPR is interpreted. This means that we have the only sixteen maximum priority levels: 0x00, 0x10, 0x20, 0x30, 0x40, 0x50, 0x60, 0x70, 0x80, 0x90, OXAO, OxBO, 0xCO, OxD0, OxE0, OxFO. The lower this number is, the higher the priority is. That is, an IRQ having a priority equal to 0x10 has a higher priority than an IRQ with a priority level equal to OxA0, If two interrupts fire at the same time, the one with the higher priority will be served first. If the processor is already servicing an interrupt and a higher priority interrupts fires, then the current interrupt is suspended and the control passes to the higher priority interrupt. When this is completed, the execution goes back to the previous interrupt, if no other interrupts with higher priority occurs in the meantime, So far, the mechanism is substantially the same of Cortex-M0/0+. The complication arises from the fact that the IPR register can be logically subdivided in two parts: a series of bits defining the preemption priority" and a series of bits defining the sub-priority. The first priority level rules the preemption priorities between ISRs. If an ISR has a priority higher than another one, it will preempt the execution of the lower priority ISR in case it fires. The sub-priority determines what ISR will be executed first, in case of multiple pending ISR, but it will not act on ISR preemption, “What complicates the understanding of interrupt priorities is the fact that in Ube official documentation sometimes the preemption priority is also called group priority. Ths leads to a lot of confusion, since novioes tends to imagine that ths bits define a sort of Access Control List (ACL) privileges. Here, to simplify the understanding ofthis mater, we will only speak about preemption priority level Interrupts Management 215 a rere Pending interrupt Priority=0x10 Active Interrupt Wihactive Interrupt | _ SWE teeta toe ta tees tte tty, Figure 16: Preemption of interrupts in case of concurrent execution Figure 16 shows an example of interrupt preemption. A is an IRQ with the lowest priority that fires at time fp, The ISR starts the execution but the IRQ B, which has a higher priority (lower priority level), fires at time tand the execution of A ISR is stopped. After a while, C IRQ fires at time t and the B ISR is stopped and the C ISR starts execution, When this finishes, the execution of B ISR is resumed until it finishes. When this happens, the execution of A ISR is resumed. This “nested” mechanism induced by interrupt priorities leads to the name of the NVIC controller, which is Nested Vectored Interrupt Controller. Prioity=0x0 interrupt fred sefromse ii ‘ont Sl ed Pending interrupt Priority=0x0 Active Interrupt supProntyoxt Proety-0%0 SubPriniy=0%x0 oO 4 tf & & t& & & Figure 17: If two interrupts with the same priority are pending, the one with the higher sub-priority is executed first Figure 17 shows how the sub-priority affects the execution of multiple pending ISRs. Here we have three interrupts, all with the same maximum priority. At time fo the IRQ A fires and it is serviced immediately. At the time fy B IRQ fires, but since it has the same priority level of other IRQs, it is leaved in pending state. At time ty also C IRQ fires, but for the same reason as before it is leaved in pending state by the processor. When The A ISR finishes, the C IRQ is served first, since it has a higher sub-priority than B. Only when the C ISR finishes the B IRQ can be served. ‘The way how 1PR bits are logically subdivided is defined by the Sc3->AIRCR register (a sub-group of bits of the System Control Block (SCB) register), and it is important to stress right from the start that this way to interpret the content of the 1PR register is global to all ISRs. Once we have defined a priority scheme (also called priority grouping in the HAL), this is common to all interrupts used in the system. Interrupts Management NVIC_PRIORTTYGROUP_O NVIC_PRIORITYGROUP_1 NVIC_PRIORITYGROUP_4 Bit 6 ‘Subpriority 2 proompt print, 8 sub-pty 216 PR rogistor IPR register ‘preempt pony, aub-pcity preempt printy, 2 ub-pty 16 preempt prey, 0 sub erty en register IPR register PR rogistor Figure 18: The subdivision of IPR bits between preemption priority and sub-priority. Figure 18 shows all five possible subdivisions of 1PR register, while Table 2 shows the maximum. number of preemption priority levels and sub-priority levels that each subdivision scheme allows. Table 2: The number of preemption priority level available based on the current priority grouping schema NVIC Priority Group Number of preemption priority levels Number of sub-priority levels NVIG_PRIORITYGROUP_O NVIC_PRIORITYGROUP_4 NVIC_pRIORITYGROUP_2. NVIC_pRIoRITYGROUP_3 NVIC_pRIORITYGROUP_A 16 ‘The CubeHAL provides the following function to assign a priority to an IRQ: void HAL_NVIC_SotPriority(IRQnType IRQn, vint92_t PreomptPriority, uint92_t SubPriority); ‘The HAL library is designed so that the PreenptPriori ty and SubPrior ity can be configured with a priority level number ranging from 0 to 16. The value is internally shifted to the most significant bits automatically. This simplifies the porting of code to other MCU with a different number of priority bits (this is the reason why only the left part of 1°R register is used by silicon vendors). Instead, to define the priority grouping, that is how to subdivide the IPR register between the preemption priority and sub-priority, the following function can be used: Interrupts Management 27 void HAL_NIC_SetPriorityGrouping(uint82_t PriorityGroup); where the PriorityGroup parameter is one of the macros from the column NVIC Priority Group in Table 2, ‘The following example shows how the interrupt priority mechanism works. Filename: sre/main-ex3.¢ > Wint&_E blink = 0; et Amt main(void) { GPIO_InitTypeDef GPIO_Initstruct. HAL_Init(Q); /* PIO Ports Clock Enable */ (HAL RCC_OPTOC_CLK_ENABLE(); 8 _HAL_RCC_GP1OB_CLK_ENABLE() ; © _HAL_RCC_GPIOR_CLK_ENABLE(); /MContigure GPIO pin : PCIE */ "2 GPIO_EnitStruct.Pin = GPIO_PINAS ; GPIO_InitStruct Mode = GP1O_NODE_I1T_RISING; GP1O_LnitStruct.Pull = GPIO_PULLOOWN; HAL_GPIO_Init(GPIOC, eGPIO_InitStruct); /#Configure GPIO pin : PB2 GPIO_InitStruct.Pin ~ GPIOPIN2 ; ) GPIO_TnitStruct Mode = GPIO_MODE_IT_FALLING; 80 GPIO_InitStruct Pull ~ GPTO_PULLUP; 8) HAL_GPTO_Init(GPTOB, 26P10_Initstruct); 88 /#Configure GPIO pin + PAS */ 84 GPIO_TnitStruct Pin = GPIO_PINS; 85 GPIO_TnitStruct Mode ~ GP10_MoDE_OUTFUT_PP; 26 GPIO_Unit Struct Pull = GP1O_NOPULL; 87 GPIO_InitStruct Speed = GPIO_SPEED_LOW; 85 HAL_GPIO_Init(GPIOA, 26P10_InitStruct); 29 90 -MAL_NVIC_SetPriority(EXTI4S 4@_18Qn, Gxt, 0); HAL_NVIC_EnableIRQ(EXTI+5_40_1RQn); 8 HAL_NVIC_SetPriority(EXTI2_IRQn, x0, 0); The example is designed to work with a Nucleo-F401RE board, Please, refer fo other book examples if you have a different Nucleo board. Interrupts Management 218 HAL_NVIC_Enab1eTRQ(EXTI2_IRON) ; white(1); } void EXTI15 4@_IRQHandler(void) { HAL GP 10_EXTI_IRQHandler (GPIO_PIN-13); } void £XT12_IRQHandler(void) { HAL _G°10_EXT1_IRQMandler(GP1O_PIN_2); ) void HAL_GPI0_£XT1_Callback(uint46_t GPIO_Pin) ( i£(GPIO_Pin ~~ GPIO_PIN43) { blink = 4 while(oLink) HAL_GPIO_TogglePin(GPIOA, GPIO_PIN_5) for(int 1 = 9; 1 < 1000000; i++); ) else { blink = ) ‘The code should be really easy to understand if the previous explanation is clear for you. Here we have two IRQs associated to EXTI lines 2 and 13, The corresponding ISRs call the HAL HAL_- GPIO_EXTI_IRQHanéler() which in tum calls the HAL_GPIO_EXTI_Cal lback( ) callback passing the GPIO involved in the interrupt. When the user button connected to PC13 signal is pushed, the ISR starts an infinite loop until the blink global variables is >@. This loop makes the LD2 LED blinking quickly. When the PB2 pin is asserted low (use the pinout diagram for your Nucleo from Appendix C to identify its position), the EX?I2_1RQHandler() fires and this causes the HAL_GPIO_- EXTI_IRQHand1er() to set the blink variable to ©, The EXT115_40_1RQHandler() can now end. A Please, take note that this is a really bad way to deal with interrupts. Locking the MCU inside an interrupt is a poor programming style, and itis the root of all evil in embedded programming, Unfortunately, this is the only example that came up to the author's mind, considering that at this point the book still covers few topics. As we will see soon, every ISR must be designed to last as little as possible, otherwise other fundamental ISRs could be masked for a long time loosing important information coming from other peripherals Interrupts Management 219 4 As exercise, try to play with interrupt priorities, and see what happens if both interrupts GF ave tse same pity You may notice that often the interrupt fires by simply touching the wire, even if it is not tied to the ground, Why does this happen? ‘There are essentially two reasons that cause the interrupt to “accidentally” trigger. First ofall, modem microcontrollers try to minimize the power leakages connected with the usage of internal pull-up/down resistors. So, the value of these resistors is chosen really high (something around 50k). If you play with the voltage divider equation, you can figure out that it is really easy to pull an I/O low or high when a pull-up/down resistor has a high resistance value, Secondly, here we are not doing adequate debouncing of the input pin. Debouncing is the process of minimizing the effect of bounces produced by “unstable” sources (e.g. a mechanical switch). Usually debouncing is performed in hardware“ or in software, by counting how much time is elapsed from the first variation of the input state: in our case, ifthe input remains low for more than a given period (usually something between 100ms and 200ms is sufficient), then we can says that the input has been effectively tied to the ground). As we will sce in Chapter 11, we can also use one channel of a timer configured to work in input capture mode to detect when a GPIO changes state. This gives us the ability to automatically count how much time is elapsed from the first event, Moreover, timer channels support integrated and programmable hardware filters, which allow us to reduce the number of external components to debounce the 1/Os. It is important to remark some fundamental things. First of all, different from Cortex-Mo/0+ based microcontrollers, Cortex-M3/4/7 cores allow to dynamically change the priority of an interrupt, even if this is already enabled. Secondly, care must be taken when the priority grouping is lowered dynamically. Let us consider the following example. Suppose that we have three ISRs with three decreasing priorities (the priority is specified inside the parenthesis): A(0x0), B(0x10), (0x20). Suppose that we have defined these priorities when the priority grouping was equal to NVIC_PRIORITYGROUP_A. If we lower it to the NVIC_PRIORITYGROUP_+ level, the current preemption levels will be interpreted as sub-priorities. This will cause that interrupt service routines A, B and C have the same preemption level (that is, 0x0), and it will not be possible to preempt them. For example, looking at Figure 20 we can see what happens to the priority of the ISR C when the priority grouping is lowered from 4 to 1, When the priority grouping is set to 4, the priority of CISR is just two levels under the maximum priority level, which is 0 (the next highest level is 0x10, which is the B's priority). This means that C can be preempted both by A and B. However, if we lower the priority grouping to 1, then the priority of C becomes 0x0 (only bit 7 acts as priority) and the remaining bits are interpreted by the NVIC controller as sub-priority. This can lead to the following scenario: 1, all interrupts will not be able to preempt each other; ‘Usually, a capacitor and a yesistor in parallel with the switch contacts ae sufficient in most eases. For example, ou can take «look to schematics of the Nuclea board to see how ST engineers have debounced the USER button connected to PC14 GPIO. Interrupts Management 220 2. if C interrupt is triggered, and the CPU is not servicing another interrupt, C is serviced immediately; 3. if CPU is servicing C ISR and then after a short while A and B are triggered, CPU will service A and then B after it completes to service C; 4, if CPU is servicing another ISR, if triggers and then after a short while A and B are triggered, A will be serviced firstly, followed by B then C. Hpreemotpronty NVIC_PRIORTTYGROUP_1. o 1 0 Not used IR register [sve pony 2 preempt print, 8 sub-pronty 16 proemot print, 0 sub-pinty Figure 20: What happens to the C ISR priority when the priority gruping is lowered from 4 to 1 o Before that the interrupt priority mechanism becomes clear, you will have to do several experiments by yourself. So, try to modify the Example 3 so that changing the priority grouping causes that the preemption priority is the same for both the IRQs. ‘To obtain the priority of an interrupt, the HAL defines the following function: void HAL_NVIC_SetPriority(IRQnType IRQn, uint32_t PriorityGroup, uint32_ty pPreesptPriori ty, wintaa_t* pSubPriority); I have to admit that the signature of this function is a little bit fuzzy, since it differs from the HAL_NVIG_SetPriority(): here we have to specify also the PriorityGroup, while the HAL_NVIC. SetPriority() function computes it internally. I do not know why ST has decided to use this signature, and I cannot see the reason to make it different from the HAL_NVIC_SetPriority(). ‘The current priority grouping can be obtained using the following function: uint#2_t HAL_NVIC_GetPr Lor ityGrouping( void); 7.4.3 Setting Interrupt Priority in CubeMX CubeMX can be also used to set the IRQ priority and the priority grouping schema. This configu- ration is done through the Configuration view, clicking on the NVIC button, The list of enableable ISRs appears, as shown in Figure 21. Interrupts Management 22a Figure 21: The NVIC configuration view allows to set the ISR priority Using the Priority Group combo box we can set the priority grouping schema, and then assign the individual priority and sub-priority to each interrupt, CubeMX will automatically generate the corresponding C code to setup the IRQ priority inside the #X_GPIO_Init() function. Instead, the global priority grouping schema is configured inside the HAL_MspInit() function, 7.5 Interrupt Re-Entrancy Let us suppose to rearrange the Example 3 so that it uses pin PC12 instead of PB2. In this case, since EXTI2 and EXTI13 share the same IRQ, our Nucleo would never stop blinking. Due to the way the priority mechanism is implemented in Cortex-M processors (that is, an exception with a given priority cannot be preempted by another one with same priority), exceptions and interrupts are not re-entrant, So they cannot be called recursively". However, in most of cases our code can be rearranged to address this limitation. In the following example" the blinking code is executed inside the main() function, leaving to the ISR only the responsibility to setup the global b1 ink variable. * Joseph Viu shows a way to bypass this limitation in his books, However, | strongly discourage from using these tricky techniques unless you really need interrupt re-entrancy in your application. “The example is designed to work with a Nucleo-Fa01RE board, Please, refer fo other book examples if you have a different [Nucleo board. Interrupts Management 222 Filename: sre/nain-ex4.c Rote & POLS *7 GPIO_InitStruct.Pin = GPIO_PIN_12 | GPIO_PIN_43; GPIO_InitStruct Mode = GPIO_NODE_IT_RISING; GPIO_InitStruct Pull = GPIO_PULLDOWN HAL_GPIO_Init(GPIOC, eGPIO_Initstruct /Contigure OPIO pin : PAS */ PIO_InitStruct Pin ~ GPIO_PIN.S, PIO_InitStruct Mode ~ GPTO_NODE_OUTPUT_PP; PIO_InitStruct Pull = GPIO_NOPULL; PIO_InitStruct Speed = GPIO_SPEEDLOW; HAL_GPIO_Init(GPIOA, UGPIO_InitStruct) HAL _NVIG_SetPr ior i tyGrouping(NVIC_PRIORITYGROUP_4); HAL_NVIC.EnableIRQ(EXTI45 40_18Qn); HAL_WIC_SetPr Lor ity(EXTI4S_1_IRQn, 0x0, 2); while(1) ( Af(blink) { HAL_GPIO_TogglePin(GPIOA, GPIO_PIN_5); for(int 1 = 9; 1 < 100000; i+) void EXTI15 40_IRQHandler(void) { HAL _G°10_FXTI_IRQHandler (GPIO_PIN-12); IAL GP10_£XT1_IRQHandler(GP10_PIN43); } void HAL_GP10_£XT1_Cal lback(uint46_t GPIO_Pin) 4£(GPIO_Pin == GPIO_PIN43) blink = 4 else blink ~ 7.6 Mask All Interrupts at Once or an a Priority Basis Sometimes we want to be sure that our code is not preempted to allow the execution of interrupts or more privileged code. That is, we want to ensure that our code is thread-safe. Cortex-M based processors allow to temporarily mask the execution of all interrupts and exceptions, without Interrupts Management 223 disabling one by one. Two special registers, named PRIMASK and FAULIWASK allow to disable all interrupts and exceptions respectively. 31 409876543210. PRIMASK FAULTMASK BASEPRI Figure 22: PRIMASK, FAULTHASK and BASEPRI registers cn if these registers are 32-bit wide, just the first bit is used to enable/disable interrupts and exceptions. The ARM assembly instruction CPSID_{ disables all interrupt by setting the PRIMASK bit to 1, while the CPSIE i instructions enables them by setting PRIMASK to zero. Instead, the instruction CPSID £ disables all exceptions (except for the NMI one) by setting the FAULTMASK bit to 1, while the CPSIE £ instructions enables them, ‘The CMSIS-Core package provides several ma Gisable_irq() and _enable_irq() automatically set and clear the PRIMASK. Any criti be placed between these two macros, as shown below: 1os that we can use to perform these operation: task can _sisable_ira(); /* ALL exceptions with ct jgurable priority are temporarily disabled. You can place critical code here ¥/ _enable_ira(); However, take in mind that, as general rule, interrupt must be masked only for really short time, otherwise you could lose important interrupts. Remember that interrupts are not queued. Another macto we can use is the _set_PRIMASK(x) one, where x is the content of the PRIMASK register (0 or 1). The macro__get _PRIMARK() returns the content of the PRIMASK register. Instead, the ‘macros __set_FAULTMASK(x) and __get_FAULTNASK() allow to manipulate the FAULTMASK register. It is important to remark that, once the PRIMASK register is again set to zero, all pending interrupts are serviced according their priority: PRTHASK causes that the the interrupt pending bit is set but the ISR is not serviced. This is the reason why we say that interrupt are masked and not disabled. Interrupts start to be serviced as soon as the PRIMASK is cleared, Cortex-M3/4/7 cores allow to selectively mask interrupts on a priority basis. The BASEPRI register masks exceptions or interrupts on a priority level. The width of the 8ASEPRI register is the same of the 1°R one, which lasts for the upper 4 bits in STM32 MCUs based on these cores. When BASEPRI is set to 0, it is disabled. When itis set to a non-zero value, it blocks exceptions (including interrupts) that have the same or lower priority level, while still allowing exceptions with a higher priority level Interrupts Management 224 to be accepted by the processor. For example, if the SASEPRI register is set to 0x60, then all interrupts with a priority between @x60-oxFF are disabled, Remember that in Cortex-M cores the higher is the priority number the lower is the interrupt priority level. The _set_BASEPRI (x) macro allows to set the content of the BASEPRI register: remember, again, that the HAL automatically shifts the priority levels to the MSB bits. So, if we want to disable all interrupts with a priority higher than 2, then we have to pass to the _set_BASEPRI() macro the value x20. Alternatively, we can use the following code: SASEPRI(2 << (8 ~ _NVIC_PRIO_BITS)); Interrupts Management 225 Eclipse Intermezzo When coding, productivity is important for every developer. Modern source code editors allow to define custom code snippets, that is fragments of source code that are automatically inserted by the editor when a given “keyword” is typed. Eclipse calls this functionality “code templates”, and they can be invoked by issuing a CtrleSpace right after a keyword is written, For example, open a source file and write the keyword “for” and right after it hit Ctrl+Space. A contextual menu pops up, as shown in the following picture. Sarre a By choosing the entry “for - for loop”, Eclipse will automatically place a new for loop inside the code, Now note a thing: the loop variable var is highlighted, as shown in the following picture. or = «Ys 8 , Ifyou write the new name for the loop variable Eclipse will automatically change it is name in all three places. Eclipse define its set of code templates, but the good news is that you can define your owns! Go inside Eclipse preferences, and then into C/C++->Editor->Templates. Here you can find all pre-defined code snippets and you can eventually add your owns. en For example, we can add a new code template that insert a software breakpoint instruction (esn(“8KPr #");) when we write the keyword bxpt, as shown in the previous picture. Code templates are highly customizable, thanks to the usage of variables and other pattern constructs For more information, refer to the Eclipse documentation’ "ap biely 2am 8. Universal Asynchronous Serial Communications Nowadays there is a really high number of serial communication protocols and hardware interfaces available in the electronics industry. The most of them are focused on high transmission bandwidths, like the more recent USB 2.0 and 3.0 standards, the Firewire (IEEE 1394) and so on. Some of these standards come from the past, but are still widespread especially as communication interface between modules on the same board. One of this is the Universal Synchronous/Asynchronous Receiver/Transmitter interface, also simply known as USART. Almost every microcontroller provides at least one UART peripheral. Almost all STM32 MCUs provide at least two UART/USART interfaces, but the most of them provide more than two interfaces (some up to eight interfaces) according the number of I/O supported by the MCU package. In this Chapter we will see how to program this really useful peripheral using the CubeHAL. Moreover, we will study how to develop applications using the UART both in polling and interrupt modes, leaving the third operative mode, the DMA, to the next chapter. 8.1 Introduction to UARTs and USARTs Before we start diving into the analysis of the functions provided by the HAL to manipulate universal serial devices, it is best to take a brief look to the UART/USART interface and its communication protocol. ‘When we want two exchange data between two (or even more) devices, we have two alternatives ‘we can transmit it in parallel, that is using a given number of communication lines equal to the size of the each data word (eg, eight independent lines for a word made of eight bits), or we can transmit each bit constituting our word one by one. A UART/USART is a device that translates a parallel sequence of bits (usually grouped in a byte) in a continuous stream of signals flowing on a single wire. ‘When the information flows between two devices inside a common channel, both devices (here, for simplicity, we will refer to them as the sender and the receiver) have to agree on the timing, that this how long it takes to transmit each individual bit ofthe information. In a synchronous transmission, the sender and the receiver share a common clock generated by one of the two devices (usually the device that acts as the master of this interconnection system). Universal Asynchronous Serial Communications 227 1 !o lo i110 11 11 10 our ! " ox LLL x Device A ! Device B Figure 1: A serlal communication between two devices using a shared clock source In Figure 1 we have a typical timing diagram’ showing the Device A sending one byte (0001101001) serially to the Device B using a common reference clock. The common clock is also used to agree on when to start sampling the sequence of bits: when the master device starts clocking the dedicated line, it means that it is going to send a sequence of bits. Ina synchronous transmission the transmission speed and duration are defined by the clock: its frequency determines how fast we can transmit a single byte on the communication channel’, But if both devices involved in data transmission agree on how long it takes to transmit a single bit and ‘when to start and finish to sample transmitted bits, than we can avoid to use a dedicated clock line. In this case we have an asynchronous transmission. our a fo eo tee eo ea N START STOP DeviogA _ Device i i I Traok | Tr! Troe! Tre) Troe | Troe Troe Tron! Toot Trabt Figure 2: The timing diagram of a serial communication without a dedicated clock line Figure 2 shows the timing diagram of an asynchronous transmission. The idle state (that is, no transmission occurring) is represented by the high signal. Transmission begins with a START bit, which is represented by the low level. The negative edge is detected by the receiver and 1.5 bit periods after this (indicated in Figure 1s T; 54), the sampling of bits begins. Eight data bits are sampled. The least significant bit (LSB) is typically transmitted first, An optional parity bit is then transmitted (for error checking of the data bits). Often this bit is omitted if the transmission channel is assumed to be noise free or if there are error checking higher up in the protocol layers. ‘The transmission is ended by a STOP bit, which last 1.5 bits. "A ‘Timing Diagram isa representation ofa set of signals in the time domain. ‘However, keepin mind thatthe maximum transmission speed is determined by a lot of other things, lke the characteristics of ‘the electrical channel, the ability ofeach device involved in transmission to sample fast signals, and s0 on. Universal Asynchronous Serial Communications 228 1x RX RX |e 1x ox}! LIL LI Lax Device A USART Device B tx |_——________. ax px fe —___________ 1x Device A UART Device B Figure 3: The signaling difference between a USART and a UART A Universal Synchronous Receiver/Transmitter interface is a device able to transmit data word serially using two I/Os, one acting as transmitter (IX) and one as receiver (RX), plus one additional V/O as one clock line, while a Universal Asynchronous Receiver/Transmitter uses only two RX/TX V/Os (see Figure 3). Traditional we refer to the first interface with the term USART and to the second one with the term UART. A UART/USART defines the signaling method, but it say nothing about the voltage levels. ‘This means that an STM32 UART/USART will use the voltage levels of the MCU I/Os, which is almost equal to VDD (it is also common to refer to these voltage levels as TTI voltage levels). The way these voltage levels are translated to allow serial communication outside the board is demanded to other communication standards. For example, the ELA-RS232 or EIA-RS484 are two really popular standards that define signaling voltages, in addition to their timing and meaning, and the physical size and pinout of connectors. Moreover, UART/USART interfaces can be used to exchange data using other physical and logical serial interfaces. For example, the FT232RL is a really popular IC that allows to map a UART to a USB interface, as shown in Figure 4. ‘The presence of a dedicated clock line, or a common agreement about transmission frequency, does not guarantee that the receiver of a byte stream is able to process them at the same transmission rate of the master. For this reason, some communication standards, like the RS232 and the RS485, provide the possibility to use a dedicated Hardware Flow Control line. For example, two devices communicating using the RS232 interface can share two additional lines, named Request To Send(RTS) and Clear To Send(CTS): the sender sets its RTS, which signals the receiver to begin ‘monitoring its data input line. When ready for data, the receiver will raise its complementary line, CTS, which signals the sender to start sending data, and for the sender to begin monitoring the Universal Asynchronous Serial Communicati 29 slave's data output line. Figure 4: A typical circuit based on FT232RL used to convert a 3.3V TTL UART interface to USB STM32 microcontrollers provide a variable number of USARTs, which can be configured to work both in synchronous and asynchronous mode. Some STM32 MCUs also provide interfaces only able to act as UART. Table 1 lists the UART/USARTs provided by STM32 MCUs equipping all Nucleo boards. The most of USARTs are also able to automatically implement Hardware Flow Control, both for the RS232 and the RS485 standards All Nucleo-64 boards are designed so that the USART2 of the target MCU is linked to the ST- LINK interface’. When we install the ST-LINK drivers, an additional driver for the Virtual COM Port(VCP) is also installed: this allows us to access to the target MCU USART2 using the USB interface, without using a dedicated TTL/USB converter. Using a terminal emulation program we can exchange messages and data with our Nucleo, ‘The CubeHAL separates the API for the management of UART and USART interfaces. All functions and C type handlers used for the handling of USARTs start with the HAL_USART prefix and are contained inside the files stm32xxx_hal_usart. {c,h}, while those related to UARTS management start with the HAL_UART prefix and are contained inside the files stm32xxx_hal_uart {c,h}. Since both the modules are conceptually identical, and since the UART is the most common form of serial interconnection between different modules, this book will only cover the features of the HAL_UART module. SPlease take note that this statement may not be true if you are using a Nucleo-42 or Nucleo-148 board, Check the ST documentation for more about this. Universal Asynchronous Serial Communications 230 USARTH23 - NUCIHORUSRE de? USAR uarrus Nuct#o anne usarria Netto tions 30 NUcItO Hon ware NuctHORDARA seo USARTID YY wamnian | YY NuctHo nse 442 uatus Nucieoies aso USAR YY NUCLEO-H105RB 340 usarrian |v vsariayt YY NuctHonmRe eo SARIS satan | uct40 t0anp wocworer® | 44g | usamraas | ¥ | x wucizo-rowes aso ARTY Usama | Y y NuctoTeRG a2 | ues yy usawnian NuctHoUsaRe 442 tL untas sawn | yy Nuctnosaanz 42 cee | ny Nucttoasme ave USAR YY ‘Table 1: The list of available USARTs and UARTs on all Nucleo boards 8.2 UART In lization Like all STM32 peripherals, even the USAR'Ts* are mapped in the memory mapped peripheral region, which starts from @x4000 0000. ‘The CubeHAL abstracts the effective location of each USART for a given STM32 MCU thanks to the USAR7_typeDef* descriptor. For example, we can simply use the USART2 macro to refer to the second USART peripheral provided by all STM32 microcontrollers with LQFP64 package. “Starting from this paragraph, the tems USART and UART ate used interchangeably, unless different noticed “The analysis ofthe Fields ofthis Csteuet is outside ofthe scope ofthis bok. Universal Asynchronous Serial Communications 231 However, all the HAL functions related to UART management are designed so that they accept as first parameter an instance of the C struct. UART_HanéleTypeDef, which is defined in the following way: typedef struct { USART_typeDef Instance; /* UART registers base address UART_Inittypebet Init; /* VARY conmunication parameters UART_AdvFeatureInitTypedef —AdvancedInit; __/* VARY Advanced Features initialization parameters */ uinta_t spixbuttPtr; /* Pointer to UART Tx transfer Buffer */ uintis_t TxXferSize; /* UART Tx Transfer size v uintis_t TxKferCount; (/* UARE Tx Transfer Counter v uinte_t =pRxBuLtP tr; /* Pointer to UART Rx transfer Buffer +/ uinti6_t RexXforSize; (/* UART Rx Transfer size ” uintt6_t RekfexCount; (/# VART Rx Transfer Counter ” DHA HandleTypeder hamatx; (/# VART Tx DMA Handle parameters v DHA _HandleTypeDet hdmarx; /* ART Rx DNA Handle parameters ” HAL_LockTypedet Lock; /* Looking object ” 10 HALLUARTStateTypedef State; /* VART conmunication state v 10 HAL_UART_ExrorTypedef —ErrorCode; /* UART Error code v } UART_HandleTypeDef; Let us see more in depth the most important fields of this struct. + Instancer is the pointer to the USART descriptor we are going to use. For example, USART2 is the descriptor of the UART associated to the ST-LINK interface of every Nucleo board. + Initsis an instance of the C struct. UART_InitTypeDef, which is used to configure the UART interface. We will study it more in depth in a while. Advancedtnit: this field is used to configure more advanced UART features like the automatic BaudRate detection and the TX/RX pin swapping. Some HALs do not provide this additional field. This happens because USART interfaces are not equal for all STM32 MCUs. This is an important aspect to keep in mind while choosing the right MCU for your application. The analysis of this field is outside the scope of this book. pTxBuftPtr and pxBustPtr: these fields point to the transmit and receive buffer respectively. ‘They are used as source to transmit TxXferSize bytes over the UART and to receive RxXferSize when the UART is configured in Full Duplex Mode. ‘The TxXferCount. and RxxXferCount fields are used internally by the HAL to take count of transmitted and received bytes. + Lock: this field is used internally by the HAL to lock concurrent accesses to UART interfaces. Universal Asynchronous Serial Communications 232 {As said above, the Lock field is used to rule concurrent accesses in almost all HAL routines. If you take a look to the HAL code, you can see several uses of the _HAL_LOCK() macro, which is expanded in this way: define _HAL_LOCK(_HANDLE_) dof \ 44((_HANDLE_)-dLoeke == HAL_LOCKED) € \ return WAL_BUSY; \ ) \ ‘ \ (HANDLE _)->Lock = HAL_LOCKED; } \ Innite (0) It is not clear why ST engineers decided to take care of concurrent accesses to the HAL routines, Probably they decided to have a thread safe approach, freeing the application developer from the responsibility of managing multiple accesses to the same hardware interface in case of multiple threads running in the same application. However, this has an annoying side effect for all HAL users: even if my application does not perform concurrent accesses to the same peripheral, my code will be poor optimized by alot of checks about the state of the Lock field, Moreover, that way to lock is intrinsically thread unsafe, because there is no critical section used to prevent race conditions in case a more privileged ISR preempts the running code. Finally, if my application uses an RTOS, it is much better to use native OS locking primitives (like semaphores and mutexes which are not only atomic, but also correctly manages the task scheduling avoiding the busy waiting) to handle concurrent accesses, without the need to check for a particular return value (#AL_BUSY) of the HAL. functions. Alot of developers have disapproved this way to lock peripherals since the first release of the HAL. ST engin solution. ers have recently announced that they are actively working on a better All the UART configuration activities are performed by using an instance of the C struct UART_- InitTypedef, which is defined in the following w: Universal Asynchronous Serial Communications 233 typedef struct { uint32_t BaudRate, uint32_t WordLength; uint32_t StopBits; uints2_t Parity; uint32_t Node; uint32_t HwFlowct1 ; uint32_t OverSanp! } UART_Inittypebef; 193 + BaudRate: this parameter refers to the connection speed, expressed in bits per seconds. Even if the parameter can assume an arbitrary value, usually the BaudRate comes from a list of well-known and standard values. This because it is a function of the peripheral clock associated to the USART (that is derived from the main HSI or HSE clock by a complex chain of PLLs and multipliers in some STM32 MCU), and not all BaudRates can be easily achieved without introducing sampling errors, and hence communication errors. Table 2 shows the list, of common BaudRates, and the related error calculation, for an STM32F030 MCU. Always consult the reference manual for your MCU to see which peripheral clock frequency best fits the needed BaudRate on the given SIM32 microcontroller. s Seen Been 576005162000 ca 7 152005110008 ot soso 230760 016 os ous ous 112000000 2ooco0o 0 —2000H0 | 12 3000000 300000000000 84000000 NA. NA 40000000 145000000 NA. NA 505260 1.05 ° ‘Table 2: Error calculation for programmed band rates at 48 MHz in both cases of oversampling by 16 or by 8 Universal Asynchronous Serial Communications 234 + WordLength: it specifies the number of data bits transmitted or received in a frame. This field can assume the value UART_NORDLENGTH_8® or UART_WORDLENGTH_98, which means that we can transmit over a UART packets containing 8 or 9 data bits, This number does not include the overhead bits transmitted, such as the start and stop bits. + StopBits: this field specifies the number of stop bits transmitted, It can assume the value UART_STOPBITS_4 or UART_STOPBI”S_2, which means that we can use one or two stop bits to signal the end of the frame. + Parity: it indicates the parity mode, This field can assume the values from Table 3. Take note that, when parity is enabled, the computed parity is inserted at the MSB position of the transmitted data (9th bit when the word length is set to 9 data bits; sth bit when the word length is set to 8 data bits). Parity is a very simple form of error checking. It comes in two flavors: add or even. To produce the patity bit, all data bits are added up, and the evenness of the sum decides whether the bit is set or not. For example, assuming parity is set to even and was being added to a data byte like 0b01011101, which has an odd number of 1's (5), the parity bit would be set to 1. Conversely, ifthe parity mode was set to oda, the parity bit would be 0 Parity is optional, and not very widely used. It can be helpful for transmitting across noisy mediums, but it will also slow down data transfer a bit and requires both sender and receiver to implement error-handling (usually, received data that fails must be re-sent). When a parity error occurs, all STM32 MCUs generate a specific interrupt, as we will see next. + Mode: it specifies whether the RX or TX mode is enabled or disabled. This field can assume one of the values from Table 4. + HwFlowctl: it specifies whether the RS232" Hardware Flow Control mode is enabled ot disabled. This parameter can assume one of the values from Table 5. Table 3: Available parity modes for a UART connection Parity Mode Description UART_PARTTY_NONE No parity check enabled UART_PARITY_EVEN ‘The parity bit is set to 4 if the count of bits equal to 4 is odd UART_PARTTY_ODD ‘The parity bit is set to 4 if the count of bits equal to 4 is even Table 4: Available UART modes UART Mode Description UART_HODE_RX ‘The UART is configured only in receive mode UART_HODE_Tx ‘The UART is configured only in transmit mode UaRT_HODE_TX_RX The UART is configured to work bot in receive an transmit mode “his ied is only used to enable the RS232 flow contol To enable the RS48S flow control, the HAL provides specific function, _pstesce_tnit(), defined inside the steszax pal uertex.c file Universal Asynchronous Serial Communications ‘Table 5: Available flow control mode for a UART connection Flow Control Mode Description 235 UART_HWCONTROL_NO# UART_HWCONTROL 21S UART_jIWCONTROL CTS UART_jIWCONTROL_R1S_CTs ‘The Hardware Flow Control is disabled ‘The Request To Send(RTS) line is enabled ‘The Clear To Send(CTS) line is enabled Both RTS and CTS lines enabled + OverSanpling: when the UART receives a frame from the remote peer, it samples the signals in order to compute the number of 1 and 0 constituting the message. Oversampling is the tech- nique of sampling a signal with a sampling frequency significantly higher than the Nyquist rate. The receiver implements different user-configurable oversampling techniques (except in synchronous mode) for data recovery by discriminating between valid incoming data and noise. ‘This allows a trade-off between the maximum communication speed and noise/clock inaccuracy immunity. The OverSanp1 ing field can assume the value UART_OVERSAMPLING_46 to perform 16 samples for each frame bit or UART_OVERSAMPLING_8 to perform 8 samples. Table 2 shows the error calculation for programmed baud rates at 48 MHz in an STM32F030 MCU in both cases of oversampling by 16 or by 8. Now it is a good time to start writing down a bit of code, Let us see how to configure the USART2 of the MCU equipping our Nucleo to exchange messages through the ST-LINK interface. int main(void) { UART_HandleTypeDef huart2; /* Initialize the HAL +/ HAL, nit); /* Configure the system clock */ systenClock_Config() huart2 huart2 huart2 huart2 huart2 huart2 huart2 huart2 Instance = USART2; Init, BaudRate ~ 35100; Init.WordLength = UART_WOROLENGTH_88; Init. StopBits ~ UART_STOPBITS 4; Init. Parity = UART_PARITY_NONE; Init. Mode = UART_MODE_TX_RX; Init. Hw lowCt1 = UART_HNCONTROL _NONE; Init. OverSampl ing = UART_OVERSAMPLING_46 IAL_UART_Init(huart2); Universal Asynchronous Serial Communications 236 ‘The first step is to configure the USART2 peripheral. Here we are using this configuration: 38400, N, 1. That is, a BaudRate equal to 38400 Bps, no parity check and just one stop bit. Next, we disable any form of Hardware Flow Control and we choose the highest oversampling rate, that is 16 clock ticks for each transmitted bit. The call to the HAL_UART_Init() function ensures that the HAL initializes the USART2 according the given options. However, the above code is still not sufficient to exchange messages through the Nucleo Virtual COM Port. Don’t forget that every peripheral designed to exchange data with the outside world must be properly bound to corresponding GPIOs, that is we have to configure the USART2 TX and RX pins. Looking to the Nucleo schematics, we can see that USART2 TX and RX pins are PA2 and PAS respectively. Moreover, we have already seen in Chapter 4 that the HAL is designed so that HAL_UART_Init() function automatically calls the HAL_UART_MspInit() (see Figure 19 in Chapter 4) to properly initialize the I/Os: itis our responsibility to write this function in our application code, which we will be automatically called by the HAL. (2) Is It Mandatory to Define This Function? ‘The answer is simply no. This is just a practice enforced by the HAL and by the code automatically generated by CubeMX. The #AL_UART_HspInit(), and the corresponding function WAL_UART_MspDeInitt() which is called by the WAL_UART_DeIni t() function, are declared inside the HAL in this way: weak vold HAL_UART_NspInit(UART HandleTypeDef huart); The function attribute _weak is a GCC way to declare a symbol (here, a function name) with a weak scope visibility, which we will be overwritten if another symbol with the same name with a global scope (that is, without the _weak attribute) is defined elsewhere in the application (that is, in another relocatable file), The linker will automatically substitute the call to the function WAL UART_Mspinst() defined inside the HAL if we implement it in our application code. ‘The code below shows how to correctly code the HAL_UART_MspInit() function. void iAL_UART_NspInit(UART_jlandleTypeDef* huart) { GPIO_InitTypedef GPIO_Initstruct, if(huart->Tastance=-USART2) { /*tUSART2 fon figuration PAZ ===> USART2_TK PAg ===> USART2_RK v Universal Asynchronous Serial Communications 237 GPIO_InitStruct.Pin = USART_TK_Pin lUSART_RK_Pin; GPIO_Initstruct. Mode = GPTO_NODE_AF_PP; OPIO_InitStruct, Pull = GPTO_NOPULL; OPIO_InitStruct..Speed ~ GPTO_SPEED_LOW; OPIO_InitStruct Alternate = GPIO_AF1_USART2; /* WRWINO: this depends on the specific $1432 NCU */ HAL_SPIO_Init(GPIOA, eOPIO_InitStruct); ‘As you can see, the function is designed so that it is common for every USART used inside the application. The if statement disciplines the initialization code for the given USART (in our case, USART2). The remaining of code configures the PA2 and PA3 pins. Please, take note that the alternate function may change for the MCU equipping your Nucleo. Consult the book examples to see the right initialization code for your Nucleo. Once we have configured the USART2 interface, we can start exchanging messages with our PC. Please, take note that the code presented before could not be sufficient to correctly initialize By eeeSint pete! or some st3s0 MCUs Some Ss? micconles, he the ‘STM32F334R8, allow the developer to choose the clock source for a given peripheral (for example, the USART2 in an STM32F334R8 MCU can be optionally clocked from SYSCLK, HSL, LSE or PCLKA). It is strongly suggested to use CubeMX the first time you configure the peripherals for your MCU and to check carefully the generated code looking for this kind of exceptions. Otherwise, the datashect is the only source for this information, 8.2.1 UART Configuration Using CubeMX As said befor the first time we configure the USART2 for our Nucleo it is best to use CubeMX. ‘The first step is enabling the USART2 peripheral inside the Pinout view, selecting the Asynchronous entry from the Mode combo box, as shown in Figure 5. Both PA2 and PA3 pins will be automatically highlighted in green. Then, go inside the Configuration section and click on the USART2 button. ‘The configuration dialog will appear, as shown in Figure 5 on the right’, This allows us to configure the USART configuration settings, such as the BaudRate, word length and so on’ re, ake note thatthe Figure § i obtained combining two captures in one figure, Its not posible to show the USART configuration dialog from the Pinout view *Some of you, especially those having a Nucleo-F, wil notice that the configuration dialog is different from the one shown in Figure 5. Pease refer the reference manual for your target MCU for more information, Universal Asynchronous Serial Communications 238 Once we have configured the USART interface, we can generate the C code. You will notice that CubeMX places all the USART2 initialization code inside the MX_USART2_UART_Init() (which is contained in the main. file). Instead, all the code related to GPIO configuration is placed into the HAL_UART_Mspinit() function, which is contained inside the stn32xxxx_hal_nsp.c file. Figure 5: CubeMX can be used to configure the UART2 interface easily 8.3 UART Communication in Polling Mode ‘STM32 microcontrollers, and hence the CubeHAL, offer three ways to exchange data between peers over a UART communication: polling, interrupt and DMA mode. Itis important to stress right from now that these modes are not only three different flavors to handle UART communications. They are three different programming approach to the same task, which introduce several benefits both from the design and performance point of view. Let us introduce them briefly. + In polling mode, also called blocking mode, the main application, or one of its threads, synchronously waits for the data transmission and reception. This is the most simple form of data communication using this peripheral, and it can be used when the transmit rate is not too much low and when the UART is not used as critical peripheral in our application (the classical example is the usage of the UART as output console for debug activities) Universal Asynchronous Serial Communications 239 + In interrupt mode, also called non-blocking mode, the main application is freed from waiting for the completion of data transmission and reception. The data transfer routines terminate as soon as they complete to configure the peripheral. When the data transmission ends, a subsequent interrupt will signal the main code about this. This mode is more suitable when communication speed is low (below 38400 Bps) or when it happens “rarely”, compared to other activities performed by the MCU, and we do not want to stuck it waiting for data transmission. + DMA mode offers the best data transmission throughput, thanks to the direct access of the UART peripheral to MCU internal RAM. This mode is best for high-speed communications and when we totally want to free the MCU from the overhead of data transmission, Without the DMA mode, it is almost impossible to reach the fastest transfer rates that the USART peripheral is capable to handle. In this chapter we will not see this USART communication mode, leaving it to the next chapter dedicated to DMA management. ‘To transmit a sequence of bytes over the USART in polling mode the HAL provides the function HAL_StatusTypeDef HAl_UART_Transmit(UART_HandleTypeDef *huart, uint®_t =pData uint16t Size, uint32_t Timeout); where: + huarte it is the pointer to an instance of the struct. UART_HandleTypebef seen before, which. identifies and configures the UART peripheral; + pData: is the pointer to an array, with a length equal to the Size parameter, containing the sequence of bytes we are going to transmit; + Timeout: is the maximum time, expressed in milliseconds, we are going to wait for the transmit completion, If the transmission does not complete in the specified timeout time, the function aborts and returns the HAL TIMEOUT value; otherwise it returns the HAL_OK value if no other errors occur, Moreover, we can pass a timeout equal to HAL_MAK_DELAY (@xFFFF FFFF) to wait indefinitely for the transmit completion, Conversely, to receive a sequence of bytes over the USART in polling mode the HAL provides the function HAL_StatusTypeDef HAL_UARI_Receive(UART_HandleTypeDef *huart, uint&_t *pData, uinti6_t Size, uint32t Timeout); where + huarts itis the pointer to an instance of the struct. UART_HandleTypeDef seen before, which identifies and configures the UART peripheral; 240 + Data: is the pointer to an array, with a length at lest equal to the Size parameter, containing the sequence of bytes we are going to receive, The function will block until all bytes specified by the size parameter are received. is the maximum time, expressed in milliseconds, we are going to wait for the receive completion. If the transmission does not complete in the specified timeout time, the function aborts and returns the HAL_TIMEOU? value; otherwise it retums the HAL_OK value if no other errors occur, Moreover, we can pass a timeout equal to HAL_MAK_DELAY (@xFFFF FFFF) to wait indefinitely for the receive completion. A Read Carefully It is important to remark that the timeout mechanism offered by the two functions works only if the KAL_IncTick() routine is called every 1ms, as done by the code generated by CubeMX (the function that increments the HAL tick counter is called inside the SysTick timer ISR). + Timeout: Ok. Now it is the right time to see an example. Filename: sre/main-ext .¢ Int wain(veia) { 22 wint®@_t opt = 0; /* Reset of all peripherals, Initializes the Flash interface and the SysTick. */ HAL_Init(); systencl Figure the system config() Mx_GPIO_Init(); MX_USART2_UART_Init(); Nessage: printWelconeMessage( ); while (1) { opt ~ readUserInput() processUserInput (opt) it(opt == 3) goto printMossage; Universal Asynchronous Serial Communications 241 void printkelconeNessage(void) { HAL_UART transmit (ahuartZ, (uintB_t*)"\@93[0;0H", strTen("\999[0,0H"), HAL_MAK_DELAY); HAL_UART Transmit (Ghuart2, (uint_t*)"\933[2J", strien("\@33[2J"), HAL _MAK_DELAY); ) HAL _UART Transmit (thuart2, (uint®_t*)WELCOME_MSG, strlen(WELCOME_MSG), WAL_MAX_DELAY) ; © HAL_VART_Transnit(Bhuart2, (uint&_t*)MAIN_MENU, strien(MAIN_MENU), HAL_MAX_DELAY) ; 1} 9 uinte_t readUserInput(veid) { char readBuf|t] ; HAL_UART_transmit(Shuart2, (uint®_t*)PROMPT, strlen(PRONPT), HAL_MAK_OELAY); HALUART Receive(@huart2, (uint®_t*)readBuf, 1, HAL_MAX DELAY); se return atoi(readBut); 2} uint8_t processUserInput(uints_t opt) { 2 char msg[30] ; if(lopt || opt > 3) return 0; sprintf(msg, "Xs", opt); © HALUART_transmit(thuart2, (uint@t*)msg, strien(msg), HAL MAX DELAY); switch(opt) { case 1 n HAL_GP1OTogglePin(GPIOA, GPIO_PINS); a break; 1 ease 2 sprintf(msg, "\r\nUSER BUTTON status: %s", HAL_GPIO_ReadPin(GPIOC, GPIO_PIN13) =~ GPIO_PIN.RESET 7 "PRESSED" *RELEASED\ 4 WAL_UART_Transmit(ehuart2, (vint8_t*)msg, strlen(msg), HAL_NAX_DELAY); 2 break; 8 return 2; ed 8 return 1; eo} ‘The example is a sort of bare-bone management console. The application starts printing a welcome ‘message (lines 36) and then entering in a loop waiting for the user choice. The first option allows Universal Asynchronous Serial Communicati 242 to toggle the LD2 LED, while the second to read the status of the USER button. Finally, the option 3 causes that the welcome screen is printed again. The two strings *\933[0;08" and "\933[2J" are escape sequences. They are standard sequences of chars used to manipulate the terminal console, The first one places the cursor in the top-left part of the available console screen, and the second one clears the screen. To interact with this simple management console, we need a serial communication program. There are several options available. The easy one is to use a standalone program like putty’ for the Windows platform (if you have an old Windows version, you can also consider to use the classical HyperTerminal tool), or kermit'® for Linux and MacOS. However, we will now introduce a solution to have an integrated serial communication tool inside the Eclipse IDE. As usual, the instructions differ between Windows, Linux and MacOS. 8.3.1 Installing a Serial Console in Windows For the Windows OS we have a simple and reliable solution. This is based on two plug-ins. The first one is a wrapper plug-in around the RXTX® Java library. To install it, go to Help->Install software. menu, then click on the Add... button, and fill the fields in the following way: (see Figure 6). Name: RXTX Location: http://rxtx.qbang.org/eclipse/ (Ss tepstey = ee 2 I Figure 6: The dialog to add a new plug-in repository Click on OK and install the release RXTX 2.1-7r4 following the instructions. Once, the installation has been completed, go to Help->Eclipse Marketplace... In the Find text box write “tef®. After a while, the TM Terminal plug-in should appear, as shown in Figure 7. Click on the Install button and follow the instructions. Restart Eclipse when requested, PapsbiEy sont hip ww columbia edufkermi yep qbangorg! Universal Asynchronous Serial Communications 243 “Temi 92015010 (ars 7) Te imemmese ncaa Sy, sense serine SSE cnet ‘Ab] (2) went nacre mt) (ta Nedecipse Ui tstater 037 Pome ees duis Coed al sae BZ) ters 00 emer) (i Enid tuto 2014 Nodes, JvaSript, Ina and Web eo Ci Sa el te dai Seger et aesensesad ners ees ® E z ah Figure 7: The Eclipse Marketplace To open the Terminal panel you can simply press Ctrl+AltsT, or you can go to Window->Show View->Other... menu and search for Terminal view. Betone Tats Caml I Bepetn jeg Tore 5 Beamewions |B carvoorowsrea) = hereto indus ercton 63.781, sri (6) soon Mrertt Carportion. a0 rig Figure 8: How to start a new terminal By default, the Terminal pane opens a new command line prompt. Click on the Open a Terminal Universal Asynchronous Serial Communications 244 icon (the one circled in red in Figure 8). In the Launch Terminal dialog (see Figure 9) select Serial Terminal as terminal type, and then select the COM Port corresponding to the Nucleo VCP, and 38400Bps as Baud Rate. Click on the OK button. Figure 9: Terminal type selection dialog Now you can reset the Nucleo, ‘The management console we have programmed using the HAL_UART library should appear in the serial console window, as shown in Figure 10. roe Tas cone repens Sy howe eres caaaelo oto Sei CONS 61051859) 2 CARMONA oes Dt (0.8591) Figure 10: The Nucleo management console shown in the terminal view 8.3.2 Installing a Serial Console in Linux and MacOS X Unfortunately, installing the RXTX plug-in on Linux and MacOS X is not a trivial task. For this reason we will go another way. ‘The first step is installing the kermit tool. To install it in Linux, type at command line:

You might also like