启动
从startup_stm32f0xx.s内的开头的Description可以看到
;* Description : STM32F051 devices vector table for EWARM toolchain.
;* This module performs:
;* - Set the initial SP
;* - Set the initial PC == iar_program_start,
;* - Set the vector table entries with the exceptions ISR
;* address
;* - Configure the system clock
;* - Branches to main in the C library (which eventually
;* calls main()).
;* After Reset the Cortex-M0 processor is in Thread mode,
;* priority is Privileged, and the Stack is set to Main.
可以得知STM32的启动流程是首先初始化SP(堆栈种指针),初始化PC(程序计数器)指针指向__iar_program_start,设置向量表,初始化时钟系统
可以看到初始化时钟系统是在设置堆栈后,并且运行__iar_program_start设置了硬之后,这是必要的。
startup的汇编内容
这也就是所有MCU启动过程中的第一步:使用汇编语言编写的启动第一部分,设置堆栈和硬件为第二部分的启动铺垫。
第二部是使用C语言编写的启动第二部分
接下来直接跳转到SystemInit
SystemInit
1.在初始化的第一步
void SystemInit (void)
{ /* Set HSION bit */RCC->CR |= (uint32_t)0x00000001;
通过数据手册可以看到
打开了HSI时钟
为什么第一步先要打开HSI时钟呢?
在许多微控制器中,HSI (High Speed Internal) 时钟通常是第一个被启用的时钟源,这是因为HSI时钟是内置在微控制器内部的,不需要外部晶振或陶瓷谐振器就能工作,因此它是最容易且快速可用的时钟源。
2.
/* Reset SW[1:0], HPRE[3:0], PPRE[2:0], ADCPRE, MCOSEL[2:0], MCOPRE[2:0] and PLLNODIV bits */
RCC->CFGR &= (uint32_t)0x08FFB80C;
//0000 0100 1000 1111 1111 1011 1000 0000 1100
在时钟配置寄存器中,从低位开始看
SW(00)使用HSI作为系统时钟
SWS(11)不使用系统时钟切换状态
HPRE(0000)SYSCLK不分频
PPRE(0000)HCLK不分频
PLLSRC(1)HSE/PREDIV作为PLL输入时钟
PLLXTPRE(1)HSE 2分频
PLLMUL(1111)PLL倍频系数,PLL输入时钟的16倍频
MCO(100)控制器时钟输出为系统时钟SYSCLK
总体可以看到在systemInit中使用了HSI作为系统时钟,HSE作为PLL输入时钟,HSI提供了即时性,HSE提供了可靠性
3.
/* Reset HSEON, CSSON and PLLON bits */RCC->CR &= (uint32_t)0xFEF6FFFF;
//1111 1110 1111 0110 1111 1111 1111 1111
从低位开始可以得知
HSION(1)启动HSI振荡器
HSEON(1)启动HSE振荡器
HSEBYP(1)HSE晶体振荡器有旁路
CSSON(0)时钟检测器关闭
PLLON(0)PLL关闭
PLLRDY(1)PLL锁定
注意在这里关闭并且锁定了PLL
4.
/* Reset HSEBYP bit */RCC->CR &= (uint32_t)0xFFFBFFFF;
//1111 1111 1111 1011 1111 1111 1111 1111
可以看确保HSE振荡器不是在旁路模式下工作,而是使用内部电路来产生时钟信号。
需要使用HSE振荡器作为系统时钟的一部分时。在启动阶段,可能首先使用HSI(高速内部振荡器)作为时钟源,随后配置HSE振荡器,并可能使用HSE作为PLL(锁相环)的输入,以产生更高频率的系统时钟
5.
/* Reset PLLSRC, PLLXTPRE and PLLMUL[3:0] bits */RCC->CFGR &= (uint32_t)0xFFC0FFFF;
将PLLSRC位清零,意味着PLL的输入时钟将默认为HSI经过预分频后的时钟(如果HSI可用并且已经使能)。
将PL LXTPRE位清零,如果选择了HSE作为PLL输入时钟,那么它不会通过预分频器。
将PLLMUL[3:0]位清零,PLL的乘法因子将被重置为其默认或最小值。
6.
/* Reset PREDIV1[3:0] bits */RCC->CFGR2 &= (uint32_t)0xFFFFFFF0;
将PREDIV1[3:0]位清零,将预分频器1的分频比数重置为其最小值或默认值,不进行分频
7.
/* Reset USARTSW[1:0], I2CSW, CECSW and ADCSW bits */RCC->CFGR3 &= (uint32_t)0xFFFFFEAC;
分别配置了USART,I2C,HDMI CEC, ADC时钟源
PCLK作为USART时钟源
HSI作为I2C1的时钟源
HSI/244作为HDMI CEC时钟源
PCLK 2或4分频作为ADC时钟
8.
/* Reset HSI14 bit */RCC->CR2 &= (uint32_t)0xFFFFFFFE;
禁用了HSI14
HSI14 和 HSI 不完全相同,尽管它们都是 STM32 微控制器内部集成的 RC(电阻-电容)振荡器,但它们有各自的特点和用途:
HSI (High Speed Internal Oscillator)
HSI 是一个典型的内部振荡器,通常提供大约 8 MHz 或 16 MHz 的时钟频率,具体取决于微控制器的型号。HSI 在上电或复位后默认启用,可以作为系统时钟源,为整个微控制器提供时钟信号。
HSI 通常用于在没有外部时钟源时快速启动系统,或者在低功耗模式下作为时钟源。
HSI14 (High Speed Internal 14 MHz Oscillator)
HSI14 提供一个大约 14 MHz 的时钟频率,专为 USB 和某些高级定时器(如 TIM1 和 TIM8)设计。在 STM32 微控制器中,HSI14 主要用于 USB 全速(Full Speed)应用和高级定时器,因为它们需要一个稳定的时钟源。
HSI14 的主要优点在于它可以直接为 USB 设备提供所需的确切时钟频率,而无需额外的时钟调节或 PLL 放大。
总结来说,HSI 和 HSI14 都是内部振荡器,但它们的频率和用途不同。HSI 更倾向于作为系统时钟源,而 HSI14 则专门用于需要特定时钟频率的外设,如 USB 和某些高级定时器。在某些 STM32 系列中,HSI14 不是默认启用的,需要在软件中显式地配置和启用。
9.
/* Disable all interrupts */RCC->CIR = 0x00000000;
失能所有的中断
之后就进入下面函数来设置时钟预频率 总线预分频 和 FLASH设置
/* Configure the System clock frequency, AHB/APBx prescalers and Flash settings */SetSysClock();
SetSysClock
这段代码是STM32微控制器中用于配置系统时钟的一个函数,名为`SetSysClock()`。下面是对这段代码详细步骤的解释:
1. 初始化变量:
`StartUpCounter` 和 `HSEStatus` 分别用于跟踪HSE启动超时计数和HSE状态。
__IO uint32_t StartUpCounter = 0, HSEStatus = 0;
2. 启用HSE:
通过设置RCC_CR寄存器的HSEON位来启动高速外部振荡器(HSE)。
/* Enable HSE */ RCC->CR |= ((uint32_t)RCC_CR_HSEON);
3. 等待HSE就绪:
使用一个循环检查RCC_CR寄存器的HSERDY位,以确认HSE是否已经稳定。如果HSE在指定时间内未能准备好,`StartUpCounter`将增加,直至达到`HSE_STARTUP_TIMEOUT`常量,此时将退出循环。
do{HSEStatus = RCC->CR & RCC_CR_HSERDY;StartUpCounter++; } while((HSEStatus == 0) && (StartUpCounter != HSE_STARTUP_TIMEOUT));
4. 检查HSE状态:
如果HSE成功启动,`HSEStatus`设置为0x01,否则设置为0x00。
5. 配置Flash预取缓冲区和等待周期:
if ((RCC->CR & RCC_CR_HSERDY) != RESET){HSEStatus = (uint32_t)0x01;}else{HSEStatus = (uint32_t)0x00;}
如果HSE成功启动,接下来配置Flash访问控制寄存器(FLASH_ACR)以启用预取缓冲区,并设置适当的等待周期。
6. 配置AHB和APB总线时钟:
设置RCC_CFGR寄存器的HPRE和PPRE位,以确定AHB和APB总线的预分频因子。在这个例子中,HCLK(AHB总线时钟)和PCLK(APB总线时钟)都被设置为与SYSCLK(系统时钟)相同。
/* Enable Prefetch Buffer and set Flash Latency */FLASH->ACR = FLASH_ACR_PRFTBE | FLASH_ACR_LATENCY;/* HCLK = SYSCLK */RCC->CFGR |= (uint32_t)RCC_CFGR_HPRE_DIV1;/* PCLK = HCLK */RCC->CFGR |= (uint32_t)RCC_CFGR_PPRE_DIV1;
7. 配置PLL:
清除RCC_CFGR寄存器中与PLL源选择、PLL输入时钟预分频和PLL倍频因子相关的位。
配置PLL以使用HSE作为输入时钟源,经过预分频器,并将PLL的倍频因子设置为6,这意味着最终的PLL输出频率将为HSE频率的6倍(假设HSE为8 MHz,PLL输出将为48 MHz)。
/* PLL configuration = HSE * 6 = 48 MHz */RCC->CFGR &= (uint32_t)((uint32_t)~(RCC_CFGR_PLLSRC | RCC_CFGR_PLLXTPRE | RCC_CFGR_PLLMULL));RCC->CFGR |= (uint32_t)(RCC_CFGR_PLLSRC_PREDIV1 | RCC_CFGR_PLLXTPRE_PREDIV1 | RCC_CFGR_PLLMULL6);
8. 启用PLL:
/* Enable PLL */RCC->CR |= RCC_CR_PLLON;
通过设置RCC_CR寄存器的PLLON位来启动PLL。
9. 等待PLL就绪:
使用循环检查RCC_CR寄存器的PLLRDY位,以确认PLL是否已经稳定。
/* Wait till PLL is ready */while((RCC->CR & RCC_CR_PLLRDY) == 0){}
10. 选择PLL作为系统时钟源:
将RCC_CFGR寄存器的SW位设置为PLL,选择PLL作为系统时钟源。
/* Select PLL as system clock source */RCC->CFGR &= (uint32_t)((uint32_t)~(RCC_CFGR_SW));RCC->CFGR |= (uint32_t)RCC_CFGR_SW_PLL;
11. 等待PLL作为系统时钟源:
使用循环检查RCC_CFGR寄存器的SWS位,以确认PLL是否已经被选为系统时钟源。
/* Wait till PLL is used as system clock source */while ((RCC->CFGR & (uint32_t)RCC_CFGR_SWS) != (uint32_t)RCC_CFGR_SWS_PLL){}
12. 处理HSE启动失败:
如果HSE未能启动,函数将到达此处,这里可以添加代码来处理这种错误情况,例如重启系统或进入错误处理程序。
else{ /* If HSE fails to start-up, the application will have wrong clock configuration. User can add here some code to deal with this error */}
SetSysClock()函数的主要目的是配置系统时钟,使其能够以期望的频率运行。在这个例子中,它首先启用并确认HSE的运行,然后使用HSE作为PLL的输入,通过PLL生成48 MHz的时钟信号,并最终选择PLL作为系统时钟源,从而为整个微控制器提供一个稳定且足够高的时钟频率。
总结
可以看到STM32的时钟启动方案是在SystemInit中,首先开启了快速易用的HSI作为系统时钟,并且关闭了所有中断,关闭了HSI14,之后在SetSysClock中重启PLL选择了HSE作为系统时钟。
初始化的后期启用HSE并使用PLL来进一步提高系统时钟频率,这种方案有几个明显的好处:
在STM32的启动方案中,首先使用HSI作为系统时钟,然后在初始化的后期启用HSE并使用PLL来进一步提高系统时钟频率,这种方案有几个明显的好处:
1. 快速启动:
HSI作为内部RC振荡器,不需要外部元件,上电后立即可用。这使得系统可以迅速启动并执行基本的初始化,如设置堆栈、初始化硬件寄存器等,而无需等待外部时钟源稳定。
2. 可靠性与容错性:
HSI提供了系统启动时的可靠时钟源,即使外部晶振或时钟源出现问题,系统仍然可以使用HSI运行,虽然可能在精度和稳定性上有所折衷,但至少可以确保基本的功能性和安全性。
3. 性能提升:
一旦系统初步初始化完成,可以启用更稳定、精度更高的HSE振荡器,并通过PLL进一步提高时钟频率。这可以显著提高系统的性能,因为PLL可以将时钟频率放大到远高于HSI所能提供的频率,从而允许CPU和外设以更高的速度运行。