📜 ⬆️ ⬇️

We start to study Cortex-M on the example of STM32, part 2

This article is a continuation of the cycle for programming microcontrollers based on the Cortex-M core.
The first article can be read here:
We start to study Cortex-M on the example of STM32
The task of the articles is a detailed description of the features that arise when programming the MC. The material is not intended for those who want to run an example of LED blinking in 10 minutes. I will try to describe in detail what is often hidden from beginners, so as not to frighten them.

I really want programmers using standard libraries, templates, examples, etc. understood how it all works. And in the absence of these libraries and examples, they could independently solve their problem.

The main focus is on studying the documentation for the Cortex-M core and documentation for a specific controller.
This time we will talk about interruptions, as well as some issues of the memory architecture and the structure of the MC firmware.

A few words about the ARM documentation


For reasons that are not completely clear to me, you cannot go to the ARM website and download the full documentation for the Cortex-M4 core. Yes, and the Cortex-M3 is also impossible.
Have to read a few documents.
1. The study will have to start with the Cortex ™ -M3 TechnicalReference Manual Revision: r1p1 - the very first revision of the technical specification for the core Cortex-M3
2. In all further revisions and descriptions of the Cortex ™ -M4 TechnicalReference Manual , only general data and changes from the previous document are described.
So please do not be surprised links to the specifications of another kernel.
')

Interrupt and Events


First of all, you need to understand what interrupts are.
In MK Cortex-M there are two concepts that often confuse Interrupt and Event .
Event is an event (hardware or software) to which the core or peripheral blocks can react. One of the reaction options may be interruption.
Interrupt is the interruption of the program operation and the transfer of control to the specialized section interrupt handler.

The relationship between Event and Interrupt is as follows:
Each Interrupt is called an Event, but not every Event causes an Interrupt.
In addition to interrupts, events can activate other features of the MC.

NVIC


Management and processing of interrupts is performed by the NVIC (Nested Vectored Interrupt Controller) priority vector interrupt controller . The interrupt controller is part of the core Cortex-M. Documentation for this controller should be started from the Cortex ™ -M3 TechnicalReference Manual Revision: r1p1

When a certain event occurs, the interrupt controller automatically interrupts the execution of the main program, and calls the appropriate interrupt handling function. After exiting the interrupt handler function, the program continues execution from the point where the interrupt occurred. Everything happens automatically (if NVIC is properly configured, but more on that below).

From the name itself it is clear that the NVIC controller supports interrupt nesting and priorities. Each interrupt when configuring NVIC is assigned a priority. If a high-priority interrupt occurs during the processing of a low-priority interrupt, then it, in turn, will interrupt the low-priority interrupt handler.

How it works?


This post does not pretend to be absolute, I advise you to study the interrupt section in the Cortex ™ -M3 Technical Reference Manual . Since this part of the core has not changed, its description is given in the first revision r1p1 to the core Cortex-M3.

Interrupt entry and exit

When an interrupt is triggered, the NVIC switches the kernel into interrupt-handling mode. After switching to the interrupt processing mode, the core registers are pushed onto the stack. Directly during the writing of the register values ​​to the stack, the initial address of the interrupt handling function is sampled.

The register moves the program status register ( Program Status Register (PSR) ), the program counter ( Program Counter (PC) ) and the link register ( Link Register (LR) ). Kernel registers are described in the Cortex-M4 Generic User Guide . Due to this, the state in which the core was located before the transition to the interrupt handling mode is remembered.

Registers R0 - R3 and R12 are also saved. These registers are used in instructions for passing parameters, therefore, placing on the stack makes it possible to use them in the interrupt handling function, and R12 often acts as a working register for the program.

Upon completion of the interrupt processing, all actions will be performed in the reverse order: the contents of the stack are retrieved and, in parallel with this, the return address is sampled.

From the moment of initiation of the interrupt to the execution of the first command of the handler, 12 clock cycles pass, the same time is necessary for the main program to resume after the completion of the interrupt processing.

Interrupt nesting

As mentioned above, NVIC supports interrupts with different priorities that can interrupt each other. In this case, there may be various situations, the processing of which is optimized in different ways.

1. Pausing a low-priority interrupt
In this situation, the low-priority interrupt processing is terminated. The next 12 cycles are saved to the stack of the new data set and high-priority interrupt processing is started. After it has been processed, the contents of the stack are automatically extracted and the low-priority interrupt processing resumes.
There are no major differences from interrupting the main program.

2. Continuous interrupt handling
This situation can occur in two cases: if two interrupts have the same priority and occur simultaneously, if a low-priority interrupt occurs during the processing of a high-priority one.
In this case, intermediate operations on the stack are not performed. There is only the loading of the address of the low-priority interrupt handler and the transition to its execution. Rejection of operations on the stack saves 6 cycles. The transition to the next interrupt does not take place in 12 cycles, but in just 6.

3. Delay high priority interrupt
The situation arises if a high-priority interrupt occurs during the transition to low-priority processing (in the very 12 cycles). In this case, the transition to the high-priority interrupt will occur at least 6 cycles from the moment of its occurrence (the time required to load the address of the interrupt handler and transition to it). Return to low priority is already described above.

Interrupt Priorities

In addition to simply setting the priority of interrupts, NVIC realizes the possibility of grouping priorities.
Interrupts in a group with a higher priority may interrupt the interrupt handlers of a group with a lower priority. interrupts from the same group but with different priority within the group cannot interrupt each other. The priority within the group determines only the order in which the handler was called when both events were activated.

The interrupt priority value is specified in the Interrupt Priority Registers registers (see the Cortex-M4 Generic User Guide ). In this case, part of the bit is responsible for the priority of the group in which the interrupt is located, and part for the priority within the group.
Setting the allocation of bits to group priority or priority within a group is done using the Application Interrupt and Reset Control Register (CAREFULLY !!!, see the Cortex-M4 Generic User Guide ).

As you may have noticed, the Cortex-M4 Generic User Guide says that setting priorities and grouping priorities depends on the specific implementation defined .
But the next is not a very pleasant thing. In the Reference manual to the MK STM32F407 about NVIC almost no information. But there is a link to a separate document. In order to deal with the implementation of NVIC in the STM32, you will have to read another document - the STM32F3xxx and the STM32F4xxx Cortex-M4 programming manual . Generally speaking, I advise you to carefully study this document and on all other issues, in it the work of the kernel is described in more detail than in the documentation from ARM.
In it, you can already find:
A programmable priority level of 0-15 for each interrupt. A higher level corresponds to a
lower priority

Of the 8 possible priority bits, only 4 are used. But this is quite enough for most tasks.

Masking interrupts

Suppose that we are faced with the task of launching a launch vehicle by pressing the red button, but only on the condition that the key is turned.
There is absolutely no sense in generating a key interrupt. But we need an interruption to press the red button. In order to enable / disable various interrupt vectors, there is an interrupt masking.
The masking of interrupts is performed using the Interrupt Set-enable Registers registers.
If the interrupt is masked, it does not mean that the periphery does not generate events! Just NVIC does not call the event handler.

Interrupt Vector Table

All possible interrupts supported by NVIC are recorded in the interrupt vector table. In essence, the interrupt vector table is nothing more than a list of addresses of interrupt handler functions. The number in the list corresponds to the interrupt number.

As written in the Cortex-M4 Generic User Guide , NVIC supports up to 240 different interrupt vectors. But implementation already depends on the specific manufacturer.
In the description of the kernel, only kernel exception interrupts are standardized (see section 2.3.2 Exception types in the Cortex-M4 Generic User Guide ):

With the description of these exceptions, I suggest you familiarize yourself. Some of them will be covered in the following articles.
The remaining interrupts are unique to the MK. The description of the vector table of your MC can be found in the corresponding Reference manual (see Vector table for STM32F405xx / 07xx and STM32F415xx / 17xx). STM32F4xx controllers support 81 interrupt vectors. You may notice that this table lists all peripheral blocks (and some more than once).
Virtually all peripheral blocks generate interrupts so that the core is distracted by working with it only upon the occurrence of a significant event (for example, receiving data on the UART).

The location of the interrupt vectors and download MK

Having dealt with the principles of work of interruptions in Cortex-M, it remains to understand only where the interrupt table is stored.
For this, it is worth considering the boot process and the controller firmware structure. This time we will only consider loading from the built-in flash memory.
The vector table at the beginning of the flash memory address space should be at least (see Cortex-M3 Technical Reference Manual :
• stack top address
• reset routine location
• NMI ISR location
• Hard Fault ISR location.

From the beginning of flash memory, the kernel reads the SP (stack top addres) and PC (reset routine location) values. Thus, the function starts automatically, with the address read into the PC register. This may be, for example, main.

After the required four components, there may be a further table of interrupt vectors. The main thing is to keep order.
If you wish, you can place the interrupt vector table in another area of ​​memory, but then you need to tell NVIC where we moved the table. The Vector Table Offset Register is responsible for this offset of the vector table (see Cortex-M4 Technical Reference Manual . This may be necessary to write the built-in bootloader), but about this some other time.

From theory to practice


TZ of the second project

An example is created for the STM32F4Discovery debug card.
When you press the button, the LED3 should light up. When the PC6 and GND contacts are closed, the LED5 LED lights up.
In the process of programming, we will play around with the interrupt priorities and see what it will lead to.

Iron part

Find the button and the LED in the documentation for the board :

If the button is not pressed on the pin, PA0 will be a logical zero; when you press the button, a logical 1 (3.3V) will appear on the button.
LED3 is connected to pin PD13.
LED5 is connected to the PD14 pin.
The most interesting thing with the PC6 contact is that it is directly connected to the male connector. We will need to ensure the registration of logical 1 when it is not shorted with contact GND. How to do this will be discussed below.

GPIO setup

For our task, you need to configure the pins PD13 and PD14 as weekends. How to do this can be found in the previous article .
With the setting of the pin PA0, everything is also quite simple - you need to configure it to enter. Despite the fact that after the calculation of the MK, almost all the pins are configured to enter, it is highly desirable to explicitly prescribe this initialization.
With pin PC7, everything is somewhat more interesting. Since he is “hanging in the air,” his condition is undefined. It is necessary for us that at the same time his state would always be “1.” To do this, in the GPIO block activate the tightening. In our case, you need a lift to power - PULL UP.
Activation of suspenders is carried out using the register GPIO port pull-up / pull-down register .

EXTI Interrupts

To perform our “TK” using interrupts, we need to configure interrupts that will be triggered when the PA0 contact transitions from state “0” to state “1”, and an interruption when the PC6 contact switches from state “1” to state “0”.

In MK STM32F4xx for this purpose serves the controller external interrupts / events - EXTI (External interrupt / event controller). I strongly recommend that you familiarize yourself with its functionality in the Reference manual . We need to do as described:
Hardware interrupt selection
To configure the 23 lines as sources, use the following procedure:
• Configure the mask bits of the 23 interrupt lines (EXTI_IMR)
• Configure the bits of the interrupt lines (EXTI_RTSR and EXTI_FTSR)
If you’ve been on 23 lines, you’ll be able to verify the correct mode.

We will need 0 and 6 lines EXTI. To unmask the corresponding interrupt lines, you need to write 0x9 value to the EXTI_IMR register.
For the line PA0, the generation of an interrupt event is required on the transition from state “0” to state “1” - on the rising edge. That is, it is necessary to write 1 to the zero bit of the EXTI_RTSR register.
For the PC6 line, on the contrary, it is necessary to generate an interrupt event on the transition from state “1” to state “0” - on the falling front. That is, you must write 1 in the sixth bit of the register EXTI_FTSR.
This completes the setup of the EXTI block. The last item will be implemented when setting up NVIC.

In addition to this, it is necessary to determine the pin from which port is connected to a specific EXTI line. This is done using the SYSCFG registers external interrupt configuration register ( Reference manual ). These registers are located in the System configuration controller , which seems to me not very logical (why not to include this setting in the EXTI?), But we will leave this fact on the conscience of ST.

NVIC setup

The processing of a specific interrupt vector is activated using the Interrupt set-enable registers (NVIC_ISERx) registers . Register descriptions are provided in the Cortex-M4 Generic User Guide . The interrupt vector table for our MC is given in the Reference manual (see Table 61).

From the table you can see that for the 0 line there is a separate interrupt, but lines 5 through 9 generate one interrupt for all.
In addition, from the table we learned the numbers of vectors, we need interrupts. Now you need to write "1" to 6 bits (activation of line 0 interrupt EXTI) of the NVIC_ISER0 register (address 0xE000E100) and 23 bits of the same register (line 5-9 interrupt activation).

Setting priorities

In order to play around with interrupt priorities, we set up priority groups so that 2 bits are responsible for the priority within the group, and 2 bits are for the priority of the group itself. To do this, write the value 0x05FA0500 to the register Application interrupt and reset control register ( STM32F3xxx and STM32F4xxx Cortex-M4 programming manual ).
Priorities are configured using the Interrupt Priority Registers registers ( STM32F3xxx and STM32F4xxx Cortex-M4 programming manual ). We will be interested in the Interrupt Priority Register 2 (0xE000E4008) and the Interrupt Priority Register 5 (0xE000E401C).
Let's not change priorities yet. Let them be the same for both interrupts.

Interrupt handling

Functions interrupt handlers are nothing more than just functions of the C language that neither receive nor return (and correctly, from whom and not to whom).
The main rule - interrupt handling should be carried out as quickly as possible !!! Otherwise, low priority interrupts may wait too long.

After the completion of interrupt processing, it is necessary to reset the activity of the event that caused the interruption - “clear interrupt”. Clearing the EXTI interrupt is done using the EXTI_PR register. Note: the record "1" clears the interruption, the record "0" has no effect whatsoever.

If with the interrupt processing of the line 0 EXTI everything is quite simple, then with the group of lines 5-9 the question arises - how to determine which line caused the interruption. You can find out by checking the register bits Pending register (EXTI_PR) - Reference manual .

Create a vector table and place it in the right place.

In order for the vector table with the correct addresses of the functions of the interrupt handlers to be located at the beginning of the MK flash memory, create and connect the startup.c file to the project.
File contents
// Enable the IAR extensions for this source file. #pragma language=extended #pragma segment="CSTACK" // Forward declaration of the default fault handlers. void ResetISR(void); static void NmiSR(void); static void FaultISR(void); static void IntDefaultHandler(void); // The entry point for the application startup code. extern void __iar_program_start(void); extern void EXTI_Line0_IntHandler(void); extern void EXTI_Line6_IntHandler(void); // A union that describes the entries of the vector table. The union is needed // since the first entry is the stack pointer and the remainder are function // pointers. typedef union { void (*pfnHandler)(void); void * ulPtr; } uVectorEntry; // The vector table. Note that the proper constructs must be placed on this to // ensure that it ends up at physical address 0x0000.0000. __root const uVectorEntry __vector_table[] @ ".intvec" = { { .ulPtr = __sfe( "CSTACK" ) }, // The initial stack pointer ResetISR, // The reset handler NmiSR, // The NMI handler FaultISR, // The hard fault handler IntDefaultHandler, // MPU Fault Handler IntDefaultHandler, // Bus Fault Handler IntDefaultHandler, // Usage Fault Handler IntDefaultHandler, // Reserved IntDefaultHandler, // Reserved IntDefaultHandler, // Reserved IntDefaultHandler, // Reserved IntDefaultHandler, // SVCall Handler IntDefaultHandler, // Debug Monitor Handler IntDefaultHandler, // Reserved IntDefaultHandler, // PendSV Handler IntDefaultHandler, // SysTick Handler //External Interrupts IntDefaultHandler, // Window WatchDog IntDefaultHandler, // PVD through EXTI Line detection IntDefaultHandler, // Tamper and TimeStamps through the EXTI line IntDefaultHandler, // RTC Wakeup through the EXTI line IntDefaultHandler, // FLASH IntDefaultHandler, // RCC EXTI_Line0_IntHandler, // EXTI Line0 IntDefaultHandler, // EXTI Line1 IntDefaultHandler, // EXTI Line2 IntDefaultHandler, // EXTI Line3 IntDefaultHandler, // EXTI Line4 IntDefaultHandler, // DMA1 Stream 0 IntDefaultHandler, // DMA1 Stream 1 IntDefaultHandler, // DMA1 Stream 2 IntDefaultHandler, // DMA1 Stream 3 IntDefaultHandler, // DMA1 Stream 4 IntDefaultHandler, // DMA1 Stream 5 IntDefaultHandler, // DMA1 Stream 6 IntDefaultHandler, // ADC1, ADC2 and ADC3s IntDefaultHandler, // CAN1 TX IntDefaultHandler, // CAN1 RX0 IntDefaultHandler, // CAN1 RX1 IntDefaultHandler, // CAN1 SCE EXTI_Line6_IntHandler, // External Line[9:5]s IntDefaultHandler, // TIM1 Break and TIM9 IntDefaultHandler, // TIM1 Update and TIM10 IntDefaultHandler, // TIM1 Trigger and Commutation and TIM11 IntDefaultHandler, // TIM1 Capture Compare IntDefaultHandler, // TIM2 IntDefaultHandler, // TIM3 IntDefaultHandler, // TIM4 IntDefaultHandler, // I2C1 Event IntDefaultHandler, // I2C1 Error IntDefaultHandler, // I2C2 Event IntDefaultHandler, // I2C2 Error IntDefaultHandler, // SPI1 IntDefaultHandler, // SPI2 IntDefaultHandler, // USART1 IntDefaultHandler, // USART2 IntDefaultHandler, // USART3 IntDefaultHandler, // External Line[15:10]s IntDefaultHandler, // RTC Alarm (A and B) through EXTI Line IntDefaultHandler, // USB OTG FS Wakeup through EXTI line IntDefaultHandler, // TIM8 Break and TIM12 IntDefaultHandler, // TIM8 Update and TIM13 IntDefaultHandler, // TIM8 Trigger and Commutation and TIM14 IntDefaultHandler, // TIM8 Capture Compare IntDefaultHandler, // DMA1 Stream7 IntDefaultHandler, // FSMC IntDefaultHandler, // SDIO IntDefaultHandler, // TIM5 IntDefaultHandler, // SPI3 IntDefaultHandler, // UART4 IntDefaultHandler, // UART5 IntDefaultHandler, // TIM6 and DAC1&2 underrun errors IntDefaultHandler, // TIM7 IntDefaultHandler, // DMA2 Stream 0 IntDefaultHandler, // DMA2 Stream 1 IntDefaultHandler, // DMA2 Stream 2 IntDefaultHandler, // DMA2 Stream 3 IntDefaultHandler, // DMA2 Stream 4 IntDefaultHandler, // Ethernet IntDefaultHandler, // Ethernet Wakeup through EXTI line IntDefaultHandler, // CAN2 TX IntDefaultHandler, // CAN2 RX0 IntDefaultHandler, // CAN2 RX1 IntDefaultHandler, // CAN2 SCE IntDefaultHandler, // USB OTG FS IntDefaultHandler, // DMA2 Stream 5 IntDefaultHandler, // DMA2 Stream 6 IntDefaultHandler, // DMA2 Stream 7 IntDefaultHandler, // USART6 IntDefaultHandler, // I2C3 event IntDefaultHandler, // I2C3 error IntDefaultHandler, // USB OTG HS End Point 1 Out IntDefaultHandler, // USB OTG HS End Point 1 In IntDefaultHandler, // USB OTG HS Wakeup through EXTI IntDefaultHandler, // USB OTG HS IntDefaultHandler, // DCMI IntDefaultHandler, // CRYP crypto IntDefaultHandler, // Hash and Rng IntDefaultHandler, // FPU }; // This is the code that gets called when the processor first starts execution // following a reset event. Only the absolutely necessary set is performed, // after which the application supplied entry() routine is called. Any fancy // actions (such as making decisions based on the reset cause register, and // resetting the bits in that register) are left solely in the hands of the // application. void ResetISR(void) { // // Call the application's entry point. // __iar_program_start(); } // This is the code that gets called when the processor receives a NMI. This // simply enters an infinite loop, preserving the system state for examination // by a debugger. static void NmiSR(void) { // // Enter an infinite loop. // while(1) { } } // This is the code that gets called when the processor receives a fault // interrupt. This simply enters an infinite loop, preserving the system state // for examination by a debugger. static void FaultISR(void) { // // Enter an infinite loop. // while(1) { } } // This is the code that gets called when the processor receives an unexpected // interrupt. This simply enters an infinite loop, preserving the system state // for examination by a debugger. static void IntDefaultHandler(void) { // // Go into an infinite loop. // while(1) { } } 

Using
 @ ".intvec" 
Places the __vector_table table at the beginning of the section declared in the linker file. The file itself can be found here:


The section itself is set at the beginning of the ROM memory. Addresses can be viewed here (the document, which describes the addressing flash memory STM32):



Combination of IAR directive and IAR special functions:
 #pragma segment="CSTACK" __sfe( "CSTACK" ) 
Writes a pointer to the top of the stack at the beginning of the flush.

The table itself is filled with the addresses of functions that implements the eternal cycle. An exception is made only for the functions of interest to us:
 extern void EXTI_Line0_IntHandler(void); extern void EXTI_Line6_IntHandler(void); 

In the function called at the start, the transition to the
 extern void __iar_program_start(void); 
This function is main (). The symbol itself can be redefined if a desire arises:


Go to the main file

First, let's write out and redefine all the addresses and bit fields that we need.
Listing
 //Definitions for SCB_AIRCR register #define SCB_AIRCR (*(unsigned volatile long*)0xE000ED0C) //acces to SCB_AIRCR #define SCB_AIRCR_GROUP22 0x05FA0500 //change priority data //Definitions for RCC_AHB1_ENR register #define RCC_AHB1_ENR (*(unsigned volatile long *)(0x40023830)) //acces to RCC_AHB1ENR reg #define RCC_AHB1_ENR_GPIOA 0x1 //GPIOA bitfield #define RCC_AHB1_ENR_GPIOC 0x4 //GPIOC bitfield #define RCC_AHB1_ENR_GPIOD 0x8 //GPIOD bitfield //Definitions for RCC_APB2_ENR register #define RCC_APB2_ENR (*(unsigned volatile long *)(0x40023844)) //acces to RCC_APB2ENR reg #define RCC_APB2_ENR_SYSCFG 0x4000 //SYSCFG bitfield //Definitions for GPIO MODE registers #define GPIOA_MODER (*(unsigned volatile long*)(0x40020000)) //acces to GPIOA_MODER reg #define GPIOC_MODER (*(unsigned volatile long*)(0x40020800)) //acces to GPIOC_MODER reg #define GPIOD_MODER (*(unsigned volatile long*)(0x40020C00)) //acces to GPIOD_MODER reg //GPIO ODR register definition #define GPIOD_ODR (*(unsigned volatile long*)(0x40020C14)) //acces to GPIOD_MODER reg #define GPIO_ODR_13PIN 0x2000 #define GPIO_ODR_14PIN 0x4000 //Bitfields definitions #define GPIO_MODER_0BITS 0x3 //Pin 0 mode bits #define GPIO_MODER_0IN 0x0 //Pin 0 input mode #define GPIO_MODER_6BITS 0x300 //Pin 6 mode bits #define GPIO_MODER_6IN 0x000 //Pin 6 input mode #define GPIO_MODER_13BITS 0xC000000 //Pin 13 mode bits #define GPIO_MODER_13OUT 0x4000000 //Pin 13 output mode #define GPIO_MODER_14BITS 0x30000000 //Pin 14 mode bits #define GPIO_MODER_14OUT 0x10000000 //Pin 14 output mode //GPIOC_PUPDR register definition #define GPIOC_PUPDR (*(unsigned volatile long*)(0x4002080C)) //acces to GPIOC_PUPDR reg #define GPIOC_PUPDR_6BITS 0x3000 //PC6 bitfield #define GPIOC_PUPDR_6PU 0x1000 //PC6 pull-up enable //SYSCFG_EXTIx registers definitions #define SYSCFG_EXTICR1 (*(unsigned volatile long*)0x40013808) //SYSCFG_EXTICR1 acces #define SYSCFG_EXTICR1_0BITS 0xF //EXTI 0 bits #define SYSCFG_EXTICR1_0PA 0x0 //EXTI 0 - port A #define SYSCFG_EXTICR2 (*(unsigned volatile long*)0x4001380C) //SYSCFG_EXTICR2 acces #define SYSCFG_EXTICR2_6BITS 0xF00 //EXTI 6 bits #define SYSCFG_EXTICR2_6PC 0x200 //EXTI 6 - port C //EXTI definitions #define EXTI_IMR (*(unsigned volatile long*)0x40013C00) //EXTI_IMR reg acces #define EXTI_LINE0 0x1 //LINE 0 definition #define EXTI_LINE6 0x40 //LINE 6 definition #define EXTI_RTSR (*(unsigned volatile long*)0x40013C08) //EXTI_RTSR reg acces #define EXTI_FTSR (*(unsigned volatile long*)0x40013C0C) //EXTI_FTSR reg acces #define EXTI_PR (*(unsigned volatile long*)0x40013C14) //EXTI_PR reg acces //NVIC registers and bits definitions #define NVIC_ISER0_REG (*(unsigned volatile long*)0xE000E100) //NVIC_ISER0 reg acces #define NVIC_ISER0_6VECT 0x40 //vect 6 definition #define NVIC_ISER0_23VECT 0x800000 //vect 30 definition #define NVIC_IPR0_ADD (0xE000E400) #define NVIC_IPR23_REG (*(unsigned volatile char*)(NVIC_IPR0_ADD + 23)) #define NVIC_IPR6_REG (*(unsigned volatile char*)(NVIC_IPR0_ADD + 6)) 

Please note that the values ​​of special registers MK declared as volatile . This is necessary so that the compiler does not try to optimize operations for accessing them, since these are not just chunks of memory and their values ​​can change without the participation of the kernel.

Setting up priority grouping

The first step is to configure the grouping of interrupt priorities:
 SCB_AIRCR = SCB_AIRCR_GROUP22; 
This action must be performed only once. In complex projects using third-party libraries, it is worth checking this fact. Changing the division of priorities into groups can lead to incorrect operation of the firmware.

Enable clocking of used peripherals

Let me remind you that before you start working with peripheral blocks, you must enable their clocking:
 //Enable SYSCFG , GPIO port A and D clocking RCC_AHB1_ENR |= RCC_AHB1_ENR_GPIOA|RCC_AHB1_ENR_GPIOC|RCC_AHB1_ENR_GPIOD; RCC_APB2_ENR |= RCC_APB2_ENR_SYSCFG; 

It is impossible to work directly with SYSCFG, you need to wait a few bars. But we will not. Let's do the initialization of GPIO.

GPIO Initialization

The LEDs are initialized in the same way as last time:
 //LED3 and LED5 initialization GPIOD_MODER = (GPIOD_MODER & (~GPIO_MODER_13BITS)) | GPIO_MODER_13OUT; GPIOD_MODER = (GPIOD_MODER & (~GPIO_MODER_14BITS)) | GPIO_MODER_14OUT; 

The PA0 button and PC7 contact are initialized as input:
 //PA0 and PC6 pins initialization GPIOA_MODER = (GPIOA_MODER & (~GPIO_MODER_0BITS)) | GPIO_MODER_0IN; GPIOC_MODER = (GPIOC_MODER & (~GPIO_MODER_6BITS)) | GPIO_MODER_6IN; 

That's just for the PC6 contact, you must enable power-up. The lift is activated using the GPIOC_PUPDR register:
 //Enable PC6 pull-up GPIOC_PUPDR = (GPIOC_PUPDR & (~GPIOC_PUPDR_7BITS)) | GPIOC_PUPDR_6PU; 


EXTI setting

And so, the following parameters need to be configured - enable interrupts for lines 0 and 6, for line 0 an interrupt on the rising edge, for line 6 - an interrupt on the falling front:
 //Set up EXTI EXTI_RTSR |= EXTI_LINE0; EXTI_FTSR |= EXTI_LINE6; EXTI_IMR = EXTI_LINE0|EXTI_LINE6; 

It remains to configure which ports of pins are connected to the EXTI line (a strange solution, for example, the MC stellaris can generate an interrupt with any combination of pins, with the STM32 it is more difficult):
 //EXTI to port connection SYSCFG_EXTICR1 = (SYSCFG_EXTICR1&(~SYSCFG_EXTICR1_0BITS)) | SYSCFG_EXTICR1_0PA; SYSCFG_EXTICR2 = (SYSCFG_EXTICR2&(~SYSCFG_EXTICR2_6BITS)) | SYSCFG_EXTICR2_6PC; 


NVIC setup

It remains to adjust the interrupt priorities and mask them to initiate processing. Please note that the NVIC_IPR registers are available for byte- wise access, which greatly simplifies access only to the necessary priority bytes of individual interrupt vectors. It is enough to make a shift by the value of the number of the interrupt vector (see listing definitions). Once again we recall that EXTI Line 0 has 6 number in the vector table, and EXTI line 5_9 - number 23. In STM32, only the most significant 4 bits of priority have a value:
 //Set interrupts priority NVIC_IPR6_REG = 0xF0; NVIC_IPR23_REG = 0x0; 

To demonstrate the priorities are set different.
Now you can enable interrupts:
 //Enable interrupts NVIC_ISER0_REG |= NVIC_ISER0_6VECT | NVIC_ISER0_23VECT; 

From this point on, pressing a button and shorting PC6 and GND will result in calling the functions of the interrupt handlers EXTI_Line0_IntHandler and EXTI_Line6_IntHandler, respectively.

Interrupt handling

In interrupt handling functions, first of all, you need to clear the interrupt, after which you can light the LEDs. An eternal loop has been added to show interrupt priorities in one of the handlers. If the priority of the interruption with the eternal cycle is lower than the priority of the second, then it cannot be caused. Otherwise, it can interrupt the first. I suggest that you try different interrupt priority values ​​yourself and see clearly what this leads to ( ATTENTION - don't forget about interrupt groups! ).
 void EXTI_Line0_IntHandler(void) { //Clear interrupt EXTI_PR = EXTI_LINE0; //Turn on LED 3 GPIOD_ODR |= GPIO_ODR_13PIN; } void EXTI_Line6_IntHandler(void) { //Clear interrupt EXTI_PR = EXTI_LINE6; //Turn LED4 GPIOD_ODR |= GPIO_ODR_14PIN; while(1); } 


Instead of conclusion


Just in case, I will give a complete listing of the resulting program.
Listing
 //Definitions for SCB_AIRCR register #define SCB_AIRCR (*(unsigned volatile long*)0xE000ED0C) //acces to SCB_AIRCR #define SCB_AIRCR_GROUP22 0x05FA0500 //change priority data //Definitions for RCC_AHB1_ENR register #define RCC_AHB1_ENR (*(unsigned volatile long *)(0x40023830)) //acces to RCC_AHB1ENR reg #define RCC_AHB1_ENR_GPIOA 0x1 //GPIOA bitfield #define RCC_AHB1_ENR_GPIOC 0x4 //GPIOC bitfield #define RCC_AHB1_ENR_GPIOD 0x8 //GPIOD bitfield //Definitions for RCC_APB2_ENR register #define RCC_APB2_ENR (*(unsigned volatile long *)(0x40023844)) //acces to RCC_APB2ENR reg #define RCC_APB2_ENR_SYSCFG 0x4000 //SYSCFG bitfield //Definitions for GPIO MODE registers #define GPIOA_MODER (*(unsigned volatile long*)(0x40020000)) //acces to GPIOA_MODER reg #define GPIOC_MODER (*(unsigned volatile long*)(0x40020800)) //acces to GPIOC_MODER reg #define GPIOD_MODER (*(unsigned volatile long*)(0x40020C00)) //acces to GPIOD_MODER reg //GPIO ODR register definition #define GPIOD_ODR (*(unsigned volatile long*)(0x40020C14)) //acces to GPIOD_MODER reg #define GPIO_ODR_13PIN 0x2000 #define GPIO_ODR_14PIN 0x4000 //Bitfields definitions #define GPIO_MODER_0BITS 0x3 //Pin 0 mode bits #define GPIO_MODER_0IN 0x0 //Pin 0 input mode #define GPIO_MODER_6BITS 0x300 //Pin 6 mode bits #define GPIO_MODER_6IN 0x000 //Pin 6 input mode #define GPIO_MODER_13BITS 0xC000000 //Pin 13 mode bits #define GPIO_MODER_13OUT 0x4000000 //Pin 13 output mode #define GPIO_MODER_14BITS 0x30000000 //Pin 14 mode bits #define GPIO_MODER_14OUT 0x10000000 //Pin 14 output mode //GPIOC_PUPDR register definition #define GPIOC_PUPDR (*(unsigned volatile long*)(0x4002080C)) //acces to GPIOC_PUPDR reg #define GPIOC_PUPDR_6BITS 0x3000 //PC6 bitfield #define GPIOC_PUPDR_6PU 0x1000 //PC6 pull-up enable //SYSCFG_EXTIx registers definitions #define SYSCFG_EXTICR1 (*(unsigned volatile long*)0x40013808) //SYSCFG_EXTICR1 acces #define SYSCFG_EXTICR1_0BITS 0xF //EXTI 0 bits #define SYSCFG_EXTICR1_0PA 0x0 //EXTI 0 - port A #define SYSCFG_EXTICR2 (*(unsigned volatile long*)0x4001380C) //SYSCFG_EXTICR2 acces #define SYSCFG_EXTICR2_6BITS 0xF00 //EXTI 6 bits #define SYSCFG_EXTICR2_6PC 0x200 //EXTI 6 - port C //EXTI definitions #define EXTI_IMR (*(unsigned volatile long*)0x40013C00) //EXTI_IMR reg acces #define EXTI_LINE0 0x1 //LINE 0 definition #define EXTI_LINE6 0x40 //LINE 6 definition #define EXTI_RTSR (*(unsigned volatile long*)0x40013C08) //EXTI_RTSR reg acces #define EXTI_FTSR (*(unsigned volatile long*)0x40013C0C) //EXTI_FTSR reg acces #define EXTI_PR (*(unsigned volatile long*)0x40013C14) //EXTI_PR reg acces //NVIC registers and bits definitions #define NVIC_ISER0_REG (*(unsigned volatile long*)0xE000E100) //NVIC_ISER0 reg acces #define NVIC_ISER0_6VECT 0x40 //vect 6 definition #define NVIC_ISER0_23VECT 0x800000 //vect 30 definition #define NVIC_IPR0_ADD (0xE000E400) #define NVIC_IPR23_REG (*(unsigned volatile char*)(NVIC_IPR0_ADD + 23)) #define NVIC_IPR6_REG (*(unsigned volatile char*)(NVIC_IPR0_ADD + 6)) void EXTI_Line0_IntHandler(void); void EXTI_Line6_IntHandler(void); void main() { //NVIC SCB_AIRCR = SCB_AIRCR_GROUP22; //Enable SYSCFG , GPIO port A,C and D clocking RCC_AHB1_ENR |= RCC_AHB1_ENR_GPIOA|RCC_AHB1_ENR_GPIOC|RCC_AHB1_ENR_GPIOD; RCC_APB2_ENR |= RCC_APB2_ENR_SYSCFG; //LED3 and LED5 initialization GPIOD_MODER = (GPIOD_MODER & (~GPIO_MODER_13BITS)) | GPIO_MODER_13OUT; GPIOD_MODER = (GPIOD_MODER & (~GPIO_MODER_14BITS)) | GPIO_MODER_14OUT; //PA0 and PC6 pins initialization GPIOA_MODER = (GPIOA_MODER & (~GPIO_MODER_0BITS)) | GPIO_MODER_0IN; GPIOC_MODER = (GPIOC_MODER & (~GPIO_MODER_6BITS)) | GPIO_MODER_6IN; //Enable PC7 pull-up GPIOC_PUPDR = (GPIOC_PUPDR & (~GPIOC_PUPDR_6BITS)) | GPIOC_PUPDR_6PU; //Set up EXTI EXTI_RTSR |= EXTI_LINE0; EXTI_FTSR |= EXTI_LINE6; EXTI_IMR = EXTI_LINE0|EXTI_LINE6; //EXTI to port connection SYSCFG_EXTICR1 = (SYSCFG_EXTICR1&(~SYSCFG_EXTICR1_0BITS)) | SYSCFG_EXTICR1_0PA; SYSCFG_EXTICR2 = (SYSCFG_EXTICR2&(~SYSCFG_EXTICR2_6BITS)) | SYSCFG_EXTICR2_6PC; //Set interrupts priority NVIC_IPR6_REG = 0xF0; NVIC_IPR23_REG = 0x00; //Enable interrupts NVIC_ISER0_REG |= NVIC_ISER0_6VECT | NVIC_ISER0_23VECT; while(1) { } } void EXTI_Line0_IntHandler(void) { //Clear interrupt EXTI_PR = EXTI_LINE0; //Turn on LED 3 GPIOD_ODR |= GPIO_ODR_13PIN; } void EXTI_Line6_IntHandler(void) { //Clear interrupt EXTI_PR = EXTI_LINE6; //Turn LED4 GPIOD_ODR |= GPIO_ODR_14PIN; while(1); } 


To check the effect of interrupt priorities and priorities of interrupt groups, try changing priorities and observe what will happen (two bits - priority within the group, 2 bits - group priority).

Source: https://habr.com/ru/post/218825/


All Articles