1. 为什么选择内部HSI时钟?
在开发STM32F103C8T6项目时,很多工程师会习惯性地选择外部晶振作为时钟源。但你可能不知道,这颗芯片内置的8MHz HSI(高速内部)RC振荡器,经过适当配置完全可以满足大多数应用场景的需求。我最近完成的一个工业传感器项目,就成功用HSI替代了外部晶振,单件成本直接降低了15%。
内部时钟最大的优势当然是节省硬件成本。省去外部晶振和两个负载电容,不仅减少了BOM成本,还简化了PCB布局。特别是在空间受限的场合,每平方毫米都很珍贵。不过要注意,HSI的精度确实不如外部晶振(典型值±1%,全温度范围±3%),但对于不需要高精度时钟的场合(如普通控制、数据采集等)完全够用。
2. HSI时钟配置全流程
2.1 准备工作
首先打开STM32CubeIDE,新建工程时记得选择"HSI"作为时钟源。我建议直接修改system_stm32f10x.c文件中的SystemInit()函数,而不是在main函数里配置时钟。这样做有个好处:所有时钟配置都在启动阶段完成,避免主程序中出现意外修改。
关键点来了:必须按照正确顺序配置!我踩过的坑是曾经忘记设置Flash延迟,结果程序跑起来各种异常。正确的顺序应该是:
- 设置Flash等待周期
- 调整HSI校准值
- 使能HSI时钟
- 配置PLL参数
- 切换系统时钟到PLL
2.2 代码实现详解
下面是我在实际项目中验证过的配置代码,关键位置都加了中文注释:
void SystemInit(void) { // Flash延迟设置(必须!) FLASH->ACR |= FLASH_ACR_PRFTBE; // 使能预取缓冲区 FLASH->ACR &= ~FLASH_ACR_LATENCY; // 清除原有设置 FLASH->ACR |= FLASH_ACR_LATENCY_2; // 2个等待周期(36MHz时需要) // 等待设置生效 while((FLASH->ACR & FLASH_ACR_LATENCY) != FLASH_ACR_LATENCY_2); // 调整HSI校准值(重要!) RCC->CR &= ~RCC_CR_HSITRIM; // 清除原有校准值 RCC->CR |= (16 << 3); // 典型校准值为16 // 使能HSI RCC->CR |= RCC_CR_HSION; while(!(RCC->CR & RCC_CR_HSIRDY)); // 等待HSI就绪 // 配置PLL:HSI/2作为输入,9倍频输出36MHz RCC->CFGR &= ~(RCC_CFGR_PLLSRC | RCC_CFGR_PLLMULL); RCC->CFGR |= RCC_CFGR_PLLSRC_HSI_Div2 | RCC_CFGR_PLLMULL9; // 使能PLL RCC->CR |= RCC_CR_PLLON; while(!(RCC->CR & RCC_CR_PLLRDY)); // 等待PLL锁定 // 切换系统时钟到PLL RCC->CFGR &= ~RCC_CFGR_SW; RCC->CFGR |= RCC_CFGR_SW_PLL; while((RCC->CFGR & RCC_CFGR_SWS) != RCC_CFGR_SWS_PLL); // 等待切换完成 // 设置AHB/APB分频器 RCC->CFGR |= RCC_CFGR_HPRE_DIV1; // AHB不分频 RCC->CFGR |= RCC_CFGR_PPRE2_DIV1; // APB2不分频 RCC->CFGR |= RCC_CFGR_PPRE1_DIV2; // APB1二分频(最大36MHz) }3. 性能验证与调优
3.1 时钟精度测试
配置完成后,怎么验证时钟是否准确?我的土方法是利用定时器测量LED闪烁频率。具体操作:
- 配置TIM2定时器,1ms中断一次
- 在中断服务程序里翻转LED
- 用逻辑分析仪测量LED引脚波形
如果测得周期确实是2ms(1ms开1ms关),说明时钟配置正确。我实测下来,HSI在室温下的误差大约在±1.5%以内,完全满足普通应用需求。
注意:如果发现定时不准,可以尝试调整HSI校准值。校准值范围0-31,每步约0.5%的调整量。
3.2 功耗对比
使用HSI还有个意外收获:功耗更低!我实测在36MHz运行时:
- 外部8MHz晶振:核心电流约12mA
- 内部HSI:核心电流约10mA
这2mA的差距对电池供电设备来说相当可观。原理是HSI不需要驱动外部晶振的负载电容,自然更省电。
4. 常见问题排查
4.1 程序运行异常
如果配置后程序跑飞,大概率是Flash等待周期没设对。记住这个对应关系:
- 0等待周期:0-24MHz
- 1等待周期:24-48MHz
- 2等待周期:48-72MHz
我们配置的36MHz应该用2个等待周期。虽然理论值24-48MHz用1个就行,但实测发现某些批次的STM32F103在36MHz时用1个等待周期会不稳定。
4.2 外设工作不正常
遇到UART波特率不准或者SPI通信出错时,先检查APB时钟:
- APB1最大频率36MHz(所以需要二分频)
- APB2最大频率72MHz
曾经有个同事把APB1设成了不分频(72MHz),结果I2C完全不能工作。正确的分频设置见上面代码的最后三行。
5. 进阶技巧:动态切换时钟源
对于需要低功耗的项目,还可以动态切换时钟源。比如在运行模式和低功耗模式间切换:
// 切换到HSI(低功耗) RCC->CFGR &= ~RCC_CFGR_SW; RCC->CFGR |= RCC_CFGR_SW_HSI; while((RCC->CFGR & RCC_CFGR_SWS) != RCC_CFGR_SWS_HSI); // 关闭PLL节省功耗 RCC->CR &= ~RCC_CR_PLLON;需要高性能时再切回PLL。这种技术在电池供电的设备中特别有用,我做的无线传感器节点就是靠这招把待机电流降到了微安级。