二、启动代码分析
1、启动模式的选择
STM32自带的启动模式有3种:
STM32的启动碚枞凇悄选择,通过设置BOOT1、BOOT0的引脚的高低电平即可选择。其中主闪存启动是将程序下载到内置的Flash进行启动(该flash可运行程序),该程序可以掉电保存,下次开机可自动启动;系统存储器启动是将程序写入到一快特定的区域,一般由厂家直接写入,不能被随意更改或擦除。内置SRAM启动,由于SRAM掉电丢失,不能保存程序,一般只用于程序的调试。
2、启动文件分析
启动文件,总的来说主要做了3个工作:分配和初始化栈、堆;定义复位向量并初始化;中断向量表及相应的异常处理程序,以107器件cl.s文件为例。
1)定义栈、堆及其初始化
定义堆、栈:
Stack_Size EQU0x400
Heap_Size EQU0x400
AREASTACK, NOINIT, READWRITE,
Stack
SPACEStack_Size
AREAHEAP, NOINIT, READWRITE,
Heap
SPACEHeap_Size
堆栈初始化定义:
__user_initial_stackheap //进行栈、堆的赋值,在_main函数执行过程中调用
LDRR0, =Heap
LDRR1, =(Stack + Stack_Size)
LDRR2, =(Heap + Heap_Size)
LDRR3, =Stack
BXLR
2)定义复位向量
Boot引脚的设置不同,复位时,起始地址的位置不同,SRAM的起始地址为0x2000000, flash的起始地址为0x8000000。Cortex-M3内核规定,起始地址必须存放堆定指针,而第二个地址必须存放复位中断入口向量。在系统复位时,内核会自动从其实地址的下一个地址(即32位)空间取出复位中断入口向量,然后跳转到复位中断服务程序,该服务程序就会跳转到main()执行程序。
中断向量表(部分向量):
EXPORT __Vectors
__Vectors DCDStack + Stack_Size ; Top of Stack
DCD Reset_Handler
DCD NMIException
DCD HardFaultException
DCD MemManageException
DCD BusFaultException
DCD UsageFaultException
DCD 0 ; Reserved
DCD 0 ; Reserved
DCD 0 ; Reserved
DCD 0 ; Reserved
DCD PendSV_Handler ; SVCall Handler
DCD DebugMon_Handler ; Debug Monitor Handler
DCD 0 ; Reserved
DCD OS_CPU_PendSVHandler ; PendSV Handler
DCD SysTick_Handler ; SysTick Handler
复位中断服务程序:
EXPORTReset_Handler
Reset_Handler
IMPORT__main //导入符号,_main为运行时库提供的函数;完成堆、栈的初始化
LDRR0, =__main //等工作,调用定义的__user_initial_stackheap
BXR0 //跳到_main,进入C的世界
3)其他中断向量及服务子程序
在启动文件中,只定义了中断向量,它们相应的服务子程序跳转到空操作,为之后扩展中断服务程序做准备。
4)拓展:_main()与main()区别
注意:_main()与main()不是相同函数。_main()是编译系统提供的一个函数,负责完成库函数的初始化和初始化应用程序执行环境,最后自动跳转到main()。所以说,前者是库函数,后者是我们自己编写的main()主函数。
对含有启动程序的系统来说,执行地址与加载地址相同不容易实现。
因为启动程序是要烧到非易失存储器里,用来在上电执行的,而这个程序必定会有RW段,如果RW放在非易失存储器,如FLASH,那就不好实现RW功能了,因此要给RW移动到能够实现RW功能的存储器,如SRAM等.因此,对含有启动程序来说,"执行地址与加载地址相同"就不容易实现。
程序的入口点在C库中的_main处,在该点,库代码执行以下操作:
u把RO,RW从他们的加载域复制到他们的运行域中去
u初始化ZI域
u跳到__rt_entry
而库函数__rt_entry()会完成以下工作:
u调用__rt_stackheap_init()设置stack和heap
u调用__rt_lib_init()初始化相应的库函数
u调用main(),即是我们自己的应用程序了
3、我司启动文件情况:
因公司使用的stm32芯片规格不一,分stm32f103、stm32f107系列等等,在进行程序编程时,为保证程序的可移植性,在InterruptVector.c文件中,通过预定义的方式来切换103/107器件,同时代码在进行编译过程中,需手动选择启动文件类型,不然代码无法编译成功。
图中编译使用的是cl.s文件。
三、时钟资源分析
1、时钟源
在STM32中,有五个时钟源,为中,有五个时钟源,为HSI、HSE、LSI、LSE、PLL。从时钟频率来分可以分为高速时钟源和低速时钟源,在这5个中HIS,HSE以及PLL是高速时钟,LSI和LSE是低速时钟。从来源可分为外部时钟源和内部时钟源,外部时钟源就是从外部通过接晶振的方式获取时钟源,其中HSE和LSE是外部时钟源,其他的是内部时钟源。下面我们看看STM32的五个时钟源,对应上面图中标红的序号标示。
①、HSE是高速外部时钟,可接石英/陶瓷谐振器,或者接外部时钟源,频率范围为4MHz~16MHz。
②、HSI是高速内部时钟,RC振荡器,频率为8MHz。
③、LSI是低速内部时钟,RC振荡器,频率为40kHz。独立看门狗的时钟源只能是LSI,同时LSI还可以作为RTC的时钟源。
④、LSE是低速外部时钟,接频率为32.768kHz的石英晶体。这个主要是RTC的时钟源。
⑤、PLL为锁相环倍频输出,其时钟输入源可选择为HSI/2、HSE或者HSE/2。倍频可选择为2~16倍,但是其输出频率最大不得超过72MHz。
2、时钟源输出
再看一下时钟源的输出,如何为外设及系统提供时钟,对应上方图中字母标示。
A、MCO是STM32的一个时钟输出IO(PA8),它可以选择一个时钟信号输出,可以选择为PLL输出的2分频、HSI、HSE、或者系统时钟。这个时钟可以用来给外部其他系统提供时钟源。
B、这里是RTC时钟源,从图上可以看出,RTC的时钟源可以选择LSI,LSE,以及HSE的128分频。
C、USB的时钟是来自PLL时钟源。STM32中有一个全速功能的USB模块,其串行接口引擎需要一个频率为48MHz的时钟源。该时钟源只能从PLL输出端获取,可以选择为1.5分频或者1分频,也就是,当需要使用USB模块时,PLL必须使能,并且时钟频率配置为48MHz或72MHz。
D、是STM32的系统时钟SYSCLK,它是供STM32中绝大部分部件工作的时钟源。系统时钟可选择为PLL输出、HSI或者HSE。系统时钟最大频率为72MHz。
E、指其他所有外设了。从时钟图上可以看出,其他所有外设的时钟最终来源都是SYSCLK。SYSCLK通过AHB分频器分频后送给各模块使用。
1)AHB总线、内核、内存和DMA使用的HCLK时钟。
2)通过8分频后送给Cortex的系统定时器时钟,也就是systick了。
3)直接送给Cortex的空闲运行时钟FCLK。
4)送给APB1分频器。APB1分频器输出一路供APB1外设使用(PCLK1,最大频率36MHz),另一路送给定时器(Timer)2、3、4倍频器使用。
5)送给APB2分频器。APB2分频器分频输出一路供APB2外设使用(PCLK2,最大频率72MHz),另一路送给定时器(Timer)1倍频器使用。
注意:APB1上面的是低速外设,包括电源接口、备份接口、CAN、USB、I2C1、I2C2、UART2、UART3等等,APB2上面连接的是高速外设包括UART1、SPI1、Timer1、ADC1、ADC2、所有普通IO口(PA~PE)、第二功能IO口等。
3、SystemInit函数分析
首先,要芯片工作进行时钟配置时函数的调用关系:startup_stm32f10x_cl.s(启动文件)→main()→SystemInit()→SetSysClock()→SetSysClockTo72()。(系统默认使用72MHz)
1)SystemInit()
在进行时钟配置之前,对RCC寄存器CR、CIR、CFGR、CFGR2进行重置。然后调用函数SetSysClock()。
/* Reset the RCC clockconfiguration to the default reset state(for debug purpose) */
/* Set HSION bit */
RCC->CR |= (uint32_t)0x00000001;//内部8MHz时钟开启
/* Reset SW, HPRE, PPRE1, PPRE2, ADCPRE and MCO bits */
#ifndef STM32F10X_CL
RCC->CFGR &= (uint32_t)0xF8FF0000; //清零
#else
RCC->CFGR &= (uint32_t)0xF0FF0000; //清零
#endif /* STM32F10X_CL */
/* Reset HSEON, CSSON and PLLON bits */
RCC->CR &= (uint32_t)0xFEF6FFFF; //清零
/* Reset HSEBYP bit */
RCC->CR &= (uint32_t)0xFFFBFFFF; //清零
/* Reset PLLSRC, PLLXTPRE, PLLMUL and USBPRE/OTGFSPRE bits */
RCC->CFGR &= (uint32_t)0xFF80FFFF; //清零
#ifdef STM32F10X_CL //互联型产品
/* Reset PLL2ON and PLL3ON bits */
RCC->CR &= (uint32_t)0xEBFFFFFF;
/* Disable all interrupts and clear pending bits */
RCC->CIR = 0x00FF0000; //中断使能关闭
/* Reset CFGR2 register */
RCC->CFGR2 = 0x00000000;
#elif defined (STM32F10X_LD_VL)|| defined (STM32F10X_MD_VL) || (defined STM32F10X_HD_VL)
/* Disable all interrupts and clear pending bits */
RCC->CIR = 0x009F0000;
/* Reset CFGR2 register */
RCC->CFGR2 = 0x00000000;
#else
/* Disable all interrupts and clear pending bits */
RCC->CIR = 0x009F0000;
#endif /* STM32F10X_CL */
SetSysClock();
2)SetSysClock()
#ifdef SYSCLK_FREQ_HSE
SetSysClockToHSE();
#elif defined SYSCLK_FREQ_24MHz
SetSysClockTo24();
#elif defined SYSCLK_FREQ_36MHz
SetSysClockTo36();
#elif defined SYSCLK_FREQ_48MHz
SetSysClockTo48();
#elif defined SYSCLK_FREQ_56MHz
SetSysClockTo56();
#elif definedSYSCLK_FREQ_72MHz //定义为72MHz
SetSysClockTo72();
#endif
定义为72MHz,便接着调用函数SetSysClockTo72()。
3)SetSysClockTo72()
先计算系统时钟SYSCLK频率(外部晶振为25MHz)。
代码中:
RCC->CFGR2|= (uint32_t)(RCC_CFGR2_PREDIV2_DIV5 | RCC_CFGR2_PLL2MUL8 |
RCC_CFGR2_PREDIV1SRC_PLL2 |RCC_CFGR2_PREDIV1_DIV5);
其中RCC_CFGR2_PREDIV2_DIV5:PREDIV2=5 //5分频
也就是PREDIV2对输入的外部时钟5分频,那么PLL2和PLL3没有倍频前均为25/5=5MHz。
RCC_CFGR2_PLL2MUL8:PLL2MUL=8 //8倍频
8倍频后PLL2时钟=5*8=40MHz,因此PLL2CLK=40MHz。
RCC_CFGR2_PREDIV1SRC_PLL2:RCC_CFGR2的第16位为1,选择PLL2CLK作为PREDIV1的时钟源。
RCC_CFGR2_PREDIV1_DIV5:PREDIV1=5 //5分频
PREDIV1CLK=PLL2CLK/5=8MHz。
(以上是对RCC_CFGR2进行的配置)
RCC->CFGR|= (uint32_t)(RCC_CFGR_PLLXTPRE_PREDIV1 | RCC_CFGR_PLLSRC_PREDIV1 |
RCC_CFGR_PLLMULL9);
RCC_CFGR_PLLXTPRE_PREDIV1:操作的是RCC_CFGR的第17位PLLXTPRE,操作这一位和操作RCC_CFGR2寄存器的位[3:0]中的最低位是相同的效果。
RCC_CFGR_PLLSRC_PREDIV1:选择PREDIV1输出作为PLL输入时钟;PREDIV1CLK=8MHZ,所以输入给PLL倍频的时钟源是8MHz。
RCC_CFGR_PLLMULL9:PLLMULL=9;//9倍频
也就是对PLLCLK=PREDIV1CLK*9=72MHz。
(以上是对RCC_CFGR进行的配置)
/* Select PLL as system clock source */
RCC->CFGR &=(uint32_t)((uint32_t)~(RCC_CFGR_SW));
RCC->CFGR |=(uint32_t)RCC_CFGR_SW_PLL; //选择PLLCLK作为系统时钟源
故系统时钟SYSCLK=72MHz。
代码中:
/* HCLK = SYSCLK */
RCC->CFGR |=(uint32_t)RCC_CFGR_HPRE_DIV1;
/* PCLK2 = HCLK */
RCC->CFGR |=(uint32_t)RCC_CFGR_PPRE2_DIV1;
/* PCLK1 = HCLK */
RCC->CFGR |=(uint32_t)RCC_CFGR_PPRE1_DIV2;
故HCLK(AHB总线时钟)=PLLCLK=SYSCLK=72MHz。
PLCK2(APB2时钟)=HCLK=SYSCLK=PLLCLK=72MHz。
PLCK1(APB1时钟)=HCLK/2=36MHz。
总结一下SystemInit()函数中设置的系统时钟大小:
SYSCLK(系统时钟)=72MHZ
AHB总线时钟=72MHZ
APB1总线时钟=36MHZ
APB2总线时钟=72MHZ
PLL时钟=72MHZ
PLL2时钟=40MHZ
以上属基于个人理解编写的学习总结,由于个人表达能力及专业能力有限,难免会有遗漏及描述不清晰的地方,若有疏忽的地方请指导一下