ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

基于STM32与OpenMV的滚球控制系统:PID算法与视觉定位实战

基于STM32与OpenMV的滚球控制系统:PID算法与视觉定位实战 大家好我是CSDN的一名技术博主。今天我们来深入探讨一个经典且富有挑战性的电子设计竞赛项目——2017年全国大学生电子设计竞赛B题“滚球控制系统”。这个题目不仅考察了参赛者对自动控制、传感器技术、嵌入式系统和机械结构的综合运用能力其核心思想在机器人平衡、精密定位等领域也有着广泛的应用前景。无论你是正在备赛的学生还是对运动控制感兴趣的工程师通过本文你都能获得一套从理论分析、硬件选型、软件设计到系统调试的完整实战方案。我们将手把手带你复现一个基础但功能完整的滚球控制系统并深入剖析其中的关键技术与常见“坑点”。1. 项目背景与核心概念解析1.1 什么是滚球控制系统滚球控制系统顾名思义是一个控制小球在特定平面上进行精确运动的自动化系统。在2017年国赛B题中具体要求是设计一个能够控制小球在正方形平板上沿指定轨迹如直线、圆、特定图形快速、平稳、准确运动的装置。平板通过两个正交方向X轴和Y轴的舵机或步进电机驱动通过实时检测小球的位置计算控制量并驱动平板倾斜从而利用重力使小球产生运动。其核心是一个典型的闭环反馈控制系统。系统通过摄像头或其它传感器“感知”小球当前位置反馈量与期望位置或轨迹给定量进行比较得到误差控制器根据误差计算出控制指令驱动执行机构电机动作改变平板倾角进而影响小球运动使其向减小误差的方向移动。1.2 系统核心组成与工作原理一个完整的滚球控制系统通常包含以下几个关键部分被控对象小球和平板。传感器模块用于检测小球在平板上的二维坐标X, Y。常见方案有摄像头视觉方案使用OpenMV、OV7670等摄像头模块通过图像处理算法识别小球中心坐标。优点是直观、非接触、精度较高缺点是算法复杂对光照敏感处理速度有要求。电阻触摸屏方案使用类似手机屏幕的电阻屏小球作为触压点。优点是检测直接、速度快缺点是屏幕易磨损小球材质有要求精度一般。激光/红外对管阵列在平板边缘布置多组对射式传感器通过遮挡判断位置。优点是响应快缺点是分辨率低只能做离散定位。控制器系统的“大脑”负责运行控制算法。通常是STM32、Arduino、树莓派等微控制器或微处理器。STM32因其强大的性能和丰富的外设是竞赛中的主流选择。执行机构将控制信号转化为平板倾角的装置。通常是两个舵机模拟或数字或步进电机分别控制平板绕X轴和Y轴的旋转。控制算法系统的“智慧”所在。最基础且有效的是PID控制算法。对于X和Y两个方向可以分别设计独立的PID控制器。更高级的可以考虑模糊PID、串级控制等。工作流程可以概括为传感器周期性采集小球位置 - 控制器计算位置误差 - PID算法计算电机控制量 - 驱动电机转动改变平板角度 - 小球在重力分力作用下运动 - 新的位置被传感器检测到进入下一个控制周期。1.3 应用场景与学习价值虽然是一个竞赛题目但该系统的原理广泛应用于平衡机器人如两轮自平衡小车、平衡球其姿态控制原理与本系统高度相似。精密运动平台某些需要高精度二维定位的仪器设备。教学演示是学习自动控制原理、嵌入式系统、传感器技术的绝佳实践平台。通过完成本项目你将系统性掌握闭环反馈控制的基本思想。PID控制器的原理、参数整定与代码实现。图像传感器或其它位置传感器的使用与数据处理。PWM信号控制舵机/步进电机的原理。嵌入式系统多任务数据采集、控制计算、电机驱动的编程框架。2. 系统设计与硬件选型在开始动手前我们需要进行详细的方案设计和硬件准备。一个合理的方案是成功的一半。2.1 总体方案设计我们选择一种较为经典且易于实现的方案组合控制器STM32F407ZGT6性能强劲资源丰富适合处理图像和控制算法。传感器OpenMV Cam M7集成MicroPython方便快速开发图像识别算法。执行机构两个数字舵机如MG996R扭矩大精度优于模拟舵机。通信方式OpenMV通过串口UART将小球坐标发送给STM32。控制算法双通道X, Y位置式PID控制。机械结构使用亚克力板或木板制作平板平板中心通过万向节或球铰与底座连接两个舵机通过连杆机构分别推动平板的两侧实现双向倾斜。2.2 硬件清单与关键参数部件型号/规格数量备注主控制器STM32F407ZGT6 核心板1带基本外围电路方便使用视觉模块OpenMV Cam M71注意镜头焦距需能看清整个平板执行器MG996R 数字舵机2工作电压需匹配电源平板亚克力板 (30cm x 30cm)1表面可贴白色纸张增强对比度小球乒乓球或高尔夫练习球1颜色鲜艳如橙色与背景对比强结构件舵机支架、连杆、万向节、螺丝若干用于搭建机械框架电源12V/5A 开关电源1为舵机和控制器供电注意稳压模块连接线杜邦线公对公、公对母若干用于连接各模块电源注意事项舵机启动瞬间电流很大可能导致STM32或OpenMV复位。务必确保电源功率充足建议5A以上并为数字电路STM32、OpenMV和动力电路舵机使用独立的稳压模块如LM2596供电或在总电源处并联大容量电容如1000μF缓冲电流冲击。2.3 机械结构搭建要点机械结构的稳定性和精度直接影响控制效果。平板平衡初始状态时平板必须保持水平。这需要在机械上精细调节确保在舵机处于中位通常对应PWM占空比7.5%时平板是水平的。舵机安装两个舵机应正交安装一个控制X方向倾斜一个控制Y方向倾斜。舵机摇臂与平板之间的连杆连接应尽量减少间隙可采用球头连杆以减少卡滞。万向节选择平板中心的支撑点应使用活动灵活的万向节保证平板能顺滑地向各个方向倾斜且没有回程差。小球选择小球表面不能太光滑易打滑也不能太粗糙摩擦过大重量要适中。乒乓球是常用选择。3. 核心模块软件设计与实现硬件连接好后我们进入软件部分。我们将分模块讲解代码实现。3.1 OpenMV视觉识别模块OpenMV的任务是识别小球并计算其中心坐标x, y然后通过串口发送给STM32。# OpenMV 主程序 main.py import sensor, image, time, pyb from pyb import UART # 初始化摄像头 sensor.reset() sensor.set_pixformat(sensor.RGB565) # 彩色图像 sensor.set_framesize(sensor.QVGA) # 320x240 分辨率 sensor.skip_frames(time 2000) # 等待设置生效 sensor.set_auto_gain(False) # 关闭自动增益 sensor.set_auto_whitebal(False) # 关闭白平衡 # 初始化串口3 (P4 TX, P5 RX) uart UART(3, 115200, timeout_char1000) # 定义小球的颜色阈值 (LAB色彩空间) # 需要根据实际小球颜色和环境光进行调整 orange_threshold (30, 60, 20, 70, 10, 50) # 示例橙色小球 # 图像中心坐标作为参考 CENTER_X sensor.width() // 2 CENTER_Y sensor.height() // 2 clock time.clock() while(True): clock.tick() img sensor.snapshot() # 抓取一帧图像 # 寻找色块 blobs img.find_blobs([orange_threshold], pixels_threshold50, area_threshold50, mergeTrue) if blobs: # 假设只有一个最大的小球 largest_blob max(blobs, keylambda b: b.pixels()) img.draw_rectangle(largest_blob.rect()) # 画框 img.draw_cross(largest_blob.cx(), largest_blob.cy()) # 画十字 # 获取小球中心坐标 ball_x largest_blob.cx() ball_y largest_blob.cy() # 将坐标转换为平板坐标系例如原点在平板中心 # 假设图像映射到平板是线性的且方向一致 plate_x ball_x - CENTER_X plate_y CENTER_Y - ball_y # 注意Y轴方向可能相反 # 通过串口发送坐标数据格式如X123,Y-45\n data_str X%d,Y%d\n % (plate_x, plate_y) uart.write(data_str) # print(data_str) # 调试用可通过IDE终端查看 else: # 未找到小球发送一个特殊值如原点 uart.write(X0,Y0\n) # 可选的帧率显示 # print(clock.fps())关键点说明find_blobs函数的阈值需要根据实际环境和小球颜色通过OpenMV IDE的“阈值编辑器”工具反复调整。坐标转换是关键。需要标定摄像头视野与实际平板区域的对应关系。这里做了简单线性映射更精确的方法需要做透视变换标定。串口通信协议自定义为”X123,Y-45\n”方便STM32端解析。3.2 STM32控制核心PID控制器与PWM输出STM32负责接收坐标、运行控制算法、输出PWM控制舵机。3.2.1 PID控制器结构体与函数首先我们定义一个PID结构体和初始化、计算函数。// 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_max; // 积分限幅 } PID; void PID_Init(PID *pid, float kp, float ki, float kd, float max_out, float max_i); 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 max_i) { pid-target 0; pid-actual 0; pid-err 0; pid-err_last 0; pid-err_sum 0; pid-Kp kp; pid-Ki ki; pid-Kd kd; pid-output 0; pid-output_max max_out; pid-output_min -max_out; pid-integral_max max_i; } 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_max) pid-err_sum pid-integral_max; if (pid-err_sum -pid-integral_max) pid-err_sum -pid-integral_max; // 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; }3.2.2 主程序逻辑框架下面展示STM32主程序的核心逻辑基于HAL库。// main.c 部分代码 #include main.h #include usart.h #include tim.h #include pid.h #include stdio.h #include string.h UART_HandleTypeDef huart1; // 用于调试打印 UART_HandleTypeDef huart2; // 连接OpenMV TIM_HandleTypeDef htim3; // 用于产生PWM通道1和2控制两个舵机 PID pid_x, pid_y; // X, Y方向PID控制器 // 小球当前坐标来自OpenMV int32_t ball_x 0, ball_y 0; // 目标坐标例如期望小球运动到的点 int32_t target_x 0, target_y 0; // 串口接收缓冲区 uint8_t uart_rx_buf[64]; uint8_t uart_rx_flag 0; // 解析OpenMV发送的字符串格式X123,Y-45 void Parse_Ball_Data(uint8_t *buf) { char *token; token strtok((char*)buf, ,); while (token ! NULL) { if (token[0] X) { sscanf(token1, %d, ball_x); } else if (token[0] Y) { sscanf(token1, %d, ball_y); } token strtok(NULL, ,); } } // 串口接收中断回调函数 void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart) { if (huart-Instance USART2) { // OpenMV连接的串口 uart_rx_flag 1; // 设置标志位在主循环中处理 HAL_UART_Receive_IT(huart2, uart_rx_buf, 64); // 重新开启接收 } } int main(void) { HAL_Init(); SystemClock_Config(); MX_GPIO_Init(); MX_USART1_UART_Init(); // 调试串口 MX_USART2_UART_Init(); // OpenMV串口 MX_TIM3_Init(); // PWM定时器 // 初始化PID控制器 // 参数需要根据实际系统调试Kp, Ki, Kd, 输出限幅, 积分限幅 PID_Init(pid_x, 0.5, 0.01, 0.2, 500, 1000); PID_Init(pid_y, 0.5, 0.01, 0.2, 500, 1000); // 启动PWM输出 (舵机中位信号频率50Hz脉宽1.5ms) HAL_TIM_PWM_Start(htim3, TIM_CHANNEL_1); HAL_TIM_PWM_Start(htim3, TIM_CHANNEL_2); __HAL_TIM_SET_COMPARE(htim3, TIM_CHANNEL_1, 750); // 对应1.5ms ARR20000 __HAL_TIM_SET_COMPARE(htim3, TIM_CHANNEL_2, 750); // 启动串口接收中断 HAL_UART_Receive_IT(huart2, uart_rx_buf, 64); // 设置目标轨迹示例一个点 target_x 50; target_y 30; while (1) { // 1. 处理接收到的球坐标数据 if (uart_rx_flag) { uart_rx_flag 0; Parse_Ball_Data(uart_rx_buf); // 可在此添加数据滤波如滑动平均滤波 } // 2. PID计算 float out_x PID_Calculate(pid_x, (float)target_x, (float)ball_x); float out_y PID_Calculate(pid_y, (float)target_y, (float)ball_y); // 3. 将PID输出转换为舵机PWM值 // 假设舵机中位对应750输出限幅500则PWM范围约为250~1250 uint32_t pwm_x 750 (int32_t)out_x; uint32_t pwm_y 750 (int32_t)out_y; // PWM限幅保护舵机 if (pwm_x 1250) pwm_x 1250; if (pwm_x 250) pwm_x 250; if (pwm_y 1250) pwm_y 1250; if (pwm_y 250) pwm_y 250; // 4. 更新PWM输出 __HAL_TIM_SET_COMPARE(htim3, TIM_CHANNEL_1, pwm_x); __HAL_TIM_SET_COMPARE(htim3, TIM_CHANNEL_2, pwm_y); // 5. 延时控制循环周期例如10ms HAL_Delay(10); // 6. 可选通过调试串口打印数据用于监控 // printf(Ball:(%d, %d), Out:(%.1f, %.1f), PWM:(%lu, %lu)\r\n, // ball_x, ball_y, out_x, out_y, pwm_x, pwm_y); } }关键点说明PWM设置舵机控制通常使用频率50Hz周期20ms脉宽0.5ms-2.5ms对应0-180度。在ARR20000的定时器下脉宽1.5ms中位对应的比较寄存器值CCR 20000 * 1.5 / 20 1500。但很多库函数配置分频后计算方式不同需根据实际测试调整750这个中位值。控制周期HAL_Delay(10)设置了10ms的控制周期100Hz。这个值需要与OpenMV的发送频率、舵机响应速度匹配。太快可能系统不稳定太慢则响应迟缓。数据滤波从OpenMV接收的坐标数据可能存在抖动在主循环中应加入软件滤波如滑动平均滤波使控制更平滑。4. 系统调试与PID参数整定系统搭建完成后最关键的步骤是调试尤其是PID参数的整定。4.1 调试准备确保机械结构稳固平板水平小球能自由滚动。单独测试每个模块OpenMV连接电脑用IDE查看是否能稳定识别小球并输出坐标。舵机编写简单程序测试每个舵机能否从中位向两边平滑转动。STM32串口测试能否正确接收OpenMV发来的字符串并解析。STM32 PWM用示波器或逻辑分析仪测量PWM波形是否正确。4.2 PID参数整定“三部曲”PID整定是一个经验过程遵循“先P后I再D”的原则。将目标点设为平板中心(0,0)用手将小球拨到某个位置后松开观察系统能否将小球拉回中心。比例系数 Kp将Ki和Kd设为0。逐渐增大Kp。Kp太小小球缓慢移向目标甚至无法到达。Kp增大响应变快。现象当Kp增大到某个值时小球开始在目标点附近来回振荡“发抖”。这个值就是临界振荡的Kp。经验值取临界Kp的 50%~70% 作为初步P值。积分系数 Ki保持Kp为上一步的值Ki从0开始缓慢增加。作用消除静态误差小球最终停在离目标点很近但不到的位置。现象Ki太大会引起系统超调加大甚至振荡。如果加入Ki后系统开始振荡说明Ki过大。经验值从小值开始如0.001慢慢增加直到静态误差在可接受范围内且系统稳定。微分系数 Kd保持Kp和Ki加入Kd。作用抑制振荡提高系统稳定性。它能预测误差变化趋势提前施加反向控制。现象Kd能有效减小超调和振荡。但Kd对噪声敏感坐标抖动会被放大如果传感器噪声大Kd不宜过大甚至可以为0。经验值从较小的值开始如0.1观察振荡是否减弱。注意过大的Kd会使系统对扰动反应迟钝。调试技巧分开调试先调试X方向固定Y轴舵机再调试Y方向。因为两个轴可能存在耦合但初步调试时可近似认为独立。利用打印信息通过串口将ball_x, ball_y, out_x, out_y实时发送到电脑用串口绘图工具如Serial Plotter观察曲线能直观看到响应过程。手动整定口诀“参数整定找最佳从小到大顺序查。先是比例后积分最后再把微分加。曲线振荡很频繁比例度盘要放大。曲线漂浮绕大弯比例度盘往小扳。曲线偏离回复慢积分时间往下降。曲线波动周期长积分时间再加长。曲线振荡频率快先把微分降下来。动差大来波动慢微分时间应加长。”5. 功能扩展与进阶优化基础的点对点控制实现后可以尝试更复杂的功能这也是竞赛的加分项。5.1 轨迹跟踪让小球跟随预设的轨迹运动如直线、圆形、八字形。思路将轨迹离散化为一系列连续的目标点(target_x[i], target_y[i])。在控制循环中每隔一段时间或当小球接近当前目标点时就将目标点更新为下一个点。关键目标点切换的频率和间距要合适。太快会导致小球跟不上太慢则运动不连贯。// 示例圆形轨迹生成 #define POINT_NUM 100 #define RADIUS 80 int32_t trajectory_x[POINT_NUM]; int32_t trajectory_y[POINT_NUM]; uint8_t point_index 0; void Generate_Circle_Trajectory(void) { for (int i 0; i POINT_NUM; i) { float angle 2 * 3.14159 * i / POINT_NUM; trajectory_x[i] (int32_t)(RADIUS * cos(angle)); trajectory_y[i] (int32_t)(RADIUS * sin(angle)); } } // 在主循环中更新目标点 // 假设每100ms更新一个点 if (HAL_GetTick() - last_time 100) { last_time HAL_GetTick(); target_x trajectory_x[point_index]; target_y trajectory_y[point_index]; point_index (point_index 1) % POINT_NUM; }5.2 提高系统性能与稳定性传感器数据滤波滑动平均滤波最简单有效。#define FILTER_LEN 5 int32_t x_buf[FILTER_LEN], y_buf[FILTER_LEN]; uint8_t buf_index 0; int32_t Filter_Data(int32_t new_val, int32_t *buf) { buf[buf_index] new_val; buf_index (buf_index 1) % FILTER_LEN; int64_t sum 0; for (int i 0; i FILTER_LEN; i) sum buf[i]; return (int32_t)(sum / FILTER_LEN); } // 使用时filtered_x Filter_Data(ball_x, x_buf);卡尔曼滤波更优但更复杂适用于噪声统计特性已知的情况。PID算法改进积分抗饱和上文代码已实现。当输出长时间限幅时停止积分累加防止系统“失控”。微分先行只对测量值微分不对设定值微分可以避免设定值突变引起的输出抖动。变参数PID根据误差大小切换不同的PID参数大误差时用大P快速接近小误差时用小P和I提高精度。机械与硬件优化使用编码器舵机或步进电机闭环驱动器实现更精确的角度控制。优化连杆机构减少空程和摩擦。为平板增加轻质围栏防止小球飞出。6. 常见问题与故障排查在开发过程中你一定会遇到各种问题。下面是一个快速排查清单问题现象可能原因排查思路与解决方案小球完全不动1. 电源功率不足或接线松动。2. 舵机未收到PWM信号或信号格式不对。3. 机械卡死。1. 检查电源电压电流测量舵机供电电压。2. 用示波器检查STM32 PWM引脚输出波形确认频率(50Hz)和脉宽(0.5-2.5ms)正确。3. 手动拨动平板检查是否顺畅。小球运动方向相反PID输出正负与舵机转动方向不匹配。在PID输出到PWM转换的公式中为out_x或out_y乘以-1。pwm_x 750 - (int32_t)out_x;小球剧烈振荡发抖1. PID参数不合理Kp或Ki过大。2. 控制周期太快。3. 传感器数据噪声太大。1. 重新整定PID大幅降低Kp和Ki。2. 适当增加主循环延时如从10ms改为20ms。3. 加强数据滤波检查OpenMV识别是否稳定。小球缓慢漂移无法精确停在目标点积分作用太弱或存在静态误差。1. 适当增加Ki。2. 检查机械零点是否漂移平板是否绝对水平。3. 检查OpenMV坐标零点是否与平板中心对准。OpenMV坐标数据跳动大1. 光照变化或反光。2. 颜色阈值设置不准确。3. 小球颜色与背景区分度不够。1. 提供均匀、稳定的光源避免阴影。2. 使用OpenMV IDE的“阈值编辑器”工具在真实环境下重新标定。3. 更换小球或背景板颜色提高对比度。串口通信数据乱码或丢失1. 波特率不匹配。2. 接线错误RX/TX接反。3. 缓冲区溢出。1. 确认STM32和OpenMV串口初始化波特率一致如115200。2. 检查接线OpenMV的TX接STM32的RXOpenMV的RX接STM32的TX。3. 确保STM32及时读取接收缓冲区或增加缓冲区大小。系统响应迟钝1. 控制周期太慢。2. 舵机响应速度慢可能是电源不足。3. PID参数过于保守。1. 减少主循环中的延时和复杂运算。2. 确保舵机供电电压达到额定值如6V并使用低内阻电源。3. 在稳定的前提下适当增大Kp。7. 工程实践与参赛建议如果你是为电子设计竞赛准备这个项目以下几点经验可能对你有帮助模块化开发与测试不要试图一次性连接所有模块。务必先让每个模块OpenMV识别、STM32 PWM、串口通信单独工作正常再进行联调。重视机械结构很多队伍失败在机械上。结构要牢固转动要顺滑重心要低。在加工条件有限的情况下可以使用激光切割亚克力板搭配标准的舵机支架和连杆套件。设计人机交互增加按键、旋钮或蓝牙/WIFI模块用于切换模式如手动、自动、轨迹选择、调整参数、启停系统。这能极大提升演示效果。准备调试工具USB转串口模块用于连接STM32的调试串口到电脑实时打印数据。逻辑分析仪用于抓取PWM、串口波形排查硬件问题。手机充电宝作为移动电源方便演示。撰写报告与整理代码竞赛中设计报告占分很高。代码要规范注释关键算法要有流程图。报告中要清晰阐述系统方案、理论分析如控制系统建模、参数整定过程和测试结果最好有曲线图。应对现场环境比赛现场光线可能和实验室不同。准备可调节的补光灯或者让OpenMV的阈值有一定的宽容度。准备备用小球和易损件如舵机。从一个小球在平板上滚动的简单想法到一个稳定、快速、精准的自动控制系统这个过程充满了挑战也涵盖了嵌入式开发的核心技能链感知、决策、执行。希望这篇长文能为你提供一个清晰的实现路径和扎实的技术支撑。记住调试过程最能锻炼人耐心观察现象理性分析数据大胆假设小心验证。当你看到小球最终听话地走向你指定的位置时那种成就感就是对所有努力最好的回报。
返回列表