两轮自平衡小车串级 PID 与 PID 神经网络整定实践
2026/9/18 15:14:04 网站建设 项目流程

简介:《基于PID神经网络的两轮自平衡小车研究》面向自动化、嵌入式控制与神经网络交叉方向的学习者与研发者,聚焦用PID神经网络优化两轮自平衡小车控制器参数这一核心问题。内容给出C语言神经网络辨识程序思路:对上一时刻编码器、陀螺仪与加速度计的数据进行处理,获得角度、角速度、位置和位置微分四个状态量,据此生成下一时刻的电机控制信号;反向调权时以符号函数近似代替偏导,结合BP算法训练NNI辨识模型与NNC控制器,再将优化参数传递给AVR控制器。内容还涉及微控制器与EEPROM之间的参数传送流程,并通过实验对比说明优化后小车直立角度波动由约±2.1°降至±1.4°,平衡性能提升约33.3%。资源包仅含1个PDF文件,约515KB,便于离线查阅与检索,已有168人学习下载。

1. 两轮自平衡小车为什么只靠串级 PID 调不稳

车模第一次上电,直立环 Kp 给到 20,电机立刻高频嗡鸣、车身抖成筛子;降到 8,车又软塌塌往一侧倒。加上速度环做串级 PID 控制,勉强站住了,可换到地毯上跑两圈,同一组 kp ki kd 又不管用了——做两轮自平衡小车的人大多都撞过这堵墙。

根子不在手抖。俯仰通道本质是一阶倒立摆:非线性、欠驱动、参数随负载和地面时变,而 PID 控制器里的三个系数是固定常数,只能在某个工作点附近近似最优。PID 神经网络要解决的就是这件事——把固定增益换成由神经网络在线输出的增益,让控制器自己朝着 pid 最优曲线的方向收敛。

它适合两类人:玩过 STM32 裸机 PID 控制电机转速、看得懂位置式 PID 用离散化差分方程的同学,想在毕业设计或数学建模里把控制算法抬一层;以及做运动控制的工程师,想给 pid 闭环控制找一条不靠反复试凑的整定路径。

2. PID 与神经网络的结合方式:位置式、增量式 PID 与三种融合形态选型

2.1 位置式 PID 用离散化差分方程落到代码里

位置式 PID 的形式最直观,也最适合讲清楚 kp ki kd 的物理含义:输出直接由误差的当前值、累积值、变化率三部分加权而成。落到单片机里就是下面这段,注意积分累加要和采样周期 dt 绑定,不要把 Ki 当成一个纯粹的比例系数用。

/* position_pid.c —— 位置式 PID 的离散化实现,带积分限幅与输出限幅 */ typedef struct { float Kp, Ki, Kd; /* 三参数,由模拟式的比例带 PB、积分时间 Ti、微分时间 Td 换算 */ float e1; /* e(k-1) */ float integral; /* 积分累加项 Σe(j)·dt */ float out_max; /* 输出限幅,最终映射到 PWM 占空比 */ } PosPID; float PosPID_Calc(PosPID *p, float target, float fb, float dt) { float e = target - fb; /* 当前误差 */ p->integral += e * dt; /* 用 dt 折算,采样周期一变积分量纲就变 */ float lim = p->out_max / (p->Ki + 1e-6f); if (p->integral > lim) p->integral = lim; /* 积分限幅是抗饱和第一道闸 */ if (p->integral < -lim) p->integral = -lim; float u = p->Kp * e + p->Ki * p->integral + p->Kd * (e - p->e1) / dt; /* dt 不能为 0,调度周期抖动会放大噪声 */ p->e1 = e; if (u > p->out_max) u = p->out_max; /* 输出限幅,防止 PWM 长时间满占空比 */ if (u < -p->out_max) u = -p->out_max; return u; }

Kp 决定响应刚度,Ki 消静态倾角偏差但引入相位滞后,Kd 抑超调却对 MPU6050 的角速度噪声极其敏感。常见做法是舍弃差分得到的微分,直接用陀螺仪输出的角速度作为微分项,等价于微分先行,噪声小一大截。位置式的短板也很清楚:积分项每拍都要累加,且 Ki 与采样周期强耦合,换个主频或改调度周期,整定好的参数就得重来。

2.2 增量式 PID 算法与两轮自平衡小车的串级 PID 控制分层

增量式只输出本拍的增量 Δu,由调用方累加。好处是天然带积分限幅能力,且算式中 Ki 已经吸收掉了采样周期 T,写成代码就三步乘加。

/* inc_pid.c —— 增量式 PID,输出 Δu(k),由调用者累加得到 u(k) */ float IncPID_Calc(IncPID *p, float target, float fb) { float e = target - fb; /* Δu = Kp·[e(k)-e(k-1)] + Ki·e(k) + Kd·[e(k)-2e(k-1)+e(k-2)] */ float du = p->Kp * (e - p->e1) + p->Ki * e + p->Kd * (e - 2.0f * p->e1 + p->e2); p->e2 = p->e1; p->e1 = e; return du; }

这里的 Ki 实际等于 Kp·T/Ti,所以采样周期 T 固定时它才是常数;改 MCU 主频或改调度周期,必须重算或重新整定,这是新手最容易漏的一条。

串级 PID 控制的层次划分在自平衡小车上基本固定:外环速度或位置(10~20 Hz 执行),内环直立角度(100~200 Hz 执行),外环的输出作为内环的目标值。内外环带宽至少要差 3~5 倍,否则两环互相打架,表现就是车在原地前后抽动、怎么调都不收敛。

2.3 PID 神经网络的三种融合形态与选型对照

到底把神经网络接在 PID 的哪一环,直接决定工作量。三种常见融合形态的差别如下。

融合形态网络结构网络输出学习算法适合场景落地难度
BP 神经网络整定 PID4-5-3 前馈网络Kp、Ki、Kd误差反向传播仿真验证、参数慢时变中,需要对象雅可比
单神经元自适应 PID单神经元 + 3 个权值Δu 增量有监督 Hebb 规则裸机在线实时、算力受限低,几十行 C
模糊 PID(对照组)模糊规则表ΔKp、ΔKi、ΔKd无学习过程经验规则可写清低,但规则靠人工

选型思路很实在:如果目标是论文或数学建模层面体现神经网络,BP 神经网络整定 PID 最好写,曲线也好看;如果目标是车真的站在 STM32F103 这种裸机上跑稳,单神经元自适应 PID 更现实,一次前向加一次权值更新约三十次浮点乘加,1 kHz 毫无压力。

前馈神经网络和 CNN 在这类问题上并不占优——图像处理用 CNN 是因为像素有局部相关性,而控制信号是 4 维短时序标量,小网络足够。循环神经网络引入历史状态的思路可行,但增量式 PID 的二阶差分本身已经携带两拍历史,收益有限,反而多了一份状态维护成本。

3. 从倒立摆建模到 PID 神经网络:两轮自平衡小车的控制结构

3.1 状态量选取与倒立摆简化模型

两轮自平衡小车俯仰通道线性化后近似成一个带右半平面极点的二阶模型,传递函数形如G(s) = b / (s² + a·s - c),其中 c 为正,意味着开环不稳定——不闭环保不住,这是选闭环控制的硬理由。建模时用拉格朗日方程推一遍完整形式,再在小角度处做泰勒展开线性化,是毕业设计里最稳妥的写法。

状态量一般取四个:俯仰角 θ、俯仰角速度 θ̇、车轮前进速度 v、航向角 ψ。执行器只有两个轮子,所以控制输入拆成共模 PWM(管直立和速度)和差模 PWM(管转向)。采样周期上,直立环取 5 ms、速度环取 50 ms 是很成熟的搭配;直立环再往下压到 1 ms 收益有限,反而放大陀螺仪噪声。

3.2 单神经元自适应 PID 的增量式结构与权值更新

单神经元自适应 PID 的输入向量直接取自误差序列:

  • x1(k) = e(k),对应比例分量
  • x2(k) = e(k) − e(k−1),对应一阶差分
  • x3(k) = e(k) − 2e(k−1) + e(k−2),对应二阶差分(微分)

输出为 Δu(k) = K · Σ wi(k)·xi(k),权值按有监督 Hebb 规则更新:wi(k+1) = wi(k) + η·e(k)·Δu(k)·xi(k)。K 是神经元比例系数,管整体收敛速度;η 是学习率,管权值跟随误差调整的猛烈程度。

/* single_neuron_pid.c —— 单神经元自适应增量式 PID,跑在裸机主循环里 */ typedef struct { float w[3]; /* 对应 e、Δe、Δ²e 的三个权值,初值可给 {0.30f, 0.10f, 0.10f} */ float e1, e2; /* 前两拍误差 */ float K; /* 神经元比例系数,典型 0.05~0.50 */ float eta; /* 学习率 η,典型 0.01~0.10 */ float u; /* 累加后的控制量 */ float out_max; /* 输出限幅 */ } SN_PID; float SN_PID_Calc(SN_PID *p, float target, float fb) { float e = target - fb; float x[3]; x[0] = e; /* 比例分量 */ x[1] = e - p->e1; /* 一阶差分 */ x[2] = e - 2.0f * p->e1 + p->e2; /* 二阶差分,等价微分项 */ float du = 0.0f; for (int i = 0; i < 3; i++) du += p->K * p->w[i] * x[i]; p->u += du; /* 增量累加得到最终输出 */ if (p->u > p->out_max) p->u = p->out_max; if (p->u < -p->out_max) p->u = -p->out_max; /* 有监督 Hebb 学习:误差越大、本拍增量越大,权值调整越猛 */ for (int i = 0; i < 3; i++) { p->w[i] += p->eta * e * du * x[i]; if (p->w[i] > 1.0f) p->w[i] = 1.0f; /* 权值限幅,防发散 */ if (p->w[i] < -1.0f) p->w[i] = -1.0f; } p->e2 = p->e1; p->e1 = e; return p->u; }

这里用 Δu 而不是 u 做学习信号,是为了量纲一致:u 的绝对值会随输出漂移,用 u 会让权值整体跟着漂。η 的边界可以拿原始陀螺仪数据实测,超过 0.1 后角速度噪声会直接灌进权值,车身出现低频呼吸式晃动。权值初值给经验值比全零或随机初值收敛快得多。

3.3 BP 神经网络整定 Kp、Ki、Kd 的网络结构与超参

输入层 4 个节点:e(k)、e(k)−e(k−1)、e(k)−2e(k−1)+e(k−2)、偏置 1。隐含层 5 个节点,sigmoid 激活。输出层 3 个节点对应 Kp、Ki、Kd,经 sigmoid 归一化到 [0,1] 后线性映射回物理区间:K = k_min + (k_max − k_min)·Ok。

超参典型取值影响
学习率 η0.20~0.30大则收敛快但易振荡
动量因子 α0.02~0.10抑制振荡,加速穿越平坦区
隐含层节点数4~6少则欠拟合,多则白烧算力
采样周期 T5~20 ms决定 Ki 的换算关系
Kp 映射区间[0, 15]由手调 PID 结果圈定
Ki 映射区间[0, 0.8]过大直接积分饱和
Kd 映射区间[0, 0.6]过大放大陀螺仪噪声

真正的难点不在网络本身,而在反向传播需要被控对象的雅可比 ∂y/∂u。实车上这个量拿不到解析式,只能用符号函数 sign(∂y/∂u) 近似,或者用前后两拍的一阶差分估计。估计不准的直接后果是学习率必须压得很小,收敛慢;这也是 BP-PID 在仿真里漂亮、上实车就拉胯的主要原因。

4. MATLAB 仿真跑通 BP 神经网络整定 PID,再落到 STM32 裸机

4.1 MATLAB 脚本:BP 神经网络在线整定 PID 三参数

先在 MATLAB 里跑通闭环,确认网络能收敛,再移植到板子上。下面这段把对象离散化后逐步迭代,网络每一步输出一组 Kp、Ki、Kd 驱动增量式 PID。

% bp_pid_tune.m —— BP 神经网络在线整定增量式 PID 三参数 clear; clc; T = 0.02; % 采样周期 20 ms,与实车直立环同频 N = 2000; % 总迭代步数 % 被控对象:两轮自平衡小车俯仰通道线性化后的不稳定二阶模型 sys = tf(1.2, [1 0.8 -12.5]); sysd = c2d(sys, T, 'zoh'); % 零阶保持离散化 [Ad, Bd, Cd, ~] = ssdata(sysd); xite = 0.25; % 学习率 η alfa = 0.05; % 动量因子 α wi = rand(5, 4); wo = rand(3, 5); wi1 = wi; wo1 = wo; % 动量项缓存 k_min = [0 0 0]; % Kp Ki Kd 映射下界 k_max = [15 0.8 0.6]; % 映射上界,来自手调 PID 的经验区间 x = [0; 0]; u = 0; u1 = 0; y = 0; y1 = 0; e1 = 0; e2 = 0; I = 0; du = 0; Y = zeros(1, N); U = zeros(1, N); for k = 1:N r = 0.1; % 0.1 rad 俯仰角阶跃,模拟被推一下 e = r - y; xi = [e; e - e1; e - 2*e1 + e2; 1]; % 输入层:误差、一阶差分、二阶差分、偏置 Ii = wi * xi; Oi = 1./(1 + exp(-Ii)); % 隐含层前向 Ik = wo * Oi; Ok = 1./(1 + exp(-Ik)); % 输出层前向 K = k_min + (k_max - k_min) .* Ok'; % 反归一化得到实际增益 Kp = K(1); Ki = K(2); Kd = K(3); du = Kp*(e - e1) + Ki*e + Kd*(e - 2*e1 + e2); % 增量式 PID u = u + du; u = max(min(u, 10), -10); % 输出限幅,等效 PWM 上限 x = Ad*x + Bd*u; y = Cd*x; % 对象推进一步 Y(k) = y; U(k) = u; % ---- 反向传播,性能指标 J = 0.5·e² ---- dyu = sign((y - y1) / (u - u1 + 1e-6)); % 对象雅可比符号近似 dudK = [e - e1; e; e - 2*e1 + e2]; % ∂du/∂K,3×1 dOk = (-e * dyu) * (dudK .* (k_max - k_min)') ... .* (2 ./ (exp(Ik) + exp(-Ik)).^2); % 输出层反传信号 dOi = (wo' * dOk) .* (2 ./ (exp(Ii) + exp(-Ii)).^2); wo1 = wo; wo = wo - xite * (dOk * Oi') + alfa * (wo - wo1); wi1 = wi; wi = wi - xite * (dOi * xi') + alfa * (wi - wi1); e2 = e1; e1 = e; y1 = y; u1 = u; end plot(T*(1:N), Y, 'LineWidth', 1.2); grid on; xlabel('时间 / s'); ylabel('俯仰角 / rad');

几个参数得说清楚。xite取 0.25 是仿真里比较稳的起点,实车要往下压到 0.05 以下;alfa只用来抑制振荡,超过 0.1 会让权值更新带上过强的惯性,反而拖慢收敛。k_mink_max这两个映射区间比网络结构本身更关键——区间圈窄了网络输出永远被截断,圈宽了等效于没加约束,Kp 在高低两端来回跳。区间怎么来?先关掉网络,用手调 PID 把车调到能站,把那组参数放大 1.5 倍作为上界、缩到 0.2 倍作为下界。

4.2 用 matlab pid tuner 与在线整定工具圈定增益初值

MATLAB 自带整定工具能省掉大部分试凑。命令行直接拿增益:

% 用 pidtune 无界面整定,拿一组参考增益和稳定裕度 sysd = c2d(tf(1.2, [1 0.8 -12.5]), 0.02, 'zoh'); [C, info] = pidtune(sysd, 'PID'); fprintf('Kp=%.3f Ki=%.3f Kd=%.3f\n', C.Kp, C.Ki, C.Kd); disp(info.Stable); % 返回 1 表示闭环稳定,可作为初筛

想要交互式拖带宽滑块就敲pidTuner(sysd, 'PID'),界面里直接把响应时间往左拉,看超调怎么变。没有 MATLAB 授权时,快马平台的 PID 智能整定网页端这类工具也能用:贴一段阶跃响应数据点进去,它拟合出一阶惯性加纯滞后模型,再给一组 kp ki kd 初值。

要提醒的是,pid tuner 基于线性模型,给出的是参考初值而非实车最优。它的价值在于让你把 3.3 节那张映射区间表填准——区间对了,网络在线微调才有意义。工程上更常见的组合是先手调得到可行参数,再让网络在这组参数附近做小幅在线修正,而不是让网络从零开始瞎找。

4.3 STM32 裸机 PID 控制与串口回传波形

板子端的分工:TIM 中断 5 ms 触发一次 MPU6050 采样(I2C + DMA),直立环在同一个中断里跑完;速度环放在 50 ms 的软件计数器里;主循环只负责串口发送和按键处理。定时器优先级要保证角度环不被串口中断打断,否则采样周期抖动会直接污染微分项。

调试阶段最忌讳用printf("%f")发浮点。STM32 上的浮点格式化在中断上下文里可能耗掉几百微秒,5 ms 周期里很容易超时,表现为车一开串口就抖。改成二进制定长帧,用 DMA 发送:

/* debug_frame.c —— 把三路关键量打包成一帧,通过串口发给上位机 */ typedef union { float f; uint8_t b[4]; } FloatBytes; void SendDebugFrame(float target, float actual, float u) { uint8_t buf[16]; FloatBytes t, a, o; t.f = target; a.f = actual; o.f = u; buf[0] = 0xAA; buf[1] = 0x55; /* 双字节帧头,便于对齐 */ for (int i = 0; i < 4; i++) buf[2 + i] = t.b[i]; for (int i = 0; i < 4; i++) buf[6 + i] = a.b[i]; for (int i = 0; i < 4; i++) buf[10 + i] = o.b[i]; uint8_t sum = 0; for (int i = 0; i < 14; i++) sum ^= buf[i]; /* 异或校验,能挡住大部分错位 */ buf[14] = sum; buf[15] = 0x0D; HAL_UART_Transmit_DMA(&huart1, buf, 16); /* DMA 发送,别阻塞主循环 */ }

帧格式统一成下表,上位机解析逻辑就能写死,不用来回改。

字节偏移内容类型说明
0~10xAA 0x55uint8×2帧头,用于重同步
2~5目标角度float 小端单位 rad
6~9实际角度float 小端单位 rad
10~13控制量 ufloat 小端映射前的输出
14校验和uint8前 14 字节异或
150x0Duint8帧尾

上位机侧用 Python 收帧,重点是丢包重同步不能写成死循环读满 16 字节,否则错位一次后面全乱:

# plot_pid.py —— 解析二进制帧并绘制角度环曲线 import serial, struct, collections import matplotlib.pyplot as plt ser = serial.Serial('COM3', 115200, timeout=0.1) buf = bytearray() target, actual, ctrl = (collections.deque(maxlen=500) for _ in range(3)) while True: buf += ser.read(64) while len(buf) >= 16: if buf[0] != 0xAA or buf[1] != 0x55: buf.pop(0) # 帧头没对齐,丢一个字节重新找 continue frame, buf = buf[:16], buf[16:] if frame[15] != 0x0D: # 帧尾不对,说明粘包错位,整帧丢弃 continue target.append(struct.unpack('<f', frame[2:6])[0]) actual.append(struct.unpack('<f', frame[6:10])[0]) ctrl.append(struct.unpack('<f', frame[10:14])[0])

拿到曲线之后,判断标准不是"看着顺眼",而是看 u(t) 有没有锯齿、角度曲线有没有等幅振荡、稳态段是不是一条水平线。

5. PID 神经网络的调参、排错与两轮自平衡小车抗扰验证

5.1 整定顺序与参数表

顺序不能乱:先关掉网络,用手调增量式 PID 把车调到能原地站住;再用这组参数反推权值初值和映射区间;然后打开网络,学习率从 0.01 起步慢慢加;最后才闭速度环。跳过前两步直接让网络从随机权值开始学,大概率看到的是车在原地画圈。

参数符号典型初值调整方向对应现象
直立环采样周期T5 ms固定不轻易改改小需重算 Ki
神经元比例系数K0.1大→响应快,易振荡车身高频抖动
学习率η0.02大→跟踪快,噪声放大低频呼吸式晃动
权值初值w1/w2/w30.30/0.10/0.10用手调 PID 反推收敛慢或根本不动
Kp 映射区间[k_min,k_max][0, 15]按手调结果圈定网络输出长期贴边界
积分抗饱和阈值0.8·out_max减小更保守起步瞬间车头下沉
陀螺仪低通截止fc80~120 Hz降低更平滑但滞后高频嗡鸣是否消失

5.2 抖动、积分饱和、超调:高频故障的定位顺序

现象优先怀疑验证方法处理
车身高频嗡鸣Kd 过大或陀螺仪噪声关掉微分项看是否消失加一阶低通,或改用角速度做微分
静止时低频前后晃学习率偏大、权值发散打印 w1~w3 曲线η 减半,权值限幅收窄
起步车头下沉再回弹积分饱和观察 u 是否长时间贴限幅积分限幅 + 积分分离
车缓慢朝一个方向漂陀螺仪零偏未校准静置打印 200 帧求均值上电静止取均值做零偏补偿
网络调完不如手调映射区间或雅可比符号不对打印 Kp/Ki/Kd 实值手工圈区间,先做小幅微调

注意:网络输出是归一化值时,务必在代码里 clamp 后再反归一化。sigmoid 在输入稍大时就会饱和到接近 1,反归一化后 Kp 可能直接顶到几十,PWM 满占空比,电机瞬间堵转。

5.3 三组验证实验:阶跃、脉冲扰动与上位机波形判读

第一组做阶跃:给目标角度 +0.05 rad,记录上升时间、超调量和稳态误差,要求超调小于 10%、稳态误差接近零。第二组做脉冲扰动:手轻推车身一下,看回正时间和振荡次数,一般要求两到三个周期内收敛,超过四个说明 Kd 或学习率还需要收。第三组是变负载与变地面:在车顶加配重、从瓷砖换到地毯,观察权值是否在线变化。

第三组才是 PID 神经网络相对固定 PID 唯一有说服力的对比实验——前两组手调 PID 也能过。判读时把三组曲线里的 u(t) 叠在同一坐标系看:如果变负载那条只是整体平移、形状没变,说明网络实际学到的只是一个偏置量,并没有真正改变增益,这时候该动的是映射区间而不是学习率;如果形状变了、振荡次数明显少于固定 PID,那才是网络在线整定在起作用。

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

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

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

立即咨询