ARTICLE DETAIL

资讯详情

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

模拟电压转PWM:从ADC采样到代码实现的完整指南

模拟电压转PWM:从ADC采样到代码实现的完整指南 简介针对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; // ARR4095无需缩放这段代码成立的前提是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[idx1] - lut_ntc_to_pwm[idx]) * frac 8);查表配线性插值只消耗几十字节闪存却能把非线性映射误差压到0.1%以内。关键在高位的索引和低位的分数部分右移8位得到的高4位用于定位落在哪个区间低8位用于区间内做插值避免只在16个点之间跳变造成输出毛刺。3. 用STM32在本地跑通AD转PWM的最小可运行系统3.1 硬件连接和引脚规划先把最小系统画出来一颗STM32F103或G431都行模拟信号从PA1进ADC1的通道1PWM从PA0TIM2_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、目标频率20kHzARR设为4095那么PSC 72e6 / 4096 / 20000 - 1 ≈ -0.12PSC为负说明ARR太大、配置无效。反过来把ARR降到999PSC2频率就是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; // ARR4095 时直接映射 }逻辑说明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对应R1kΩ、C0.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值。本文还有配套的精品资源点击获取
返回列表