ATmega128 PWM直流调速实战:定时器配置与PID闭环控制
发布时间:2026/9/12 9:41:20 锦皓数字建站

简介一份面向嵌入式初学者与电子竞赛备赛者的ATmega128 PWM直流电机调速C语言工程包用于解决直流电机需要精确调速的实际需求演示了利用定时器PWM模式调节电机转速的完整思路。资源共1个C语言源文件压缩包仅526B代码精简、无多余资源便于直接分析核心逻辑。目前已有172人学习下载。文件中的代码围绕PWM调速的关键步骤展开配置定时器工作模式、选择PWM输出通道、通过OCRx寄存器调节占空比并包含类似setMotorSpeed的调速函数。对正在学习AVR单片机、想理解Timer0/Timer1的CTC模式或需要最小调速工程参考的开发者而言这份小体积代码包提供了可移植的寄存器操作范例能帮助跳脱理论学习直接对照源码掌握PWM输出与电机驱动接口的基本用法同时也可作为后续加入中断平滑调速、过载保护等扩展功能的基础框架。由于工程极小非常适合快速上手或作为课设/毕设的起始模板。1. 用 ATmega128 做 PWM 直流调速先分清楚什么能改、什么不能改PWM 调速入门时大多以为只要把占空比写进寄存器就完事实际调试中会发现真正的坑都在电机侧。同样是 50% 的占空比空载时转速可能接近额定值的一半带负载后直接掉到三成这就是为什么围绕 ATmega128 做直流电机调速的资料很多却很少有人把速度闭环写成可以直接抄的完整代码。这个标题要解决三件事用定时器输出稳定可调的 PWM 信号用 H 桥电路把信号放大成能驱动电机的功率再用速度反馈把转速稳在目标值附近。适合正在调小车底盘、风机或泵类设备的开发者也适合从 51 单片机转 AVR 后第一次接触定时器 PWM 的人。先给出本文的结论在 16MHz 主频下ATmega128 的 16 位定时器 1 配快速 PWM 模式是这套场景里参数冗余度最高、最不容易出问题的组合后面所有配置都围绕这个主线展开。2. ATmega128 定时器分配与 PWM 信号产生的完整路径2.1 四个定时器的分工与 PWM 引脚分布ATmega128 一共有四个定时器。定时器 0 和定时器 2 是 8 位定时器 1 和定时器 3 是 16 位其中定时器 2 带有独立振荡器输入引脚可以工作在异步模式。做直流电机调速时通常把主 PWM 放在 16 位定时器 1 上把周期性速度环调度放在定时器 0 上定时器 2 的异步能力留给低功耗时钟需要第二路 PWM 或编码器输入捕获时再动用定时器 3。四个定时器的分工决定了引脚冲突的排查顺序也决定后续加功能时会不会被迫改板子。引脚分布必须查数据手册确认。常见 64 引脚封装下OC0 在 PB4OC1A 和 OC1B 在 PD5、PD6OC2 在 PD7OC3A/OC3B/OC3C 在 PE3/PE4/PE5。整理成表定时器位宽输出引脚主要用途定时器 08 位OC0PB4系统节拍、蜂鸣器定时器 116 位OC1APD5、OC1BPD6主 PWM直流电机调速首选定时器 28 位OC2PD7异步定时、低频时钟定时器 316 位OC3APE3、OC3BPE4、OC3CPE5第二路 PWM、编码器捕获这里有一个容易看错的点定时器 1 内部还有一颗比较寄存器 OCR1C对应比较单元 C但 ATmega128 的封装没有把 OC1C 引脚单独引出来OCR1C 主要用于 16 位模式 15 的内部比较。数据手册里 PD7 的复用功能是 OC2如果误以为 PD7 能输出定时器 1 的第三路信号示波器上永远等不到波形。选定时器 1 而不是定时器 3除了资源占用考虑还因为 PD5/PD6 在多数最小系统板上直接引出调试时不用从 PE 端口飞线。硬件规划阶段就把 PWM 输出固定在 PD5后续写代码和焊接都有明确落点。2.2 快速 PWM 与相位修正 PWM 的取舍ATmega128 的波形发生模式分成三类快速 PWM、相位修正 PWM、相位与频率修正 PWM。快速 PWM 用单斜坡计数器从 0 数到 TOP 后立刻清零周期最短输出响应快相位修正 PWM 用三角波计数先增后减输出脉冲中心对齐但同样计数深度下周期是快速 PWM 的两倍。直流电机调速选快速 PWM 就够用。电机线圈是惯性负载对 PWM 波形的相位没有要求反而更在意周期长短周期越短电感引起的电流纹波越小铜损和温升越低。相位修正 PWM 的优势体现在需要产生互差 180° 的两路互补信号时或者用中心对齐波形抑制电磁干扰的场合比如无刷电机换相控制。第一次做直流调速时不需要为了追求标准而选相位修正模式。波形模式由 TCCR1A 的 WGM11:WGM10 和 TCCR1B 的 WGM13:WGM12 共同决定。记住三个常用组合就够了模式 14 对应 WGM13:101110TOP 值在 ICR1 里模式 15 对应 1111TOP 值在 OCR1A 里模式 10 对应 1010是 16 位相位修正 PWMTOP 值也在 ICR1。调试时把问题拆成“TOP 在哪”和“占空比写进哪个寄存器”两步比死背模式号可靠得多。2.3 用寄存器手工配置 OC1ATCCR1A、TCCR1B 与 ICR1 的关系一些集成开发环境提供图形化定时器配置向导生成代码后看起来方便但换个芯片型号或换个引脚就失效。理解寄存器之间的时序关系才能在电机不转时快速定位问题。下面这段初始化代码把定时器 1 配置成快速 PWM 模式 14不分频直接在 OC1A 引脚输出#include avr/io.h #define PWM_TOP 1249 // 16MHz / 12800Hz - 1 void pwm1_init(void) { // TOP 值放在 ICR1决定 PWM 频率 ICR1 PWM_TOP; // 非反相快速 PWMCOM1A11, COM1A00WGM111, WGM100 TCCR1A (1 COM1A1) | (1 WGM11); // WGM131, WGM121CS12:10001预分频 1 TCCR1B (1 WGM13) | (1 WGM12) | (1 CS10); // PD5 设为输出OC1A 复用功能才会出现在引脚上 DDRD | (1 PD5); // 初始占空比 50% OCR1A PWM_TOP / 2; }代码里的关键参数是这样来的ICR11249计数器从 0 计数到 1249总共 1250 个时钟周期16MHz 除以 1250 就是 12.8kHz。这个频率落在直流电机驱动的常用区间低于人耳可听范围的上限L298N 这样的老驱动芯片也能承受。COM1A1 置 1 表示比较匹配发生时 OC1A 被清零计数溢出时重新置位占空比越大高电平时间越长这就是非反相输出。预分频位 CS12:10 改为 010 时频率降到 800Hz电机会有明显的啸叫改为 100 时用 256 分频同一 TOP 值下频率降到 50Hz几乎等于模拟电平控制响应很差。改变转速只需要更新 OCR1A其他寄存器保持不动。要注意的是 ICR1 同时承担输入捕获功能如果后续想用 ICP1 引脚测编码器频率就必须避开模式 14。遇到这种情况常见做法是改用模式 15把 TOP 值放到 OCR1A把占空比放到 OCR1B 或 OCR1C代码改动只涉及模式位和两个寄存器的赋值位置理解原理后十分钟就能改完。3. 直流电机调速的最小闭环H 桥接线与 PID 速度环3.1 驱动级选型L298N 与 MOSFET H 桥的差别ATmega128 的引脚输出电流只有毫安级不可能直接驱动电机中间必须经过功率放大级。最常见的两种方案是集成 H 桥芯片 L298N 和分立 MOSFET H 桥。L298N 内部是两个 H 桥支持 5~35V 供电峰值电流 2A焊接后不需要额外设计栅极驱动非常适合学习阶段。缺点是饱和压降接近 2V6V 电机接上去最高只能分到 4V 左右转速上限缩水明显而且它的开关速度不快PWM 频率超过 20kHz 后发热急剧增加。这也是上一节把频率定在 12.8kHz 的原因之一频率、芯片、电机三者要匹配。电机功率在几十瓦以上时分立 MOSFET H 桥更实际。四个 MOSFET 分成两组互补导通PWM 信号加在高侧或低侧管的栅极另一侧保持恒通或同步整流。栅极驱动电阻取 10~100Ω 可以抑制开通瞬间的振铃续流二极管必须紧贴管子放置否则关断瞬间感性负载产生的尖峰电压会直接打到 ATmega128 引脚上。H 桥输出电压的平均值就是电源电压乘占空比这个数学关系看起来很直接但实际波形里叠加了管压降和死区占空比与电枢两端平均电压并不是严格线性。安全提示PWM 调速烧驱动十有八九是续流问题而不是占空比写错。电机突然断电时电感电流没有回路会在开关管漏极上产生高压续流二极管或肖特基二极管就是为了给这个电流提供通路。焊接完成后用示波器量一下漏极波形尖峰超过电源电压 1.5 倍就要检查二极管方向。3.2 速度采样霍尔编码器与外部中断计数开环 PWM 调速能转但负载变化时转速稳不住。手边有编码器的话建议直接把闭环加上。直流电机编码器大多是霍尔式输出 A、B 两路正交脉冲转速与脉冲频率成正比方向由 A、B 相位差确定。ATmega128 上最简单做法是把 A 相接到 INT0B 相接普通 I/O在中断服务程序里同时判断方向和计数#include avr/io.h #include avr/interrupt.h volatile int16_t encoder_count 0; ISR(INT0_vect) { // 读取 B 相当前电平判断是正转还是反转 if (PIND (1 PD1)) encoder_count; else encoder_count--; }这个中断每来一个上升沿就计数一次方向由 B 相的瞬时电平决定。接线时 A 相接 PD0B 相接 PD1两个引脚都需要使能内部或外部上拉。编码器引线较长时A、B 信号线会串入共模干扰常见的处理是在单片机端并 0.1μF 电容到地并用双绞线传输。中断里 PIND 的读取要放在最前面不要插入其他耗时操作否则高速运转时中断嵌套和重复触发会把计数值做成不稳定。霍尔编码器输出的是数字方波不需要 ADC这是它比电位器测速方便的地方。测速精度取决于每圈脉冲数和测速周期。每圈 13 个脉冲的减速电机在 10ms 周期内只能测到很粗的转速低速时经常出现测速值为 0 的情况。解决办法是拉长测速窗口到 50ms或者选用每圈几百脉冲的编码器这要在精度和响应速度之间做取舍。3.3 PID 参数标定与 PWM 饱和处理速度环用位置式 PID每 10ms 执行一次。第一版参数建议从纯比例开始先给电机一个固定占空比让它空载转起来记录稳态转速再按“误差每 100RPM 补偿多少占空比”的原则估算 Kp。逐步加入积分项消除静差微分项在电机这类惯性负载上通常不需要加多了反而把霍尔编码器的量化噪声放大。#define KP 2 #define KI 0.05f #define KD 0 static int16_t prev_err 0; static float integral 0; int16_t speed_pid(int16_t target, int16_t current) { int16_t err target - current; integral err; // 积分限幅防止堵转时积分饱和 if (integral 500) integral 500; if (integral -500) integral -500; int16_t out (int16_t)(KP * err KI * integral KD * (err - prev_err)); // 输出限幅到 0~1000对应 0~80% 占空比 if (out 1000) out 1000; if (out 0) out 0; prev_err err; return out; }参数含义积分限幅 500 对应约 25% 占空比贡献量防止电机长期堵转时积分项一直累计恢复后产生大幅超调输出上限 1000 是刻意留出的安全边界结合 ICR112491000 对应约 80% 占空比。PID 输出直接写入 OCR1A 之前要先做整型化而不是直接赋值因为浮点数和寄存器整型之间的隐式转换在不同编译器下行为不一致。速度环的调度可以放在定时器 0 的溢出中断里。为了和 PWM 周期解耦常见做法是定时器 0 配置 2ms 中断软件计数器数满 5 次后调用一次 pidvolatile uint8_t tick_10ms 0; ISR(TIMER0_OVF_vect) { if (tick_10ms 5) { tick_10ms 0; OCR1A speed_pid(target_rpm, rpm_from_encoder); encoder_count 0; } }这里把 encoder_count 清零放在 PID 计算之后保证下一个测速周期从零开始累计。定时器 0 预分频和初值要根据主频重新计算不要直接照抄例程否则实际周期偏了 PID 参数全部作废。4. PWM 频率、占空比与死区设置的三个关键参数4.1 频率怎么选12.8kHz、20kHz 还是更低PWM 频率对电机的影响可以从三个方面看噪声、电流纹波、开关损耗。频率低于 5kHz 时线圈电流的波动幅度很大电机在脉冲电流下发热明显转动声音也有周期性顿挫感频率超过 15kHz 后人耳基本听不到高频啸叫手感更平顺。但这不代表频率越高越好开关管的开关损耗与频率成正比栅极驱动能力不足时管子会在开通和关断的过渡区被高电压大电流共同撕裂。实际选型时可以参考下表频率范围可听噪声电流纹波适合的驱动典型场景1kHz~2kHz明显很大继电器、低速开关玩具电机5kHz~10kHz中等偏大分立 H 桥大电流电机12.8kHz几乎不可闻中等L298N、MOSFET通用直流减速电机20kHz不可闻较小MOSFET、专用栅极驱动工业调速40kHz 以上不可闻很小高速开关驱动微型高速电机12.8kHz 的优势在于它是 16MHz 主频的整数除法结果ICR1 取 1249 即可不用引入分频误差。改成 20kHz 时 ICR1 取 799分辨率从 1250 级降到 800 级电机控制仍然够用。频率调高前先确认驱动芯片的开关能力L298N 这类达林顿结构芯片在 20kHz 以上发热明显MOSFET 方案则要看图腾柱驱动电流是否足够。公式记住这一个就够TOP 值等于主频除以分频系数再除以目标频率减一。4.2 占空比与平均电压的非线性关系占空比理论计算公式是平均电压等于电源电压乘以占空比但实际电机两端量到的平均电压会偏低。H 桥每个桥臂的管压降、续流二极管压降和线路阻抗都会吃电压占空比很小时这些压降占比显著所以从 0% 开始增加占空比的前几个百分点电机往往不转这一段叫做启动盲区。解决方法是给控制量加一个启动偏移最小输出占空比设为 8%其余控制量在 8% 到 100% 之间线性映射。这样虽然牺牲了一点点低速范围但换来了从零速起转的确定性。占空比的细粒度也影响低速表现。定时器 1 是 16 位ICR1 取 1249 时占空比有 1250 个台阶电机几乎分辨不出相邻台阶的差别。8 位定时器只有 255 个台阶低速时相邻两步的转速变化能被明显感知表现为一抖一抖。同样是 20kHz 频率8 位和 16 位定时器的台阶数量差距很大所以调速首选 16 位定时器。写代码时可以用宏封装换算关系避免到处出现魔法数字#define PWM_DUTY_TO_OCR(duty_percent) ((uint16_t)((uint32_t)ICR1 * (duty_percent) / 100))这个宏先把乘数提升到 32 位再除法避免 16 位乘法溢出。直接写ICR1 * duty / 100在 duty 超过 80 时会截断调高占空比反而让转速往下掉这是很隐蔽的 bug。4.3 H 桥死区时间怎么设置与测量H 桥互补驱动里上管还没完全关断时下管就被打开两个管子中间形成直通短路瞬时电流可以烧毁 MOSFET。死区时间就是专门留出的“两个管子都关闭”的保护窗口。死区计算公式是 t_dead t_off_max - t_on_min margint_off_max 是管子在最差温度下的关断延迟t_on_min 是对管的最短开通延迟margin 一般取 0.2~0.5μs。低速功率 MOSFET 的死区通常取 0.5~1μs10kHz 下 1μs 只占整个周期的 1%对占空比精度影响很小。分立 H 桥的死区靠着栅极驱动芯片的 DT 引脚或外部 RC 延时实现集成 H 桥 L298N 内部已经处理外部无需配置。死区过大时低速段占空比丢失很多电机启动有咔哒停顿感死区过小时电流波形上会出现直通尖峰。用示波器可以测实际死区两个探头分别接高侧管栅极和低侧管栅极在互补输出边沿触发测量两路都是低电平的时间段那就是有效死区。H 桥 PWM 电路的数学原理并不复杂但工程中真正的复杂度全在死区、续流和器件延迟这些非理想特性上。把这三点理清驱动部分的可靠性会上一个台阶。5. 示波器验证 PWM 波形与故障保护触发5.1 三步确认波形正确拿到代码先别接电机示波器探头直接点在 PD5 引脚上做三项检查。第一步看频率测量菜单读数应为 12.8kHz 左右偏差在 0.5% 以内说明定时器配置正确如果差了一个量级多半是预分频设定不对。第二步看占空比改 OCR1A 的值观察高电平宽度同步变化如果读数固定或跳变检查程序里是否有多处写 OCR1A 的代码互相覆盖。第三步看边沿正常 PWM 的上升和下降沿应该陡峭一旦看到大量振铃先怀疑示波器探头的地线夹过长而不是程序问题。把示波器探头移到 H 桥输出端时波形高低电平整体抬高到电源电压形状应与 PD5 一致。如果高电平处压降超过 0.5V说明开关管没有完全导通检查栅极驱动电压是否足够。这一步能区分问题是出在单片机侧还是驱动级。5.2 堵转过流保护与 PWM 关断流程故障保护是把调速从玩具变成设备的必经环节。最常见的故障是堵转堵转电流可达正常电流的五六倍。典型做法是在电机回路串采样电阻电阻两端电压接比较器比较器输出接 ATmega128 的 INT1 引脚触发后立即关断 PWMvolatile uint8_t fault_flag 0; ISR(INT1_vect) { // 先禁止比较输出再写占空比 0 TCCR1A ~(1 COM1A1); OCR1A 0; fault_flag 1; }把 TCCR1A 的 COM1A1 位清零OC1A 立刻退出比较输出状态等价于断开 PWM 信号链此时即使电机回路还带着电压也没有新的开通脉冲能送到栅极。fault_flag 置位后主程序可以在下次启动前检查这个标志只有收到“复位故障”命令才重新使能 COM1A1避免堵转刚排除就自动重启动造成二次冲击。验证时用一块金属顶住电机轴模拟堵转观察保护触发时间是否在 100ms 内完成再按复位按键确认 PWM 能恢复正常输出。把 fault_flag 接到 LED 或串口输出还能进一步判断保护是被什么信号误触发的这一步在批量调试时尤其有用。本文还有配套的精品资源点击获取
锦
锦皓数字建站
深耕本土企业品牌数字化升级,专注原创端正雅致商务官网,从视觉设计到稳定运维全程保驾护航。