模拟电压转PWM:从ADC采样到代码实现的完整指南
2026/9/14 15:07:23 网站建设 项目流程

简介:针对STC单片机的模数转换并输出脉宽调制信号示例工程,面向单片机初学者及嵌入式开发人员,解决将电位器、传感器等模拟量转换为数字值后,通过软件定时器生成可变占空比PWM的控制需求,可应用于电机调速、LED亮度调节等场景。压缩包共22个文件,整体约37KB,以C语言源码、头文件、Keil工程配置为主,同时包含Hex固件、列表及调试映射文件,便于直接编译烧录,也能对照源码逐段理解ADC采样与PWM输出的衔接过程。已有245人学习下载。工程内提供主程序、ADC采集函数、PWM输出函数及必要的显示驱动模块,代码结构清晰,从ADC参考电压与采样通道配置,到定时器溢出中断中动态更新占空比,均有完整实现;由于采用软件方式生成PWM,不依赖硬件PWM模块,可适配不同STC型号或扩展为多路输出,直接参考迁移门槛较低。

1. 为什么非要费劲把模拟电压变成PWM

模拟信号直接驱动执行器,要么功率损耗难控制,要么长线传输抗干扰差。把电压量转成PWM,本质是把"模拟电平"换成"数字脉冲宽度",让信号在噪声环境里照样能读出百分之一量级的精度。这个标题里的AD转PWM,不是简单的"读个ADC然后改占空比",而是要处理采样率、PWM频率、分辨率匹配、滤波延迟和故障保护之间的一堆权衡。做电机驱动、温控、调光电源,或者要跨板卡传模拟量的场合,都会撞上这组问题。下面按"原理→最小实现→参数调优→排错"的顺序,把一条能直接落地的路径讲清楚,含代码和配置,照着能跑,调参时能知道该动谁。

2. AD采样与PWM生成:先把两套原理焊在一起

2.1 ADC采样的三个基础参数:分辨率、采样率、参考电压

AD转PWM的第一道门槛在采样端。分辨率直接决定占空比的最小台阶。12位ADC能给出4096个等级,映射到PWM那边如果只用8位,等于把采样精度白白扔掉。常见做法是让PWM定时器的计数周期也用12位,使两边分辨率对齐。假设定时器是16位,把ARR(自动重装载值)设成4095,占空比寄存器CCR的取值范围就正好对应ADC的4096个等级,映射无需做除法缩放。

采样率要看信号变化速度。PWM输出的频率往往在1kHz到20kHz,如果模拟输入变化比PWM周期还快,采样点落在波形不同位置会让输出占空比来回跳。这时需要在ADC前面放一阶RC滤波,把带宽压到PWM频率的1/3以下。参考电压方面,ADC的Vref和系统供电不是一回事,部分MCU内部有VREFINT校准通道,做高精度映射前读一次内部基准,能修正供电波动带来的误差。硬件上Cortex-M0以上多数带内部基准,直接读不需要外部器件。

提示:ADC和PWM的分辨率不齐时优先动PWM的ARR,不要去缩放ADC读数。缩放会产生量化噪声,严重时输出会出现可观察的台阶。

2.2 PWM频率选择:周期、占空比和分辨率三者互相牵制

PWM频率和分辨率存在一个固定矛盾:定时器时钟固定后,频率越高,ARR越小,占空比可调等级越少。以72MHz的定时器时钟为例作对比:

PWM频率定时器时钟ARR取值占空比分辨率适用场景
1kHz72MHz7199916位温控、慢速调光
20kHz72MHz359911.8位小型电机、电磁阀
100kHz72MHz7199.5位高频低精度场合

20kHz刚好超过人耳听觉范围,同时还能保住约12位分辨率,是多数电机驱动项目的甜点频率。低频段适合加热、缓慢温控这类惯性系统,1kHz PWM配合RC滤波就能得到足够平滑的直流输出,但分辨率可以冲得更高。PWM频率的低频下限受滤波电路体积约束,频率太低需要很大的电容电阻才能把纹波压下来。折中之后,1kHz到20kHz覆盖了绝大多数AD转PWM场景。

2.3 从ADC读数到占空比的映射方法

最直接的映射是线性映射。当ARR恰好等于4095时:

uint16_t adc_value = get_adc(); // 0..4095 TIM2->CCR1 = adc_value; // ARR=4095,无需缩放

这段代码成立的前提是ARR恰好为4095。如果ARR不是4095,则要用32位中间变量做乘法缩放,避免直接移位丢失低位:

uint32_t pwm_ccr = ((uint32_t)adc_value * (ARR_VALUE + 1)) >> 12; TIM2->CCR1 = (uint16_t)pwm_ccr;

乘法缩放的逻辑是:先把4096个输入等级映射到 0..ARR 区间,右移12位等效于除以4096。直接用adc_value * ARR / 4096也可以,但在部分编译器里乘法结果溢出为16位,必须显式转成32位。映射里最容易犯的错是直接把ADC值赋给CCR而不考虑ARR,导致占空比的最大值只有量程的一部分,输出端表现为"永远放不到满占空比"。

2.4 非线性映射:NTC、光敏这类场景直接查表

线性映射对分压电阻、电位器这类线性传感器足够。但NTC热敏电阻、光度传感器的响应曲线是非线性的,AD转PWM后直接驱动加热器或LED,输出的功率曲线和物理量之间会歪掉。这时用分段查表加线性插值更可靠:

static const uint16_t lut_ntc_to_pwm[16] = { 0, 180, 410, 690, 1020, 1400, 1830, 2310, 2840, 3410, 3680, 3880, 4010, 4100, 4140, 4095 }; uint16_t idx = adc_value >> 8; // 取高4位作区间索引 uint16_t frac = adc_value & 0xFF; // 低8位为区间内位置 uint16_t pwm_ccr = lut_ntc_to_pwm[idx] + ((lut_ntc_to_pwm[idx+1] - lut_ntc_to_pwm[idx]) * frac >> 8);

查表配线性插值只消耗几十字节闪存,却能把非线性映射误差压到0.1%以内。关键在高位的索引和低位的分数部分:右移8位得到的高4位用于定位落在哪个区间,低8位用于区间内做插值,避免只在16个点之间跳变造成输出毛刺。

3. 用STM32在本地跑通AD转PWM的最小可运行系统

3.1 硬件连接和引脚规划

先把最小系统画出来:一颗STM32F103或G431都行,模拟信号从PA1进ADC1的通道1,PWM从PA0(TIM2_CH1)输出,输出端串联一个100Ω电阻再接负载,或者直接进示波器观察。PA1附近的地线要单独走一条短而粗的线回到MCU的VSSA,因为数字翻转噪声会通过地线耦合进模拟通道。ADC输入引脚上并联一个0.1μF电容到地,再加一个10kΩ串联电阻,这两个元件组成低通滤波器,截止频率约159Hz,足以滤掉高频噪声,同时不会让信号本身失真。

3.2 CubeMX里需要配好的关键参数

使用STM32CubeMX生成工程时,有四个位置必须设置正确。ADC的采样分辨率选12位,扫描模式关掉(只采一个通道)。采样周期选到该ADC规格书推荐值的1.5倍以上,因为模拟输入源的阻抗越高,需要的采样时间越长。PWM定时器选择TIM2,通道1输出,预分频器按下面的公式反推:

PSC = (定时器时钟 / (ARR + 1) / 目标PWM频率) - 1

假如定时器时钟72MHz、目标频率20kHz,ARR设为4095,那么PSC = 72e6 / 4096 / 20000 - 1 ≈ -0.12,PSC为负说明ARR太大、配置无效。反过来把ARR降到999,PSC=2,频率就是72e6 / 3 / 1000 = 24kHz,分辨率约10位。这个一减一除的过程提醒一件事:频率、精度、定时器时钟三者必须同时放进一个公式里约束,不能单独拍脑袋定ARR。

3.3 用HAL库实现ADC连续采样到PWM输出的代码

工程生成后,在主循环里做轮询转换最简单,但不推荐在需要稳定PWM的场合用。常见做法是ADC连续转换加DMA,让ADC自己持续采样并把结果搬到内存,主循环只负责读数据、算映射、写CCR:

// 假设 DMA 通道已配置为外设到内存、循环模式 uint16_t adc_buffer[8] = {0}; HAL_ADC_Start_DMA(&hadc1, (uint32_t *)adc_buffer, 8); while (1) { uint32_t sum = 0; for (int i = 0; i < 8; i++) { sum += adc_buffer[i]; // 对8次采样求和 } uint16_t avg = sum >> 3; // 右移3位除以8,取平均 TIM2->CCR1 = avg; // ARR=4095 时直接映射 }

逻辑说明:DMA在循环模式下会持续把ADC转换结果写入adc_buffer,主循环里累加8个样本再取平均。作用是对开关纹波做软件滤波——PWM负载开关会在电源轨上留下毛刺,单次采样偶尔会抓到纹波峰值,8次平均能消掉大多数开关噪声,又不会把响应速度拖慢到不可接受。注意不用手动重启ADC转换,DMA循环模式会自动维持转换序列;HAL_ADC_Start_DMA的第三个参数8表示缓冲区长度,与硬件深度无关。

3.4 采样时刻:能不能锁在PWM的固定相位上

很多人做到这一步就停了,但采样时机是AD转PWM的隐藏坑。PWM输出的高电平期间,负载电流从电源汲取,模拟输入参考地上会有毛刺;如果ADC恰好在这个窗口采样,读数会偏高或偏低,输出占空比随之抖动。两个绕开办法:一是把ADC触发源改成定时器的更新事件,让采样点锁定在每个PWM周期的固定相位;二是把采样点放在PWM高电平的中间时刻,即中心对齐模式下定时器计数到一半的位置。

STM32的定时器可以产生TRGO事件,ADC由这个事件触发。用HAL库配置时,把ADC触发源选为定时器的TRGO输出,再把定时器主输出模式设为更新事件。这样ADC转换时刻与PWM周期严格同步,纹波被稳定地采样在同一个相位上。实际调波形时,示波器上能看到占空比的逐周期抖动明显减少。

4. 精度、纹波与稳定性:AD转PWM参数怎么调才对

4.1 PWM频率和滤波截止频率的配合关系

AD转PWM的最终输出如果目标是平直的直流电平,PWM后级必然要有滤波。一阶RC滤波的截止频率取PWM频率的1/10到1/20,能获得约20dB的纹波衰减。PWM频率20kHz时,截止频率选1kHz,对应R=1kΩ、C=0.16μF,取标称0.22μF即可。如果追求更低纹波,可以用二阶LC滤波,但要注意LC的谐振峰值需要阻尼电阻,否则在谐振点附近会产生更大的振荡。

滤波器和PWM频率是相互约束的:频率选得太低,滤波器和纹波一起变大;频率选得太高,分辨率掉下来。在储能电感体积受限的板子上,20kHz几乎是通用MCU方案的上限。做音频功放的PWM调制时可以拉到几百kHz,但那是专门的D类功放芯片,不属于通用MCU方案。

4.2 死区时间:驱动H桥或半桥时绕不开的参数

如果AD转PWM的输出用来驱动电机或功率级,死区是必须处理的一环。H桥上下桥臂的MOS管切换需要时间,如果上下管同时导通,电源直接对地短路。死区时间就是上下管之间刻意插入的互锁间隔。STM32高级定时器TIM1/TIM8支持插入死区,通过BDTR寄存器调整。常见做法:

  • 100kHz以内的开关频率配500ns到2μs死区
  • 频率更高时死区按MOS管的关断延迟加驱动芯片延迟来计算
  • 死区太短会炸管,太长会引入输出失真

死区对占空比有一个偏数学的约束:实际输出占空比约等于设定占空比减去死区时间与PWM周期的比值。在开环控制里这个偏差会表现为电机低速时扭矩偏小;接上负反馈闭环后,ADC读到的反馈量会自动补偿。如果开环使用,就要在软件里按死区时间预先修正CCR的值。

4.3 故障保护:ADC检测到过流就立即关PWM输出

AD转PWM系统里,ADC不光是用来产生PWM,还可以兼职做故障检测。用一个分流电阻采样输出电流,读取值超过阈值时立即关闭PWM输出。直接在主循环里判断速度太慢,要放在ADC中断里做:

void HAL_ADC_ConvCpltCallback(ADC_Handle_t *hadc) { if (hadc->Instance == ADC1) { uint16_t val = adc_buffer[0]; if (val > OVERCURRENT_THRESHOLD) { TIM2->CCER &= ~TIM_CCER_CC1E; // 关闭PWM输出通道1 } } }

这个回调运行在中断上下文,从检测到过流到关闭输出的时间在微秒级,比主循环抢出大量安全余量。故障恢复时用软件标志位延迟使能CC1E,避免在故障未消除时PWM反复开关。更可靠的做法是在硬件上并联一颗比较器独立保护,MCU级保护作为第二道防线,两种机制互补。

4.4 中心对齐模式和边沿对齐模式的取舍

边沿对齐模式的PWM所有通道高电平起始点对齐,EMI辐射集中,但更新事件发生在周期边界,采样相位固定方便。中心对齐模式下计数值先增后减,PWM波形关于周期中心对称,EMI分布更均匀,噪声更容易通过差分方式抵消。对AD转PWM来说,中心对齐模式还带一个好处:计数达到ARR/2的中间时刻正好是PWM高电平的中点,在这个点采样能稳定避开上升沿和下降沿两侧的振铃区域。代价是同等周期下定时器计数频率需要翻倍,功耗略微上升。

5. 调波形时必查的三个细节

5.1 用示波器看周期抖动而不是只看占空比

判断AD转PWM系统是否健康,第一个动作是看PWM边沿的抖动,不是只看平均占空比。把示波器时基放到1μs,打开余辉模式,观察上升沿是否不断左右漂移。如果抖动超过PWM周期的1%,说明ADC采样值在逐周期跳变,常见原因是采样窗口落在纹波上,或者ADC结果没做平滑。改成DMA连续多点平均,或者把ADC触发源锁到定时器更新事件上,抖动会明显改善。

5.2 用软件在初始化时自动标定零点

ADC的零点偏移和PWM死区补偿会叠加出不小的误差。一个实用的技巧是开机时读取ADC通道短接到地的电压作为零点,在换算公式里减掉。这不需要外部校准设备。如果板子允许,把初始化顺序安排成:先开ADC读零点,再开PWM,最后进入正常控制循环,能避免开机瞬间PWM占空比乱跳。

5.3 验证线性度最省事的方法是五点扫描

标定完零点后,用信号源给一组已知电压,记录对应PWM占空比,做五个点测试:零点、1/4量程、1/2量程、3/4量程、满量程。偏差若是整体线性变化,优先查参考电压精度和分压电阻温漂;偏差若是集中在某一区间,查那一段的ADC输入引脚是否被数字走线干扰。实测时用信号源输出1V、1.5V、2V、2.5V、3V,记录各点占空比误差,误差不超过±0.5%算合格。这个方法比拿着示波器满板子找噪声快很多,建议固件里留一个调试命令直接打印映射后的CCR值。

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

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

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

立即咨询