简介:这是一套基于单片机PID控制的制动能量回收系统仿真设计资源,面向嵌入式、自动控制及新能源方向的初学者和项目开发者,用于理解再生制动过程中动能回收与控制算法如何结合。压缩包共二十九个文件,以C语言源程序、头文件、Keil工程文件、Proteus仿真设计为主,另附hex烧录文件和工程备份,整体约107KB。项目中单片机通过AD转换采集电机速度与电压信号,以PID算法实时调节电机电流与扭矩,再由12864液晶屏展示转速、电池电压等关键参数,形成完整的闭环控制流程。程序兼顾不同制动强度和电池电量状态下的能量回收策略,同时保留了仿真工程与可运行映像,便于读者逐模块查看AD采样、PID调节、显示驱动等代码细节,并进行二次开发。目前已有三百七十一人学习,适合作为课程设计、毕业设计或从业者的入门实践参考。
1. 制动能量回收仿真为什么要先解决 PID 环
制动能量回收不是让电机反转这么简单。车辆减速时电机进入发电工况产生反向拖滞力矩,同一套驱动电路既要保证制动力矩线性可控,又要尽量把动能回馈到电池。两个目标互相拉扯:电流大,回收效率高,但制动力突变会让乘客顿挫;电流压软了,回收电量又少得没意义。这类课题的仿真文件里,最值得研究的往往不是电机模型,而是把目标电流和实测电流对上的那条 PID 闭环。
做毕业设计和台架预研的工程师,先用仿真把 PID 环路调通,比直接上驱动板省去大量烧管子、抓波形的成本。仿真能把采样周期、PWM 频率、电流环带宽逐个拆开观察,实物接上电池后很难做到。下面按「模型搭建 → 控制器设计 → Simulink 仿真 → 单片机程序」给出可复现的最小方案,最后落到搬实物前的参数边界与调试技巧。
2. 制动能量回收系统的模型结构与控制对象
2.1 再生制动工况下的功率流向与效率边界
先看能量从哪来到哪去。设整车等效质量 m、当前车速 v,动能 E = ½mv²,制动时这部分动能一部分通过电机转成电,另一部分以热量耗散在机械制动盘和电机铜损铁损上。忽略坡度和风阻,制动功率 P_b = m·v·a,a 是减速度。能量回收系统的任务就是:在满足总制动力矩的前提下,让电机电流产生的再生制动力矩尽量大,同时不让减速度波动超出驾驶员预期。
硬件链路通常是电机 → 三相逆变器 → 直流母线 → DC-DC 变换器 → 电池。回收时逆变器把电机侧交流电整流到直流母线,DC-DC 再把母线电压调整到电池可接受的充电区间。PID 控制在这条链路里的位置是电流环:给定目标再生电流 i_ref,控制 PWM 占空比,使实际电流紧跟 i_ref。仿真设计的关键就是把这个电流环的惯性、时延、饱和全部量化出来,否则程序写得再漂亮也是空转。
2.2 被控对象传递函数推导与参数表
把电机一相电枢电路简化,电压方程为:
U = R·i + L·di/dt + e
其中 e = Ke·ω 是反电动势,Ke 为反电动势常数,ω 为电角速度。对电流环来说,电流对电压的传递函数为:
G_i(s) = I(s) / U(s) = 1 / (Ls + R)
机械侧转矩平衡方程为 J·dω/dt = T_drive - T_load,再生制动时 T_drive = -Kt·i。反电动势 e 对电流环是慢变扰动,可以在控制器里加前馈补偿;对转速环而言,电流是输入、转速是输出,约为一阶惯性环节。下面是一组适合仿真的典型参数,覆盖一个 1.2kW 级轮毂电机系统的量级:
| 参数 | 符号 | 数值 | 说明 |
|---|---|---|---|
| 相电阻 | R | 0.15 Ω | 含线路阻抗 |
| 相电感 | L | 0.5 mH | 用 LCR 表在 1kHz 下实测 |
| 转矩常数 | Kt | 0.12 Nm/A | 与 Ke 数值相等(SI 单位制) |
| 等效转动惯量 | J | 0.02 kg·m² | 折算到电机轴,含车轮 |
| 粘滞阻尼 | B | 0.001 Nm·s/rad | 低速时可忽略 |
| 电池电压 | V_bat | 72 V | 铅酸或锂电池组 |
| PWM 频率 | f_pwm | 20 kHz | 避开音频噪声段 |
没有铭牌数据时,可以用下面的脚本从额定值倒推 Ke 和 Kt,这是仿真设计里常用的一步,避免凭空拍参数:
% 由铭牌数据估算 R/L/Ke 初值(初始化脚本片段) U_rated = 72; % 额定电压 V I_rated = 16.7; % 额定电流 A n_rated = 3000; % 额定转速 rpm omega_n = 2*pi*n_rated/60; % 额定角速度 rad/s R = 0.15; % 相电阻,电桥实测 L = 0.5e-3; % 相电感,LCR 表实测 Ke = (U_rated - I_rated*R)/omega_n; % 反电动势常数 V/(rad/s) Kt = Ke; % SI 制下转矩常数与 Ke 相等脚本把电枢压降 I_rated·R 从额定电压里扣掉,剩余部分对应额定转速下的反电动势,这样估算的 Ke 比直接用 U/ω 更接近真实值。仿真文件里所有模块都应引用这套变量,不要在每个模块里手填数字,否则换参数时容易漏改一处导致波形对不上。
2.3 仿真工具选型:Simulink 还是 Proteus
标题里的「仿真设计」通常对应两套文件:一套是 Simulink 的 .slx 做控制策略验证,一套是 Proteus 搭的单片机外围电路仿真。两者侧重点不同。Simulink 适合看波形——电流跟踪、SOC 变化、制动减速度曲线;Proteus 适合看逻辑——单片机引脚时序、ADC 采样、PWM 输出是否按程序走。如果只做一套,优先 Simulink,因为 PID 收敛的难点在被控对象的惯性上,而 Proteus 里的理想元件反而把这种惯性掩盖了。
提示:如果课题要求必须交 Proteus 仿真文件,建议把 Simulink 调好的 Kp/Ki 原样搬过去,Proteus 里只验证程序逻辑,不要指望在两个环境里同时做参数整定,那样会浪费大量时间。
也有人用 Simulink 的 Motor Control Blockset 做 PMSM/BLDC 的磁场定向控制,再自动生成 C 代码。那条路适合量产,但对学习型课题太重,生成代码的可读性差,答辩时说不清参数含义。本文采用简化的一阶电枢模型做被控对象,PID 用手写 C 代码,仿真和程序一一对应,调试时可以逐行对照。
3. PID 控制器的离散化设计与参数整定
3.1 位置式与增量式 PID 在单片机上的取舍
单片机里实现的 PID 是离散的。位置式直接计算占空比绝对值:
u(k) = Kp·e(k) + Ki·Σe(k) + Kd·[e(k) - e(k-1)]
增量式只算相邻两次输出的差值:
Δu(k) = Kp·[e(k)-e(k-1)] + Ki·e(k) + Kd·[e(k) - 2e(k-1) + e(k-2)]
然后 u(k) = u(k-1) + Δu(k)。增量式的优势第一在于输出是叠加量,即使 e 突然阶跃,Δu 也不会让占空比瞬间打满,天然压制了阶跃冲击;第二在于电机从驱动模式切到再生制动模式时,电流参考方向翻转,位置式的积分项残留着上一模式的累计值,需要手动清零,而增量式把积分藏在输出里,只要对 u(k-1) 限幅,就不会出现积分饱和导致的超调。制动能量回收建议直接用增量式,省掉模式切换时的积分复位逻辑。
离散化换算要注意:增量式代码里的 Ki 已经吸收了采样周期 Ts。连续域 PI 为 C(s) = Kp + Ki/s,后向差分离散后,增量表达式的积分增益是 Ki_cont·Ts,而不是连续值 Ki_cont 本身:
# 连续域 PI 到增量式离散系数的换算 Ts = 1e-4 # 电流环采样周期 s Kp = 3.14 # 连续比例增益 Ki = 942.0 # 连续积分增益 kp = Kp ki = Ki * Ts # 增量式代码中的 ki,已吸收采样周期 print("kp =", kp, "ki =", ki)很多人仿真收敛后搬到单片机就发散,十有八九是把 Ts 这个因素漏了:Simulink 的连续 PI 模块和 C 代码的增量式 Ki 差了 10000 倍(采样周期倒数)。换算后先在代码里打印 kp/ki 和仿真参数对比,再上电。
3.2 电流内环与转速外环的级联结构
制动能量回收的教科书式做法是级联 PID 结构——内环是电流环,外环是转速环或制动力矩环。外环根据制动踏板深度算出目标电流 i_ref,内环快速跟踪 i_ref。分层的理由是电流环响应远快于转速变化,两个环带宽差一个数量级时可以独立整定,互不干扰,这也是级联 PID 控制相对单环 PID 在制动场景下的根本优势。
一个实用的带宽分配原则:电流环带宽设为 PWM 频率的 1/10 到 1/20,转速环带宽设为电流环的 1/5 到 1/10。以 20kHz PWM 为例,电流环带宽 1~2kHz,转速环 200Hz 左右。内环只放 PI 不放 D,因为电流采样噪声在微分项上会被放大得非常难处理;转速环可以用 PID,但微分项要加低通滤波,或者干脆不用。外环输出 i_ref 时还要做斜率限制,防止踩下制动瞬间外环直接给出满幅电流指令,这一点实车标定里几乎是必做的。
3.3 参数整定方法与初始值估算
整定前先用模型估算初始值,不要盲试。对电流环,控制器取 PI,被控对象 G(s) = 1/(Ls+R),开环传递函数为:
L(s) = (Kp·s + Ki) / [s·(Ls + R)]
令 Ki/Kp = R/L,恰好对消对象零点,得到 L(s) = Kp/(Ls)。若要求穿越频率 ω_c,则 Kp ≈ L·ω_c,Ki = Kp·R/L。代入表 1 参数并取 ω_c = 2π×1000:
Kp ≈ 0.5e-3 × 6280 ≈ 3.14,Ki ≈ 3.14 × 0.15 / 0.5e-3 ≈ 942
这是理想零极点对消的结果,仿真里通常乘 0.7~0.9 作为保守初值,避免参数误差导致对消不完全而激发谐振峰。
转速环如果手头有开环阶跃响应曲线,可以用 Ziegler-Nichols 临界比例度法:仅用 P 控制加大增益让系统临界振荡,记录临界增益 K_u 与临界振荡周期 T_u,然后按经验表取 P、PI、PID 三组参数。注意 Z-N 给出的参数偏激进,用在制动这类安全相关工况要打七折再试。
网上资料里常见的 pb、ti、td 是早期仪表的记号:比例带 PB = 100/Kp(百分数),积分时间 TI = Kp/Ki,微分时间 TD = Kd/Kp。搜到老式仪表或 PLC 的调参经验时,先换算成 Kp、Ki、Kd 再填到程序里,直接照抄会造成数量级错误。压力调节、温度控制里流传的「经验值」也是同一个换算逻辑,套用到电流环时要把采样周期和对象时间常数一起折算。
4. 基于 Simulink 的制动能量回收仿真模型搭建
4.1 模型分层与模块划分
Simulink 模型按信号流分成五层:指令层、对象层、机械层、能量层、控制层,每层之间用 Goto/From 或数据总线连接。分层模块划分如下表,照着搭不会漏信号:
| 层 | 模块 | 输出 | 说明 |
|---|---|---|---|
| 指令层 | 阶跃/斜坡信号 | 目标电流 i_ref | 模拟制动踏板下踩 |
| 对象层 | 电枢电路 1/(Ls+R) | 实际电流 i | 叠加反电动势扰动 |
| 机械层 | 1/(Js+B) 积分链 | 转速 ω | 回算反电动势 |
| 能量层 | 电池 SOC 积分器 | SOC/% | 判断回收收益 |
| 控制层 | PI 子系统、PWM 零阶保持器 | 占空比 d | 离散采样动作 |
PWM 环节在 Simulink 里用零阶保持器和饱和模块模拟即可。除非要观察电流纹波细节,否则不必真的搭三角载波比较器,那会让仿真步长被迫缩小,拖慢整个模型的运行速度。
4.2 参数化脚本与最小模型命令
把表 1 的参数写进独立的初始化脚本 init_brake.m,模型里所有模块引用工作区变量。这样改一次参数即可重跑全部工况,不用进模型里逐个双击翻找:
% init_brake.m 制动能量回收仿真初始化脚本 V_bat = 72; % 电池端电压 V R = 0.15; % 相电阻 Ohm L = 0.5e-3; % 相电感 H Kt = 0.12; % 转矩常数 Nm/A Ke = 0.12; % 反电动势常数 V/(rad/s) J = 0.02; % 等效转动惯量 kg*m^2 B = 0.001; % 粘滞阻尼 Nm*s/rad f_pwm = 20e3; % PWM 频率 Hz Ts_ctrl = 1e-4; % 电流环采样周期 10kHz Ts_spd = 1e-3; % 转速环采样周期 1kHz % 电流环 PI 初值,按零极点对消计算 wc = 2*pi*1000; % 穿越频率 rad/s Kp_cur = 0.8 * L * wc; % 乘 0.8 保守化 Ki_cur = Kp_cur * R / L; % 制动工况参数 V0 = 20; % 制动初速度 m/s target_i = 30; % 外环给出的目标电流 A脚本尾部的 Kp_cur、Ki_cur 会写入工作区,仿真前先跑一遍并打印数值。如果电流阶跃响应在 3ms 内跟上且无超调,说明 0.8 系数合适;震荡就把系数降到 0.5,响应太慢往 1.0 调。
4.2.1 求解器设置:固定步长优于变步长
仿真器选固定步长,步长取 Ts_ctrl 的整数分之一,比如 1e-5s。这样控制环的采样动作和物理积分严格对齐,变步长求解器在离散控制器切换点会产生数值毛刺,看上去像振荡,实际是求解器步径自适应造成的假象。停止时间设 5s,足够覆盖一次减速到停车的过程。
4.3 仿真参数设置与结果判读
判读结果重点看四条曲线:实际电流 i 对 i_ref 的跟踪、转速 ω 的下降斜率、电池 SOC 的上升量、母线电压是否被拉低。电流曲线若在第一拍就顶到上限且长时间压不下来,是积分饱和;转速若在中段出现变斜率,说明反电动势前馈缺失或外环带宽太低。一个可接受的指标是:电流超调小于 5%,跟踪建立时间小于 5 个采样周期,SOC 在 3 秒制动内回升 0.1%~0.5%,具体取决于电池容量设定。
仿真里最容易踩的坑是把制动指令当成阶跃信号给内环。实车驾驶员踩踏板是有限斜率的,阶跃指令会放大外环积分饱和问题。建议在指令层加一个 Rate Limiter 模块,斜率限幅设为 100A/s 量级,这样仿真结果才贴近可实现的制动体验。
5. 单片机 C 程序实现与仿真文件联调
5.1 定时器资源与中断优先级分配
仿真验证通过后把控制器搬到单片机,常见选择是 STM32F103 或 STC 系列,两者都有硬件 PWM 和 ADC 触发。PWM 20kHz 由定时器自动重装载值决定;电流环用 ADC 采样完成中断触发,保证每个 PWM 周期固定点采样;转速环用基本定时器 1ms 中断。中断优先级分配原则:电流环最高,要求严格等间隔;转速环次之;主循环只做显示、按键、通讯等非实时任务。
| 功能 | 定时器 | 频率 | 优先级 |
|---|---|---|---|
| PWM 输出 | TIM1 CH1 | 20 kHz | 硬件 |
| 电流采样 + 电流环 PI | ADC 注入通道 | 10 kHz | 最高 |
| 转速环计算 | TIM2 | 1 kHz | 次高 |
| 串口输出调试数据 | USART1 | 按需 | 低 |
电流环中断服务函数里只做三件事:读 ADC、执行 PID、更新占空比。printf、延时、浮点打印一律不放进去,否则中断执行时间超过 100us 就会丢采样点,电流波形出现周期性毛刺。调试数据改用 DMA + 环形缓冲,在主循环空闲时发送。
5.2 增量式 PID 的 C 代码实现
下面是一份可直接移植的增量式 PID 实现,结构体把参数和状态封装在一起,方便同时实例化电流环和转速环两个控制器:
/* pid.h */ typedef struct { float kp; /* 比例增益 */ float ki; /* 积分增益,已含采样周期 */ float kd; /* 微分增益 */ float out; /* 上一次输出,增量式累积量 */ float out_max; /* 输出限幅 */ float err_last; /* e(k-1) */ float err_prev; /* e(k-2) */ } pid_t; float pid_update(pid_t *p, float ref, float fb);/* pid.c */ float pid_update(pid_t *p, float ref, float fb) { float err = ref - fb; /* 当前偏差 */ float du; du = p->kp * (err - p->err_last) /* 比例增量 */ + p->ki * err /* 积分增量 */ + p->kd * (err - 2.0f * p->err_last + p->err_prev); /* 微分增量 */ p->err_prev = p->err_last; p->err_last = err; p->out += du; /* 累加到输出 */ if (p->out > p->out_max) p->out = p->out_max; if (p->out < -p->out_max) p->out = -p->out_max; return p->out; }代码说明:du 是本次要加到占空比上的增量,比例项对偏差变化率敏感,积分项消除稳态误差,微分项压制超调。输出限幅放在累加之后是增量式抗积分饱和的关键——out 一旦顶到边界,后续 du 无论正负都只能从边界调整,不会无限累积。out_max 取 0.95 而不是 1.0,给 PWM 死区留余量,防止上下桥臂直通。
电流环中断里这样调用:
/* 10kHz 电流环中断,TIM1 更新事件触发 */ void TIM1_UP_IRQHandler(void) { if (TIM_GetITStatus(TIM1, TIM_IT_Update)) { TIM_ClearITPendingBit(TIM1, TIM_IT_Update); i_fb = adc_read_current(); /* 读取相电流,均值滤波 */ duty = pid_update(&pid_curr, i_ref, i_fb); pwm_set_duty(duty); /* 更新比较寄存器 */ } }注意中断函数内避免浮点除法、三角函数、sqrt。STM32F103 没有硬件 FPU,浮点乘加够用,但要避免慢库函数。若换用不带 FPU 的 51 单片机,把 Kp/Ki 定点化为 Q15 格式,中断时间能显著缩短。
5.3 与仿真数据的对比验证
联调的核心是让单片机输出与 Simulink 相同的观测量。做法:单片机里开环形缓冲,把每次电流环中断的 ref、fb、duty 按 16bit 打包,串口发二进制帧;上位机用 MATLAB 解析后与 Simulink 波形叠画。比较前先对时间轴对齐,以 i_ref 跳变的时刻为基准,而不是按启动时刻对齐:
% plot_compare.m 比较仿真相与实测波形 sim = load('sim_i.csv'); % Simulink To Workspace 导出 mcu = load('uart_i.bin', 'uint16'); t_sim = sim(:,1); i_sim = sim(:,2); t_mcu = (0:length(mcu)-1) * 1e-4; figure; plot(t_sim, i_sim, 'b-', t_mcu, mcu*0.01, 'r--'); legend('Simulink','MCU'); xlabel('Time (s)'); ylabel('Current (A)');两条曲线形态一致但幅值差一个比例系数时,先查 ADC 采样电阻和电流传感器标度;相位滞后明显,查电流环是否被中断里的多余代码拖慢;波形一致但叠加高频噪声,查 PWM 开关时刻是否与 ADC 采样点重叠。建议把 ADC 触发点挪到 PWM 周期谷底,避开开关噪声。
6. 仿真到实物前的调试技巧与参数边界
6.1 三种积分饱和处理的取舍
增量式 PID 配合输出限幅能解决大部分饱和问题,但外环给出的 i_ref 长时间超过硬件能力时,内环会一直顶在限幅值。三种常见处理:积分钳位(输出饱和时冻结 Ki 项)、反馈抑制(把计算输出与实际输出的差值按 1/Kp 反馈回积分项)、增量式自带的自然回退。电流环推荐积分钳位,实现成本最低:在 pid_update 里加一个 sat_flag,out 到达限幅时把 ki 项临时置零即可。转速环因为有踏板斜率限制兜底,用自然回退就够。
6.2 采样周期与 PWM 频率的匹配规则
采样周期必须小于对象时间常数的一半。表 1 中 L/R = 0.5ms/0.15 ≈ 3.3ms,10kHz 采样远满足要求。换用大电感电机时 L/R 变大,采样率可以降低,但 PWM 频率要与采样率保持整数倍关系,避免两个时钟竞争 ADC 触发。验证 ISR 是否超时,用一个 GPIO 翻转法:
/* 电流环 ISR 时间测量:示波器挂 PB0 测高电平宽度 */ GPIO_SetBits(GPIOB, GPIO_Pin_0); /* 进入 ISR 拉高 */ i_fb = adc_read_current(); duty = pid_update(&pid_curr, i_ref, i_fb); pwm_set_duty(duty); GPIO_ResetBits(GPIOB, GPIO_Pin_0); /* 退出 ISR 拉低 */高电平宽度应小于采样周期的 30%;超过 50% 说明中断体过重,需要把滤波、标定等计算挪到主循环。
6.3 仿真与实物的差异清单
仿真模型永远比实物干净。下表列出最常见的差异来源,每一项在实物调试前都要有对应预案:
| 差异项 | 仿真假设 | 实物常见问题 |
|---|---|---|
| 电感值 | 恒定 | 电流越大电感越小,需测饱和曲线 |
| 死区时间 | 忽略 | 低负载时电流零点附近钳位失真 |
| ADC 噪声 | 理想 | 需硬件滤波加软件均值 |
| 温度 | 恒定 | R 随温度升高,Ki 需按 R/L 重新折算 |
| 制动踏板 | 阶跃 | 实车需斜率限制,否则冲击感明显 |
把 Simulink 调好的 Kp 直接搬到单片机通常能工作,但只对同一个电机有效。换电机后先重测 L、R,再按第 3.3 节的公式重算参数,不要凭手感一点点加。实车上最后一步是标定踏板斜率限幅和电流上限,这两个值仿真里可以给得很宽,实物上往往由电池允许充电倍率和制动舒适性共同决定。
本文还有配套的精品资源,点击获取