一、STM32F103
中断介绍
1.1 什么是中断
中断:打断CPU
执行正常的程序,转而处理紧急程序,然后返回原暂停的程序继续运行;
举例:当你正在写作业时,做到一半又去吃饭,吃完饭后又回来接着原来的作业继续完成。
对于单片机来说,中断是指CPU
正在处理某个事件A
,发生了另一件事件B
,请求CPU
迅速去处理(中断发生);
CPU
暂时停止当前的工作(中断响应),转去处理事件B
(中断服务);
待CPU
处理事件B完成后,再回到原来的事件A
(断点)继续执行,这一过程称之为中断。
中断的作用和含义:
- 实时控制:在确定的时间内对相应事件做出相应;例如:温度控制;
- 故障处理:检测到故障,需要第一时间进行处理;
- 数据传输:不确定数据何时会来,利用中断进行控制;
中断的作用:高效处理紧急程序,并且不会占用CPU资源。
1.2 嵌套向量中断控制器(NVIC
)
NVIC
即嵌套向量中断控制器,全称Nested vectored interrupt controller
。属于是内核的器件,其作用是对STM32
中的中断进行管理,因为Contex M3
内核中的中断数量很多,当同时出现多个中断时,优先处理哪个中断?以及那些中断不处理等,都要靠NVIC
进行控制。
Contrex M3
内核都是支持256
个中断,其中包含了16
个内核异常和240
个外部中断,并且具有256
级的可编程中断设置。
但是对于ST
公司来说,用不了Contrex M3
内核中的所有中断以及中断优先级,进而对其进行了一定的裁剪。STM32F103
中共有:
10
个内核异常;60
个可屏蔽中断;
注意一下:Contrex M3
的外部中断和STM32F103
的外部中断不是一个概念;
Contrex M3
:除了内核异常之外的都是外部中断;STM32F103
:外部中断EXTI
只有6
个;
在中断的使用中有一个极其重要的一部分为中断服务函数(触发中断后,系统执行的部分,例如上文的吃饭过程)中断服务函数是中断的入口。
1.2.1 异常和中断向量表
定义一块固定的内存,以4字节对齐(32
位),用于存放中断服务函数的首地址,系统已经将中断服务函数定义好了,放在中断向量表中,我们只需要进行调用即可。
例如下面的异常和中断中断向量表:
其中前10
个中断为内核异常,剩下的60
个为可屏蔽中断,其编号为0~59
,优先级从7~66
。
优先级号越小,优先级越高。当表中的某处异常或中断被触发,程序计数器指针(PC
)将跳转到该异常或中断的地址处执行,该地址处存放这一条跳转指令,跳转到该异常或中断的服务函数处执行相应的功能。因此,异常和中断向量表只能用汇编语言编写。
1.3 中断优先级
二、中断相关寄存器
三、中断配置源码
3.1 系统启动
无论是是何种MCU
,从简单的51
,MSP430
,到ARM9
,ARM11
,A7
都必须有启动文件,因为对于嵌入式开发,绝大部分情况都是使用C语言,而C
语言一般都是从main
函数开始,但是对于MCU
来说,他是如何找到并执行main
函数的,就需要用到“启动文件”。
STM32F103
的启动文件为stm32f10x_vectors.s
,启动文件是使用机器认识的汇编语言,经过一些必要的配置,最终能够调用main
函数,使得用户程序能够在MCU
上正常运行起来的必备文件。
本文以MDK
环境下的stm32f10x_vectors.s
为模板讲解,不同编译器下的启动文件不同。
3.1.1 栈初始化
代码的开始,就是开辟栈空间,用于局部变量,函数调用,函数参数等;
Stack_Size EQU 0x00000400AREA STACK, NOINIT, READWRITE, ALIGN=3
Stack_Mem SPACE Stack_Size
__initial_sp
汇编伪指令讲解:
EQU
:宏定义的伪指令,相当于等于,类似于C
语言中的宏定义define
;AREA
: 告诉汇编器汇编一个新的代码段或者数据段;SPACE
:用于分配一定大小的内存空间,单位字节;
因此上面的代码结合起来就是分配一个长度为1kb
的栈空间,命名为STACK
,并且不初始化,可读可写,按照2的3次方(8个字节)对齐开辟。
__initial_sp
只是一个标号,标号主要用于表示一片内存空间的某个位置,等价于C
语言中的地址概念。地址仅仅表示存储空间的位置。
__initial_sp
紧挨着SPACE
语句放置,表示栈的结束地址,即栈顶地址,栈是由高向低生长的。
3.1.2 堆初始化
接下来是开辟堆空间,主要用于动态内存分配,使用malloc
,calloc
等函数分配的变量空间是在堆上的;
Heap_Size EQU 0x00000400AREA HEAP, NOINIT, READWRITE, ALIGN=3
__heap_base
Heap_Mem SPACE Heap_Size
__heap_limit
同样分配一个长度为1kb
的堆空间,命名为HEAP
,并且不初始化,可读可写,按照2的3次方(8个字节)对齐开辟。
标号_heap_base
表示堆的起始地址,标号__heap_limit
紧挨着SPACE
语句放置,表示堆的结束地址,堆是由低向高生长的(与栈相反)。
3.1.3 导入异常和中断处理函数
接着是导入外部文件定义的异常和中断处理函数,为建立异常和中断向量表做准备;
THUMBPRESERVE8; Import exceptions handlersIMPORT NMIExceptionIMPORT HardFaultExceptionIMPORT MemManageExceptionIMPORT BusFaultExceptionIMPORT UsageFaultException......IMPORT FLASH_IRQHandlerIMPORT RCC_IRQHandlerIMPORT EXTI0_IRQHandlerIMPORT EXTI1_IRQHandlerIMPORT EXTI2_IRQHandlerIMPORT EXTI3_IRQHandlerIMPORT EXTI4_IRQHandler......IMPORT TIM1_BRK_IRQHandlerIMPORT TIM1_UP_IRQHandlerIMPORT TIM1_TRG_COM_IRQHandlerIMPORT TIM1_CC_IRQHandlerIMPORT TIM2_IRQHandlerIMPORT TIM3_IRQHandlerIMPORT TIM4_IRQHandlerIMPORT I2C1_EV_IRQHandlerIMPORT I2C1_ER_IRQHandler......IMPORT DMA2_Channel1_IRQHandlerIMPORT DMA2_Channel2_IRQHandlerIMPORT DMA2_Channel3_IRQHandlerIMPORT DMA2_Channel4_5_IRQHandler;*******************************************************************************
; Fill-up the Vector Table entries with the exceptions ISR address
;*******************************************************************************; 定义一个数据段 命名为RESET,DATA表示数据, READONLY表示可读AREA RESET, DATA, READONLYEXPORT __Vectors
其中:
PRESERVE8
表示指定当前文件的推栈按照8字节对齐;THUMB
表示后面指令位THUMB
指令;IMPORT
用于声明外部符号,告诉汇编器这些符号来自其他模块或库,需要在链接时进行解析。这种机制使得汇编程序能够调用外部库中的函数或访问外部定义的全局变量,实现代码的模块化和复用;EXPORT
:声明一个标号具有全局属性,用于将符号暴露给当前源文件以外的其他文件;
代码中AREA
定义了一段名为RESET
的READONLY
只读数据段,只读属性保存在Flash
区(如果STM32
从Flash
启动,则此异常和中断向量表的地址为0x0800 0000
),
3.1.4 异常和中断向量表
Cortex M3
内核规定起始地址必须存放栈顶指针,而第二个地址则必须存放复位中断入口向量地址,这样在Cortex-M3
内核复位后,会自动从起始地址的下一个32
位空间取出复位中断入口向量,跳转执行复位中断服务程序。
Cortex-M3
内核固定了异常和中断向量表的位置, 但是起始地址是可变化的。
__Vectors DCD __initial_sp ; Top of StackDCD Reset_HandlerDCD NMIExceptionDCD HardFaultExceptionDCD MemManageExceptionDCD BusFaultExceptionDCD UsageFaultExceptionDCD 0 ; ReservedDCD 0 ; ReservedDCD 0 ; ReservedDCD 0 ; ReservedDCD SVCHandlerDCD DebugMonitorDCD 0 ; ReservedDCD PendSVCDCD SysTickHandlerDCD WWDG_IRQHandlerDCD PVD_IRQHandlerDCD TAMPER_IRQHandlerDCD RTC_IRQHandlerDCD FLASH_IRQHandlerDCD RCC_IRQHandlerDCD EXTI0_IRQHandlerDCD EXTI1_IRQHandlerDCD EXTI2_IRQHandlerDCD EXTI3_IRQHandlerDCD EXTI4_IRQHandlerDCD DMA1_Channel1_IRQHandlerDCD DMA1_Channel2_IRQHandlerDCD DMA1_Channel3_IRQHandlerDCD DMA1_Channel4_IRQHandlerDCD DMA1_Channel5_IRQHandlerDCD DMA1_Channel6_IRQHandlerDCD DMA1_Channel7_IRQHandlerDCD ADC1_2_IRQHandlerDCD USB_HP_CAN_TX_IRQHandlerDCD USB_LP_CAN_RX0_IRQHandlerDCD CAN_RX1_IRQHandlerDCD CAN_SCE_IRQHandlerDCD EXTI9_5_IRQHandlerDCD TIM1_BRK_IRQHandlerDCD TIM1_UP_IRQHandlerDCD TIM1_TRG_COM_IRQHandlerDCD TIM1_CC_IRQHandlerDCD TIM2_IRQHandlerDCD TIM3_IRQHandlerDCD TIM4_IRQHandlerDCD I2C1_EV_IRQHandlerDCD I2C1_ER_IRQHandlerDCD I2C2_EV_IRQHandlerDCD I2C2_ER_IRQHandlerDCD SPI1_IRQHandlerDCD SPI2_IRQHandlerDCD USART1_IRQHandlerDCD USART2_IRQHandlerDCD USART3_IRQHandlerDCD EXTI15_10_IRQHandler......
以上代码定义了处理器复位后各个异常和中断对应的处理函数。异常和中断向量表的第一个元素存放的是__initial_sp
栈顶的指针;第二个元素存放的是Reset_Handler
函数入口地址。
3.1.5 Reset_Handler
系统上电或者复位后后,首先执行的代码就是Reset_Handler
:
; Reset handler routine
Reset_Handler PROCEXPORT Reset_HandlerIF DATA_IN_ExtSRAM == 1
; FSMC Bank1 NOR/SRAM3 is used for the STM3210E-EVAL, if another Bank is
; required, then adjust the Register Addresses; Enable FSMC clockLDR R0,= 0x00000114 LDR R1,= 0x40021014STR R0,[R1] ; Enable GPIOD, GPIOE, GPIOF and GPIOG clocksLDR R0,= 0x000001E0LDR R1,= 0x40021018STR R0,[R1] ; SRAM Data lines, NOE and NWE configuration
; SRAM Address lines configuration
; NOE and NWE configuration
; NE3 configuration
; NBL0, NBL1 configuration LDR R0,= 0x44BB44BB LDR R1,= 0x40011400STR R0,[R1] LDR R0,= 0xBBBBBBBB LDR R1,= 0x40011404STR R0,[R1] LDR R0,= 0xB44444BB LDR R1,= 0x40011800STR R0,[R1] LDR R0,= 0xBBBBBBBB LDR R1,= 0x40011804STR R0,[R1] LDR R0,= 0x44BBBBBB LDR R1,= 0x40011C00STR R0,[R1] LDR R0,= 0xBBBB4444 LDR R1,= 0x40011C04STR R0,[R1] LDR R0,= 0x44BBBBBBLDR R1,= 0x40012000STR R0,[R1] LDR R0,= 0x44444B44LDR R1,= 0x40012004STR R0,[R1] ; FSMC Configuration
; Enable FSMC Bank1_SRAM Bank LDR R0,= 0x00001011LDR R1,= 0xA0000010STR R0,[R1] LDR R0,= 0x00000200 LDR R1,= 0xA0000014STR R0,[R1] ENDIFIMPORT __mainLDR R0, =__mainBX R0ENDP
PROC
、ENDP
这一对伪指令把程序分为若干个过程,使程序结构更加清晰。
_main
标号表示C/C++
标准实时库函数里的一个初始化子程序main
的入口地址。该程序的一个主要作用是初始化堆栈(跳转_user_initial_stackheap
标号进行初始化堆栈),并初始化映像文件,最后跳转到C
程序中的main
函数。这也正解释了为什么所有的C程序必须有一个main
函数作为程序的起点,因为这是由C/C++
标准实时库所规定的。
__user_initial_stackheapLDR R0, = Heap_MemLDR R1, =(Stack_Mem + Stack_Size)LDR R2, = (Heap_Mem + Heap_Size)LDR R3, = Stack_MemBX LR
四、源码下载
源码下载路径:stm32f103
。
参考文章
[1] Mini2440
裸机开发之中断控制器
[2] STM32
--中断使用
[3] 嵌入式Linux
之常用ARM汇编
[4] STM32
的启动过程 — startup_xxxx.s
文件解析
[5] STM32
的内存管理相关