小型汽油机发电机电控核心:增量式PID转速环与点火查表实现
2026/9/13 11:15:56 网站建设 项目流程

简介:这是一份基于PID算法的小型汽油机电控源码包,面向汽车电子、嵌入式开发学习者和相关工程技术人员,用于理解并实现发动机喷油、点火及发电机控制逻辑。压缩包共3个文件,均为C语言源码,分别对应主控制流程、启动初始化以及数据定义存储部分,整体大小仅29KB,结构紧凑便于直接分析调用。已有136人学习。代码展示了PID控制器在闭环控制中的具体落地方式,包含比例、积分、微分三项调节的工程写法,同时覆盖点火提前角控制和燃油喷射策略等关键知识点。通过研读这些源码,可掌握发动机ECU基础调度框架、传感器信号处理、执行器驱动思路,还能参考其变量组织和模块划分技巧,用于实际项目移植或PID参数整定,尤其适合需要快速入门发动机电控或动手改写控制参数的开发者。

1. 小型汽油机转速不会自己稳住:电控PID解决的问题是负载突变下的掉速

把一台3 kW的负载接到小型汽油发电机上,转速可以在1秒内跌掉500 r/min。机械调速器响应太慢,电控节气门之所以能顶住,靠的是对转速做闭环PID。所谓“发动机的电控PID代码”,核心是两套逻辑:一套用PID稳定转速,一套用查表控制点火,两个环都放在小型汽油机控制程序里。它主要解决三类问题:发电机输出频率的稳定、突加负载时的转速回落、以及点火角不会干扰转速环。适合做小型汽油机发电机组电控的工程师,也适合准备把算法从试验台搬进量产程序的开发者。

2. 转速环的两种实现与增量式PID的工程取舍

2.1 被控对象是哪个物理量:转速闭环为什么放在第一优先级

发电机并接负载的时刻,电磁阻力矩几乎是阶跃增加的。转子侧的力矩平衡关系是 J·dω/dt = 发动机输出扭矩 - 负载阻力矩。负荷一上来,转速掉下去,磁电传感器采集到的脉冲间隔变宽。如果这时只把节气门直接推到机械行程上限,转速又会拉过头,来回折腾几次之后,油门执行器、进气管和点火系统之间会出现明显的相位拉扯。

PID就是钳住这一问题的闭环算法,它根据“目标转速 - 实测转速”的误差来决定油门开度的增量方向。对发电机组来说,转速的优先级高于电压:输出电压幅值由AVR励磁调节器负责,而发电频率由转速决定。常规控制器的划分方式是,转速PID住在发动机调速器位置,AVR独立维持电压闭环,两者在波动工况下形成级联PID控制的外层关系——电压环校正励磁,转速环抑制转速跌落。把转速环作为主控制回路,是这一类发动机电控程序落地代码时的第一准则。

这里有个容易被忽略的点:小型机的油门执行器多数是直流舵机或步进电机,它们本身带有位置保持能力,启动时不需要知道“现在油门在哪个绝对位置”也能安全起步。所以整个控制程序里,转速环必须做成“按误差给增量”的结构,而不是要求一个绝对占空比,否则断电重启后执行器会和程序保存的位置值失去同步。

2.2 位置式与增量式:小型汽油机控制程序里为什么选增量式

先看两种PID的差异。位置式PID对偏差做完整积分,输出的是绝对控制量;增量式PID只计算本次相对于上一次的输出变化量 Δu,由外部执行器自己累加位置。小型汽油机控制程序里,舵机、步进电机这类保持型执行器天然适合增量式:程序断电再上电,油门位置由机械回位弹簧或舵机自带电位器决定,代码只在下一次控制循环里从“当前物理位置”继续加加减减。

下面是用C写的增量式PID骨架,单片机定时中断里直接调用:

typedef struct { float kp; /* 比例增益 */ float ki; /* 积分增益 */ float kd; /* 微分增益 */ float err_prev; /* e(k-1) */ float err_prev2; /* e(k-2) */ int16_t out_inc; /* 本次输出增量 */ } IncPid_t; int16_t IncPid_Update(IncPid_t *p, float target, float current) { float e = target - current; /* 增量式PID:Δu = Kp*(e(k)-e(k-1)) + Ki*e(k) + Kd*(e(k)-2e(k-1)+e(k-2)) */ p->out_inc = (int16_t)(p->kp * (e - p->err_prev) + p->ki * e + p->kd * (e - 2.0f * p->err_prev + p->err_prev2)); p->err_prev2 = p->err_prev; p->err_prev = e; return p->out_inc; }

比例项用的是相邻两次误差的差,积分项对当前误差累积,微分项取二阶差分,这种写法不需要在PID对象里保存绝对值。调用方拿到 out_inc 之后,累加到油门占空比上:

throttle_pos += IncPid_Update(&pid, 3000.0f, current_rpm); throttle_pos = CLAMP(throttle_pos, 100, 800); /* 10% ~ 80% 占空比 */

注意采样和控制是异步的,IncPid_Update 里的三个增益本质上都带时间尺度:Ki 和 Kd 的数值只有在控制周期固定时才有意义。如果控制周期从 10ms 改成 50ms,Ki 要按比例缩小,Kd 要按比例放大,否则积分项一个周期顶过去五个周期,转速会先狂摆再慢慢收敛。

2.3 采样周期、控制周期和输出频率:三个周期不能混成一个

小型汽油机的曲轴每转一圈只产生一到两个磁电脉冲,转速信号天然是周期性变化量。程序里至少要拆出三个时间尺度:磁电信号的输入捕捉周期决定转速分辨率;PID控制周期决定调速带宽;PWM输出频率决定油门执行器的最终平滑度。我按常见做法列了一组可以起步的值:

周期典型值作用设置不合理现象
转速采样周期2ms读取最新转速值,供PID调用太短则转速跳动大,太长则掉速看不到
PID控制周期10ms计算油门增量过慢拉不住突加负载,过快油门抖动
PWM输出频率50Hz~200Hz驱动舵机/步进电机太低会听到共振异响,太高执行器发热

实际调试点里最容易踩的坑是把PID控制周期写在延时函数后面,误以为“跑得越快越好”。速度环带宽高于物理执行机构能力时,油门舵机会变成噪声放大器:转速还没有真正降下来,PID已经把节气门来回打了好几次。发电机空载低速工况下这种抖动尤其明显,因为负载电流小、曲轴力矩波动占主导,采样到的转速本身就带着周期性脉动。

3. 发动机电控PID程序的C代码骨架:转速捕捉、油门限幅与串口调试

3.1 磁电传感器转速捕捉:用输入捕获算0.1r/min分辨率

小型汽油机的飞轮或磁电机转子上一般有信号齿,磁电线圈每过一个齿输出一个脉冲。程序用定时器捕获这个脉冲的上升沿,通过相邻两次捕捉的计数值计算转速。转速计算公式是:转速 = 60,000,000 / (两次脉冲间隔μs × 每转脉冲数)。

下面是基于STM32输入捕获的中断处理函数,这段代码结构在多数单片机型号上都能直接套用:

#define PULSE_PER_TURN 2U /* 飞轮每转产生2个磁电脉冲 */ #define TIMER_FREQ 1000000UL /* 定时器计数频率:1MHz */ static uint32_t last_cap = 0; static volatile uint16_t speed_rpm = 0; void TIM_CaptureCallback(uint32_t count) { uint32_t diff; /* 处理定时器回绕:diff是两次脉冲之间的计数值,单位μs */ if (count >= last_cap) diff = count - last_cap; else diff = (count + UINT32_MAX) - last_cap + 1; last_cap = count; if (diff == 0) return; /* 例:diff=10000μs,每转2脉冲,则转速=60000000/2/10000=3000rpm */ speed_rpm = (uint16_t)(60000000UL / PULSE_PER_TURN / diff); }

这段代码的关键在 diff 的边界处理:定时器计数值溢出回绕后,直接相减会得出负值。用条件判断拆成两段再相加,能保证回绕瞬间不冒出一次假转速。另外实际齿数不是2的机型,PULSE_PER_TURN 换成真实齿数即可,函数整体不用动。

转速平滑方面,磁电信号在点火瞬间容易叠加高压干扰,导致 diff 突然变小、转速跳高。常规做法是在中断里加一个简易中值滤波:连续存3次 diff,取中间值参与计算,或者直接丢弃偏离上次值超过20%的样本。不要在中断里做完整排序,那个耗时对实时性没有意义。

3.2 油门执行器限幅与怠速锁定:防止PID积分逃逸

增量式PID本身没有饱和保护,如果油门输出一直累加,而执行器已经到机械位置,下一次增量仍然往同一个方向加,恢复后转速会过冲一大截。程序里必须同时限幅和锁存:限幅限制油门占空比范围,锁存让执行器到限位后不再累加。

void SpeedControl_Task(void) { static uint8_t tick; int16_t inc; if (++tick < 5) /* 控制周期=5个2ms采样周期,即10ms */ return; tick = 0; /* 低于450rpm认为启动拖动阶段,不做PID调速 */ if (speed_rpm < 450) { throttle_wanted = THROTTLE_START; return; } inc = IncPid_Update(&engine_pid, target_rpm, speed_rpm); throttle_wanted += inc; if (throttle_wanted > THROTTLE_MAX) { throttle_wanted = THROTTLE_MAX; /* 到达上限后丢弃增量,防止回归时超调 */ } else if (throttle_wanted < THROTTLE_MIN) { throttle_wanted = THROTTLE_MIN; } SetThrottlePulse(throttle_wanted); }

这个任务函数把启动状态和PID调速状态做了硬隔离。启动转速阈值在450r/min以下,PID不介入,由程序提供一个固定的起动开度;转速上来后才切到PID输出。注意限幅之后不是 return,而是把目标值钳住,这样设计是为了下一次循环能基于当前合法位置继续计算增量。如果限幅后直接清空增量,油门响应会变迟钝,实际恢复时间被拉长。

3.3 串口观察PID变量:转速环调试的唯一直观手段

裸机开发阶段看不到变量,最常见的做法是通过串口按固定帧输出调试数据。输出内容至少包括:正常运行时间、目标转速、实际转速、油门占空比、PID误差五个量。

printf("t=%lu spd=%d set=%d thr=%d err=%d\r\n", tick_ms, speed_rpm, target_rpm, throttle_wanted, target_rpm - speed_rpm);

串口波特率用 115200,每50ms输出一帧,在电脑上用串口助手或Python脚本画曲线。注意 printf 重定向要放在独立低优先级线程或者 DMA 里,不能卡住定时器中断。你不需要把“PID内部参数”全打出来,只观察误差的方向和油门的变化速度就够了:误差长期正数说明油门不够或积分过弱;油门在目标值附近来回摆动说明Kp过大;负载突加后误差恢复慢则积分作用不足。

4. 点火提前角不进PID:查表加修正才是小型汽油机控制程序的稳妥做法

4.1 点火时刻为什么不能交给闭环PID

点火提前角直接决定缸内燃烧峰值压力的出现位置。提前角偏差几度,燃烧压力就在活塞到达上止点之前或之后形成,体现为怠速不稳、功率下降甚至活塞温度过高。PID的积分环节正好不合适:积分项会在偏差持续时不断累积,直到把点火角推向极限。燃烧过程又不能马上用传感器把峰值压力测回来,积分调节效果滞后时间太长,实际表现就是急加油门时爆震、收油门时放炮。

所以小型汽油机控制程序里的点火大多是开环查表加修正。转速PID负责油门,点火模块独立工作:以转速和负载为输入查二维表,得到基础点火角,再叠加蓄电池电压、进气温度、爆震回退三个修正量。查表在单片机里是最快也最不易失控的控制方式,点火的声学特性决定了它不能被积分环节“温吞”地去修正。

4.2 二维点火表的代码实现:横坐标转速,纵坐标负载

负载可以取电子节气门的绝对位置,也可以取进气压力MPA值。发电机工况下负载变化很快,用节气门位置比进气压力更直接,标定工作量也小。下面的查表函数是一个工程上常见的骨架:

/* 5x5点火提前角表,单位0.1° BTDC,横轴转速rpm,纵轴油门/节气门开度 */ static const int8_t ign_map[5][5] = { /* 1000 2000 3000 4000 rpm */ /* 20% */ { 80, 120, 140, 130 }, /* 40% */ { 70, 110, 130, 125 }, /* 60% */ { 60, 100, 120, 115 }, /* 80% */ { 55, 90, 110, 105 }, /*100% */ { 50, 80, 100, 95 }, }; int8_t IgnitionLookup(int16_t rpm, uint16_t throttle) { int8_t base; uint8_t rpm_idx, thr_idx; /* 转速和油门都按边界值取最近点,转速边界2000rpm以下用首行 */ rpm_idx = (uint8_t)CLAMP((rpm - 1000) / 1000, 0, 4); thr_idx = (uint8_t)CLAMP((throttle - 200) / 200, 0, 4); base = ign_map[thr_idx][rpm_idx]; /* 蓄电池电压低时退后2°,防止点火能量不足失火 */ if (battery_voltage < 11.0f) base -= 2; /* 爆震回退上限3°,只在爆震持续时生效 */ if (knock_count > 3) base -= 3; return base; }

点火表数值要给三个方向的边界保护:转速低于怠速、转速高于最高转速、负载超过满量程时,都要有钳位,避免数组越界读到随机数据。另外注意base存在修正量叠加后小于0的可能,点火角如果退到上止点之后,燃烧效率会显著变差,所以函数返回前应该再做一次下限钳制,典型下限是 5°BTDC。

标题里的“点火”在整段程序中扮演的是配合角色:怠速不稳定时,PID会先把怠速旁通阀或节气门调到一个稳定位置,点火角只在最后5°范围内做转速补偿,两者不会同时向同一个方向做大范围调整。

4.3 转速PID与点火表的交互边界:按工况划分控制权

火花点火提前角对扭矩的调节作用在一两个工作循环内就能显现,比油门执行器快一个数量级。如果在怠速闭环里同时放开转速PID和点火角PID,两者都在争转速误差,就会出现典型的控制器打架:一个加点火角让转速升高,另一个看到转速偏高又收油门,最终系统在某个非最优工作点震荡。

常规的职责划分是:转速偏差大于80r/min时只有油门PID动作;偏差小于80r/min进入怠速稳定区,点火角按怠速MAP做小范围闭环,幅度5°以内;进入稳态发电工况后点火角回到查表值。这个边界值写在代码里要带滞回,比如进入小偏差区是80r/min,退出是120r/min,否则会在边界上频繁切换。

5. 给发电机做一次阶跃负载调参,把增量式PID参数收敛下来

5.1 负载试验台与合格指标

调参不能空载看波形,必须做负载阶跃。自制一个发电机调参台不复杂:输出端接一只大功率电阻箱或几盏并联的卤素灯,用接触器控制投入和切除,投入瞬间就是最真实的阶跃负载。记录转速跌落值和恢复时间,合格线通常这样定:

调参指标建议范围
转速跌落峰值不超过目标转速的6%
恢复时间1s ~ 2s内回到±1%
稳态转速误差±0.5%以内
油门执行器振荡目测无可见往复抖动

5.2 使用Ziegler-Nichols经验法获得增量式PID初值

先把Kd和Ki设为0,只保留Kp。从小到大增加Kp,每次做一次阶跃负载试验,直到转速出现等幅持续振荡,记录此时的临界增益Ku和振荡周期Tu。然后用Ziegler-Nichols经验公式:Kp=0.6Ku,Ti=0.5Tu,Td=0.125Tu。增量式PID代码里用的是增益而不是时间,需要换算:Ki = Kp * Ts / Ti,Kd = Kp * Td / Ts,其中Ts是控制周期,比如10ms。

这套方法在加热炉、压力锅、小型汽油机上通用,但不要把加热炉压力PID的参数经验值直接搬过来:加热系统的纯滞后几秒到几十秒,汽油机转速环的纯滞后只有几十毫秒,盲目套温度控制的Kp往往只是其千分之一量级。

5.3 两个必看的抖动陷阱

第一个是Kd偏大的典型现象,发电机稳态运行时空载或轻载时转速无规律跳动,声音发闷,尤其停机前转速下降段更明显。转速采到的信号本身带齿距误差和高频干扰,微分项对噪声敏感,会把采样抖动放大成油门波动。多数转速环甚至干脆不用Kd或用极小值0.01~0.02。

第二个是“满油门限幅后恢复过冲”:突加负载时油门顶到80%上限,转速慢慢回升,等误差变小时PID仍在向80%方向累加增量,转速会一口气冲到目标值之上。程序要做的不是把限幅放宽,而是在限幅状态期间冻结积分累加,也叫抗积分饱和。实现起来就在3.2节的限幅分支里把inc丢弃即可。

5.4 空载怠速与发电负载两套参数的切换

小型汽油机空载3000r/min和带3kW负载3000r/min需要的PID参数不同:空载惯量大、油门位置低,Kp稍大也不会抖动;带载后磁电信号毛刺多,相同的Kp可能引起转速震荡。量产程序里常见做法是准备两套参数,按发电机输出功率或负载电流切换,切换时对增量做一阶滤波,避免参数跳变瞬间油门突跳。

还有一种简化的做法,只在负载接入后的前200ms内降低Kd或关掉Kd,200ms后恢复正常参数,这样既保留空载的快速响应,又规避了带载时微分项的噪声放大的问题。如果硬件算力足够,再考虑模糊PID兜底:在目标转速附近细分误差区间,每个区间给不同Kp,配合原本的增量式PID框架,复杂度增加不大,效果比单参数插值更直观。

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

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

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

立即咨询