最近在整理往届智能车竞赛的技术资料时,翻到了21届华南赛区预赛第一、最终在国赛舞台上完成“最后一舞”的华南理工大学“疯狂电路组”的备赛笔记和代码。这支队伍的经历非常典型:从校赛的磕磕绊绊,到分区赛的惊艳表现,再到国赛面对强敌的稳定发挥。他们的技术方案,尤其是在电路设计、控制算法和系统调试上的思路,对于后来者极具参考价值。本文将深度复盘这支冠军队伍的核心技术架构与实战经验,从硬件选型、软件策略到现场调试,为你呈现一套完整的智能车竞赛备赛闭环方案。无论你是初次参赛的新手,还是寻求突破的老队员,都能从中找到可直接复用的思路和避坑指南。
1. 背景与核心概念:智能车竞赛与“疯狂电路组”
全国大学生智能汽车竞赛是一项以“立足培养、重在参与、鼓励探索、追求卓越”为宗旨的经典赛事。参赛队伍需要制作一个能够自主识别赛道,并快速稳定行驶的模型汽车。它涵盖了自动控制、模式识别、传感技术、汽车电子、电气、计算机、机械等多个学科的知识,是对学生综合工程实践能力的全面锤炼。
“疯狂电路组”这个队名,直接揭示了他们的技术侧重点——硬件电路。在多数队伍将精力集中于图像处理和PID调参时,他们认识到,一个响应迅速、噪声低、驱动能力强的硬件平台,是软件算法得以完美执行的基石。他们的“疯狂”体现在对硬件极致的追求:自定义主板、精心设计的电机驱动、高度集成的传感器调理电路。这套稳定的硬件系统,为他们复杂的控制算法(如模糊控制、赛道记忆算法)提供了可靠的“身体”,使其在高速过弯、应对复杂路况时表现出了卓越的稳定性和鲁棒性。他们的经历证明,在智能车竞赛中,“软硬兼施”,且硬件先行,是通往高水平的必经之路。
2. 环境准备与版本说明
要复现或借鉴“疯狂电路组”的方案,首先需要搭建类似的开发环境。需要注意的是,智能车竞赛技术迭代快,但核心思想相通。以下环境基于他们参赛时的主流选择,当前备赛可根据最新平台调整。
2.1 主控平台
- 核心微控制器:恩智浦(NXP) i.MX RT1064。这是当时和目前很多顶尖队伍的选择。它基于Arm Cortex-M7内核,主频高达600MHz,拥有充足的计算能力处理图像与复杂控制算法。
- 开发环境:IAR Embedded Workbench for Arm 或 Keil MDK。官方推荐IAR,其编译优化效率较高。
- 固件库:NXP官方提供的SDK(Software Development Kit)。建议使用较新的稳定版本,并注意其中驱动库(如GPIO、PIT定时器、FlexIO、eLCDIF等)的用法。
2.2 编程语言与关键库
- 主要语言:C语言。嵌入式开发的主流,要求对指针、内存管理、位操作有深刻理解。
- 关键算法库:可能需要自行实现或移植:
- 图像处理库:基本的二值化、边缘检测、巡线算法。
- 数学库:
math.h用于三角函数、开方等运算。 - 控制算法:PID控制器、模糊控制器等需自行编码实现。
2.3 机械与硬件环境
- 车模:竞赛指定的车模平台(如A、B、C、D等车型),“疯狂电路组”当年使用的是D车(四轮)或类似车型。
- 传感器:
- 摄像头:全局快门摄像头(如MT9V034),用于获取赛道图像。
- 电感:用于电磁组,检测交变磁场信号。
- 编码器:正交编码器,安装在电机上,用于测速。
- 自制电路板:这是“疯狂电路组”的核心。包括:
- 主控板:集成单片机最小系统、摄像头接口、编码器接口、陀螺仪接口、通信接口等。
- 电机驱动板:基于MOSFET的H桥驱动电路,支持大电流、高频PWM。
- 传感器调理板:对摄像头信号、电感信号进行放大、滤波和电压匹配。
- 电源系统:7.4V或12V锂电池,配合高效的DC-DC降压模块,为不同模块提供稳定、干净的3.3V、5V、6V等电压。
2.4 调试工具
- J-Link或DAP-Link调试器:用于程序下载和在线调试。
- 串口调试助手:用于打印程序运行数据(速度、偏差、控制量等)。
- 示波器:硬件调试神器,用于观察PWM波形、电源噪声、传感器信号质量。
- 逻辑分析仪:辅助分析数字通信时序(如摄像头SCCB协议)。
版本说明:本文重点在于分享设计思路、算法框架和调试方法。具体的芯片型号、库函数版本可能随赛事规则更新而变化。建议在最新规则下,借鉴其架构思想,而非照搬具体型号。
3. 核心系统设计拆解
“疯狂电路组”的胜利是系统工程的胜利。我们将从硬件、感知、控制、决策四个层面进行拆解。
3.1 硬件系统设计:稳定性的基石他们的自制PCB是性能超越的基础。
电源设计:
- 理念:模拟电路(摄像头、运放)和数字电路(单片机、逻辑芯片)分开供电,避免数字噪声干扰敏感的模拟信号。
- 实践:使用多个LDO(低压差线性稳压器)或高性能DC-DC模块。为电机驱动提供独立的大电流电源路径,并与控制电源进行隔离(如使用光耦)。
- 关键点:电源入口处必须有足够容量和低ESR的钽电容或陶瓷电容进行去耦,每个芯片的电源引脚附近放置0.1uF的退耦电容。
电机驱动电路:
- 选型:选用低内阻(Rds(on))、高开关频率的N-MOSFET(如IRLR7843)。
- 驱动芯片:使用专用的半桥/全桥驱动芯片(如IR2104S)来驱动MOSFET的栅极,确保快速开通和关断,减少发热和损耗。
- 保护电路:必须包含自举电容、死区时间设置、栅极电阻、以及续流二极管,防止直通和电压尖峰。
传感器接口电路:
- 摄像头接口:电平转换电路,将摄像头的3.3V或5V数据/时钟信号安全地送入单片机。并行为摄像头提供稳定的模拟电源(AVDD)。
- 电感调理电路:使用运算放大器(如LMV358)搭建交流放大和检波电路,将微弱的20kHz正弦信号放大并转换为直流电压供ADC采集。重点是多级滤波,抑制环境工频干扰和其他噪声。
3.2 感知系统:获取准确的赛道信息
图像采集与处理:
- 采集:利用单片机的FlexIO或CSI接口模拟摄像头时序,高效DMA传输图像数据到内存。
- 二值化:采用动态阈值法或大津法(OTSU),适应不同光照条件。
- 赛道提取:通过“边线搜索”或“中线提取”算法,将图像中的赛道边缘转化为可用于控制的偏差信息。他们可能采用了“前瞻点”策略,即不只看车头下方的线,还预测远处赛道的走向。
速度测量:
- 使用MCPWM模块的正交编码器接口,直接读取电机编码器的脉冲数,通过定时器计算得到精确的轮速。
3.3 控制系统:精准执行决策这是软件算法的核心。
速度环PID控制:
- 目的:让车的实际速度快速、无超调地跟随设定速度。
- 实现:编码器测速作为反馈,与目标速度比较得到误差,经PID运算后输出PWM占空比控制电机。
// 简化的速度PID结构体 typedef struct { float Target; // 目标速度 float Current; // 当前速度 float Err; // 本次误差 float Err_Last; // 上次误差 float Err_Sum; // 误差积分 float Kp, Ki, Kd; // PID参数 float Output; // 输出值(PWM占空比) float OutputMax; // 输出限幅 } PID_TypeDef; // PID计算函数(位置式) float PID_Calculate(PID_TypeDef *pid) { pid->Err = pid->Target - pid->Current; pid->Err_Sum += pid->Err; // 积分项 // 积分限幅,防止积分饱和 if(pid->Err_Sum > 1000) pid->Err_Sum = 1000; if(pid->Err_Sum < -1000) pid->Err_Sum = -1000; pid->Output = pid->Kp * pid->Err + pid->Ki * pid->Err_Sum + pid->Kd * (pid->Err - pid->Err_Last); // 输出限幅 if(pid->Output > pid->OutputMax) pid->Output = pid->OutputMax; if(pid->Output < -pid->OutputMax) pid->Output = -pid->OutputMax; pid->Err_Last = pid->Err; // 更新上次误差 return pid->Output; }方向环控制(核心中的核心):
- 输入:图像处理得到的赛道横向偏差(或曲率)。
- 输出:左右轮的速度差或舵机打角。
- 策略:“疯狂电路组”很可能使用了模糊PID或参数自适应PID。例如,在偏差大时使用大的P值快速纠正,在偏差小时使用小的P值避免震荡;在入弯时提前增加微分作用以抑制过冲。
- 舵机控制:如果使用舵机,其控制也是一个位置PID环,设定位置由方向环的输出决定。
3.4 决策系统:让车拥有“智慧”
- 赛道记忆与速度规划:
- 在第一次慢速运行时,记录下整个赛道的特征(如直道、弯道位置、曲率)。
- 第二次运行时,根据记忆信息,在直道段规划高速度,在弯道段提前减速,实现全局速度优化。这是他们能在长直道加速,又能平稳过弯的关键。
- 特殊元素处理:
- 环岛:识别到环岛入口特征时,切换到一个专用的“环岛控制状态机”,控制车沿环岛内侧或外侧行驶固定圈数后切出。
- 十字路口:识别十字后,通常需要维持原方向直行,这需要图像算法能区分十字和普通弯道。
- 坡道:通过陀螺仪检测俯仰角变化,在上下坡时微调速度目标,防止飞坡或卡住。
4. 完整软件框架与关键代码示例
一个清晰、模块化的软件框架至关重要。以下是借鉴“疯狂电路组”思路的一个简化版主程序框架。
4.1 工程文件结构
SmartCar_Project/ ├── CMSIS/ # ARM Cortex-M 核心支持文件 ├── Drivers/ # 单片机外设驱动(来自SDK) ├── User/ │ ├── main.c # 主函数 │ ├── bsp/ # 板级支持包 │ │ ├── bsp_motor.c/.h # 电机驱动 │ │ ├── bsp_encoder.c/.h # 编码器 │ │ ├── bsp_camera.c/.h # 摄像头 │ │ ├── bsp_imu.c/.h # 陀螺仪/加速度计 │ │ └── bsp_uart.c/.h # 串口调试 │ ├── algorithm/ # 核心算法 │ │ ├── image_processing.c/.h # 图像处理 │ │ ├── pid_controller.c/.h # PID算法 │ │ ├── fuzzy_logic.c/.h # 模糊控制(如果使用) │ │ └── track_memory.c/.h # 赛道记忆 │ ├── task/ # 任务模块 │ │ ├── task_sensing.c/.h # 感知任务(采集图像、编码器) │ │ ├── task_control.c/.h # 控制任务(计算PID,输出PWM) │ │ └── task_decision.c/.h # 决策任务(状态机,速度规划) │ └── system/ │ ├── sys_tick.c/.h # 系统滴答定时器 │ └── sys_param.c/.h # 系统参数存储(Flash) └── README.md4.2 主程序与任务调度采用前后台或基于时间片轮询的简单调度,足够应对智能车需求。
// File: User/main.c #include "sys_tick.h" #include "bsp_uart.h" #include "task_sensing.h" #include "task_control.h" #include "task_decision.h" // 全局变量,用于任务间通信 volatile int g_SystemTick = 0; // 系统心跳,1ms递增 CarState_t g_CarState; // 车辆状态(速度、偏差、角度等) ControlOutput_t g_ControlOut; // 控制输出(左右轮PWM) int main(void) { // 1. 硬件初始化 Board_Init(); // 初始化时钟、GPIO等 SysTick_Init(1000); // 1ms中断 UART_Init(115200); Motor_Init(); Encoder_Init(); Camera_Init(); IMU_Init(); printf("System Start!\r\n"); // 2. 算法模块初始化 PID_Init(&speed_pid_left, 1.0, 0.05, 0.0); // 初始化左轮速度PID PID_Init(&speed_pid_right, 1.0, 0.05, 0.0);// 初始化右轮速度PID PID_Init(&direction_pid, 2.5, 0.0, 1.0); // 初始化方向环PID ImageProcessor_Init(); // 3. 主循环(时间片轮询) while(1) { // 任务1:感知(每10ms执行一次) if(g_SystemTick % 10 == 0) { Task_Sensing(&g_CarState); // 采集图像、编码器、IMU数据 } // 任务2:决策与规划(每20ms执行一次) if(g_SystemTick % 20 == 0) { Task_Decision(&g_CarState); // 根据赛道信息规划目标速度、判断特殊元素 } // 任务3:控制(每5ms执行一次,频率最高) if(g_SystemTick % 5 == 0) { Task_Control(&g_CarState, &g_ControlOut); // 计算PID并输出PWM } // 任务4:调试信息发送(每100ms执行一次) if(g_SystemTick % 100 == 0) { Send_Debug_Data(&g_CarState, &g_ControlOut); // 通过串口发送数据到上位机 } // 其他后台任务... __WFI(); // 等待中断,进入低功耗模式(可选) } } // SysTick中断服务函数(1ms一次) void SysTick_Handler(void) { g_SystemTick++; // 可以在这里进行一些精确的计时操作 }4.3 图像处理核心函数示例
// File: User/algorithm/image_processing.c /** * @brief 从原始图像中提取赛道中线偏差 * @param image_buffer: 摄像头原始图像缓冲区 * @param image_width, image_height: 图像宽高 * @param row_to_analyze: 要分析的行(通常取图像下方几行) * @retval 偏差值,正数表示中线在图像中心右侧,负数表示在左侧 */ float Get_Track_Deviation(uint8_t *image_buffer, int width, int height, int row_to_analyze) { int left_edge = -1; int right_edge = -1; int mid_point; float deviation; // 1. 二值化(这里使用固定阈值简化示例,实际应用动态阈值) uint8_t binary_row[width]; for(int col = 0; col < width; col++) { int pixel_index = row_to_analyze * width + col; binary_row[col] = (image_buffer[pixel_index] > 100) ? 255 : 0; // 阈值100 } // 2. 从左向右扫描,寻找左边缘(黑到白跳变) for(int col = 5; col < width - 5; col++) { // 忽略边缘像素 if(binary_row[col] == 0 && binary_row[col+1] == 255) { left_edge = col; break; } } // 3. 从右向左扫描,寻找右边缘(白到黑跳变) for(int col = width - 5; col > 5; col--) { if(binary_row[col] == 255 && binary_row[col-1] == 0) { right_edge = col; break; } } // 4. 计算中线及偏差 if(left_edge != -1 && right_edge != -1) { // 找到两边边缘,计算中线 mid_point = (left_edge + right_edge) / 2; } else if(left_edge != -1) { // 只找到左边缘(可能右边是赛道外),假设赛道宽度固定 mid_point = left_edge + FIXED_TRACK_WIDTH / 2; } else if(right_edge != -1) { // 只找到右边缘 mid_point = right_edge - FIXED_TRACK_WIDTH / 2; } else { // 两边都没找到,返回一个极大偏差或保持上一次偏差 return LAST_KNOWN_DEVIATION; // 需要定义一个静态变量保存上次偏差 } // 图像中心点 int image_center = width / 2; // 偏差 = 中线 - 图像中心 (单位:像素) deviation = (float)(mid_point - image_center); return deviation; }5. 现场调试与参数整定实战经验
“疯狂电路组”的笔记里充满了调试心得,这是他们能快速适应赛场的关键。
5.1 PID参数整定“三步法”
- 先速度环,后方向环:确保车能稳定地加速、减速、匀速跑,再调转向。
- 先P,后I,最后D:
- P(比例):决定响应速度。从小到大增加,直到车出现轻微震荡,然后回调到震荡前的值。
- I(积分):消除静态误差。如果车在直道上无法达到设定速度,缓慢增加Ki。但积分太强会引起超调震荡。
- D(微分):预测变化,抑制震荡。在过弯时如果车头摆动厉害,适当增加Kd。但微分对噪声敏感,容易引入高频抖动。
- 分场景调参:直道、小弯、大弯、环岛的理想PID参数可能不同。可以设计一个简单的参数表,根据识别到的赛道类型进行切换。
5.2 图像调试技巧
- 上位机软件:使用诸如“山外上位机”、“匿名上位机”等工具,将摄像头图像、赛道中线、偏差曲线实时发送到电脑,直观判断图像处理效果。
- 动态阈值:一定要实现。可以取图像中感兴趣区域(ROI)的灰度值统计(均值、方差)来计算阈值。
- 抗干扰处理:对图像进行中值滤波,去除椒盐噪声;进行形态学操作(开运算、闭运算)连接断线或消除孤点。
5.3 现场快速适配
- 光照影响:准备不同光照条件下的参数组(如“强光”、“弱光”、“正常”),赛前根据现场灯光快速切换。
- 电池电压:电池电压下降会导致电机功率不足。可以在速度环PID中引入电压补偿,或者直接监测电压,当电压低于阈值时微调速度目标值。
- 机械检查清单:
- 轮胎摩擦力是否均匀?有无灰尘?
- 所有螺丝是否紧固?特别是电机、舵机、摄像头支架。
- 传感器高度和角度是否与调试时一致?
- 电路板连接线是否牢固?有无虚焊?
6. 常见问题与排查思路
| 问题现象 | 可能原因 | 排查思路与解决方案 |
|---|---|---|
| 车模启动后原地抖动或反转 | 1. 电机线序接反。 2. 编码器A/B相序接反。 3. PID参数极性错误(正反馈)。 | 1. 交换任意一个电机的两根线。 2. 交换编码器的A、B相。 3. 将速度环PID的输出乘以-1试试。 |
| 直线跑偏 | 1. 机械结构不对称(左右轮距、前束角)。 2. 左右轮电机性能差异。 3. 摄像头未安装居中或水平。 | 1. 精细调整机械,确保对称。 2. 分别测试左右轮空转速度,在软件中为两个电机设置不同的PWM补偿值。 3. 校准摄像头安装,并通过图像确认中线在直道上是否居中。 |
| 过弯时冲出赛道 | 1. 方向环P值太小,转向不足。 2. 速度太快,离心力过大。 3. 图像前瞻太近,反应迟钝。 | 1. 适当增加方向环P值或D值。 2. 在弯道处规划更低的目标速度。 3. 增加图像处理的前瞻行数,或使用“弯道识别”提前减速。 |
| 图像时有时无,或出现横条纹 | 1. 摄像头电源不稳定,有噪声。 2. 数据线接触不良或受干扰。 3. 单片机采集时序不匹配。 | 1. 用示波器检查摄像头电源纹波,加强滤波电容。 2. 使用屏蔽线,缩短接线长度,确保插接牢固。 3. 检查单片机IO口配置(上拉/下拉),调整采集延时参数。 |
| 编码器计数不准或为零 | 1. 编码器供电不足。 2. 单片机编码器接口配置错误(计数模式、滤波)。 3. 编码器线断或接触不良。 | 1. 确保编码器供电电压稳定(通常5V)。 2. 仔细查阅数据手册,配置正确的正交编码模式。 3. 用万用表测量通断,检查接线。 |
| 车在赛道上“画龙”(左右摇摆) | 1. 方向环D值太小或P值太大。 2. 控制周期太长,响应延迟。 3. 图像处理延迟大,反馈信息滞后。 | 1. 适当增加D值,或略微减小P值。 2. 提高控制任务的执行频率(如从10ms提到5ms)。 3. 优化图像算法,减少处理时间;或使用更快的单片机。 |
7. 最佳实践与工程化建议
借鉴冠军队伍的经验,以下实践能极大提升备赛效率和最终成绩:
- 版本管理:必须使用Git。为代码、原理图、PCB设计建立仓库。每次重大修改前提交,便于回溯和团队协作。
- 参数可配置化:将所有PID参数、图像阈值、速度规划表等写在独立的头文件(如
config.h)或通过串口在线调参。避免在代码中写死“魔法数字”。 - 系统化测试:
- 单元测试:单独测试电机驱动、编码器读数、图像采集函数是否正常。
- 集成测试:在桌面上模拟赛道环境,验证图像处理和控制逻辑。
- 压力测试:让车长时间运行,观察有无内存泄漏、程序跑飞等情况。
- 文档与日志:
- 维护一个“调试日志”,记录每次调参的变化和效果。
- 为关键函数写注释,特别是复杂的算法部分。
- 硬件改动(如换电阻、电容)要及时更新原理图。
- 冗余设计:
- 软件上,对关键数据(如速度、偏差)进行滑动平均滤波。
- 硬件上,为关键电源预留测试点,为重要信号线预留排针。
- 赛场应急预案:
- 准备备用核心板、驱动板、摄像头、轮胎。
- 代码中预留“应急模式”开关,例如一个拨码开关,拨到不同位置对应不同的保守参数组,以备赛场出现意外情况时快速降级运行。
- 团队协作:明确分工(硬件、软件、算法、机械),但定期交叉评审。写软件的要懂一点硬件原理,画电路的要明白软件的需求。
从华南预赛第一到国赛舞台的“最后一舞”,“疯狂电路组”的旅程是技术深度、工程严谨性和团队协作的完美结合。他们的成功并非源于某个惊为天人的“黑科技”,而是将每一个基础环节——从一块电路板的滤波电容到一行PID代码的积分限幅——都做到了极致。智能车竞赛的魅力正在于此:它用一个明确的赛题,逼着你去深入理解并整合自动控制、嵌入式开发、数字图像处理等多门课程的知识,最终收获的远不止一座奖杯。希望这份复盘能为你点亮前行的路,在调车房里,每一次耐心的调试,每一次果断的尝试,都是在为你自己的“最后一舞”积蓄力量。