FreeRTOS的源代码里,采用了自编写的启动文件,而不是用的官方的启动文件(也就是以前创建工程时必须要添加的一个.s汇编文件,文件名格式大概是startup_stm32f10x_hd.s),但其内容基本只是对这个启动文件进行简单的编辑而已,主要是自定义了中断处理函数(比如TIM2_Handle())的名称而已。关于这个文件的分析可以先学习Cortex-M3权威手册(有中文版的),大概200多页。看完这个手册可以对CM3内核有很清楚的认识,对分析操作系统的底层是很有帮助也是必要的!
IMPORT xPortPendSVHandler
IMPORT xPortSysTickHandler
IMPORT vPortSVCHandler
IMPORT vUARTInterruptHandler
IMPORT vTimer2IntHandler
__Vectors DCD __initial_sp ; Top of Stack
DCD Reset_Handler ; Reset Handler
DCD NMI_Handler ; NMI Handler
DCD HardFault_Handler ; Hard Fault Handler
DCD MemManage_Handler ; MPU Fault Handler
DCD BusFault_Handler ; Bus Fault Handler
DCD UsageFault_Handler ; Usage Fault Handler
DCD 0 ; Reserved
DCD 0 ; Reserved
DCD 0 ; Reserved
DCD 0 ; Reserved
DCD vPortSVCHandler ; SVCall Handler
DCD DebugMon_Handler ; Debug Monitor Handler
DCD 0 ; Reserved
DCD xPortPendSVHandler ; PendSV Handler
DCD xPortSysTickHandler ; SysTick Handler
对比官方的startup_stm32f10x_hd.s,可以发现,只是把默认的几个handle函数的名称给改成了自定义的名称比如vPortSVCHandler,vPortUARTInterruptHandler等。因此在写对应的终端处理函数时,得使用对应的自定义名称,比如串口1的中断处理函数void USART1_IRQHandler() 要改为void vPortUARTInterruptHandle()。
关于这一连续的汇编代码的作用参考权威手册,是设置中断向量表的,按照STM32F10x参考手册里的中断编号顺序,定义每一个中断的处理函数的位置也就是中断向量表,并且给出了默认的处理函数。
比如复位中断处理:Reset_Handler
Reset_Handler PROC
EXPORT Reset_Handler [WEAK]
IMPORT __main
LDR R0, =__main
BX R0
ENDP
可以看到,复位中断处理里就是转接到外部的main函数,这也是为什么主函数要叫main的原因,如果这里把main改成word,那么在main.c里就要把void main改成void word才行。
而外部中断(这里是指相对内核的外部主要是各种外设的中断比如定时器,串口等),都有同一个默认的中断处理
RTCAlarm_IRQHandler
USBWakeUp_IRQHandler
B .
ENDP
ALIGN
也就是什么都不做,系统死机。所以对于中断的开启与配置一定要小心,如果开启了中断则一定要写出相应的中断处理函数才行,即使这个函数是个空函数也要写出来,因为空函数里有中断返回的汇编代码这样就不会死机。
在main()主函数里的prvHardwareSetup()函数里有两行是设置向量表和优先级分组(见上一篇),
/* Set the Vector Table base address at 0x08000000 */
NVIC_SetVectorTable( NVIC_VectTab_FLASH, 0x0 );
NVIC_PriorityGroupConfig( NVIC_PriorityGroup_4 );
设置的向量表的地址是内部Flash的起始地址0x08000000,在启动后会映射到地址0x00000000(见CM3权威手册),而启动文件里的向量地址表是:
; Vector Table Mapped to Address 0 at Reset
AREA RESET, DATA, READONLY
EXPORT __Vectors
__Vectors DCD __initial_sp ; Top of Stack
DCD Reset_Handler ; Reset Handler
DCD NMI_Handler ; NMI Handler
DCD HardFault_Handler ; Hard Fault Handler
DCD MemManage_Handler ; MPU Fault Handler
DCD BusFault_Handler ; Bus Fault Handler
DCD UsageFault_Handler ; Usage Fault Handler
看顶行注释就知道向量表是放在地址0里,READONLY就是指的ROM,也就是内部FLASH区域,从0地址开始申请一系列4字节空间并赋值来存储向量表,其中__initial_sp即是在文件开头时在READWRITE区(也就是RAM内存区)申请的栈地址,下面的各种handler对应的是各个中断函数的代码首地址。进入debug模式,用jlink(或者stlink)调试时,可以查看各个handler的地址值,也可以在下面的Memory窗口中查看0x00000000或者0x08000000地址的内容,会发现与之对应。
会发现Reset_Handler函数的代码地址是0x08000198,但是在ROM里存储的是0x08000199,后面的所有的代码地址都是相应的加了1,这是因为CM3只有Thumb状态,其代码地址要保证是奇数也就是LSB要是1,见CM3权威手册(中文版)40页。
而设置向量表地址的下一行设置优先级分组,CM3内核支持256级优先级与128级抢占优先级,除了复位,NMI和硬fault三个中断有固定的最高负优先级分别为-3,-2,-1外,其他异常(中断)的优先级均可编程,详见权威手册(中文版)107页。抢占优先级只有128个(所谓抢占优先级是指一个低优先级异常在响应时,另一个高优先级异常发生时,系统会挂起正在响应的低优先级异常转而去响应高优先级异常),是因为优先级又被分为抢占优先级与亚优先级(见 权威手册111页),亚优先级不会发生抢占(抢占优先级相同的两个异常同时挂起时会先响应高亚优先级)并且至少有1位,这样抢占优先级最多7位也就是128级。抢占优先级可以设为0位,这个在NVIC的AIRCR寄存器里的优先级分组3个位来设置,共8种情况。
对于STM32F10X,优先级被精简为只用4位(详见《 STM32F10xxx Cortex-M3编程手册》纯英文的,118页),也就是0到15共16个优先级,使用高4位,也就是对于优先级位11的其值为0xb0(b后面的可以是任意值,写入无效,但读取总是0)。优先级分组共5组(见编程手册135页)
源代码默认用的是Group_4,右键查看定义可以知道
/* Preemption Priority Group -------------------------------------------------*/
#define NVIC_PriorityGroup_0 ((u32)0x700) /* 0 bits for pre-emption priority
4 bits for subpriority */
#define NVIC_PriorityGroup_1 ((u32)0x600) /* 1 bits for pre-emption priority
3 bits for subpriority */
#define NVIC_PriorityGroup_2 ((u32)0x500) /* 2 bits for pre-emption priority
2 bits for subpriority */
#define NVIC_PriorityGroup_3 ((u32)0x400) /* 3 bits for pre-emption priority
1 bits for subpriority */
#define NVIC_PriorityGroup_4 ((u32)0x300) /* 4 bits for pre-emption priority
0 bits for subpriority */
代表的是这前4位全是抢占优先级,没有亚优先级,也就是优先级分成0到15个抢占优先级。