资讯动态

基于STM32与串级PID的车载平衡滚球系统设计与实现

发布时间:2026/8/16 1:28:18 来源:尧图企业网站定制
大家好我是专注于嵌入式与智能控制开发的博主。最近2026年全国大学生电子设计竞赛电赛的赛题方向备受关注其中“车载平衡滚球运动”作为一个融合了经典倒立摆控制与移动机器人技术的综合性题目对参赛者的机械设计、传感器融合、控制算法和系统集成能力提出了很高的要求。本文将围绕这一主题提供一个从零开始的完整示例项目涵盖系统设计、核心代码实现、PID调参技巧以及常见问题排查。无论你是初次接触电赛的新手还是希望深化控制理论实践的开发者都能通过本文搭建一个可运行、可调试的平衡滚球运动平台原型。1. 项目背景与核心概念解析1.1 什么是“车载平衡滚球运动”“车载平衡滚球运动”是一个典型的机电一体化控制问题。其核心目标是设计一个可移动的小车车体在其顶部安装一个平板平衡平台平台上放置一个滚球。小车需要具备两个基本能力平衡能力通过控制小车的左右移动来抵消滚球在平板上的滚动趋势使滚球始终稳定在平板中心附近。运动能力在保持滚球平衡的前提下控制小车整体按照预定轨迹如直线、曲线运动或完成指定任务如将球运送到目标区域。这本质上是一个二级倒立摆问题的变体。一级倒立摆是控制摆杆不倒而这里是控制平板上的球不滚落同时车体本身也是一个被控对象复杂度更高。1.2 技术挑战与核心知识点完成该项目你需要掌握以下核心技术栈硬件平台单片机如STM32、ESP32、电机驱动模块如TB6612、DRV8833、直流减速电机与编码器、姿态传感器如MPU6050、陀螺仪加速度计模块、测距传感器如超声波、激光测距用于球位置检测。控制理论PID控制算法是基础且核心的方法。通常需要两个PID环内环用于快速响应控制小车移动以平衡球外环用于精确控制使球到达目标位置。传感器融合如何准确、实时地获取滚球在平板上的位置X, Y坐标是小车实现平衡的关键。常见方案有摄像头视觉识别、红外阵列、电阻式触摸屏或多个测距传感器组合。系统建模与仿真在动手制作前使用MATLAB/Simulink或Python进行动力学建模和算法仿真可以大幅降低实物调试的难度和风险。1.3 应用场景与学习价值除了电赛此类技术广泛应用于教育机器人直观演示经典控制理论。精密运输平台在半导体、生物实验室中平稳运输对振动敏感的物品。自平衡机器人是两轮自平衡车、独轮车等更复杂系统的基础。 掌握它意味着你打通了从传感器数据采集、滤波处理、控制算法到电机驱动的全链路嵌入式开发能力。2. 系统设计与环境准备2.1 整体系统架构我们设计一个简化版的系统采用二维控制仅考虑球在平板一条轴上的运动便于理解和实现。被控对象滚球。执行机构由两个直流电机驱动的双轮小车。传感器滚球位置检测使用一个超声波传感器HC-SR04安装在平板一端测量球到边缘的距离间接推算球心位置。这是成本较低的方案。更优方案是使用线性CCD或摄像头。车体姿态检测使用MPU6050获取车体倾角俯仰角用于补偿平板不平带来的影响本例简化暂不考虑。电机速度反馈电机编码器用于实现速度闭环使控制更平滑。控制器STM32F103C8T6蓝桥杯/电赛常用核心板。控制算法串级PID控制。系统框图如下[目标球位置] -- [外环位置PID] -- [目标小车速度] -- [内环速度PID] -- [PWM驱动] -- [电机/小车] ^ | | | [超声波测距] -- [滚球] -- [平板] -- [小车车体] -- [编码器反馈] -----------------------|2.2 硬件清单与连接组件型号/说明连接至STM32主控STM32F103C8T6-电机驱动TB6612FNGPWMA: PA8, AIN1: PA9, AIN2: PA10 (电机A)PWMB: PB13, BIN1: PB14, BIN2: PB15 (电机B)直流减速电机带编码器 6V电机线接TB6612输出编码器A相接PA0/PA1 (TIM2), B相接PA2/PA3 (TIM5)超声波HC-SR04Trig: PB0, Echo: PB1MPU6050GY-521模块SCL: PB6, SDA: PB7 (I2C1)电源18650电池组 7.4V为TB6612供电STM32由USB或稳压模块供电2.3 软件开发环境IDE: Keil uVision 5 或 STM32CubeIDE固件库: HAL库本文示例基于HAL库易于移植核心工具: STM32CubeMX用于图形化配置引脚和生成初始化代码调试工具: 串口助手用于打印调试信息、PID参数3. 核心控制原理与算法拆解3.1 滚球位置感知我们假设平板长度为 L例如30cm。超声波传感器安装在平板一端测量值为distance_cm球面到传感器的距离。设球半径为 r。 则球心在平板上的位置ball_position以平板中心为原点可近似计算为ball_position (L - distance_cm - r) - L/2值为正表示球在中心右侧为负表示在左侧。需要定期如10ms采样并滤波如均值滤波或卡尔曼滤波以减少噪声。3.2 串级PID控制算法这是本项目的核心。我们采用两个PID控制器串联。外环位置环输入误差目标球位置通常为0即平板中心与当前ball_position的差值。输出期望的小车运动速度target_velocity。这个速度是为了让球回到中心而需要小车移动的速度。作用实现“球到哪车就跟到哪”的宏观平衡策略。P参数过大小车会抖动过小反应迟钝。内环速度环输入误差外环输出的target_velocity与通过编码器计算出的当前小车实际速度current_velocity的差值。输出最终的PWM占空比直接驱动电机。作用确保小车能精确、快速地达到外环要求的速度。它让电机响应更平滑克服阻力提高系统稳定性。算法伪代码// 每10ms执行一次的控制循环 void Control_Loop() { // 1. 读取传感器 float ball_pos Get_Ball_Position(); // 经过滤波的位置 float car_vel Get_Car_Velocity(); // 编码器计算的速度 // 2. 外环PID计算位置环 float target_vel PID_Outer_Calculate(0.0, ball_pos); // 目标位置是0 // 3. 内环PID计算速度环 float pwm_output PID_Inner_Calculate(target_vel, car_vel); // 4. 输出PWM驱动电机 Set_Motor_PWM(pwm_output); }3.3 PID参数整定思路调参是成败关键。务必遵循“先内后外先P后I再D”的原则单独调试速度环内环将外环断开直接给一个固定的target_vel。P从小到大增加直到小车能快速响应速度指令但开始出现轻微振荡。I加入少许I消除静差例如给定速度后实际速度稳定值略低于目标。D如果响应超调严重或振荡加入少量D抑制。目标小车能平稳、准确地跟随阶跃速度信号。闭合外环调试位置环将内环参数固定闭合外环。P从小开始观察小车是否能开始尝试平衡球车会随着球动。增大P直到系统开始发散球来回滚动幅度越来越大。将此时P值的60%~70%作为初始值。I如果球始终无法稳定在中心存在稳态误差加入很小的I。D如果球在中心附近高频抖动加入少量D。关键外环的响应速度必须慢于内环。如果外环变化太快内环跟不上系统就会崩溃。4. 完整实战代码示例4.1 STM32CubeMX工程配置创建STM32F103C8工程。时钟配置为72MHz。引脚电机PWM配置TIM1_CH1 (PA8) 和 TIM1_CH4 (PB13) 为PWM Generation CHx。电机方向配置PA9, PA10, PB14, PB15为GPIO_Output。编码器配置TIM2和TIM5为Encoder Mode。超声波配置PB0为GPIO_Output (Trig) PB1配置为输入捕获或EXTI中断这里简化为GPIO_Input并配合定时器做延时测量。MPU6050配置I2C1。串口配置USART1用于调试打印PA9-TX, PA10-RX注意与电机方向引脚冲突需调整。生成代码。4.2 PID控制器实现 (pid.c / pid.h)// pid.h #ifndef __PID_H #define __PID_H typedef struct { float target; // 目标值 float actual; // 实际值 float err; // 当前误差 float err_last; // 上次误差 float err_sum; // 误差积分 float Kp, Ki, Kd; // PID参数 float output; // 输出值 float output_max; // 输出限幅 float output_min; float integral_limit; // 积分限幅 } PID; void PID_Init(PID *pid, float kp, float ki, float kd, float max_out, float i_limit); float PID_Calculate(PID *pid, float target, float actual); #endif// pid.c #include pid.h void PID_Init(PID *pid, float kp, float ki, float kd, float max_out, float i_limit) { pid-target pid-actual 0.0f; pid-err pid-err_last pid-err_sum 0.0f; pid-Kp kp; pid-Ki ki; pid-Kd kd; pid-output 0.0f; pid-output_max max_out; pid-output_min -max_out; pid-integral_limit i_limit; } float PID_Calculate(PID *pid, float target, float actual) { pid-target target; pid-actual actual; pid-err pid-target - pid-actual; // 积分项并限幅防止积分饱和 pid-err_sum pid-err; if (pid-err_sum pid-integral_limit) pid-err_sum pid-integral_limit; if (pid-err_sum -pid-integral_limit) pid-err_sum -pid-integral_limit; // PID计算 pid-output (pid-Kp * pid-err) (pid-Ki * pid-err_sum) (pid-Kd * (pid-err - pid-err_last)); // 输出限幅 if (pid-output pid-output_max) pid-output pid-output_max; if (pid-output pid-output_min) pid-output pid-output_min; pid-err_last pid-err; return pid-output; }4.3 主控制逻辑 (main.c 部分关键代码)// main.c #include main.h #include pid.h #include motor.h #include encoder.h #include ultrasonic.h #include mpu6050.h #include stdio.h // 用于printf // 定义PID结构体 PID pid_outer; // 外环-位置环 PID pid_inner; // 内环-速度环 // 全局变量 float ball_position 0.0f; float car_velocity 0.0f; float target_position 0.0f; // 目标球在中心 // 定时器中断回调函数10ms周期 void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim) { if (htim-Instance TIM6) { // 使用一个基本定时器TIM6产生10ms中断 // 1. 读取传感器数据 ball_position Get_Filtered_Ball_Position(); // 自定义函数包含超声波读取和滤波 car_velocity Get_Car_Velocity_From_Encoder(); // 自定义函数编码器脉冲换算速度 // 2. 外环PID计算输入目标位置和球位置输出目标车速 float target_velocity PID_Calculate(pid_outer, target_position, ball_position); // 3. 内环PID计算输入目标车速和实际车速输出PWM值 float pwm_out PID_Calculate(pid_inner, target_velocity, car_velocity); // 4. 设置电机PWM (pwm_out为正向右为负向左) if (pwm_out 0) { Motor_Set_Direction(FORWARD); Motor_Set_PWM((uint32_t)pwm_out); } else { Motor_Set_Direction(BACKWARD); Motor_Set_PWM((uint32_t)(-pwm_out)); } // 5. 调试信息通过串口打印可注释掉以节省时间 // printf(Pos:%.2f, Vel:%.2f, PWM:%.2f\r\n, ball_position, car_velocity, pwm_out); } } int main(void) { HAL_Init(); SystemClock_Config(); MX_GPIO_Init(); MX_TIM_Init(); // 初始化PWM、编码器、基础定时器TIM6 MX_USART1_UART_Init(); MX_I2C1_Init(); // 初始化外环PID (参数需实际调试) PID_Init(pid_outer, 1.5f, 0.01f, 0.2f, 50.0f, 100.0f); // 输出限幅±50积分限幅100 // 初始化内环PID PID_Init(pid_inner, 10.0f, 0.5f, 0.05f, 7200.0f, 500.0f); // PWM限幅对应ARR值 // 启动10ms定时器中断 HAL_TIM_Base_Start_IT(htim6); while (1) { // 主循环可处理其他任务如接收上位机指令改变target_position // 例如让球运动到平板左侧5cm处 // target_position -5.0f; HAL_Delay(100); } }4.4 传感器与执行器驱动示例超声波读取函数简化版未使用中断// ultrasonic.c float Get_Ball_Position(void) { float distance_cm 0.0f; // 发送10us高脉冲触发 HAL_GPIO_WritePin(TRIG_GPIO_Port, TRIG_Pin, GPIO_PIN_SET); delay_us(10); HAL_GPIO_WritePin(TRIG_GPIO_Port, TRIG_Pin, GPIO_PIN_RESET); // 等待回响引脚变高开始计时 while(HAL_GPIO_ReadPin(ECHO_GPIO_Port, ECHO_Pin) GPIO_PIN_RESET); uint32_t start_time HAL_GetTick(); // 注意此方法精度较低建议用定时器捕获 // 等待回响引脚变低结束计时 while(HAL_GPIO_ReadPin(ECHO_GPIO_Port, ECHO_Pin) GPIO_PIN_SET); uint32_t end_time HAL_GetTick(); // 计算距离 (声速340m/s) distance_cm (end_time - start_time) * 0.034 / 2; // 单位ms换算需调整 // 转换为球心位置 (假设平板长30cm球半径2cm) const float L 30.0f, r 2.0f; float pos (L - distance_cm - r) - L/2.0f; return pos; }编码器速度计算// encoder.c int32_t Get_Encoder_Counts(void) { // 读取TIM2的计数器值注意计数器可能溢出 int32_t cnt (int32_t)__HAL_TIM_GET_COUNTER(htim2); __HAL_TIM_SET_COUNTER(htim2, 0); // 读取后清零计算增量 return cnt; } float Get_Car_Velocity_From_Encoder(void) { static int32_t last_count 0; int32_t current_count Get_Encoder_Counts(); int32_t diff current_count - last_count; last_count current_count; // 计算速度diff(脉冲数) / 时间间隔(10ms) * 转换系数 // 转换系数 (轮子周长) / (编码器每转脉冲数) const float wheel_circumference 20.0f; // cm假设 const int pulses_per_rev 780; // 编码器分辨率 float vel_cm_per_s (diff / 780.0f) * wheel_circumference * 100; // 乘以100是因为10ms0.01s return vel_cm_per_s; }5. 系统调试与常见问题排查调试是项目成功的关键务必循序渐进。5.1 分模块调试流程电机与驱动测试编写代码让电机正反转确认PWM和方向控制正常。编码器测试旋转电机通过串口打印编码器计数值确认计数方向正确、数值连续。超声波测试固定传感器测量不同距离的物体验证读数准确性和稳定性。MPU6050测试读取原始数据并计算姿态角确认I2C通信正常。速度环单独测试给定目标速度观察电机能否跟随调整内环PID至响应快速无振荡。整体开环测试手动移动球观察串口打印的ball_position是否变化符合预期。闭环调试先用手扶住小车让PID工作感受电机出力方向是否正确球往左滚车应该往左追。然后尝试缓慢放手。5.2 常见问题与解决方案问题现象可能原因排查思路与解决方案上电后电机狂转或不动1. 电源接错或电压不足。2. PWM输出引脚配置错误。3. 电机驱动模块使能信号未拉高。1. 用万用表检查电机驱动模块供电电压。2. 用示波器或LED检查PWM引脚是否有输出。3. 检查TB6612的STBY引脚是否接高电平。编码器读数始终为0或不变1. 编码器AB相接反或接触不良。2. 定时器未配置为编码器模式。3. 编码器供电问题。1. 交换A、B相线序试试。2. 在CubeMX中确认TIMx配置为“Encoder Mode”。3. 检查编码器是否有5V或3.3V供电。超声波测距不准或跳动大1. 触发和回响信号受干扰。2. 测量时序不精确用HAL_GetTick精度低。3. 被测物体表面不规整。1. 给Trig和Echo信号线加短延时和滤波电容。2.强烈建议使用定时器输入捕获模式精确测量高电平时间。3. 对连续多次采样进行中值滤波或均值滤波。小球平衡不住来回滚动幅度越来越大1. 外环PID参数尤其是P过大系统发散。2. 传感器数据噪声大导致控制指令混乱。3. 内环速度环响应太慢跟不上外环。1.大幅减小外环P值从非常小的值开始调。2. 加强传感器数据滤波。3. 先确保内环性能足够好响应快且稳定。小球能平衡但始终偏离中心存在稳态误差。1. 适当增加外环的I积分参数。2. 检查机械结构是否对称平台是否水平。3. 检查超声波安装是否歪斜导致零点偏移。系统在高频小幅抖动微分项D或比例项P引入高频噪声。1. 检查传感器数据是否本身有高频毛刺加强滤波。2. 尝试减小D参数或对误差进行低通滤波后再进行D项计算。3. 可能是P值仍偏大。控制响应迟钝球滚远后车才动外环P值太小或内环输出限幅太小。1. 在系统稳定的前提下逐步增大外环P值。2. 增大内环PID的输出限幅让电机有更大的出力。5.3 高级调试技巧串口绘图将ball_position,target_velocity,pwm_out等关键变量通过串口发送到电脑使用SerialPlot、VOFA等工具绘制实时曲线直观观察系统响应和振荡情况。参数整定助手可以编写简单上位机通过串口实时修改PID参数并下发给STM32避免反复烧录程序。加入“软启动”在系统初始化或目标位置突变时对target_position进行斜坡变化避免阶跃信号对系统造成过大冲击。6. 优化方向与进阶扩展完成基础平衡后你可以从以下方向提升项目6.1 从一维到二维平衡当前示例只控制一个方向前后。要实现真正的平衡需要扩展到二维X-Y平面。硬件需要两个正交安装的超声波传感器或一个摄像头进行图像处理。控制需要两组独立的串级PID控制器分别控制X轴和Y轴。需要注意两个轴之间的耦合可能很弱可以独立控制。6.2 更优的球位置检测方案摄像头OpenMV/OpenCV在平板上方架设摄像头通过颜色识别或特征识别追踪球精度高能获取XY坐标。电阻式触摸屏将整个平板换成触摸屏直接读取球压下的坐标响应快但成本高且需处理压力信号。红外对管阵列在平板下方布置网格状的红外发射接收管通过遮挡判断球位置成本适中精度尚可。6.3 先进控制算法模糊PID针对PID参数难以适应不同工况如球速变化的问题可以用模糊规则动态调整PID参数。线性二次型调节器LQR基于状态空间模型的最优控制器性能通常优于PID但需要精确的系统数学模型。模型预测控制MPC更高级的算法能够处理约束如电机PWM限幅但计算量大对单片机性能要求高。6.4 增加运动控制功能在平衡的基础上让小车带着球运动到指定地点。路径规划给定目标点坐标规划小车行进路径。上层控制器在现有的平衡控制环之上再增加一个“位置环”根据小车当前位置和目标位置计算出target_position的设定值从而引导球和小车一起移动。6.5 工程化改进状态机管理引入状态机如初始化、就绪、平衡、运动、错误等使程序逻辑更清晰健壮。遥控与交互增加蓝牙或Wi-Fi模块通过手机APP或电脑上位机遥控小车、设置目标点、实时调整参数。安全保护增加限位开关或软件限幅防止球滚出平板边缘时小车失控猛冲。从零搭建一个“车载平衡滚球运动”系统是一个极具挑战也收获满满的项目。它强迫你将理论控制原理与实践嵌入式编程、硬件调试紧密结合。本文提供的示例代码和调试方法是一个可行的起点但真正的精髓在于调试过程中对系统行为的观察、分析和思考。记住没有一次成功的调参耐心和细致的观察比盲目修改参数更重要。建议你先在仿真软件中验证算法再动手搭建硬件可以节省大量时间和物料成本。希望这篇长文能为你备战电赛或开展个人项目提供扎实的帮助。如果在实践中遇到具体问题欢迎在评论区交流讨论。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价