ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

没有32kHz晶振怎么办?嵌入式RTC时钟源切换与低功耗校准实战

没有32kHz晶振怎么办?嵌入式RTC时钟源切换与低功耗校准实战 有一回项目组拿来一块定制板MCU、Flash、传感器、射频前端都齐了唯独PCB上RTC相关的位置全部空着BOM里根本没有32.768kHz晶振这玩意。这板子的目标产品是个低功耗数据采集终端平时基本都在睡大觉醒来要能报出准的时间还要按预设周期把采集到的数据发出去。做软件的人一看就明白这不是硬件漏焊是成本控制把低速晶振砍了。问题直接抛给我没有32kHz Xtal软件到底怎么处理时间、唤醒、休眠这一整套逻辑这篇文章就把我在这个定制板卡软件开发过程中的完整思路、方案选型、代码改法和踩坑记录整理出来。适合正在做定制板卡、低功耗物联网设备或者手里刚好拿了一块没有低速晶振的板子、不知道从哪下手的固件开发工程师。内容不挑具体平台但示例会以STM32为主因为它的HAL库和CubeMX工具大家用得最多其他平台思路完全一样。1. 先说清楚32kHz晶振到底在板子上负责什么很多人觉得32kHz晶振不就是给RTC用的吗少了它顶多时间不准。实际上一颗32.768kHz晶振在系统里的角色比想象中多得多。要搞清楚没有它为什么难受先得知道它到底服务哪些模块。32.768kHz这个频率不是随便定的。2的15次方正好等于32768所以32.768kHz经过15级二进制分频正好得到1Hz的秒脉冲。这个特性让它在数字电路中特别好用RTC的秒计数、日历切换几乎都是基于这个分频链设计的。外部低速晶振在芯片手册里一般叫LSELow Speed External特点是精度高、温漂小、功耗低常年稳定在32.768kHz附近所以被当作RTC和低功耗定时器的基准。1.1 低速时钟的三个主力用途第一个用途是RTC日历。设备需要记录当前时间、日期或者计算两个时间点之间的差值就需要一个稳定的秒级基准。LSE精准稳定所以绝大多数带RTC的MCU默认都用它。第二个用途是低功耗唤醒定时器。系统进入sleep或者deep-sleep之后主时钟通常会停掉但需要一个计时器在指定时间把系统拉起来。这个定时器的时钟源如果是外部晶振唤醒时间点就很准如果用的是内部RC误差会直接累积。第三个用途更隐蔽是无线协议栈的sleep clock。BLE、Zigbee这些协议在收发完数据后射频部分也进入睡眠但协议栈需要一个32kHz左右的时钟来维持连接事件调度、定时唤醒、链路层时间补偿。比如Nordic的nRF52系列如果没有外部32.768kHz晶振协议栈会强制使用内部LPO连接间隔、扫描窗口这些参数的时间精度就会受影响严重时甚至会增大丢包概率。1.2 为什么厂商敢把32kHz晶振省掉省掉一颗32.768kHz晶振对硬件成本的影响并不是大家想象的“就几毛钱”。在大批量产品里一颗晶振加上两颗负载电容、PCB布局空间、焊接工序综合成本累加起来相当可观。而且晶振属于有源器件里比较“娇气”的震动、温度冲击都可能引起停振或者频率跳变砍掉它还能少一个可靠性隐患。那是不是硬件工程师不懂RTC的重要性不是的。关键是很多MCU内部都集成了低速RC振荡器芯片厂商在手册里给出的典型值就是32kHz左右精度虽然比不上外部晶振但省掉了所有外部元件。很多产品对时间精度的要求其实没那么苛刻比如一个数据采集器只要知道“距离上次上报过去了多久”日误差几十秒完全可以接受。再加上现在物联网设备普遍会联网同步时间走时不准可以被周期校准拉回来这就让硬件设计师有了砍掉晶振的底气。但对软件工程师来说板子上没有32kHz晶振意味着所有默认设计都被推翻了。你不能再照着CubeMX默认生成的代码跑也不能假设RTC的时钟源是完美稳定的。你的工作目标就变成在没有外部低速晶振的前提下用软件手段让系统的时间、唤醒、通信调度尽量接近“有晶振”时的表现并且把精度损失控制在业务可接受范围内。2. 没有32kHz晶振能用什么替补时钟源软件要改首先得知道能用什么替代方案。这一节我把实际项目中可行的替补方案逐个拆开讲包括硬指标、优缺点、以及选型时容易忽略的坑。2.1 内部低速RCLSI/LPO的硬指标内部低速RC是大多数芯片自带的廉价替代方案。不同厂商叫法不一样STM32叫LSINordic叫LPONXP的部分型号叫LPOsc或FIRC但本质都一样一个集成在MCU内部的RC振荡器标称频率在32kHz左右。LSI的硬指标必须拿到手册里逐项看尤其这三个参数频率范围不同芯片差异很大。有的LSI在工厂校准后能做到32.768kHz±1%以内有的没校准的能偏到±5%甚至全温度范围±10%。拿到芯片后第一件事是把具体型号的datasheet找出来确认室温频率准确度不要想当然。温漂系数RC振荡器的频率和温度强相关。一本正经的datasheet会给出全温度范围内的频率变化曲线很多低成本芯片只在典型值里给个范围那就只能自己实测。启动时间内部RC起振一般很快微秒到毫秒级相比外部晶振的几百毫秒到秒级是巨大优势。但要注意有些芯片的LSI启动稳定后频率还会缓慢漂移RTC刚上电的几秒钟走时不稳定。内部RC的好处是零成本、无需外部元件、起振快。坏处是精度差、温漂大、每颗芯片个体差异明显。如果你的产品放在室内恒温环境日误差可能只有几秒如果放在户外冬夏温差大的地方一天漂几十秒甚至上百秒都很正常。2.2 从外部高速晶振分频这种野路子这个思路是既然芯片内部有高速晶振比如8MHz、16MHz的HSE能不能从它分频出32kHz信号供给RTC和低速外设理论上可以但实际用起来问题很多。首先系统睡眠时通常会把HSE关掉否则待机电流会多出几百微安甚至毫安级这对低功耗设备是灾难性的。如果你希望RTC在休眠时继续走HSE必须保持运行这直接和低功耗目标冲突。其次从HSE分频出来的低频信号相位噪声和抖动可能比外部晶振差对RTC精度并没有明显改善。最后部分芯片的RTC时钟源选项里根本没有“HSE分频”这一项你想用也用不了。不过有一种情况适用系统本身在整个生命周期里从不睡眠高速晶振一直开着只是板子恰好没有32kHz晶振。这种场景下有一些芯片支持把HSE分频输出给RTC软件上只需要在RTC时钟源选择时配置为HSE分频路径。但说实话这种产品形态不太常见我见过的大多数没有32kHz晶振的板子恰恰是低功耗物联网设备所以这个方案只能算“在某些特定条件下可行”。2.3 换一颗带晶振的RTC芯片值不值如果产品对时间精度要求非常高比如一天误差不能超过1秒或者设备长期离线不能经常校准那软件再折腾也弥补不了内部RC的物理局限。这时候最靠谱的方案是外挂一颗带集成晶振的RTC芯片。市面上常见的型号有RX8025T、PCF85063A、DS3231等它们内部已经集成好了32.768kHz晶振精度可以做到几个ppm甚至秒级年误差。软件上只需要通过I2C读写芯片内部的寄存器读取和设置时间即可。这个方案的代价是BOM成本增加、PCB面积变多、代码多一套I2C驱动。我当时的项目原本计划外挂一颗RX8025T结果采购说交期要六周产品等不起最后只能走内部RC软件校准路线。所以这个决定一定要在软件开写之前和硬件、采购一起确认清楚不然代码写到一半再换方案工作量翻倍。3. 软件上怎么把这些方案落到实处的确定没有外部32kHz晶振、又不能临时换RTC芯片之后剩下能做的就是靠软件硬扛了。这一节重点说STM32平台上的实操细节包括CubeMX配置、HAL库代码改动、低功耗唤醒定时器的适配以及校准接口的设计。3.1 底层配置以STM32 HAL为例切换RTC时钟源STM32的CubeMX默认配置几乎都是把LSE当作RTC时钟源如果你的板子没有LSE晶振默认生成的代码会在RTC初始化时直接卡死或者RTC不走。正确的做法是手动把时钟源切到LSI。CubeMX图形界面里在RTC配置页把Active Clock Source从LSE改成LSI然后再在时钟树配置里确认LSI被使能。生成的代码会变成这样但实际工程里我更喜欢直接手写初始化代码因为你还要做校准、测量频率这些额外操作完全依赖CubeMX反而碍事RCC_OscInitTypeDef RCC_OscInitStruct {0}; RCC_PeriphCLKInitTypeDef PeriphClkInitStruct {0}; __HAL_RCC_RTC_ENABLE(); RCC_OscInitStruct.OscillatorType RCC_OSCILLATORTYPE_LSI; RCC_OscInitStruct.LSIState RCC_LSI_ON; RCC_OscInitStruct.PLL.PLLState RCC_PLL_NONE; HAL_RCC_OscConfig(RCC_OscInitStruct); PeriphClkInitStruct.PeriphClockSelection RCC_PERIPHCLK_RTC; PeriphClkInitStruct.RTCClockSelection RCC_RTCCLKSOURCE_LSI; HAL_RCCEx_PeriphCLKConfig(PeriphClkInitStruct); hrtc.Instance RTC; hrtc.Init.HourFormat RTC_HOURFORMAT_24; hrtc.Init.AsynchPrediv 127; hrtc.Init.SynchPrediv 255; HAL_RTC_Init(hrtc);这段代码里最要命的是分频系数的选择。STM32 HAL默认的AsynchPrediv和SynchPrediv组合是基于“LSI正好等于32.768kHz”这个假设来配的。如果LSI实际频率是33.5kHz你按默认配置算出来的秒就是33.5kHz/(1271)/(2551)得到的周期走一天下来会快出一个不小的数字。所以切到LSI之后必须测量该芯片的实际LSI频率再倒推合适的预分频值。测量方法我后面会详细写这里先说思路通过定时器或者外部引脚把LSI输出捕捉出来和已知的高精度时基对比算出实际频率然后配置AsynchPrediv和SynchPrediv让分频后尽可能接近1Hz。3.2 低功耗唤醒定时器同样要跟着改RTC只是低速时钟的一个使用者低功耗唤醒定时器比如STM32的RTC WakeUp Timer、LPTIM或者Nordic的RTC实例同样依赖这个时基。改了RTC时钟源之后唤醒周期会被误差放大。举个实际例子你设置唤醒周期为10秒LSI实际频率偏高2%那真实唤醒间隔约10.2秒。单次看不出来一天下来累积误差就是1700多秒近半个小时。低功耗设备如果依赖这个唤醒定时器去决定“什么时候联网发送数据”那这种误差会直接导致通信时间点错乱。解决思路是在应用层别直接拿RTC计数值当作真实时间而是引入一个“逻辑时间变量”每次唤醒中断时按当前校准后的秒长累加。比如你测出当前LSI秒长是1.003秒那每次RTC中断逻辑时间就加1003毫秒就算中断唤醒间隔有偏差应用层看到的逻辑时间也是可控的。另外如果用RTOS并且开了tickless模式要注意低功耗定时器的时钟源也必须跟着切换。我遇到过一个问题任务调度的时基在休眠时用的是RTC唤醒中断唤醒后恢复Systick结果LSI漂移导致tickless计算出来的睡眠时间不准代码里出现“任务被唤醒后时间戳倒流”的诡异情况。排查了很久最后发现是低功耗定时器的补偿值没有根据实际时钟频率修正。3.3 预留校准接口把精度补回来内部RC不是不能用但必须把校准能力内置到固件架构里。我在驱动层会预留一个时间校准接口不管最后外部参考源是什么上层都能调用它来修正RTC误差。接口可以设计成这样一个固定函数// 输入当前偏差单位ppm // 实现根据芯片具体寄存器调整RTC校准值 void rtc_apply_correction(int32_t ppm);内部实现一般有两种方式。一种是通过RTC的校准寄存器比如STM32L4系列RTC有CALR寄存器可以按0.954ppm步进进行调整另一种更通用粗暴的方式是直接改写RTC的当前计数比如发现本地时间快了5秒就直接把RTC的时间往回拨5秒。前者适合细调后者适合粗调。我的建议是两层都做粗调用寄存器校准细调用逻辑时间补偿。这样上层业务只需要说“我的时间偏差了多少ppm”底层驱动负责把修正做到计时链路里。把校准接口放在驱动层是为了以后换芯片方便不用动业务代码。4. 精度校准没晶振之后的走时校正方案没晶振不代表时间精度就一定烂到家。只要系统有办法从外部获得准确时间或者能在产线做一次静态校准走时误差就可以被控制在业务可接受的范围内。这一节把我在项目中用过的几种校准方案整理出来重点讲计算方法和注意事项。4.1 用外部精确源做周期校准这个方案适用于联网设备。设备每次联网后从服务器获取权威时间与本地RTC时间做差得到误差值然后换算成ppm写入校准寄存器或者直接调整RTC计数值。计算公式很简单ppm (本地时间 - 参考时间) / 经过时间 * 1e6关键在于经过时间的选取。如果只跑了几秒钟就测量偏差通信耗时、任务调度延迟、代码执行时间都会引入噪声算出来的ppm根本不靠谱。我的经验是至少观察10分钟以上再做一次完整计算如果设备联网频率很低可以连续观察几次再收敛。还有一个容易被忽略的点校准不能太激进。比如单次测量显示系统快了2000ppm直接一次性调到位下次测量又显示慢300ppm来回调整会让时间忽快忽慢用户体验很差。建议加一个低通滤波或者限制每次调整的最大步进比如每次最多只校准100ppm让时间平滑收敛到正确值。4.2 温度补偿和分段校准思路LSI的温漂比晶振大得多。如果你的设备在室内温度变化不大做一次静态校准就差不多了。但如果设备要放在户外、车载、冷链运输这种温度剧烈变化的环境静态校准根本不够需要动态补偿。动态补偿的做法是在设备运行时定期读取MCU内部温度传感器根据温度-频率偏移曲线实时调整RTC的校准值。这个方案的关键是曲线数据。你可以从芯片手册里找典型的LSI温漂曲线也可以自己做一批板子在温箱里标定。标定工作量不小但一旦有了曲线代码实现就很直接了每隔一段时间比如30秒读一次内部温度传感器查表得到当前温度对应的频率偏移ppm调用rtc_apply_correction写入最新校准值。如果你的产品没有时间精力做温补但又有离线计时需求我建议老老实实加上外置RTC芯片。软件再努力也改变不了物理器件本身的温漂特性硬要凑合最后验收时挨骂的还是自己。4.3 同步策略让RTC在关键节点对齐除了平滑校准有时候也需要“硬同步”。比如设备长期离线后刚开机或者从飞行模式恢复手里没有任何参考时间本地RTC可能已经偏了好几分钟。这种场景下一旦拿到外部准确时间直接设置RTC时间比慢慢校准高效得多。策略选择上有讲究。如果业务允许时间跳变比如设备只用于记录传感器数据时间戳偶尔跳一下没关系那就直接HAL_RTC_SetTime最简单可靠。如果业务要求时间单调递增比如用于计费、前后端对账那就要做平滑调整每次调整上限比如1秒多个周期慢慢拉回。我在一个项目中做过折中方案每次联网后如果偏差在3秒以内用校准寄存器慢慢纠正如果偏差超过3秒直接硬设置到服务器时间。这个阈值可以根据实际业务容忍度调整但有个原则能平滑纠正就尽量平滑只有偏差大到不能接受时才允许跳变。5. 实操记录我在这个项目里踩过的坑理论说完了讲点实际项目里的血泪经验。没有32kHz晶振这事情表面上只是时钟源切换实际开发中会冒出一堆奇奇怪怪的问题。我把几个印象最深的记录下来希望能帮你少走弯路。5.1 睡眠电流测出来偏大的真凶项目初期我们以为砍掉32kHz晶振后待机功耗肯定更低结果第一版样机实测睡眠电流比预期高了整整60多微安。60微安对一次充电用半年的设备来说相当于白白浪费了很大一部分电池容量根本没法交差。排查的时候先怀疑是外部器件漏电量了一圈都没问题。最后把MCU的每个外设时钟挨个关掉发现LSI的驱动配置有问题。很多STM32芯片的LSI可以配置成“输出模式”把LSI信号引到某个引脚做外部时钟源。CubeMX默认生成的代码在某些系列上会把LSI输出打开就算引脚悬空内部驱动电路也在持续耗电。关闭LSI输出功能后电流立刻降下来了。这个坑提醒我切到LSI后要检查LSI本身是不是被配置成了输出模式以及所有用到LSI的外设RTC、LPTIM、IWDG是否真的都挂在这个时钟树下不要因为省了一颗晶振反而引入额外功耗。5.2 日历芯片和内部RC起振打架项目中期产品经理又提出一个需求希望设备能多一个硬RTC备份时间防止MCU复位后时间丢失。硬件工程师在改版时预留了外置RTC芯片接口代码里要同时驱动MCU内部RTC和外置RTC芯片。问题出在低功耗唤醒后的时序上。外置RTC芯片通过I2C通信而I2C时钟来自MCU的系统时钟系统时钟又需要在唤醒后经过锁相环重新稳定。因为内部RC的启动特性不如外部晶振稳定唤醒后立刻去读外置RTC偶发出现I2C总线超时日志里看到一堆NACK错误。解决办法是在唤醒处理函数里加一个延迟等系统时钟稳定后再去访问外置RTC。具体延迟时间需要实测我这边定在1ms到2ms之间就能稳定通过。如果你们用的芯片起振时间更长这个数字要适当加大。另外外置RTC的初始化代码尽量别放在中断里挪到主循环或单独的任务里执行降低时序冲突的概率。5.3 批量产线上的校准数据怎么烧进去静态校准要求每块板子在出厂前测出LSI的实际频率偏差再把校正值写进去。这需要产线测试流程里多一步最初生产同事很抵触觉得增加了节拍时间。我们设计了一个很轻量的软校准流程。设备烧录固件后进入产测模式上位机发送一个CAL_MEAS duration_ms指令设备收到后开始用RTC计时持续时间到后把RTC计数的变化值通过串口返回。上位机用自己的高精度时钟记录实际耗时两者对比就能算出当前板子的ppm偏差。上位机把偏差值写入Flash指定区域固件启动时读取并应用。整个流程在产线上多花不到3秒但产品走时精度从日误差几十秒提升到了几秒以内效果非常明显。如果你的产品有联网功能产线校准可以和无线校准协同进行产线写一个初值设备首次联网后自动修正后续就能维持一个很好的精度水平。6. 常见问题速查表我把整个开发过程中最常遇到的问题整理成一张速查表方便你遇到类似情况时快速定位。这张表不是万能的但能覆盖大多数没有32kHz晶振的定制板卡开发场景。问题现象可能原因处理建议RTC不走读出来永远是0时钟源没切到LSICubeMX默认还在用LSE检查RTC时钟源选择确保LSI使能且稳定RTC走时快/慢得很夸张分频系数基于标称频率配置没按实际LSI频率调整测量LSI实际频率倒推AsynchPrediv和SynchPrediv睡眠唤醒时间点偏差大到影响业务唤醒定时器依赖LSI误差随周期累积应用层用逻辑时间累加或者做周期校准睡眠电流比预想中高LSI输出功能开启或无关外设时钟没有关闭关闭LSI的外部输出引脚逐个检查时钟门控唤醒后I2C读外置RTC失败系统时钟未完全稳定I2C时序冲突唤醒后延迟1ms以上再访问或把读取放到主循环批量产品走时一致性差每颗芯片LSI个体差异大产线软校准写入每台设备的独立偏差值联网设备校准后时间反复横跳单次测量噪声大校准过于激进加滤波限制单次校准步进拉长校准观察窗口RTOS任务时间戳出现倒流tickless模式低估或高估了睡眠时长根据LSI误差修正tickless的补偿参数每个项目情况不同遇到问题最好先看芯片手册里“时钟控制器”和“RTC”这两个章节很多答案其实都在手册里只是平时不细看。没有32kHz晶振的开发挑战说到底不是技术上的绝路而是要在心里始终绷着一根弦系统的时间基准已经变了所有依赖时间的逻辑都必须重新过一遍。这批项目做下来我的体会是没有32kHz晶振的板子并不可怕可怕的是软件团队始终按“有晶振”的思路去写代码。把时钟源切换、校准接口、低功耗唤醒策略这些条件从一开始就作为设计输入后面会省掉很多debug时间。如果你现在正拿着一块没有低速晶振的板子发愁建议先从确认芯片的LSI规格和可校准能力入手再决定是直接使用内部RC、还是外挂一颗带晶振的RTC芯片。两条路都通关键是别让软件在错误的时钟假设上裸奔。
返回列表