☰
STM32双ADC同步采样实现Boost升压电路电压电流监测与效率计算
2026/9/24 16:41:06 网站建设 项目流程

简介:一套基于STM32与C语言实现的DC-DC高效升压电路工程包,面向嵌入式电源开发者与电子工程学习者,适合电池供电、太阳能系统等需要把低压直流提升至较高电压的应用场景,重点解决输入输出电压同步采样与精确稳压控制问题。工程基于Keil uVision5开发,完整覆盖STM32底层驱动、ADC双通道采样、PID调节、PWM波生成等核心模块,通过占空比控制与反馈机制维持输出电压稳定,代码结构清晰,便于理解Boost拓扑中电感储能、开关管通断与能量转移过程。压缩包共239个文件,约8.28MB,包含43个.h头文件、41个.c源文件,以及一批编译中间文件、调试备份与工程配置信息,从源码到可执行文件一应俱全,可直接打开工程进行实验或二次修改。已有904人学习下载,适合需要参考完整升压控制实现、学习STM32电源管理程序框架或进行电源实验的开发者。

1. 一块Boost升压电路要同时采样,要的不只是电压表

把3.7V锂电池升到5V给4G模组供电,这活儿很多工程师都干过。板子焊完上电,用万用表量输出5.0V,挺正常;可一旦模组以最大功率发射,电流从几十毫安瞬间跳到2A,输出电压立刻往下掉,万用表只给一个平均值,根本看不出掉到哪、掉了多久。更麻烦的是算效率:想验证Boost电路是不是“高效”,手上得同时有输出电压和输出电流,采样时刻相差几百微秒,算出来的功率连参考价值都没有。这个标题里的“同时采样”,说的就是让电压和电流在同一个触发沿上被采集,再拿C语言在STM32上做滤波、标定、效率计算。这套做法适合给便携设备、电池供电的模组做供电链路验证,也适合把固定电压输出的Boost改成带监控、带保护的可控电源。下面按Boost参数、STM32双ADC同步采集、C语言处理、Keil调试的顺序讲一遍完整落地方案。

2. 从占空比到效率:Boost升压电路原理与关键元件的选型计算

2.1 电感储能与占空比:把3.7V抬高到12V的第一步

Boost拓扑的核心是电感储能。开关管导通时,输入电压直接加在电感两端,电感电流线性上升,把能量存在磁场里;开关管关断时,电感电流不能突变,电感两端极性反转,和输入电压串联后共同给输出电容和负载供电。输出电压高于输入电压,靠的就是这个“串联”过程。

在连续导通模式(CCM)下,理想占空比公式是:

[ D = 1 - \frac{V_{in}}{V_{out}} ]

拿3.7V锂电升5V,D≈0.26;升12V,D≈0.69。占空比直接决定了电感两端电压的施加时间,也就决定了储能多少。但这个公式有个前提:效率100%。实际Boost芯片效率在85%~95%之间,考虑损耗后更接近:

[ D \approx 1 - \frac{\eta \cdot V_{in}}{V_{out}} ]

效率越低,占空比越大,因为需要更多能量补给损耗。设计时先用理想公式算初值,再用实际效率修正。这个修正值会影响电感电流峰值计算,不能省。

2.2 电感选型与饱和电流:同步Boost下还要防电流负向

电感是Boost电路里最不能拍脑袋选的元件。选小了,纹波电流大,输出纹波和开关管应力都升高;选大了,响应慢,体积也大。纹波电流按这个公式估算:

[ \Delta I_L = \frac{V_{in} \cdot D}{f_{sw} \cdot L} ]

其中(f_{sw})是开关频率。电感平均电流等于输入电流:

[ I_{in} = \frac{V_{out} \cdot I_{out}}{V_{in} \cdot \eta} ]

峰值电流是两者之和的一半。以3.7V升5V、1.2MHz开关频率为例,按上面公式估算,不同输出电流下电感参数大致如下:

输出电流建议电感量实际峰值电流电感饱和电流建议值
1A10μH约1.6A2.5A以上
2A4.7μH约3.2A5A以上
3A3.3μH约4.8A7.5A以上

电感饱和电流一定要按峰值电流留出至少30%余量。电感一旦饱和,感值骤降,纹波电流暴增,轻则发热,重则烧开关管。饱和电流的标识在规格书里叫Isat,不是额定电流,选型时最容易看错。

这里多说一句标题里的“同步Boost”。同步整流Boost把续流二极管换成功率MOSFET,导通损耗低,效率能到95%以上,但代价是轻载时电感电流会反向,电路从输出侧抽能量回输入侧。用同步Boost芯片时,要确认它有没有PFM轻载模式,避免在待机时反而更耗电。

2.3 反馈电阻、输出电容与PCB布局:稳定性的最后一道关口

输出电压由反馈电阻分压决定。绝大多数Boost芯片内部基准(V_{ref})是0.6V或0.8V,输出电压满足:

[ V_{out} = V_{ref} \times (1 + \frac{R_1}{R_2}) ]

以Vref=0.6V为例,常用输出电压的电阻组合:

目标输出电压R1(上分压)R2(下分压)
5V73.2kΩ10kΩ
9V140kΩ10kΩ
12V190kΩ10kΩ

反馈电阻取值不要太高,FB引脚输入漏电流会在高阻值下引入误差;也不要太低,分压电阻本身就成了额外的负载。10kΩ级别的下分压是比较实用的起点。

输出电容决定输出纹波,陶瓷电容首选,低ESR对纹波抑制明显。12V输出建议22μF和47μF并联,兼顾瞬态响应和纹波。PCB布局上,开关节点SW的铜皮要短而粗,反馈电阻和FB走线要远离SW节点,否则高频开关噪声会耦合进反馈环,造成输出电压抖动。

3. 输出电压与电流同时采样:STM32 ADC通道分配与双ADC同步

3.1 采样电路:电阻分压、采样电阻与电流检测放大器

先明确采样对象。要算效率,至少需要输出电压、输出电流、输入电压、输入电流四路信号;如果只验证Boost输出特性,电压和电流两路就够。STM32F103有ADC1、ADC2、ADC3三个ADC,其中ADC1和ADC2可以组成双ADC同步模式,天然适合“电压电流同一时刻采样”这个需求。

电压通道用电阻分压,把12V降到ADC量程内。以12V输出为例,上分压330kΩ、下分压100kΩ,ADC引脚电压约2.79V,留出过压保护余量。分压电阻后面并联一个10nF~100nF电容,滤掉高频噪声,同时给ADC采样电容提供电荷源。

电流通道用低边采样电阻加电流检测放大器。采样电阻放在输出负载的低边,一端接地,一端接负载地。0.02Ω采样电阻在3A电流下产生60mV压降,经INA180A3放大50倍后是3.0V,正好贴近ADC满量程。没有INA180时,用普通运放搭差分放大器也能工作,但要注意输入共模范围和失调电压。4G模组发射阶段电流变化速度很快,电流放大器带宽至少要有100kHz级别,否则采到的电流已经被“削平”了。

3.2 ADC1/ADC2同步规则模式:让电压电流落在同一个触发沿

为什么不用一个ADC轮流采两个通道?规则通道是顺序转换,通道0采完再采通道1,中间差几十微秒。对静态负载没影响,但4G模组发射时电流以毫秒级变化,电压电流不在同一时刻,算出来的功率曲线是“错位”的,效率自然也不准。

双ADC同步规则模式解决的就是这个问题:ADC1作为主,ADC2作为从,两个ADC在同一个外部触发沿同时启动转换。ADC1采电压,ADC2采电流,得到的就是真正同一时刻的样本。下面是以STM32F103标准外设库为例的初始化代码:

// 双ADC同步规则模式初始化(STM32F103,标准外设库) // 电压采样:PA0,电流采样:PA1 void ADC_Sync_Init(void) { GPIO_InitTypeDef GPIO_InitStructure; ADC_InitTypeDef ADC_InitStructure; DMA_InitTypeDef DMA_InitStructure; // 开启ADC1/ADC2、GPIOA、DMA1时钟,ADC时钟=72MHz/6=12MHz RCC_APB2PeriphClockCmd(RCC_APB2Periph_ADC1 | RCC_APB2Periph_ADC2 | RCC_APB2Periph_GPIOA, ENABLE); RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1, ENABLE); RCC_ADCCLKConfig(RCC_PCLK2_Div6); // PA0、PA1配置为模拟输入 GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0 | GPIO_Pin_1; GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AIN; GPIO_Init(GPIOA, &GPIO_InitStructure); // ADC1为主设备,规则同步模式;ADC2为从设备,跟随ADC1触发 ADC_DeInit(ADC1); ADC_DeInit(ADC2); ADC_InitStructure.ADC_Mode = ADC_Mode_RegSimult; // 双ADC规则同步模式 ADC_InitStructure.ADC_ScanConvMode = DISABLE; // 每个ADC只转换1个通道 ADC_InitStructure.ADC_ContinuousConvMode = DISABLE; // 不连续转换,等外部触发 ADC_InitStructure.ADC_ExternalTrigConv = ADC_ExternalTrigConv_T1_CC2; ADC_InitStructure.ADC_DataAlign = ADC_DataAlign_Right; ADC_InitStructure.ADC_NbrOfChannel = 1; ADC_Init(ADC1, &ADC_InitStructure); ADC_Init(ADC2, &ADC_InitStructure); // ADC1采PA0上的输出电压,ADC2采PA1上的输出电流 ADC_RegularChannelConfig(ADC1, ADC_Channel_0, 1, ADC_SampleTime_239Cycles5); ADC_RegularChannelConfig(ADC2, ADC_Channel_1, 1, ADC_SampleTime_239Cycles5); // 采样节拍由TIM1_CH2事件触发,这里先把定时器配置为10kHz RCC_APB2PeriphClockCmd(RCC_APB2Periph_TIM1, ENABLE); // 72MHz/(7199+1)=10kHz,Pulse=3600使触发点落在计数周期中部 TIM1->ARR = 7199; TIM1->CCR2 = 3600; TIM1->CCMR1 |= TIM_CCMR1_OC2M_1 | TIM_CCMR1_OC2M_2; // PWM模式1 TIM1->CCER |= TIM_CCER_CC2E; TIM1->CR1 |= TIM_CR1_CEN; // DMA1通道1搬运ADC1的数据寄存器,一次搬运32位数据 DMA_DeInit(DMA1_Channel1); DMA_InitStructure.DMA_PeripheralBaseAddr = (uint32_t)&ADC1->DR; DMA_InitStructure.DMA_MemoryBaseAddr = (uint32_t)adc_buf; DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralSRC; DMA_InitStructure.DMA_BufferSize = 1; DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable; DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Disable; DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_Word; DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_Word; DMA_InitStructure.DMA_Mode = DMA_Mode_Circular; DMA_InitStructure.DMA_Priority = DMA_Priority_High; DMA_InitStructure.DMA_M2M = DMA_M2M_Disable; DMA_Init(DMA1_Channel1, &DMA_InitStructure); // 开启外部触发和ADC,使能DMA ADC_ExternalTrigConvCmd(ADC1, ENABLE); ADC_ExternalTrigConvCmd(ADC2, ENABLE); ADC_DMACmd(ADC1, ENABLE); ADC_Cmd(ADC1, ENABLE); ADC_Cmd(ADC2, ENABLE); // 校准ADC1和ADC2 ADC_ResetCalibration(ADC1); while (ADC_GetResetCalibrationStatus(ADC1)); ADC_StartCalibration(ADC1); while (ADC_GetCalibrationStatus(ADC1)); ADC_ResetCalibration(ADC2); while (ADC_GetResetCalibrationStatus(ADC2)); ADC_StartCalibration(ADC2); while (ADC_GetCalibrationStatus(ADC2)); DMA_Cmd(DMA1_Channel1, ENABLE); }

几个关键参数值得说明。ADC_Mode_RegSimult让ADC1和ADC2以规则同步模式运行,两个ADC在同一边沿启动各自规则通道的转换。外部触发选用ADC_ExternalTrigConv_T1_CC2,也就是TIM1的通道2事件,这样采样节拍可以用定时器精确控制,而不是靠软件轮询。采样时间选了239.5个ADC时钟周期,约20μs一次转换,对毫秒级的负载变化绰绰有余,同时降低信号源内阻对采样精度的影响。DMA搬运的数据宽度是Word,这一点在下一节细说。

3.3 DMA数据排列:为什么读到的ADC2跑到高位去了

双ADC同步模式下,ADC1和ADC2的结果不是各自进自己的DMA通道,而是合并成一个32位数据放在ADC1的数据寄存器里,由ADC1的DMA搬走。数据排列是固定的:低16位是ADC1的结果,高16位是ADC2的结果。取数时代码这样写:

// 从DMA缓冲区取电压电流原始ADC值 uint16_t adc_v_raw = (uint16_t)(adc_buf[0] & 0xFFFF); // ADC1 -> 电压 uint16_t adc_i_raw = (uint16_t)(adc_buf[0] >> 16); // ADC2 -> 电流

新手最容易在这里栽跟头:DMA只配了ADC1的通道,但以为ADC2会把结果送到另一个地址;或者DMA数据宽度配了HalfWord,结果每次只搬低16位,高16位永远读不到。F103的ADC2本身没有DMA接口,同步模式下必须通过ADC1的DMA把两个结果一起带走。只要发现电流数据恒为0或偶尔跳变到错误值,优先检查DMA数据宽度和ADC_DMACmd是否配置正确。

DMA的模式用循环模式,配合定时器触发,每来一个触发沿就自动搬一次数据,CPU只需要在主循环里读adc_buf,不需要进中断。如果需要更精确的“采集时刻”记录,可以在DMA半传输中断里打个时间戳,但基础应用用循环模式已经足够。

4. C语言实现:标定、滑动平均、效率计算与软件保护

4.1 滑动平均滤波:丢掉一个最大一个最小

ADC原始值不能直接用。Boost的开关噪声会通过地回路串进采样通道,偶尔采到一两个尖峰,直接参与计算会让效率和保护逻辑都失真。经典做法是滑动平均,但工程上用得更稳的是“去掉极值再平均”:

#define SAMPLE_BUF_SIZE 8 uint16_t v_buf[SAMPLE_BUF_SIZE]; uint16_t i_buf[SAMPLE_BUF_SIZE]; uint8_t sample_index = 0; // 每得到一组同步采样值就调用一次 void Sample_Push(uint16_t v_raw, uint16_t i_raw) { v_buf[sample_index] = v_raw; i_buf[sample_index] = i_raw; sample_index = (sample_index + 1) % SAMPLE_BUF_SIZE; } // 去掉最大最小后取平均,返回滤波后的电压、电流ADC值 void Sample_Filter(uint16_t *v_out, uint16_t *i_out) { uint32_t v_sum = 0, i_sum = 0; uint16_t v_min = 0xFFFF, v_max = 0; uint16_t i_min = 0xFFFF, i_max = 0; uint8_t i; for (i = 0; i < SAMPLE_BUF_SIZE; i++) { v_sum += v_buf[i]; v_min = (v_buf[i] < v_min) ? v_buf[i] : v_min; v_max = (v_buf[i] > v_max) ? v_buf[i] : v_max; i_sum += i_buf[i]; i_min = (i_buf[i] < i_min) ? i_buf[i] : i_min; i_max = (i_buf[i] > i_max) ? i_buf[i] : i_max; } *v_out = (uint16_t)((v_sum - v_min - v_max) / (SAMPLE_BUF_SIZE - 2)); *i_out = (uint16_t)((i_sum - i_min - i_max) / (SAMPLE_BUF_SIZE - 2)); }

窗口长度8个点,10kHz采样率下每0.8ms输出一个滤波值,动态跟踪快,又能去掉单点尖峰。如果负载是4G模组这种突发型,窗口长度不要超过16个点,否则发射上升沿会被拉平,保护反应也跟着变慢。

这里有个可以抬杠的点:去掉最大最小会让数据少两个样本,数学上不是“标准平均”。但实际工程里,开关噪声尖峰通常只影响一个点,保留它会让平均值偏大,去掉它代价极小。这个取舍在Boost采样场景里是划算的。

4.2 增益标定与效率计算:把ADC码值换算成功率

ADC值换算成物理量,核心是线性标定。理想情况下用公式(y = k \cdot x + b),但增益k和偏置b不能只靠理论算——电阻精度、运放失调、基准电压误差都会让实际值与理论值偏离。标定做法:

  1. 空载状态下读电压ADC值,用万用表同时测实际输出电压;
  2. 接一个固定电阻负载,读第二组电压ADC值和万用表实测值;
  3. 两组数据解出k和b,保存到Flash。

电流通道同理,用电子负载或者精密电阻做负载,记录两到三个电流点。标定完成后写入固件参数。换算和效率计算的C代码:

// 标定参数,来自两段式标定,单位:V/ADC码、A/ADC码 #define V_GAIN (3.30f / 4095.0f) // 理论值,实际用标定后的k #define I_GAIN (3.00f / 4095.0f) #define V_DIV (430.0f / 100.0f) // 分压比反推:(330k+100k)/100k #define I_SENSE (1.0f / 50.0f / 0.02f) // 放大50倍、0.02Ω电阻 // 计算电压电流和效率,单位保留3位小数 void Power_Compute(uint16_t v_raw, uint16_t i_raw) { float v_out = (float)v_raw * V_GAIN * V_DIV; float i_out = (float)i_raw * I_GAIN / I_SENSE; // 输入功率按电池电压估算,实际项目需补一路输入采样 float v_in = 3.70f; float p_out = v_out * i_out; float p_in = p_out / 0.90f; // 估算输入功率,精确需实测Iin float eta = p_out / p_in * 100.0f; printf("Vout=%.2fV Iout=%.3fA Pout=%.3fW Eta=%.1f%%\r\n", v_out, i_out, p_out, eta); }

注意Power_Compute里用了float。STM32F103没有硬件浮点单元,float运算是用软件库模拟的,每次效率计算多花几十微秒。10kHz采样频率下,每秒算10000次float乘除会占掉不少CPU时间。实际工程中要么降低计算频率(每100ms算一次),要么全部改定点数。定点做法是把电压电流先放大1000倍存成int32,效率用整数除法算。精度足够,CPU占用几乎为零。

4.3 过流保护与软启动:采样结果参与闭环

同时采样的价值不只为了显示,还在于让采样结果直接参与保护和闭环控制。过流保护最简单的做法是软件比较,连续N次检测到电流超过阈值才动作,防止单点噪声误触发:

#define OVERCURRENT_MA 3000 // 过流阈值,单位mA #define OVERCURRENT_CNT 5 // 连续5次判定过流 uint8_t overcurrent_count = 0; uint8_t overcurrent_flag = 0; void Overcurrent_Detect(uint16_t i_raw) { uint16_t i_ma = (uint16_t)((float)i_raw * I_GAIN / I_SENSE * 1000.0f); if (i_ma > OVERCURRENT_MA) { if (++overcurrent_count >= OVERCURRENT_CNT) { overcurrent_flag = 1; // 拉低Boost芯片EN脚或关闭PWM输出 GPIO_ResetBits(GPIOB, GPIO_Pin_5); } } else { overcurrent_count = 0; } }

阈值和去抖次数要按实际负载特性调。4G模组发射时电流短时间超过平均值是正常的,阈值设太低会导致误保护;保护恢复逻辑也要设计好,常见做法是限制恢复次数,比如30秒内只允许恢复3次,超过后锁定断电。

软启动方面,如果Boost芯片没有内置软启动,可以在软件里控制EN引脚:上电后先输出一个低占空比的PWM信号给负载开关,让输出电容缓慢充电,避免冲击电流拉低输入电压。配合ADC采样能看到启动瞬间电压电流波形,确认没有过冲后再切换到正常运行状态。

5. 用Keil调试Boost采样:SWV波形、printf与采样时刻校准

5.1 把printf重定向到调试串口

Keil下最直接的观察手段是重定向printf到USART1,再配合串口助手看数据。使用Keil自带的MicroLIB,重定向只需要一个函数:

int fputc(int ch, FILE *f) { while (USART_GetFlagStatus(USART1, USART_FLAG_TXE) == RESET); USART_SendData(USART1, (uint8_t)ch); return ch; }

在Keil工程里勾选“Use MicroLIB”,否则fputc的重定向会因为半主机模式卡死。串口波特率115200,1ms打印一次电压电流效率,观察4G模组发射周期内的动态响应。

如果不想占用串口,Keil的SWV功能可以直接看变量波形。在调试模式下打开“Trace”设置,配置内核时钟为72MHz,然后在“Logic Analyzer”窗口添加需要观察的全局变量,例如adc_v_raw和adc_i_raw。SWV的优点是几乎不侵入代码,缺点是F103的SWO引脚带宽有限,变量刷新率太高时数据会丢。

5.2 用SW节点波形校准ADC采样时刻

Boost电路的采样精度问题,一半在硬件布局,一半在采样时刻。用示波器看Boost芯片的SW引脚,能看到周期性开关方波,方波的上升沿和下降沿都有振铃。如果ADC触发时刻正好落在边沿上,采样结果会被开关噪声污染。

解决方法有两种。一种是用固定频率定时器触发,确保触发时刻避开实际开关边沿;更彻底的做法是用SW信号本身去同步ADC触发——SW引脚经反相器整形后接到STM32定时器的外部触发输入,把触发点配到开关管关断的中间位置,这正好是电感续流最平稳的时间窗口。校准后,用固定负载记录一组实测数据:

负载电流万用表输出电压固件采样电压误差
0A12.02V12.01V-0.08%
0.5A11.98V11.96V-0.17%
1.0A11.93V11.90V-0.25%
2.0A11.81V11.76V-0.42%

误差随电流增大而增大,说明采样时刻还是偏向了功率开关噪声区域,或者电流检测放大器的带宽限制了峰值跟随能力。把这一组数据代回标定公式,重新计算增益和偏置,下一轮固件的误差通常能压到1%以内。

本文还有配套的精品资源,点击获取

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询