板载STM32F401芯片的HSE"/>
NUCLEO板载STM32F401芯片的HSE
写在前面
时钟配置很容易走弯路,因为Reference Manual上所提及的部分不足以覆盖所有所需注意的点。此处列举两个我踩了坑的地方:
- 时钟的配置必须在SystemInit阶段完成,而不能在main函数中完成,否则会导致USART输出乱码(虽然LED频率似乎是正确的)。
- 时钟使能后要循环等待直至其稳定,否则会产生奇怪影响(未等待HSE稳定即启动PLL,会导致系统时钟从配置好的84MHz变成96MHz)。
除此之外还有一些其他莫名其妙的坑点,手册RM0368里的时钟配置流程没有介绍。不过这其实反映出我的手册阅读方法可能存在问题。
整个配置过程分为4个步骤:寻找参考,配置HSE、配置PLL、切换时钟源。文中会含有大量的原理图与手册截图,可能需要读者具有一定的英语水平。
寻找参考
对于嵌入式编程而言,芯片、开发板的手册以及开发板的原理图是至关重要的。这些参考材料一般可从购买渠道获取,或是访问相应厂商的官网获取。
例如,我所使用的NUCLEO-F401RE开发板是ST公司的产品,进入ST官网后,点击左上角放大镜输入“NUCLEO-F401RE”即可搜索相关资源。在开发板对应页面中,在下层导航栏中点击Documentation可获取开发板手册,点击CAD Resources可获取开发板原理图与PCB图。
而板上芯片STM32F401RET6也有对应的手册,一般常用的有Reference Manual与Programming Manual,设计外围电路时也会用到Product Specification。
配置HSE
HSE即外部高速晶振,启动HSE的前提是板上存在外部晶振。这一步需要查看所用开发板的原理图与设计手册(我所用的NUCLEO开发板手册号为UM1724),检查外部晶振的参数与连接方式。
UM1724所提及的晶振中,只有第一个是可以直接使用的,其他都不是NUCLEO开发板自带的晶振。经过实板检查,下面罗列的三个配置即是板上默认配置。
MCO即Microcontroller Clock Output,即主芯片STM32F401RET6所用的时钟信号来自板载ST-Link上与ST-Link的STM32F103CBT6直接相连的8MHz晶振。
由于板上X3默认是未焊接的空焊盘,所以MCO可以直接视作HSE时钟信号。
此处有两个需要注意的地方:
- 断路器SB50处的“Default: closed”是指其默认连通,而非默认断开。检查开发板背面可发现对应断路器焊有连接件。
- RM0368手册中的HSE bypass有一定迷惑性: 此处的HI-Z是指高阻态,但原理图中作为OSC_OUT的PF1/PD1/PH1管脚是否是高阻态呢?这需要一些数字电路知识,我暂时还没有学。不过,试验表明设置旁路HSE也没有问题。
手册RM0368上提供了一些配置HSE的指导:
中断暂时不用管,上述文字传达给我们两个信息:
- RCC_CR的HSEON位控制HSE的使能与否。
- RCC_CR的HSERDY位由硬件设置,作为HSE是否达到稳定的指标。
所以配置HSE的代码如下:
RCC->CR |= RCC_CR_HSEON;
while((RCC->CR & RCC_CR_HSERDY) == RESET);
while语句用于等待HSE就绪。要判断HSE就绪有很多指标可以看,但CR寄存器的HSERDY位是最基本的指标。某些写法会用到TIMEOUT,使得芯片在HSE无法就绪时使用HSI。这固然是合乎嵌入式编程要求的,我后续会把这种情况加入进去,现在先把安全性暂时搁置。
这一步尚且可以靠ST官网的资源解决问题,接下来才是重头戏。
配置PLL
PLL(锁相环)分为两种:主PLL和专用PLL(PLLI2S),后者用于为高质量音频传输提供精确时钟。我们暂时只使用主PLL,其特性如下:
- 以HSI或HSE的时钟信号为基础,分两条线输出时钟信号:第一条用于生成高速系统时钟(84MHz),第二条用于生成USB接口时钟(48MHz)等。
- 需要四个参数:PLL_M,PLL_N,PLL_P,PLL_Q。
用ST官方提供的开发工具STM32CUBEMX可实现可视化的时钟配置。此处不赘述该工具的使用方法,只需根据自己所用的硬件参数设置好HSE的输入频率,然后调整可选输入框,直至所有通道均达到蓝色小字所标明的最大值即可。
此处经调整得出 M = 8,N = 336,P = 4,Q = 7。然后通过阅读手册确定具体的配置方法:
查RCC_PLLCFGR可知其用于配置四个参数和PLL的时钟源(HSI或HSE),其中:
- 2 ≤ PLL_Q ≤ 15,且该值的配置必须保证经过Q这条线的所有时钟频率低于48MHz。
- PLL_P = 2, 4, 6, 8,置位为 (PLL_P >> 1) - 1。例如要配置其为8,则实际置位为0b11。
- 192 ≤ PLL_N ≤ 432,若要配置PLLI2S则有额外注意,此处略去。
- 2 ≤ PLL_M ≤ 63,若要配置PLLI2S则有额外注意,此处略去。
于是配置代码如下:
RCC->PLLCFGR = (8 << 0) // PLL_M = 8| (336 << 6) // PLL_N = 336| (((4 >> 1) - 1) << 16) // PLL_P = 4| (7 << 24) // PLL_Q = 7| RCC_PLLCFGR_PLLSRC_HSE; // 设置HSE为PLL时钟源
查RCC_CFGR可知其用于配置分频和系统时钟切换,根据时钟树图我们可以确定:AHB和APB2不分频,APB1分频系数为2,查手册可得代码如下:
RCC->CFGR |= RCC_CFGR_HPRE_DIV1 // AHB: /1| RCC_CFGR_PPRE1_DIV2 // APB1: /2| RCC_CFGR_PPRE2_DIV1; // APB2: /1
下一点是手册上没有提,但很容易想到的:启动PLL并等待其就绪。
RCC->CR |= RCC_CR_PLLON;
while((RCC->CR & RCC_CR_PLLRDY) == RESET);
切换时钟源
等待结束后什么都没有发生,时钟频率似乎还是HSI的16MHz。通过观察库自带的SystemCoreClockUpdate函数可以看出,系统判断此时使用哪个时钟的依据是RCC_CFGR的SWS位。所以切换时钟源这一步操作虽然看似无厘头,但其实也能够猜到几分。于是我直接进行如下切换:
RCC->CFGR &= ~(RCC_CFGR_SW); // 清空时钟选择(实际上默认为HSI)
RCC->CFGR |= RCC_CFGR_SW_PLL; // 选择PLL作为系统时钟源
while((RCC->CFGR & RCC_CFGR_SWS) != RCC_CFGR_SWS_PLL); // 等待PLL被事实上切换
然后寄了,看别人的代码里在这一步之前需要加上:
FLASH->ACR = FLASH_ACR_PRFTEN | FLASH_ACR_ICEN | FLASH_ACR_DCEN | FLASH_ACR_LATENCY_5WS;
这堆玩意完全看不懂。查手册发现了这个:
鉴定为:默认参数无法适应已变化的时钟频率。经过PLL配置,HCLK已经由16MHz变为84MHz。
按照这上面的要求,还需要做两件事:
- 调整VOS的置位。查手册可知VOS在PWR_CR中。要设置PWR_CR,需先在RCC中使能PWR(然而这一步跳过似乎也没有引起什么问题)。
- 调整wait states的数量。为了保证低电压也能用,还是设置5WS比较保险。不知道是否会影响芯片的性能,以后可能会回来进行修改。
这一段后面紧跟着就是CPU升频所需的操作:
总结来说就是:设置合适的LATENCY(即wait states的数量),再修改RCC_CFGR的SW位。
RCC->APB1ENR |= RCC_APB1ENR_PWREN;
PWR->CR &= ~(PWR_CR_VOS);
PWR->CR |= PWR_CR_VOS_1; // 设置VOS[1:0]为0x10,以适应84MHz时钟
FLASH->ACR |= FLASH_ACR_LATENCY_5WS; // 设置6个wait states,以确保低电压下仍能适应84MHz
然而还有一个疑问:若仿照别人的代码将DCEN、ICEN和PRFTEN全部置位,则接收机所接收到的油门和方向PWM脉宽均会长1us(两者都是右摇杆控制)。可能与手柄的微调按钮有关。但这些都是后话了。
总结
全部代码如下:
void SystemInit(void)
{...// 在SystemInit里随便找个位置放进去RCC->CR |= RCC_CR_HSEON;while((RCC->CR & RCC_CR_HSERDY) == RESET);RCC->PLLCFGR = (8 << 0) | (336 << 6) | (((4 >> 1) - 1) << 16)| (7 << 24) | RCC_PLLCFGR_PLLSRC_HSE;RCC->CFGR |= RCC_CFGR_HPRE_DIV1| RCC_CFGR_PPRE1_DIV2| RCC_CFGR_PPRE2_DIV1;RCC->CR |= RCC_CR_PLLON;while((RCC->CR & RCC_CR_PLLRDY) == RESET);RCC->APB1ENR |= RCC_APB1ENR_PWREN;PWR->CR &= ~(PWR_CR_VOS);PWR->CR |= PWR_CR_VOS_1;FLASH->ACR |= FLASH_ACR_LATENCY_5WS/*| FLASH_ACR_PRFTEN | FLASH_ACR_ICEN | FLASH_ACR_DCEN*/;RCC->CFGR &= ~(RCC_CFGR_SW);RCC->CFGR |= RCC_CFGR_SW_PLL;while((RCC->CFGR & RCC_CFGR_SWS) != RCC_CFGR_SWS_PLL);...
}
需要手册→其他资料→手册式的迭代才能彻底掌握芯片操控的底层逻辑。
更多推荐
NUCLEO板载STM32F401芯片的HSE
发布评论