拓冰建站拓冰建站
首页 / 资讯中心 / 正文

电赛循迹系统实战:五路传感器与PID算法实现车载平衡滚球精准控制

在准备电赛这类综合性极强的电子设计竞赛时很多同学都会卡在“循迹”这个看似基础实则考验综合能力的环节上。尤其是在“车载平衡滚球运动控制系统”这类复杂题目中如何让小车稳定、精准地沿预定轨迹跑完一圈往往成为决定成败的关键。网上资料虽多但要么过于零散要么只讲传感器原理缺乏从硬件选型、算法调试到系统联调的完整闭环方案。本文将围绕“26电赛H题车载平衡滚球运动控制系统 任务二循迹一圈”这一核心需求为你拆解一套从零到一的完整实战方案。无论你是初次接触电赛的新手还是希望优化现有方案的同学都能从中获得可直接复用的代码、清晰的调试思路以及避坑指南。我们将重点讲解五路循迹传感器的应用、PID控制算法的实现、以及如何与滚球平衡系统协同工作最终实现稳定的一圈循迹。1. 系统概述与核心需求分析在开始动手之前我们必须清晰地理解题目要求和系统构成。这有助于我们选择正确的技术路径避免在错误的方向上浪费宝贵时间。1.1 题目任务解读车载平衡滚球运动控制系统“车载平衡滚球运动控制系统”是一个典型的机电一体化综合设计题目。它通常包含两个核心子系统平衡系统控制一个平台或“车”使其上的滚球能够保持平衡或完成特定轨迹运动。这涉及到姿态感知如陀螺仪、加速度计和电机驱动控制。循迹系统控制承载平衡系统的移动底盘使其能够沿着地面铺设的黑色轨迹线自动行驶。任务二“循迹一圈”明确要求移动底盘即“车”部分具备自主循迹能力。这意味着我们需要在平衡系统可能持续工作的背景下叠加一个精确的路径跟踪控制层。两个子系统之间存在耦合循迹时的启停、转弯会影响车的姿态进而干扰滚球的平衡。因此系统设计必须考虑两者的协同与解耦。1.2 循迹任务的核心技术指标一个成功的循迹一圈任务不能仅仅满足于“能跑完”而应追求稳定、快速、精准。我们可以将其分解为以下几个可量化的技术指标稳定性小车在直道和弯道上行驶平稳无明显左右摇摆或冲出轨迹线的“画龙”现象。循迹精度能够始终将轨迹线保持在车体中心附近对十字路口、锐角弯等复杂路径有良好的处理能力。速度适应性在保证稳定性的前提下尽可能提高行驶速度。通常需要实现速度的闭环控制。系统兼容性循迹控制算法不能与上层的滚球平衡控制算法产生严重冲突需共享MCU资源且互不干扰。抗干扰能力能适应环境光线变化、地面轻微不平整等实际情况。1.3 总体方案设计思路基于以上分析一个典型的系统方案框图如下[传感器层] -- [信号处理层] -- [控制决策层] -- [执行层] | | | | 五路循迹模块 ADC读取/滤波 PID控制器 电机驱动 编码器可选 速度计算 路径决策 PWM输出 | | |---[上层系统滚球平衡控制]---|工作流程感知五路循迹传感器阵列实时检测地面黑线位置编码器可选测量实际车速。处理MCU读取传感器模拟/数字值经过滤波处理后计算出车体偏离轨迹中心的“误差”。决策将“误差”输入PID控制器计算出用于纠正方向的转向控制量。同时根据路径情况如直道、弯道和编码器反馈综合决策目标速度。执行将计算出的转向控制量和速度控制量转换为左右电机的PWM占空比通过电机驱动模块驱动小车。协同整个循迹控制循环在一个固定的定时器中断中运行与滚球平衡控制的周期错开或分优先级处理确保系统实时性。2. 硬件选型与电路设计硬件是算法落地的基础。正确的选型和可靠的电路设计能极大降低后期调试的难度。2.1 核心传感器五路循迹模块详解五路循迹模块是本次任务的核心传感器。与三路、七路相比五路在成本、精度和复杂度之间取得了良好平衡。工作原理模块上并列安装5对红外发射管和接收管。红外光照射到地面黑色轨迹线吸收大部分红外光反射弱白色地面反射强。接收管根据接收到的反射光强度输出不同的模拟电压或经比较器后输出数字高低电平。优点分析检测范围宽5个探头横向分布能覆盖更宽的路径提前感知弯道。误差分辨率高可以计算出更精细的横向位置偏差而不仅仅是“左偏”或“右偏”。例如通过加权算法可以将5个传感器的状态映射为一个连续的偏差值。应对复杂路径对十字交叉线、断续线有更好的识别能力。模块接口通常输出5路模拟信号AO1~AO5和5路数字信号DO1~DO5。我们强烈推荐使用模拟信号因为它能提供灰度信息使控制更加平滑。数字信号只有0/1在临界点时容易抖动。安装要点传感器离地高度通常在1-3cm需根据实际模块调整。探头间距应略小于轨迹线宽度以确保任何时候至少有1-2个探头能检测到黑线。安装平面要平整且与地面平行。2.2 主控单元与电源模块主控MCUSTM32F4系列如F407、F429或STM32G4系列是电赛热门选择。它们拥有充足的定时器用于PWM生成和编码器接口、ADC通道用于读取模拟传感器和运算能力运行浮点PID。对于更复杂的双系统协同其性能绰绰有余。电机驱动模块推荐使用集成芯片方案如TB6612FNG或DRV8833。它们支持双路直流电机控制内置防共态导通逻辑驱动能力强体积小。绝对避免使用L298N其发热大、效率低。电源模块这是系统稳定的基石必须重视。需求分析MCU3.3V/5V、传感器5V、电机驱动需根据电机额定电压常用7.4V或12V。电机启动瞬间电流很大。设计方案使用一块大容量如3000mAh以上的2S或3S锂电池作为总电源。采用DC-DC降压模块如LM2596、MP1584从电池电压降压得到电机驱动所需电压如12V和5V总线电压。从5V总线再通过LDO如AMS1117-3.3得到纯净的3.3V给MCU和数字电路。关键点电机电源与MCU/传感器电源必须隔离或做充分滤波大电容磁珠防止电机噪声干扰控制系统导致程序跑飞或传感器读数异常。2.3 辅助传感器与执行器编码器虽非强制但强烈建议为驱动电机加装增量式编码器。它可以提供精确的车速和行走距离反馈是实现速度闭环、精确位置控制如停车的关键。车体结构建议使用差速转向的小车底盘。结构简单控制模型清晰。轮胎选择抓地力好的避免打滑。3. 软件架构与核心算法实现软件是系统的灵魂。一个清晰的架构和稳健的算法是实现高性能循迹的保证。3.1 软件整体架构设计建议采用“前后台”或“多任务”架构确保实时性。高速中断服务程序定时器中断用于执行核心控制循环。例如设置一个1ms或2ms的定时器中断在其中依次执行读取传感器、计算偏差、运行PID、更新PWM。这个中断的优先级应设为最高。编码器接口中断用于捕获脉冲计算速度。主循环执行非实时性任务如读取按键、更新OLED显示、通过串口发送调试数据、处理上位机指令等。管理状态机控制小车的“启动”、“循迹”、“停车”等状态切换。3.2 循迹偏差计算算法如何将5个传感器的读数转化为一个代表横向偏差的数值是算法的第一步。这里介绍两种常用方法方法一加权平均法适用于模拟传感器假设5个传感器从左到右编号为S0, S1, S2, S3, S4安装在车头中心对称位置。S2是中间传感器。读取每个传感器的模拟值ADC_Value[i]。由于黑线上读数小白地上读数大我们将其反相或归一化处理得到一个“黑线强度”Line_Strength[i] MAX_ADC - ADC_Value[i]。为每个传感器分配一个位置权重例如Weight[i] {-2, -1, 0, 1, 2}对应其相对于车中心的位置。计算加权偏差Error// 示例代码片段 float sum_strength 0; float sum_weighted 0; for (int i 0; i 5; i) { line_strength[i] ADC_MAX - adc_value[i]; // 假设ADC_MAX为白地值 sum_strength line_strength[i]; sum_weighted line_strength[i] * position_weight[i]; // position_weight {-2, -1, 0, 1, 2} } if (sum_strength THRESHOLD) { // 防止除零且确保检测到线 current_error sum_weighted / sum_strength; // 归一化的偏差范围大约在[-2, 2] } else { // 丢失黑线处理策略例如保持上一次偏差或执行搜索 }这样得到的current_error是一个浮点数。0表示黑线在正中心负值表示偏左正值表示偏右。数值大小代表偏离程度。方法二状态机法适用于数字传感器将5位数字信号0/1映射为有限的状态如“偏左大”、“偏左小”、“居中”、“偏右小”、“偏右大”、“十字路口”、“丢失”等。根据当前状态和上一状态来决定转向策略。这种方法更依赖于逻辑判断。3.3 PID控制算法实现与调参PID是循迹控制的核心。我们将使用位置式PID并分别对转向和速度进行控制。1. 转向PID控制控制目标是使偏差Error趋于0。// pid.h typedef struct { float Kp, Ki, Kd; // PID参数 float integral; // 积分项 float prev_error; // 上一次误差 float output_max; // 输出限幅 float output_min; } PID_Controller; void PID_Init(PID_Controller *pid, float kp, float ki, float kd, float out_max, float out_min); float PID_Calculate(PID_Controller *pid, float error);// pid.c #include pid.h void PID_Init(PID_Controller *pid, float kp, float ki, float kd, float out_max, float out_min) { pid-Kp kp; pid-Ki ki; pid-Kd kd; pid-integral 0; pid-prev_error 0; pid-output_max out_max; pid-output_min out_min; } float PID_Calculate(PID_Controller *pid, float error) { float proportional pid-Kp * error; pid-integral error; // 积分抗饱和 float integral_term pid-Ki * pid-integral; if (integral_term pid-output_max) { integral_term pid-output_max; pid-integral pid-output_max / pid-Ki; // 反向计算饱和时的积分值 } else if (integral_term pid-output_min) { integral_term pid-output_min; pid-integral pid-output_min / pid-Ki; } float derivative pid-Kd * (error - pid-prev_error); pid-prev_error error; float output proportional integral_term derivative; // 输出限幅 if (output pid-output_max) output pid-output_max; if (output pid-output_min) output pid-output_min; return output; }2. 速度PID控制需编码器控制目标是使实际车速逼近目标车速。速度测量在定时中断中读取编码器累计的脉冲数计算间隔时间内的速度。目标速度设定可以是一个固定值也可以根据路径动态调整如直道加速、弯道减速。实现一个独立的PID控制器其输出作为电机的基础PWM值。3. 最终PWM合成转向PID的输出steer_output和速度PID的输出speed_base共同决定左右轮PWM。// 假设 speed_base 是正数steer_output 正代表需要右转左轮加速右轮减速 float pwm_left speed_base - steer_output; float pwm_right speed_base steer_output; // 对 pwm_left, pwm_right 进行限幅0~PWM_MAX set_motor_pwm(MOTOR_LEFT, pwm_left); set_motor_pwm(MOTOR_RIGHT, pwm_right);4. PID调参“三步法”调Kp比例将Ki和Kd设为0。逐渐增大Kp直到小车在直道上出现小幅、高频的振荡。此时Kp约为临界值的60%-70%。调Kd微分加入Kd可以抑制振荡提高稳定性。逐渐增大Kd直到高频振荡消失过弯时响应迅速且平稳。Kd过大会引入高频噪声可能需要传感器滤波。调Ki积分用于消除静态误差如长期偏向一侧。Ki值通常很小。慢慢增加Ki观察小车能否在长直道最终精确居中。Ki太大会导致系统响应迟钝和超调。3.4 复杂路径处理策略十字路口识别当中间三个传感器S1, S2, S3同时检测到黑线且持续一段时间可判定为十字路口。策略可以是保持原方向直行一段固定时间或距离然后恢复循迹。锐角弯/急弯当偏差Error突然变得很大且持续增大可能进入急弯。此时可以临时增大PID参数尤其是Kp或直接给一个固定的较大转向PWM。丢线处理当所有传感器都检测不到黑线sum_strength过小启动丢线处理。例如根据最后已知的偏差方向让小车向那个方向原地缓慢旋转直到重新检测到黑线。4. 完整实战从零搭建循迹系统本章节将提供一个基于STM32 HAL库的简化版完整代码框架帮助你快速搭建项目。4.1 工程创建与基础配置使用STM32CubeMX创建工程选择你的MCU型号。时钟配置配置到最高主频。引脚配置ADC启用5路ADC通道IN0~IN4对应连接5个循迹传感器模拟输出。配置为扫描模式、连续转换。定时器启用一个高级定时器如TIM1的两个通道生成PWM输出给电机驱动。启用一个通用定时器如TIM2用于编码器接口模式连接左右编码器。启用一个基本定时器如TIM6用于产生1ms的中断作为控制周期。UART启用一个串口用于调试信息输出。生成代码使用IDEKeil或IAR打开工程。4.2 核心模块驱动代码1. ADC读取与滤波 (adc.c)// adc.h #define TRACE_SENSOR_NUM 5 extern volatile uint16_t g_adc_raw[TRACE_SENSOR_NUM]; extern float g_line_strength[TRACE_SENSOR_NUM]; void TraceSensor_Init(void); void TraceSensor_UpdateValues(void); // 在定时中断中调用 float TraceSensor_GetError(void);// adc.c #include adc.h #include main.h // 包含HAL库和ADC句柄定义 extern ADC_HandleTypeDef hadc1; // 假设你的ADC句柄 volatile uint16_t g_adc_raw[TRACE_SENSOR_NUM] {0}; float g_line_strength[TRACE_SENSOR_NUM] {0}; const float POSITION_WEIGHT[TRACE_SENSOR_NUM] {-2.0f, -1.0f, 0.0f, 1.0f, 2.0f}; #define WHITE_BASE 3500 // 白地ADC基准值需校准 #define BLACK_BASE 500 // 黑线ADC基准值需校准 void TraceSensor_Init(void) { HAL_ADC_Start_DMA(hadc1, (uint32_t*)g_adc_raw, TRACE_SENSOR_NUM); } void TraceSensor_UpdateValues(void) { // DMA自动更新g_adc_raw这里进行滤波和计算 static float filtered_adc[TRACE_SENSOR_NUM] {0}; const float alpha 0.3f; // 一阶低通滤波系数 for(int i0; iTRACE_SENSOR_NUM; i) { // 1. 低通滤波 filtered_adc[i] alpha * g_adc_raw[i] (1-alpha) * filtered_adc[i]; // 2. 计算黑线强度 (0~1.0) float strength (WHITE_BASE - filtered_adc[i]) / (WHITE_BASE - BLACK_BASE); if(strength 0) strength 0; if(strength 1.0f) strength 1.0f; g_line_strength[i] strength; } } float TraceSensor_GetError(void) { float sum_weighted 0, sum_strength 0; for(int i0; iTRACE_SENSOR_NUM; i) { sum_weighted g_line_strength[i] * POSITION_WEIGHT[i]; sum_strength g_line_strength[i]; } if(sum_strength 0.5f) { // 检测到有效黑线 return sum_weighted / sum_strength; } else { // 丢线处理返回一个极大值或特殊值由上层处理 return 999.0f; } }2. 电机控制与PID (motor.c/control.c)// control.h void Control_Init(void); void Control_Loop(void); // 在1ms定时中断中调用// control.c #include control.h #include pid.h #include adc.h #include motor.h // 假设有设置PWM的函数 PID_Controller steer_pid; PID_Controller speed_pid; float target_speed 30.0f; // 目标速度 cm/s float current_speed 0.0f; // 由编码器计算得到 void Control_Init(void) { // 初始化转向PID参数需调试 PID_Init(steer_pid, 10.0f, 0.05f, 2.0f, 30.0f, -30.0f); // 初始化速度PID PID_Init(speed_pid, 1.0f, 0.01f, 0.05f, 10.0f, -10.0f); Motor_Init(); // 初始化电机PWM等 } void Control_Loop(void) { // 1. 更新传感器值 TraceSensor_UpdateValues(); // 2. 获取循迹偏差 float trace_error TraceSensor_GetError(); // 3. 丢线判断与处理 if(trace_error 100.0f) { // 假设999为丢线 // 执行停车或搜索策略 Motor_SetPWM(0, 0); return; } // 4. 计算转向控制量 float steer_out PID_Calculate(steer_pid, trace_error); // 5. 更新当前速度 (此处需实现编码器速度计算函数) // current_speed Encoder_GetSpeed(); // 假设我们暂时使用开环速度 static float speed_base 20.0f; // 基础PWM值 // 6. 速度闭环 (如果启用编码器) // float speed_error target_speed - current_speed; // float speed_adjust PID_Calculate(speed_pid, speed_error); // speed_base speed_adjust; // 7. 合成最终PWM float pwm_left speed_base - steer_out; float pwm_right speed_base steer_out; // 8. PWM限幅 (假设PWM范围0~100) if(pwm_left 100) pwm_left 100; if(pwm_left 0) pwm_left 0; if(pwm_right 100) pwm_right 100; if(pwm_right 0) pwm_right 0; // 9. 输出到电机 Motor_SetPWM(pwm_left, pwm_right); }3. 定时器中断服务函数// 在stm32f4xx_it.c中找到对应的中断函数 void TIM6_DAC_IRQHandler(void) { if(__HAL_TIM_GET_FLAG(htim6, TIM_FLAG_UPDATE) ! RESET) { __HAL_TIM_CLEAR_IT(htim6, TIM_IT_UPDATE); Control_Loop(); // 核心控制循环 } }4.3 主函数与调试信息输出// main.c #include main.h #include adc.h #include control.h #include stdio.h UART_HandleTypeDef huart2; // 假设调试串口 int __io_putchar(int ch) { HAL_UART_Transmit(huart2, (uint8_t*)ch, 1, 1000); return ch; } int main(void) { HAL_Init(); SystemClock_Config(); MX_GPIO_Init(); MX_ADC1_Init(); MX_TIM1_Init(); // PWM定时器 MX_TIM2_Init(); // 编码器定时器 MX_TIM6_Init(); // 控制周期定时器 MX_USART2_UART_Init(); TraceSensor_Init(); Control_Init(); // 启动控制周期定时器中断 HAL_TIM_Base_Start_IT(htim6); // 启动PWM输出 HAL_TIM_PWM_Start(htim1, TIM_CHANNEL_1); // 左电机 HAL_TIM_PWM_Start(htim1, TIM_CHANNEL_2); // 右电机 printf(System Start!\r\n); while (1) { // 主循环处理非实时任务 // 例如每100ms通过串口发送一次传感器数据和PID输出用于上位机绘图调试 static uint32_t last_tick 0; if(HAL_GetTick() - last_tick 100) { last_tick HAL_GetTick(); float err TraceSensor_GetError(); printf(Err:%.2f\r\n, err); // 发送给上位机如SerialPlot } HAL_Delay(10); } }5. 系统调试与性能优化代码写完后调试才是真正的开始。遵循科学的调试步骤可以事半功倍。5.1 分模块调试流程传感器调试将小车抬起用串口打印出5路传感器的原始ADC值。分别将传感器对准白纸和黑线记录数值。确定WHITE_BASE和BLACK_BASE。移动黑线观察计算出的Error值变化是否连续、平滑。这是后续控制的基础。电机与开环调试注释掉PID控制手动给左右电机设置固定的PWM值测试小车能否前进、后退、左转、右转。确保电机接线正确左右轮转向逻辑正确。转向PID调试开环速度将速度设定为固定值只启用转向PID。先将Ki,Kd设为0只调Kp。将小车放在直道上观察其是否振荡。逐步加入Kd抑制振荡最后加入微小的Ki消除稳态误差。速度闭环调试需编码器固定转向如让小车空转或走直线调试速度PID使其能快速、稳定地达到目标速度。系统联调将转向环和速度环结合在简单圆形或8字形轨迹上测试。逐步提高目标速度观察稳定性。5.2 上位机调试工具的使用串口打印数字不直观强烈建议使用上位机软件进行图形化调试。推荐工具SerialPlot、VOFA、匿名上位机等。方法在控制循环中通过串口发送关键数据如Error、steer_out、pwm_left、pwm_right。好处可以实时绘制曲线清晰看到偏差变化、PID输出、系统响应速度极大提高调参效率。5.3 常见问题与解决方案问题现象可能原因排查与解决思路小车剧烈振荡画龙1. Kp过大2. 传感器安装过高或过低3. 控制周期太慢1. 减小Kp2. 调整传感器高度确保信号跳变清晰3. 缩短定时中断周期如1ms过弯时冲出轨1. 弯道速度过快2. Kd太小或Ki太大3. 传感器检测不到弯道内侧线1. 弯道减速根据Error大小动态调整目标速度2. 调整PID参数增加微分作用3. 检查传感器间距是否合适尝试增加探头数量响应迟钝纠偏慢1. Kp太小2. 电机PWM最大值限制过低3. 机械结构松动1. 适当增大Kp2. 提高PWM限幅值3. 紧固轮子、联轴器等十字路口误动作识别逻辑不准确或处理策略不当增加十字路口的判定条件如中间多路传感器持续检测到线的时间并优化直行通过策略突然丢线后乱跑丢线处理策略缺失或不当实现完善的丢线处理状态机例如根据历史偏差进行小角度旋转搜索代码运行一段时间后卡死1. 中断嵌套或优先级问题2. 栈溢出3. 电源干扰1. 检查中断优先级控制循环中断优先级最高但不宜过长2. 增大堆栈大小3. 加强电源滤波电机电源与数字电源隔离6. 与滚球平衡系统的协同与进阶优化完成基础的循迹后需要考虑如何与题目另一核心——滚球平衡系统——协同工作。6.1 系统资源分配与任务调度两个控制系统共享一个MCU必须做好资源规划和任务调度。传感器资源循迹用GPIO/ADC平衡用I2C/SPI读取陀螺仪。通常无冲突。执行器资源循迹控制底盘两个电机平衡控制平台的两个舵机或电机。需使用不同的定时器/PWM通道。计算资源两者都需要运行PID。确保CPU负载足够如果计算复杂可以考虑使用带FPU的MCU。任务调度策略优先级平衡控制的优先级应高于循迹控制。因为一旦球掉落任务直接失败。可以将平衡控制放在更高优先级的中断中。时间片为两者分配固定的控制周期。例如平衡控制每1ms执行一次循迹控制每2ms执行一次。使用不同定时器触发。数据共享如果平衡状态如平台倾角需要影响循迹决策如减速需要通过全局变量加临界区保护或消息队列进行通信。6.2 动态参数调整与自适应控制为了让小车表现更智能可以引入动态调整策略变PID参数根据偏差大小动态调整PID参数。大偏差时用大Kp快速纠正小偏差时用小Kp配合Kd保持稳定。弯道减速根据偏差的绝对值或变化率来动态降低目标速度target_speed base_speed - k * fabs(error)。路径记忆与预测对于固定赛道可以记录第一次慢速运行时的传感器数据或电机指令第二次运行时进行预判实现更优控制。6.3 稳定性与鲁棒性提升软件滤波除了ADC的一阶滤波可以对计算出的Error再进行一次滑动平均滤波平滑突变。死区处理当偏差Error的绝对值小于一个很小阈值时将误差视为0避免电机高频微振。输出平滑对PID的输出进行限幅和变化率限制防止电机指令突变。故障安全增加看门狗在程序跑飞时自动复位。关键数据如PID参数异常时使用默认安全值。循迹一圈是电赛中经典且富有挑战性的任务。成功的关键不在于算法多么高深而在于对每个模块的深入理解、细致的调试和系统的整合能力。从传感器的校准、PID参数的整定到与平衡系统的协同每一步都需要耐心和实践。建议你先在平坦地面上用简单轨迹调试通过再逐步增加难度和速度。
分享:

看完干货,该让你的企业上线了

免费需求沟通 · 48 小时内出具建站方案 · 河南本地可上门