简介:这是一套基于STM32的电压可调与电流智能检测系统完整设计资料,适合电子竞赛、课程设计及嵌入式电源类项目开发者参考。系统由USB 5V供电,支持3.3V至12V可调电压输出,电流检测覆盖1uA到500mA宽范围,配合LCD12864实时显示电压电流,并可通过串口上传数据或修改输出电压。资源共450个文件、压缩包约9.14MB,包含STM32完整源代码(C/H文件及Keil工程)、原理图与PCB图(SchDoc/PcbDoc),以及编译生成文件、日志、预览图等,便于直接查看、修改与二次开发。内容组织清晰,主控选用STM32L152,配套标准外设库驱动,电路设计涵盖精密电流检测与可调电源模块。目前已有226人学习下载,适合需要完整软硬件参考方案、快速搭建原型或深入理解高精度电流检测电路设计的读者。
1. 从1uA到500mA:这个stm32电源为何把量程跨度写成设计重点
调试低功耗设备时最烦的事,是待机电流明明是微安级,一入网瞬间又冲到几百毫安。手头万用表测不了动态,台式电源又不能随负载曲线自动记录。这套“基于stm32的电压可调、电流智能检测系统”正好干这件事:USB 5V输入,输出3.3V-12V可调,电流测量范围标称1uA-500mA,LCD12864显示,串口既能上报也能下发电压指令。它不是普通可调电源的简单改版,而是把量程跨度做成闭环的一部分:stm32既要闭环调压,又要切换电流量程。因此拿到这个工程时,我没有急着看主程序,而是先翻原理图里的采样电阻和反馈网络。理解了这两处,才能真正修改它为己所用。
2. 电压可调输出:STM32作为反馈基准的Buck-Boost实现
2.1 升压拓扑与反馈点的选型
先要明确一点:USB 5V供电,输出却要覆盖3.3V到12V,说明单纯Boost或单纯LDO都做不到。输出5V以下时Boost工作不起来;输出12V时LDO压降又太大。常见做法是“Buck-Boost电源芯片+外部反馈基准”。我检查解压后的源码时看到stm32l1xx_tim.c和stm32l1xx_adc.c同时存在,推断设计者用PWM而不是DAC作为基准源,这一点很实际。STM32L152本身有DAC,但DAC的参考电压和输出噪声会影响电源纹波,占用引脚也定死。用高级定时器输出PWM,再经过一级RC低通滤波,可获得0-2.5V的连续基准,接到电源IC的反馈端。
电压调节关系可以用下面的公式表达:
Vout = Vfb * (1 + R1/R2)
其中R1是上分压电阻,R2是下分压电阻,Vfb是电源芯片反馈引脚处的电压。电源IC内部误差放大器会把FB脚稳定在一个固定参考电压附近;如果我们在FB脚上强制注入一个低阻抗的外部基准,Vfb就近似等于PWM滤波后的电压,输出电压随之改变。实际工程里,这个外部基准经常用运放缓冲,避免电源IC的偏置电流导致基准漂移。这里给出一组常见元件参数:
| 电源IC类型 | 外部基准Vfb范围 | R1/R2 | 对应Vout范围 |
|---|---|---|---|
| 普通Boost | 0~2.5V | 24k/4.7k | 0.5~6.2V |
| Buck-Boost | 0~2.5V | 100k/10k | 1.1~12.5V |
| 运放缓冲反馈 | 0~3.0V | 120k/20k | 3.3~12.3V |
这个工程实际用的是第三类,用运放缓冲PWM滤波后的基准,再驱动Buck-Boost的反馈端。分压电阻建议用0.1%的金属膜电阻,温度系数25ppm以内;如果只做验证,1%电阻也可以,但固件里必须做校准修正。R2两端并联一个几十pF的电容,用于补偿环路相位,避免输出电容ESR偏低时自激。
2.2 原理图上的关键节点与PCB布局
画原理图时,不要急着连每个引脚,先从电源主回路走一遍。输入USB 5V经保险和滤波电容到达Buck-Boost的开关输入脚;输出端接储能电感和输出电容;反馈分压点要从输出电容正端单独引一条sense线,不能直接从上端功率走线抽头,否则负载电流在铜箔上的压降会被当成输出电压误差反馈回去。这一点在PCB布局上最重要。
我一般把PCB分成三个区域:输入保护/滤波区、功率转换区、模拟采样区。模拟采样区的地用小铜皮单独连到主地,只在电源IC的AGND焊盘单点连接。电流放大器的电源用10欧电阻加0.1uF做退耦,防止功率开关噪声串入。PWM滤波RC靠近MCU引脚,注意RC时间常数要大于PWM周期。假设PWM频率20kHz,时间常数设为10ms,纹波能压到几毫伏,但输出动态响应也变慢。如果要求负载瞬态响应快,把PWM频率提到100kHz,RC用10k和0.22uF。
| 网络名 | 布线要求 |
|---|---|
| VIN_USB | 走线宽度2mm以上,先过保险再进芯片 |
| SW_OUT | 短且粗,铺铜,远离反馈和采样电阻 |
| FB_SENSE | 宽度0.25mm,远离开关节点,旁边由地线包裹 |
| PWM_FILT | 靠近STM32引脚,下方用完整地平面 |
另外,采样放大器的输入线要避免与LCD排线平行走长距离。LCD12864的读写信号是cmos电平,翻转时会产生串扰,可能被运放输入端耦合。如果布局空间紧张,可以在运放输入脚加一个100pF到地的电容,把高频噪声滤掉,代价是响应速度变慢,但电流检测本来就是慢信号,影响不大。
2.3 固件:PWM占空比到输出电压的换算
电源芯片反馈电阻确定后,固件只需要把目标电压换算成定时器比较值。工程里用TIM3_CH1输出PWM,ARR设置为1024,满占空比对应的RC滤波电压是V_PWM_MAX。代码里我习惯写成一个独立的转换函数:
#define PWM_ARR 1024u #define R_UP_OHM 120000.0f /* 上分压电阻 120k */ #define R_DOWN_OHM 20000.0f /* 下分压电阻 20k */ #define V_PWM_MAX 2.50f /* 100%占空比时RC滤波后的基准电压 */ uint32_t vout_to_pwm_compare(float vout) { float vfb_req; float ratio; vfb_req = vout * R_DOWN_OHM / (R_UP_OHM + R_DOWN_OHM); ratio = vfb_req / V_PWM_MAX; if (ratio < 0.05f) ratio = 0.05f; /* 下限死区,约1.1V输出 */ if (ratio > 0.95f) ratio = 0.95f; /* 上限死区,约12.3V输出 */ return (uint32_t)(ratio * PWM_ARR); }代码里的分压比R_DOWN / (R_UP + R_DOWN)决定了输出电压与反馈基准的换算。例如目标5V时,分压比20/(120+20)=0.1429,vfb_req约0.714V,ratio约0.286,CCR1约293。死区限制比0.05和0.95更安全,避免占空比在极端位置时电源芯片进入过流保护或失去调节能力。调用时直接:
TIM3->CCR1 = vout_to_pwm_compare(5.0f);如果实际输出电压偏低,不要急着改占空比,先量Vfb节点电压是否等于计算值。常见问题是RC滤波在MCU引脚输出低时没有放电回路,电压会偏高。解决办法是在RC后加一个100k下拉电阻,固件里再校一次比例系数。
3. 三档电流量程自动切换与ADC采样算法
3.1 为什么单档位测不了1uA到500mA
STM32L152内部ADC是12位,参考电压3.3V,一个LSB约0.8mV。直接用一个采样电阻测量电流时,如果选择0.1欧姆,500mA产生的压降是50mV,ADC刚好能读,但1uA只产生0.0001mV,完全淹没在噪声里。反过来,用1000欧姆采样电阻测1uA能得到1mV,可500mA会产生500V,硬件直接烧毁。因此必须把量程拆成多档。
这个工程的做法是三个采样电阻串联在负载低端:1000欧姆、10欧姆、0.1欧姆,分别对应低、中、高档。低档覆盖0.04uA到165uA,解决微安级测量;中档覆盖几微安到16.5mA,用于常见传感器功耗;高档覆盖0.4mA到1.65A,由于系统限制在500mA,实际不超过500mA即可。三者之间有重叠区间,自动切换不会出现测量盲区。
| 档位 | 采样电阻 | 运放增益 | 满量程电流 | ADC一个LSB对应电流 |
|---|---|---|---|---|
| L | 1000欧姆 | 20 | 165uA | 0.04uA |
| M | 10欧姆 | 20 | 16.5mA | 4uA |
| H | 0.1欧姆 | 20 | 1.65A | 0.4mA |
运放增益统一固定在20倍,好处是放大器的带宽和失调电压特性一致,切换档位时不需要重新标定增益,只需要把ADC读数除以不同的采样电阻系数。低档的压降问题要注意:1000欧姆电阻在165uA时压降0.165V,如果负载是3.3V供电的MCU,还能接受;但输出电压低时,这个压降会影响实际负载电压,所以切换阈值必须提前到150uA以下,不能真等到满量程再切。
3.2 采样电阻切换与运放电路
三个电阻串联后,用两个低导通电阻MOS管分别旁路中档和低档,实现量程选择。H档只保留0.1欧姆电阻常通,M档短路10欧姆,L档短路1000欧姆,切换由两个GPIO控制。不能用模拟开关CD4052直接切换电流通路,因为其内部导通电阻有几十欧姆,在0.1欧姆档会完全淹没信号。正确的做法是:模拟开关只切换运放的输入源,或者用分立MOS管做负载开关。
运放选轨到轨、低失调产品,比如LMV321或SGM8051。差分输入直接从采样电阻两端取信号,经20倍放大后进ADC。低档1uA时采样电阻压降1mV,放大20倍后为20mV,ADC读数约25,可以分辨。如果要求更高精度,可以把低档增益改到50倍,但运放输出在小电流时会更接近地电平,需要注意失调电压的影响。
3.3 自动量程切换代码与滞回
自动切换策略是:每次ADC采样后判断当前量程是否合适,如果接近满量程就切到更高档,如果读数太小就切回低档。切换后要重新采样一次,避免旧量程残值污染结果。代码如下:
typedef enum { RANGE_L=0, RANGE_M=1, RANGE_H=2 } range_id_t; static range_id_t cur_range = RANGE_L; static const float rs[] = {1000.0f, 10.0f, 0.1f}; /* 三个采样电阻 */ float read_current_auto(void) { uint32_t sum; range_id_t r = cur_range; float v_adc, i; sum = adc_read_avg(ADC_CH_CUR, 16); /* 16次取平均 */ if (r == RANGE_L) { if (sum > 3500) { /* 约2.8V,接近满幅 */ r = RANGE_M; set_range(r); sum = adc_read_avg(ADC_CH_CUR, 16); } } else if (r == RANGE_M) { if (sum > 3500) { r = RANGE_H; set_range(r); sum = adc_read_avg(ADC_CH_CUR, 16); } else if (sum < 20) { /* 小于80uA,切回低档 */ r = RANGE_L; set_range(r); sum = adc_read_avg(ADC_CH_CUR, 16); } } else { if (sum < 5) { /* 高档读数太小,切回中档 */ r = RANGE_M; set_range(r); sum = adc_read_avg(ADC_CH_CUR, 16); } } cur_range = r; v_adc = sum * 3.3f / 4095.0f; i = v_adc / (20.0f * rs[cur_range]); /* 增益20倍 */ return i; }这一段的关键是滞回:M档切换到低档的阈值不是低档满量程,而是读数很小时才切,否则会频繁抖动。代码里的20对应中档80uA,这个值仍高于低档LSB,切换后低档能稳定读出来。高档由于0.1欧姆信号幅度小,阈值设为5个LSB,约2mA,低于这个值就依赖M档。实际校准后这些阈值要根据噪声重新调整,原则是“高档能读的时候绝不切低档,低档没饱和的时候尽量别升档”。
4. LCD12864显示与串口指令:监控与电压修改的实现
4.1 串口协议设计
串口在这里承担两个角色:一是周期上报电压电流数据,二是接收指令修改输出电压。直接发裸数据容易乱,我建议用文本帧,格式固定为指令:参数\r\n。这个工程里用了最简单的状态机,收到换行符才解析,避免半包问题。协议定义如下:
| 指令 | 含义 | 返回/动作 |
|---|---|---|
| SETV:3.30 | 设置输出电压3.30V | 返回OK |
| GETV | 查询当前电压电流 | 返回V=3.30, I=0.123mA |
| RST | 恢复默认3.3V | 返回OK |
波特率在工程里通常是115200,8N1。stm32l1xx_usart.c里需要打开USART1的接收中断。每次上位机发来的数据可能被拆成两包,所以不能在一个字节中断里立即执行指令,只做缓存和换行判断。
4.2 串口解析与电压修改代码
static char cmd_buf[32]; static uint8_t cmd_idx = 0; void USART1_IRQHandler(void) { uint8_t ch; if (USART_GetITStatus(USART1, USART_IT_RXNE) != RESET) { ch = USART_ReceiveData(USART1); if (ch == '\n') { cmd_buf[cmd_idx] = 0; parse_command(cmd_buf); cmd_idx = 0; } else if (cmd_idx < 31) { cmd_buf[cmd_idx++] = ch; } } } void parse_command(char *buf) { float v; if (strncmp(buf, "SETV:", 5) == 0) { v = atof(buf + 5); if (v < 3.3f) v = 3.3f; if (v > 12.0f) v = 12.0f; TIM3->CCR1 = vout_to_pwm_compare(v); USART_Printf("OK\r\n"); } else if (strcmp(buf, "GETV") == 0) { USART_Printf("V=%.2f, I=%.3fmA\r\n", vout_now, current_now_mA); } else if (strcmp(buf, "RST") == 0) { TIM3->CCR1 = vout_to_pwm_compare(3.3f); USART_Printf("OK\r\n"); } }atof在MDK的microlib下可用,但要注意字符串里不能有空格。上位机发送SETV:3.30时,\r会停留在缓冲区里,这里解析时遇到\r直接忽略,或者发送端不加\r只加\n。电压上限12V的约束不能只靠上位机,固件里必须钳制,防止误操作把后端负载烧掉。修改电压后不要立刻认为输出电压已经稳定,Buck-Boost需要几十毫秒的调整时间,最好在状态机里延时100ms再读取反馈电压校验。
4.3 LCD12864显示刷新
LCD12864在这个工程里用的是ST7920控制器,8位并口接法。接线表如下:
| LCD引脚 | 功能 | 连接 |
|---|---|---|
| RS | 指令/数据 | PB0 |
| RW | 读写 | PB1 |
| E | 使能 | PB2 |
| D4-D7 | 数据位 | PC4-PC7 |
主循环里不要每圈都刷屏,12864全屏刷新一次要几十毫秒,会拖慢串口响应。我一般用200ms定时刷新一次,只更新电压和电流两行,行首固定显示“V:”和“I:”。为了避免数字跳动,先用sprintf格式化到字符串缓冲区,再一次写入并口。stm32工作电压3.3V,LCD12864的3.3V版本可以直接驱动;如果是5V版本,并口需要加电平转换,否则MCU引脚长期过压。
5. 校准、PCB防护与低电流验证技巧
5.1 零点校准与偏移消除
1uA档最容易栽在运放失调电压上。运放失调电压典型值1mV,乘以增益20倍后是20mV,对应ADC读数25,相当于0.04mA?不,低档对应25/40953.3/(201000)约等于1uA,误差不可忽略。出厂前要做零点校准:断开负载,读取当前ADC值记为offset,之后计算时先把总和减去offset再转电流。实测时负载拔掉,低档ADC可能有几十个计数的常数,这是正常现象。
static int32_t offset_code = 0; void current_offset_calibrate(void) { offset_code = adc_read_avg(ADC_CH_CUR, 64) - 1024; /* 背景噪声修正 */ }这个offset只在低档时参与修正,高档基本不需要。如果低档零点随时间漂移,说明PCB受潮或采样电阻热电动势影响,需要重新校准。
5.2 低电流测量的PCB防护细节
1uA对应的采样电压只有几毫伏,常规过孔和焊盘之间的漏电流都会造成干扰。低档采样电阻附近不要涂助焊剂,清洗后用三防漆覆盖。采样电阻两端做开尔文连接,避免大电流走线压降叠加到信号上。地线采用单点接地,功率地和小信号地在电源输入端汇合,不要形成地环路。如果LCD排线必须从采样电路上方穿过,中间加一层地平面隔离。
5.3 用精密电阻验证整机精度
验证量程最简单的方法是用精密电阻做负载:设置输出电压5V,分别接1k、10k、100欧姆电阻,理论电流是5mA、0.5mA、50mA,观察串口返回的电流值。注意电阻功率,500mA不能用普通贴片电阻,要选2W以上水泥电阻。上位机一次完整流程如下:
SETV:5.00 OK GETV V=5.00, I=5.132mA如果读到的电流比理论值整体偏高,先检查负载电流是不是真的全部流过采样电阻。很多低端电源设计把LCD背光或分压电阻的电流也走了同一回路,导致测量值偏大。把无关负载全部移到采样电阻之前,才是干净的电流检测路径。把这几处处理好,低档位稳定到±0.2uA以内是可行的。
本文还有配套的精品资源,点击获取