1. 为什么需要从外部晶振切换到内部HSI时钟
最近在做一个基于STM32F103C8T6的项目,原本使用的是外部8MHz晶振作为时钟源。后来客户提出要降低成本和提高可靠性,要求改用内部HSI时钟。这个改动看似简单,但实际操作中需要考虑很多细节问题。
使用内部时钟最直接的好处就是可以省去外部晶振和相关电路。一个8MHz晶振虽然不贵,但批量生产时也能节省不少成本。更重要的是,外部晶振在振动、高温等恶劣环境下容易出现稳定性问题。我曾经遇到过因为晶振失效导致整个系统崩溃的情况,改用内部时钟后这类问题就彻底解决了。
不过内部时钟也有它的局限性。HSI(高速内部时钟)的精度比外部晶振要低,默认精度在±1%左右。对于需要高精度时钟的应用,比如USB通信,就不太适合。但在大多数控制类应用中,这个精度已经足够用了。另外,HSI的最高频率只能到64MHz,比使用外部晶振时的72MHz要低一些。
2. 硬件改造方案
2.1 移除外部晶振电路
首先需要把板子上的8MHz晶振和两个负载电容去掉。这里要注意的是,有些设计可能还会用到32.768kHz的RTC晶振,如果项目用不到RTC功能,这个也可以一并移除。
我遇到过一些工程师担心直接去掉晶振会不会影响芯片工作。其实STM32在设计时就考虑到了这种情况,内部HSI时钟是默认启用的,即使没有外部晶振,芯片也能正常工作,只是时钟精度会有所下降。
2.2 检查复位电路
移除晶振后,建议检查一下复位电路。因为时钟源的改变可能会影响上电时序。标准的10k电阻加0.1uF电容的复位电路在大多数情况下都能正常工作,但如果发现系统启动不稳定,可以适当调整复位时间常数。
3. 软件配置关键步骤
3.1 修改SystemInit函数
系统时钟的配置主要在system_stm32f10x.c文件中的SystemInit函数里完成。我们需要重写这个函数,把原来基于外部晶振的配置改为使用内部HSI。
void SystemInit(void) { // 设置Flash延时 FLASH->ACR |= FLASH_ACR_PRFTBE; FLASH->ACR &= ~FLASH_ACR_LATENCY; FLASH->ACR |= FLASH_ACR_LATENCY_2; while((FLASH->ACR & FLASH_ACR_LATENCY) != FLASH_ACR_LATENCY_2); // 设置HSI校正值 RCC_AdjustHSICalibrationValue(16); // 开启HSI RCC->CR |= RCC_CR_HSION; // 选择HSI作为PLL时钟源,并2分频 RCC->CFGR |= RCC_CFGR_PLLSRC_HSI_Div2; // 设置PLL倍频为16倍 RCC->CFGR |= RCC_CFGR_PLLMULL16; // 使能PLL RCC->CR |= RCC_CR_PLLON; while((RCC->CR & RCC_CR_PLLRDY) == 0); // 选择PLL作为系统时钟源 RCC->CFGR &= ~RCC_CFGR_SW; RCC->CFGR |= RCC_CFGR_SW_PLL; while((RCC->CFGR & RCC_CFGR_SWS) != RCC_CFGR_SWS_PLL); // 设置AHB、APB1、APB2时钟分频 RCC->CFGR |= RCC_CFGR_HPRE_DIV1; // AHB = SYSCLK = 64MHz RCC->CFGR |= RCC_CFGR_PPRE2_DIV1; // APB2 = 64MHz RCC->CFGR |= RCC_CFGR_PPRE1_DIV2; // APB1 = 32MHz }3.2 时钟树分析
理解时钟树对正确配置系统时钟非常重要。使用HSI时,时钟路径是这样的:
- 内部HSI提供8MHz时钟
- 这个时钟经过2分频变成4MHz
- 然后通过PLL倍频16倍得到64MHz
- 最后作为系统时钟SYSCLK输出
如果不经过2分频直接使用HSI,系统时钟就只有8MHz。经过这样的配置后,各总线时钟如下:
- AHB总线:64MHz
- APB2总线:64MHz
- APB1总线:32MHz(因为APB1最大只能到36MHz)
4. 性能优化与问题排查
4.1 延时函数调整
时钟频率改变后,原来的延时函数需要重新校准。比如原来的1us延时函数是基于72MHz时钟的,现在要改为64MHz:
void Delay_us(uint32_t xus) { SysTick->LOAD = 64 * xus; // 64MHz时钟下,1us需要计数64次 SysTick->VAL = 0x00; SysTick->CTRL = 0x00000005; while(!(SysTick->CTRL & 0x00010000)); SysTick->CTRL = 0x00000004; }4.2 外设时钟检查
所有使用时钟的外设都需要检查是否在允许的频率范围内。特别是:
- USART波特率可能需要重新计算
- SPI时钟频率可能需要调整
- PWM频率会发生变化
我曾经遇到过因为没调整USART波特率导致通信失败的问题。后来发现虽然误差在可接受范围内,但长时间通信还是会出现错位。
4.3 功耗测试
使用内部时钟后,整体功耗会比使用外部晶振时略高,因为HSI的功耗比外部晶振要大一些。在电池供电的应用中,这点需要特别注意。可以通过以下方式优化:
- 合理使用睡眠模式
- 动态调整时钟频率
- 关闭不使用的外设时钟
5. 稳定性测试与生产建议
5.1 温度测试
内部RC振荡器的频率会随温度变化而漂移,建议在产品的整个工作温度范围内测试时钟稳定性。我的经验是在-20°C到70°C范围内,频率变化大约在±1%以内,对于大多数应用来说已经足够。
如果对时钟精度要求特别高,可以考虑:
- 使用HSI时钟校准功能
- 在软件中加入时钟补偿算法
- 在关键部分使用硬件定时器补偿
5.2 批量生产一致性
在大批量生产时,不同芯片之间的HSI时钟可能会有微小差异。建议:
- 在生产测试环节加入时钟校准
- 保留一定的软件调整余量
- 对时序要求严格的部分使用相对时间而非绝对时间
我曾经负责过一个量产项目,就因为没考虑不同芯片间HSI的差异,导致部分产品出现时序问题。后来在产测中加入时钟校准步骤后问题就解决了。
5.3 软件兼容性
如果项目中有使用到实时操作系统(RTOS),需要检查任务调度器是否受时钟变化影响。大多数RTOS都会使用SysTick定时器,时钟频率改变后需要重新配置时间片长度。
在FreeRTOS中,需要修改configTICK_RATE_HZ对应的时钟配置。我曾经就遇到过因为没改这个配置导致任务调度出问题的案例。