有一个现象值得一说,对于大多数人来说,他们开发单片机程序的主要工作就是搞单片机应用开发,也就是大多数都是main函数之后的事,于是很多人以为单片机上电复位后直接就是从main函数开始执行程序的。如果他只是一个学生,我多多少少还能理解,毕竟他年幼无知尚未步入单片机大门,但是如果他是一个所谓的久经沙场的老工程师,这是会被笑话的。
也许有人说,我不清楚单片机怎么启动的,我不照样可以开发单片机软件,写的代码不照样可以让产品正常工作吗?我有必要知道单片机是怎么启动的吗?有如下几点可以说明你很有必要,而且,甚至就是必须,明白单片机是如何启动的。
首先,这是一个素养问题,既然他是一位专业单片机工程师,总会有外行或者想入行的人想请教他关于单片机的各种问题,这种情况下,他却无法回答或者给出错误解答,难免显得尴尬有失体面,还会让人觉得他不懂装懂还装大咖的感觉。
其次,这是一个水平问题,如果他连单片机怎么启动这么基本的问题都没搞清楚,他让同行怎么相信你搞过单片机,更别说他曾经开发过多个项目,拥有丰富经验了。
今天我就花点时间来谈谈MCU的启动整个过程,由于目前STM32大行其道,所以我以STM32为例对此进行详细阐述,以达到抛砖引玉的目的。
STM32执行代码是以PC寄存器为指引的,PC指到哪CPU就取哪里的指令来执行,真所谓指哪打哪。上电复位之后,PC寄存器指向了中断向量表中的复位中断向量这个位置,CPU会随之取出复位中断向量中的数据,这个数据是什么呢?它不是复位中断服务函数的入口地址,而是跳转到复位中断服务函数的可执行代码。再重申一遍,复位中断向量中存放的是一条跳转指令,而不是地址。CPU执行这条代码后,PC寄存器的值就被改变成了复位中断服务函数的入口地址,至此,程序运行到了复位中断服务函数。
下面就是复位中断服务函数的代码:
; Reset handler
Reset_Handler PROC
EXPORT Reset_Handler [WEAK]
IMPORT __main
IMPORT SystemInit
LDR R0, =SystemInit
BLX R0
LDR R0, =__main
BX R0
ENDP
简单说明这几行代码的含义:
①PROC...ENDP是定义函数(或说子程序)的伪指令,PROC代表函数的开始,而ENDP代表函数结束,所以它们总是成对出现。在这里就是定义了名为Reset_Handler的子程序。
②EXPORT是表示其后紧跟的标号提供给外部使用,所以EXPORT Reset_Handler就代表外部文件可以调用Reset_Handler函数。
③IMPORT表示其后紧跟的标号是在别的文件中定义的,并不是本地文件(它是舶来品,不是国产的^o^),所以“IMPORT __main”就代表对_main这个标号进行了声明,接下来就可以直接用_main了;同理,“IMPORT SystemInit”就代表了对SystemInit这个编号进行了声明,接下来就可以直接用SystemInit了。在汇编语言中,一个标号就代表了一个程序地址,所以__main和SystemInit其实就代表了外部函数。
④LDR就是装在寄存器了,LDR R0, =SystemInit的意思就是把SystemInit标号所代表的地址装在给R0寄存器,BLX R0的意思就是直接跳转到R0所指向的代码空间去执行代码,由于R0此时是指向SystemInit,所以就是跳转到SystemInit处执行代码;同理 LDR R0, =__main表示把_main标号装载进R0中,其后的BX R0代表跳转到_main处执行代码。细心的同学可能发现了一个问题,为什么一个是BLX,一个是BX呢?关于它们的区别就是如下:
BLX 指令从ARM 指令集跳转到指令中所指定的目标地址,并将处理器的工作状态有ARM 状态切换到Thumb 状态,该指令同时将PC 的当前内容保存到寄存器R14 中。因此,当子程序使用Thumb 指令集,而调用者使用ARM 指令集时,可以通过BLX 指令实现子程序的调用和处理器工作状态的切换。BX 指令跳转到指令中所指定的目标地址,目标地址处的指令既可以是ARM 指令,也可以是Thumb指令。两者的区别在于程序跳转后要不要切换工作状态。
通过上述的介绍,可见复位中断服务函数并没有传说中的那么神秘,它只不过是执行了在别的文件中定义的__main代码段和SystemInit代码段。下面简单说明__main和SystemInit分别是什么东西。
①SystemInit函数是定义在外部文件中的,那到底是定义在哪个文件中呢?
它就位于system_stm32fxxx.c中,具体代码如下:
void SystemInit(void)
{
/* FPU settings ------------------------------------------------------------*/
#if (__FPU_PRESENT == 1) && (__FPU_USED == 1)
SCB->CPACR |= ((3UL << 10*2)|(3UL << 11*2)); /* set CP10 and CP11 Full Access */
#endif
/* Reset the RCC clock configuration to the default reset state ------------*/
/* Set MSION bit */
RCC->CR |= RCC_CR_MSION;
/* Reset CFGR register */
RCC->CFGR = 0x00000000;
/* Reset HSEON, CSSON , HSION, and PLLON bits */
RCC->CR &= (uint32_t)0xEAF6FFFF;
/* Reset PLLCFGR register */
RCC->PLLCFGR = 0x00001000;
/* Reset HSEBYP bit */
RCC->CR &= (uint32_t)0xFFFBFFFF;
/* Disable all interrupts */
RCC->CIER = 0x00000000;
/* Configure the Vector Table location add offset address ------------------*/
#ifdef VECT_TAB_SRAM
SCB->VTOR = SRAM_BASE | VECT_TAB_OFFSET; /* Vector Table Relocation in Internal SRAM */
#else
SCB->VTOR = FLASH_BASE | VECT_TAB_OFFSET; /* Vector Table Relocation in Internal FLASH */
#endif
}
②_main属于C/C++库函数,其作用如下,该函数的具体源代码可以通过单步调试的方法查看,这里我不详细介绍了。
·完成全局/静态变量的初始化工作
·初始化堆栈
·库函数的初始化
·程序的跳转,进入main()函数。
//;定义一个变量Stack_Size,并赋值为0x00000400;
Stack_Size EQU 0x00000400
//;定义一个数据段(或数据节)STACK,不零初始化,可读可写,并以8个字节对齐;
AREA STACK, NOINIT, READWRITE, ALIGN=3
//;开辟一个大小为0x00000400(即1KB)的初始化为零的连续的内存空间,并命名为Stack_Mem;
Stack_Mem SPACE Stack_Size
//;_initial_sp是标号,表示堆栈的栈顶地址。
__initial_sp
//;定义一个变量Heap_Size,并赋值为0x00000200;
Heap_Size EQU 0x00000200
//;定义一个数据段HEAP,不初始化为零,可读可写,并以8个字节对齐;
AREA HEAP, NOINIT, READWRITE, ALIGN=3
//;_heap_base是标号,表示堆基地址
__heap_base
//;开辟一个大小为0x000000200(即512Bytes)的初始化为零的内存空间,并命名为Heap_Mem;
Heap_Mem SPACE Heap_Size
//;_heap_limit是标号,表示堆顶地址。
__heap_limit
PRESERVE8 //;表示当前文件中的堆栈区按8字节对齐
THUMB //;指示汇编器将THUMB后的指令翻译成T32。
; Vector Table Mapped to Address 0 at Reset
AREA RESET, DATA, READONLY //;定一个代码段RESET,只可读;
EXPORT __Vectors //;定义1个全局变量 _Vectors
EXPORT __Vectors_End //;定义1个全局变量 _Vectors_End
EXPORT __Vectors_Size//;定义1个全局变量 _Vectors_Size
//;分别定义了多个连续的字存储单元,这些存储单元用于存储向量表。标号_Vectors是栈顶地址 //;_initial_sp,__Vectors_End表示向量表的结尾地址。
__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 SVC_Handler ; SVCall Handler
DCD DebugMon_Handler ; Debug Monitor Handler
DCD 0 ; Reserved
DCD PendSV_Handler ; PendSV Handler
DCD SysTick_Handler ; SysTick Handler
; External Interrupts
DCD WWDG_IRQHandler ; Window Watchdog
DCD PVD_IRQHandler ; PVD through EXTI Line detect
DCD TAMPER_IRQHandler ; Tamper
DCD RTC_IRQHandler ; RTC
DCD FLASH_IRQHandler ; Flash
DCD RCC_IRQHandler ; RCC
DCD EXTI0_IRQHandler ; EXTI Line 0
DCD EXTI1_IRQHandler ; EXTI Line 1
DCD EXTI2_IRQHandler ; EXTI Line 2
DCD EXTI3_IRQHandler ; EXTI Line 3
DCD EXTI4_IRQHandler ; EXTI Line 4
DCD DMA1_Channel1_IRQHandler ; DMA1 Channel 1
DCD DMA1_Channel2_IRQHandler ; DMA1 Channel 2
DCD DMA1_Channel3_IRQHandler ; DMA1 Channel 3
DCD DMA1_Channel4_IRQHandler ; DMA1 Channel 4
DCD DMA1_Channel5_IRQHandler ; DMA1 Channel 5
DCD DMA1_Channel6_IRQHandler ; DMA1 Channel 6
DCD DMA1_Channel7_IRQHandler ; DMA1 Channel 7
DCD ADC1_2_IRQHandler ; ADC1_2
DCD USB_HP_CAN1_TX_IRQHandler ; USB High Priority or CAN1 TX
DCD USB_LP_CAN1_RX0_IRQHandler ; USB Low Priority or CAN1 RX0
DCD CAN1_RX1_IRQHandler ; CAN1 RX1
DCD CAN1_SCE_IRQHandler ; CAN1 SCE
DCD EXTI9_5_IRQHandler ; EXTI Line 9..5
DCD TIM1_BRK_IRQHandler ; TIM1 Break
DCD TIM1_UP_IRQHandler ; TIM1 Update
DCD TIM1_TRG_COM_IRQHandler ; TIM1 Trigger and Commutation
DCD TIM1_CC_IRQHandler ; TIM1 Capture Compare
DCD TIM2_IRQHandler ; TIM2
DCD TIM3_IRQHandler ; TIM3
DCD TIM4_IRQHandler ; TIM4
DCD I2C1_EV_IRQHandler ; I2C1 Event
DCD I2C1_ER_IRQHandler ; I2C1 Error
DCD I2C2_EV_IRQHandler ; I2C2 Event
DCD I2C2_ER_IRQHandler ; I2C2 Error
DCD SPI1_IRQHandler ; SPI1
DCD SPI2_IRQHandler ; SPI2
DCD USART1_IRQHandler ; USART1
DCD USART2_IRQHandler ; USART2
DCD USART3_IRQHandler ; USART3
DCD EXTI15_10_IRQHandler ; EXTI Line 15..10
DCD RTCAlarm_IRQHandler ; RTC Alarm through EXTI Line
DCD USBWakeUp_IRQHandler ; USB Wakeup from suspend
__Vectors_End
//;定义一个变量__Vectors_Size 用于表示向量表的大小;
__Vectors_Size EQU __Vectors_End - __Vectors
//;定义一个代码段|.text|,只可读;
AREA |.text|, CODE, READONLY
; Reset handler
Reset_Handler PROC
EXPORT Reset_Handler [WEAK]
IMPORT __main
IMPORT SystemInit
LDR R0, =SystemInit
BLX R0
LDR R0, =__main
BX R0
ENDP
; Dummy Exception Handlers (infinite loops which can be modified)
NMI_Handler PROC
EXPORT NMI_Handler [WEAK]
B .
ENDP
HardFault_Handler\
PROC
EXPORT HardFault_Handler [WEAK]
B .
ENDP
MemManage_Handler\
PROC
EXPORT MemManage_Handler [WEAK]
B .
ENDP
BusFault_Handler\
PROC
EXPORT BusFault_Handler [WEAK]
B .
ENDP
UsageFault_Handler\
PROC
EXPORT UsageFault_Handler [WEAK]
B .
ENDP
SVC_Handler PROC
EXPORT SVC_Handler [WEAK]
B .
ENDP
DebugMon_Handler\
PROC
EXPORT DebugMon_Handler [WEAK]
B .
ENDP
PendSV_Handler PROC
EXPORT PendSV_Handler [WEAK]
B .
ENDP
SysTick_Handler PROC
EXPORT SysTick_Handler [WEAK]
B .
ENDP
Default_Handler PROC
EXPORT WWDG_IRQHandler [WEAK]
EXPORT PVD_IRQHandler [WEAK]
EXPORT TAMPER_IRQHandler [WEAK]
EXPORT RTC_IRQHandler [WEAK]
EXPORT FLASH_IRQHandler [WEAK]
EXPORT RCC_IRQHandler [WEAK]
EXPORT EXTI0_IRQHandler [WEAK]
EXPORT EXTI1_IRQHandler [WEAK]
EXPORT EXTI2_IRQHandler [WEAK]
EXPORT EXTI3_IRQHandler [WEAK]
EXPORT EXTI4_IRQHandler [WEAK]
EXPORT DMA1_Channel1_IRQHandler [WEAK]
EXPORT DMA1_Channel2_IRQHandler [WEAK]
EXPORT DMA1_Channel3_IRQHandler [WEAK]
EXPORT DMA1_Channel4_IRQHandler [WEAK]
EXPORT DMA1_Channel5_IRQHandler [WEAK]
EXPORT DMA1_Channel6_IRQHandler [WEAK]
EXPORT DMA1_Channel7_IRQHandler [WEAK]
EXPORT ADC1_2_IRQHandler [WEAK]
EXPORT USB_HP_CAN1_TX_IRQHandler [WEAK]
EXPORT USB_LP_CAN1_RX0_IRQHandler [WEAK]
EXPORT CAN1_RX1_IRQHandler [WEAK]
EXPORT CAN1_SCE_IRQHandler [WEAK]
EXPORT EXTI9_5_IRQHandler [WEAK]
EXPORT TIM1_BRK_IRQHandler [WEAK]
EXPORT TIM1_UP_IRQHandler [WEAK]
EXPORT TIM1_TRG_COM_IRQHandler [WEAK]
EXPORT TIM1_CC_IRQHandler [WEAK]
EXPORT TIM2_IRQHandler [WEAK]
EXPORT TIM3_IRQHandler [WEAK]
EXPORT TIM4_IRQHandler [WEAK]
EXPORT I2C1_EV_IRQHandler [WEAK]
EXPORT I2C1_ER_IRQHandler [WEAK]
EXPORT I2C2_EV_IRQHandler [WEAK]
EXPORT I2C2_ER_IRQHandler [WEAK]
EXPORT SPI1_IRQHandler [WEAK]
EXPORT SPI2_IRQHandler [WEAK]
EXPORT USART1_IRQHandler [WEAK]
EXPORT USART2_IRQHandler [WEAK]
EXPORT USART3_IRQHandler [WEAK]
EXPORT EXTI15_10_IRQHandler [WEAK]
EXPORT RTCAlarm_IRQHandler [WEAK]
EXPORT USBWakeUp_IRQHandler [WEAK]
WWDG_IRQHandler
PVD_IRQHandler
TAMPER_IRQHandler
RTC_IRQHandler
FLASH_IRQHandler
RCC_IRQHandler
EXTI0_IRQHandler
EXTI1_IRQHandler
EXTI2_IRQHandler
EXTI3_IRQHandler
EXTI4_IRQHandler
DMA1_Channel1_IRQHandler
DMA1_Channel2_IRQHandler
DMA1_Channel3_IRQHandler
DMA1_Channel4_IRQHandler
DMA1_Channel5_IRQHandler
DMA1_Channel6_IRQHandler
DMA1_Channel7_IRQHandler
ADC1_2_IRQHandler
USB_HP_CAN1_TX_IRQHandler
USB_LP_CAN1_RX0_IRQHandler
CAN1_RX1_IRQHandler
CAN1_SCE_IRQHandler
EXTI9_5_IRQHandler
TIM1_BRK_IRQHandler
TIM1_UP_IRQHandler
TIM1_TRG_COM_IRQHandler
TIM1_CC_IRQHandler
TIM2_IRQHandler
TIM3_IRQHandler
TIM4_IRQHandler
I2C1_EV_IRQHandler
I2C1_ER_IRQHandler
I2C2_EV_IRQHandler
I2C2_ER_IRQHandler
SPI1_IRQHandler
SPI2_IRQHandler
USART1_IRQHandler
USART2_IRQHandler
USART3_IRQHandler
EXTI15_10_IRQHandler
RTCAlarm_IRQHandler
USBWakeUp_IRQHandler
B .
ENDP
ALIGN
;*******************************************************************************
; User Stack and Heap initialization
;*******************************************************************************
IF :DEF:__MICROLIB
EXPORT __initial_sp
EXPORT __heap_base
EXPORT __heap_limit
ELSE
IMPORT __use_two_region_memory
EXPORT __user_initial_stackheap
//IF…ELSE…ENDIF结构。如果使用(或定义)_MICROLIB,那么,定义了3个全局属性变量。
//否则,定义一个全局属性的标号__user_initial_stackheap,并且通知编译器本文件中
//应用的_use_two_region_memory在其他文件中定义了。注意:MiCROLIB缺省的情况下使用的是Keil C库。
__user_initial_stackheap
LDR R0, = Heap_Mem
LDR R1, =(Stack_Mem + Stack_Size)
LDR R2, = (Heap_Mem + Heap_Size)
LDR R3, = Stack_Mem
BX LR
ALIGN
ENDIF
END
//注意:堆区和栈区的名称分别代表堆区和栈区的起始地址,由于栈逆生长,所以它的名称代表栈区的栈底地址。