最近在做工业现场的低功耗采集节点,手里板子换了好几轮,最后停在CH32L103上。这颗基于RISC-V内核的工业MCU,主打低功耗和高集成度,官方定位很清楚:给电池供电、需要混合信号处理和多种通信接口的现场设备当主控。用了一个多月,把睡眠、停止、待机三种模式都实际调了一遍,也顺手把定时器、ADC、运放、UART、CAN这些外设捋了个遍。这篇直接把选型思路、功耗调优手法、外设配置过程和踩坑记录写出来,给正在评估这芯片或者准备从传统Cortex-M平台切过来的朋友做个参考。
1. 选型逻辑:CH32L103适合接什么活
1.1 芯片底子:内核、主频、存储与封装
CH32L103用的是沁恒自家的青稞RISC-V内核,最高主频标到144MHz,单论算力在低功耗MCU里算偏上的。存储按型号后缀分档,小的从64KB Flash起步,大的能到128KB,SRAM也是分了几个档位,做节点类应用不用太抠内存,但要跑嵌入式Web服务器这类大程序还是要精打细算。
这芯片的看家本领是集成度。内置12位ADC、12位DAC,还带运放和比较器,模拟链路基本可以自己搭完;通信接口方面,UART、SPI、I2C这些常规的不说,CAN 2.0B、USB2.0全速设备接口、10M以太网MAC也有,部分型号还能驱动段码LCD。一颗芯片把混合信号和通信都集齐了,BOM能省掉不少外围器件。工业级温度范围也是有保障的,封装选择多,QFN、LQFP都有,小节点可以选紧凑封装省面积。
开发环境用的是MounRiver Studio,官网直接下载,SDK也是开放的,官方EVT例程覆盖了大部分外设。后面章节涉及到的代码和API,都以你手上最新版官方SDK为准。
1.2 三类工业场景与选型对应
结合工业应用的实际需求,CH32L103比较典型的落地场景有三类:
| 场景 | 核心需求 | 用到的外设资源 | 关注点 |
|---|---|---|---|
| 电池供电传感终端 | 大部分时间睡,醒来采数上报 | 停机模式+RTC唤醒+ADC+UART | 待机电流、唤醒时间 |
| 总线节点设备 | 长期在线,稳定收发,掉电检测 | CAN/UART+SPI+定时器+PVD | 通信可靠性和功耗平衡 |
| 电机辅助控制板 | PWM输出,电流采样,快速保护 | 高级定时器+ADC+比较器+运放 | 死区时间、故障刹车路径 |
电池供电的温度、压力、振动变送器是CH32L103的主场。这类设备一般要求整机待机功耗在微安级,醒过来要快速完成采集、信号处理、通讯,再睡回去。CAN或RS485总线节点则更看重通信接口的稳定性和掉电检测的可靠性。电机控制板往往需要一路或几路互补PWM,同时用ADC采样相电流,用比较器做硬件级过流保护。
选型时先想清楚设备属于哪类工况,再反过来挑具体型号。比如只做传感终端,选Flash最小的档位就够,把钱省下来;如果还要带以太网升级接口,就得上带MAC的型号并预留PHY的位置。
1.3 同价位竞品怎么取舍
拿CH32L103和常见的STM32L0/L4系列、GD32L系列比,差异点在集成度上。对手芯片也都有低功耗模式,但要同时拿到运放、比较器、DAC、USB、CAN、以太网MAC这么多外设在一颗芯片上,选择面就很窄了,往往要外挂芯片凑功能。
从GD32或者STM32那套Cortex-M平台切过来的朋友,最需要适应的不是性能,而是外设库的写法。RISC-V的寄存器映射和中断控制器跟ARM不同,旧代码里直接搬肯定不行,外设库风格也有变化。好处是芯片可玩性高、价格友好,国内资料和官方支持这几年也跟上了。
初学者判断一颗新MCU能不能落地,我一般看三点:数据手册有没有参考电路、官方例程覆盖了多少外设、遇到问题能在哪里找到答案。CH32L103这三点都过关,但小坑也还在后面等着。
2. 低功耗设计:从纸面参数到实测功耗
2.1 三种低功耗模式的定位与唤醒路径
CH32L103的低功耗模式大致分三档:睡眠、停止、待机。不同模式的电流级别、可保留资源和唤醒延迟差的不是一个量级,别指望一个停止模式打天下。
睡眠模式是浅睡,CPU的时钟停了,但外设时钟还能继续跑,任意中断都能唤醒,唤醒延迟很短。适合那种“处理器歇着、外设盯着”的场景,比如UART接收、定时器计数时让CPU先睡。
停止模式是真正的深睡,所有时钟都停了,但SRAM内容保持,RTC也在跑。唤醒源集中在RTC闹钟、外部引脚和部分可配置的唤醒事件。工业节点的设计重点就在这里,前面讲低功耗时的“睡眠”绝大多数指的是这个模式。
待机模式是功耗最低的一档,基本只剩下备份域供电,唤醒后程序相当于复位重跑,SRAM里的数据能不能保住要查手册具体定义。适合那些醒来不需要恢复现场数据的设备,设计上要明确把“待机唤醒”当成一次上电启动来对待。
三种模式对比我一般这样记录:
| 模式 | 电流量级 | CPU状态 | 外设状态 | 唤醒源 | 适用场景 |
|---|---|---|---|---|---|
| 睡眠 | 微安到毫安之间 | 停止运行 | 时钟继续,中断可唤醒 | 任意中断 | 快速暂停、等待事件 |
| 停止 | 微安级 | 断电 | 时钟停止,SRAM保持 | RTC、外部引脚、唤醒事件 | 间歇工作节点 |
| 待机 | 更低 | 断电 | 仅备份域保持 | RTC、复位、外部引脚 | 长周期上报设备 |
注意数据手册上的电流是芯片级“典型值”,到了真实板子上,外部电路的漏电往往比芯片自身还大,这就引出了下一节要说的核心问题。
2.2 主频、时钟与内核电压怎么配合
工业低功耗设备最常见的浪费,就是让MCU一直跑在最高主频上。CH32L103标到144MHz,但你以为的高性能在很多现场设备里根本用不到。跑多快够用就调到多快,这句话值得刻在工位上。
主频越高,内核逻辑翻转越频繁,供电电压也对应拉高,功耗是非线性上涨的。实际项目里我习惯把系统分两档跑:任务密集型操作,比如批量计算、Flash写入,才用高频档;常规的采集、判断、通信,低频档就够。很多电池供电的工业节点72MHz以下完全能完成所有工作,剩下的时间全部用来睡觉。
频率切换不是拍脑袋改两个寄存器就完事。要注意时钟切换时的稳定性,切换前关掉依赖该时钟源的外设,切换后重新校准外设波特率或定时器预分频,否则UART乱码和定时器漂移就会排队来找你。
2.3 把板级功耗做实的七个细节
芯片进入停止模式并不代表整板功耗就达标了。实测中真正让电流飙上去的往往是外围,这里列几个必查项。
GPIO浮空输入是漏电重灾区。芯片进停止模式后,悬空引脚电平不确定,引脚保护二极管会来回导通,一个引脚漏几微安不奇怪。不用的GPIO要么设为模拟输入,要么固定输出低电平,别放那不管。
外设时钟门控要做到位。不用的外设时钟不使能,用完之后也要及时关。不要依赖“反正上电默认关”,初始化时开了就一直开着的话,ADC、运放这些会在后台悄悄耗电。
模拟外设也要单独关。ADC停止转换不代表完全断电,运放和比较器在使能状态下的静态电流不可忽略。进入停止模式前,把运放设到关断或最低功耗模式,DAC输出改成高阻。
注意外部以太网PHY。芯片带10M MAC,不少朋友以为“选型时带MAC就行”,结果PHY芯片上电后一直耗电。很多PHY在无Link时会自动进入节能,但最好还是通过MDIO寄存器把不用的PHY直接关掉,或者用MOS管把PHY电源切了。
电源路径的选择直接影响整机功耗。LDO静态电流通常是微安级,对超低功耗设备可能就成了瓶颈。如果供电方案里有DC-DC,注意轻载时的效率和纹波,有的DC-DC在微安负载下效率反而低得离谱。
调试器连接状态是个隐形杀手。调试器SWD接口持续给目标芯片供电或维持逻辑电平,芯片压根进不了真正的停止模式。测功耗前把调试器断开,用干净的外部电源供电。
最后,用状态机管理电源模式切换。启动、采集、发送、睡眠、唤醒,每个状态清晰定义,避免代码到处乱睡乱醒,也能减少重复进出停止模式带来的额外开销。
2.4 功耗预算估算方法
选型阶段就要把平均功耗算出来,公式很简单:
Iavg = (Tactive ÷ Tcycle × Iactive) + (Tsleep ÷ Tcycle × Isleep)
举例:一个设备每秒唤醒一次,醒来干活2ms,运行电流3mA;其余998ms处于停止模式,电流1uA。平均电流就是0.002×3mA + 0.998×1uA,约等于7uA。这颗电池等效容量按2000mAh算,理论寿命可以到二十万小时以上,实际再乘上0.7的降额系数已经很可观。
但别忘了唤醒过程的启动开销也要计入。从停止模式恢复到可以跑代码,有几十微秒到几百微秒的时钟稳定时间,如果频繁开关外设还要额外时间。唤醒越频繁,“启动尖峰”占总功耗的比例越大。所以设计目标不是把睡眠电流压到最低,而是把“工作+启动+睡眠”的总周期时间调到最优。
3. 外设配置:工业场景真正在用的那几项
3.1 高级定时器:互补PWM与死区配置
无刷电机、半桥驱动这类电力电子控制,必须用高级定时器的互补PWM输出,上下管驱动波形不能同时导通,否则就是炸管。CH32L103的高级定时器支持互补通道和死区插入,这是直通保护的基础。
配置上先设好时基:预分频决定计数时钟,自动重载值决定PWM频率。然后配置输出比较通道,设置占空比对应的比较值。互补通道不需要单独配置,使能主通道后把互补输出打开,死区时间由专门寄存器控制。
死区时间不是拍脑袋定的,要结合功率管的开关参数来算。比如IGBT关断延迟200ns、开通延迟100ns,死区至少得大于两者差值余量,工程上一般取500ns到2us,留足安全裕度。死区设太小容易直通,设太大又增加损耗和控制失真。
另一个关键是刹车输入。电机过流、过压这些故障信号必须走硬件链路直接切断PWM输出,不能等软件中断去处理。把比较器输出或外部故障信号接到刹车引脚,配置PWM输出强制进入安全电平。这里有个容易踩的坑:刹车电平极性搞反,上电就是刹车状态,PWM死活不出来,先查这一点能省至少半天排查时间。
代码示意:
/* 20kHz PWM,互补输出,死区500ns 的示意配置 */ TIM_TimeBaseInitStructure.TIM_Prescaler = 72 - 1; TIM_TimeBaseInitStructure.TIM_Period = 100 - 1; TIM_TimeBaseInit(TIM1, &TIM_TimeBaseInitStructure); TIM_OCInitStructure.TIM_OCMode = TIM_OCMode_PWM1; TIM_OCInitStructure.TIM_OutputState = TIM_OutputState_Enable; TIM_OCInitStructure.TIM_OutputNState = TIM_OutputNState_Enable; TIM_OCInitStructure.TIM_OCPolarity = TIM_OCPolarity_High; TIM_OCInitStructure.TIM_OCNPolarity = TIM_OCNPolarity_High; /* 死区时间计算:具体寄存器位的含义以官方手册为准 */ TIM_BDTRInitStructure.TIM_DeadTime = 50; /* 约500ns,需按时钟源换算 */ TIM_BDTRInitStructure.TIM_OSSIState = TIM_OSSIState_Enable; TIM_BDTRInit(TIM1, &TIM_BDTRInitStructure);3.2 模拟链路:ADC、DAC、运放和比较器的组合
工业信号链最常见的形式是传感器输出微弱信号,经过运放放大到ADC适合的电压范围,再由MCU采集处理。CH32L103把运放做在芯片内部,很多场合能省掉外部仪表放大器和基准源。
ADC配置的关键不是“使能了没”,而是采样时间和输入阻抗是否匹配。传感器源阻抗高,采样电容还没充满就开始转换,结果必然是跳码。要按传感器内阻去设置ADC采样时间。还有一个低级错误,忘了配置ADC的参考电压,内部基准和外部基准差别可能导致采集值系统性偏移。
DAC在这个芯片里主要两用:一是产生偏置电压,配合比较器做阈值可调的快速保护;二是做波形发生,比如给压电阀输出激励信号。DAC的输出缓冲器需要注意驱动能力,直接接重负载会拉低精度。
比较器的价值在速度。电机堵转、电源过压这类故障,从发生到MCU中断响应要经过比较器翻转、中断触发、软件处理,整个链路太长。比较器直接输出到定时器刹车输入端,故障到关断PWM只要硬件几个时钟周期就完成,这套组合拳才是工业节点该有的保护逻辑。
3.3 通信外设:UART、SPI、I2C、CAN、USB与以太网的取舍
通信接口是外设配置里最乱的环节,因为每个接口都有坑。
UART是工业现场最常用的调试和通信手段。需要注意停止模式下UART是无法接收数据的,因为外设时钟停了。有的型号支持特定唤醒输入,但保险的做法是外部中断唤醒后再启动UART接收。波特率偏差在低温环境下更明显,要选好晶振规格。
SPI接外部Flash或传感器,主要是时钟极性和相位匹配。从设备手册里读CPOL和CPHA,对接不上就是全FF或全00。SPI速率不是越高越好,走线长了就要降速,否则采样窗口错位。
I2C的坑在于上拉电阻和时序。总线电容大,上拉电阻选大了上升沿变缓,通信不稳定;选小了漏电流又大。多设备挂总线还要注意地址冲突。
CAN接口适合工业总线组网。配置时除了波特率,终端电阻是必查项,两端120欧匹配,否则收发器工作在恶劣信号条件下,偶发错误会让你怀疑人生。CAN的错误管理机制也要利用起来,读写错误状态寄存器做故障诊断。
USB和10M以太网MAC属于高价值外设,但也是有代价的。USB做固件升级、参数导出很方便,运行时Debug通道也很舒服。以太网MAC本身不产生功耗,但它需要外接PHY芯片和网络变压器,PHY的功耗和启动时间都算整个节点头上。所以我说,以太网这个功能除非产品明确要求,否则选型时不要因为“反正芯片带着MAC”就硬上。
真要跑嵌入式Web库(比如mongoose)这种程序,得先算资源账。以太网协议栈加HTTP解析,加上你的业务逻辑,Flash和RAM消耗很容易超过低配型号的容量,使用前先用编译报告看一下实际占用,再决定裁剪哪些功能。我在一个小节点上试过精简配置的Web服务,只保留状态页和参数设置,勉强能塞进64KB Flash,但基本没有余量做OTA了。
3.4 外设引脚冲突排查与复用规划
引脚复用问题排在现场“外设没反应”类故障的第一位,基本能占一半。CH32L103的每个外设引脚映射都写在数据手册的复用表里,布局前先建一张引脚分配表,把下载口、晶振、复位脚先占好,再分配通信外设,最后安排模拟外设。
模拟外设要远离数字开关节点。PWM输出和ADC输入靠得太近,采样值会跟着PWM抖动,这就是布局问题不是配置问题。规划时尽量让模拟信号走一侧,数字节点走另一侧,中间隔地线。
遇到外设不工作,我的排查顺序很固定:先查RCC时钟,再查GPIO复用,然后查外设中断是否使能,最后查具体外设配置。步骤走下来,九成问题都能定位,剩下的一成是板子硬件问题。
4. 实操记录:低功耗温度变送器节点从入门到点亮
4.1 需求拆解与功耗目标
用一个真实项目来串一下前面所有内容。设备是一个电池供电的温度变送器节点,配PT100传感器,每秒醒来一次采集温度,通过UART把温度值发出去。整机目标:停止模式待机电流小于5uA,唤醒后10ms内完成从采集到发送的整个流程。
这个需求定义得特别典型:慢周期采样、小数据量、低占空比运行。每秒运行一小段,其余时间全睡,正好发挥CH32L103的停止模式优势。
4.2 时钟方案与启动路径设计
低功耗设备一定要有独立RTC时钟源。我的方案是用外部32.768kHz晶振给RTC提供时钟,同时在停止模式下保持RTC工作,用它的闹钟事件做周期性唤醒源。主时钟平时全关,只在任务需要时启动。
主时钟的选择讲究快速稳定。外部晶振加锁相环起振要等几百微秒,这个时间在占空比计算里全是纯消耗。我实测用内部高速振荡器做任务时钟,启动速度明显更快。精度要求高的场合再加锁相环校准。
核心思想是:慢时钟负责计时和叫醒,快时钟负责干活和安全。两边分工明确,功耗性能才是好的。
4.3 外设初始化与工作流状态机
初始化分为三块:基础时钟和GPIO、RTC唤醒配置、ADC与UART外设配置。初始化完进入主循环:
/* 工作流状态机示意 */ typedef enum { STATE_IDLE, /* 初始化后进入 */ STATE_ACQUIRE, /* 唤醒后采集 */ STATE_TRANSMIT, /* 数据发送 */ STATE_SLEEP /* 进入停止模式 */ } SystemState; int main(void) { System_Init(); ADC_Init(); UART_Init(); RTC_WakeUp_Init(); g_state = STATE_ACQUIRE; while (1) { switch (g_state) { case STATE_ACQUIRE: adc_value = ADC_GetValue(); g_state = STATE_TRANSMIT; break; case STATE_TRANSMIT: UART_SendFrame(adc_value); g_state = STATE_SLEEP; break; case STATE_SLEEP: /* 关闭不需要的外设时钟,配置唤醒源,等待唤醒 */ Enter_Stop_Mode(); /* 唤醒后重新回到采集状态 */ g_state = STATE_ACQUIRE; break; default: g_state = STATE_IDLE; break; } } } void RTC_Alarm_IRQHandler(void) { /* 清除闹钟标志,设置状态事件 */ g_wakeup_event = 1; }这段代码的架构很简单,但工程里很好用。状态机让每个阶段的行为都有明确入口和出口,调试时一眼能看出程序卡在哪个状态。这是状态机模型在低功耗管理里最实在的落地方式。
进入停止模式前必须做两件事:一是把ADC、运放这些模拟外设的时钟关掉,否则它们还会耗电;二是把GPIO口设置成确定的电平,不能留浮空。执行顺序也很重要,先关外设时钟、再设置GPIO、最后执行WFI指令。
4.4 停机与唤醒实测:数据对比
写固件是一回事,上电看数据是另一回事。我用串联采样电阻加示波器的方法看了一段完整的唤醒周期电流曲线。
停止模式电流实测约2.8uA,比手册典型值稍高,原因是板上有两个外部上拉电阻和一个电源指示灯的限流电阻,这几个静态消耗加起来大约1uA。唤醒后电流马上冲到几毫安,持续约150us完成时钟稳定,然后ADC采集和UART发送约800us后,电流回落到停止状态。
看到这个波形,功耗预算才真正落地。每秒周期里,实际耗电大头是唤醒后那1ms的工作电流,而不是停止模式那几微安。这也验证了前面说的,优化重点是缩短工作时间和唤醒启动开销,而不是过分纠结待机电流差零点几微安。
整机最终实测平均电流约5uA,达到了设计指标。如果还要压,下一步可以把这个1ms周期再砍半,或者从外部电阻上再挤一点。
5. 常见问题与排查技巧实录
5.1 整板功耗降不下去的几个典型原因
如果你进停止模式后发现整板电流还有几十微安,按下面顺序查,基本都能破案。
第一,确认芯片是否真的进入了停止模式。在唤醒后才点亮一个指示灯,看灯的闪烁行为,比读寄存器直观多了。芯片没进停止模式,后面全白查。
第二,检查调试器连接。这个坑我栽过几次,SWD接口连着开发器,芯片内部的调试逻辑保持上电,导致功耗始终降不下去。测量功耗前果断拔线。
第三,逐段排查外部电路静态漏电。断开所有外设板,只留最小系统,看电流是否回落。然后逐个插回外设,哪个插回去电流涨了就是哪个的问题。最常见的是LED限流电阻、外部上拉下拉电阻、线性稳压器的静态电流。
第四,检查GPIO悬空。把没用到的引脚统一配置为模拟输入或固定电平输出,很多看似神秘的多余电流从此消失。
5.2 外设“没反应”的排查顺序
外设不工作的排查,我有一套固定顺序。先查RCC时钟有没有使能,然后查GPIO的复用和模式配置,再查外设的中断使能或DMA配置。最后看外设本身的状态寄存器有没有报错。
CAN发不出去,先查终端电阻和波特率误差,两个120欧终端电阻位置是否正确,波特率计算是否精确到波特率误差容限内。UART输出乱码,查时钟源和波特率分频是否匹配。以太网调不通,检查PHY的地址配置和RMII接口的参考时钟,网络变压器中心抽头的接法按参考设计来,别自由发挥。
我在一个项目里遇到心肌缺血式的现象:PWM死活没输出。排查半天最后发现刹车输入引脚默认电平把输出锁死了。这类保护功能的默认状态一定要在初始化时确认清楚。
5.3 工业现场的可靠性与看门狗经验
低功耗设备做可靠性设计,和普通设备有一个显著差异:看门狗的处理。有的看门狗在停止模式下还在计数,而你的设备打算睡好几秒才醒,看门狗先是把设备重启了。这里需要评估设计需求,决定是允许看门狗在低功耗期间计数并在唤醒后及时喂狗,还是确实需要关闭看门狗功能。唤醒后第一件事就是喂狗,然后干正事。
电源电压监测是另一个提升可靠性的手段。电源掉电时先触发PVD中断,把关键参数写进非易失区,再进入安全状态。没有这层保护,突然断电可能把半写的Flash数据留下,下次上电设备就起不来了。工业现场电压跌落不是小概率事件。
再补一个排查速查表,都是实际项目里反复用到的:
| 问题现象 | 优先检查项 | 常见根因 |
|---|---|---|
| 整板电流偏高 | 调试器连接、外部静态负载 | 调试逻辑未断电、电阻漏电 |
| 停止模式唤醒后死机 | 唤醒事件标志清除顺序 | 中断标志未清,重复进入中断 |
| PWM无输出 | 刹车输入极性、互补输出使能 | 刹车默认锁死输出 |
| ADC采集跳码 | 采样时间、参考电压配置 | 采样时间不足,源阻抗过大 |
| CAN偶发错误 | 终端电阻、波特率误差 | 总线匹配不到位,时钟偏差 |
| UART乱码 | 时钟源切换后的重新初始化 | 切换时钟后波特率分频未更新 |
| 唤醒后外设不工作 | 唤醒流程是否重新初始化外设 | 误以为外设保持上电状态 |
实操总结
我个人在实际项目里的体会是:CH32L103的低功耗能力不是拿来即用的,必须把唤醒时间、唤醒源数量、板级静态漏电三者一起调,缺一个都在实测数据上露馅。另外有个小技巧:调试低功耗代码时,把调试器和串口线都断开再测电流,不然光是调试逻辑的消耗就能把结果带偏一截。
这芯片的混合信号和外设集成度在同价位里是突出的,只要把电源状态机设计清楚,电池供电的工业节点做下来会很顺手。后续如果要做带以太网或USB升级的版本,也可以直接站在这套电源状态机的架构上扩展,不用推翻重来。