简介:这份资源是2024年全国大学生电子设计竞赛H题巡线小车的完整工程,面向备赛电赛控制类赛题的学生与嵌入式爱好者。方案以STM32为主控、OpenMV摄像头实现巡线,核心思路是计算黑线与中心线的偏差并据此转向,与官方要求的灰度传感器方案原理一致,可帮助读者理解偏差控制、电机调速与四轮同步等关键环节。压缩包共1053个文件,以570个C源文件、256个头文件、51个汇编文件为主,另含工程配置、库文件与少量Python脚本,整体约27.45MB,结构完整可直接编译参考。目前已有4259人学习下载。读者可从中获取完整巡线控制代码、ABCD四点丝滑衔接的调试思路,以及MPU6050零漂处理、初始摆放角度、变速策略等优化方向,适合在此基础上进一步打磨稳定性与圈速。
1. 从一道“送分题”变成“送命题”:2024电赛H题巡线小车到底考什么
2024年电赛H题,表面上是“循迹小车”,实际考的是多传感器融合下的实时控制链路。很多队伍拿到题目第一反应是“PID调参嘛,玄学”,结果跑下来发现:直道能跑,弯道冲出;慢速能跑,提速就抖;单圈能跑,跑三圈就飘。问题不在PID本身,而在于STM32主控与OpenMV视觉模块之间的任务分配、数据同步和异常兜底。这场考试筛选的不是“会调PID的人”,而是“能把视觉、控制、执行三层串成稳定闭环的人”。如果你正在准备电赛控制类赛题,或者想用一套完整工程吃透“视觉+运动控制”的工程化落地,下面这份考点拆解和答题思路,就是照着拿分去的。
2. 考点分布:STM32+OpenMV巡线小车的三层架构与分值权重
2.1 视觉层:OpenMV到底该输出什么,不该输出什么
很多队伍把OpenMV当成“摄像头+识别结果输出器”,让它直接算偏差、甚至直接给PID输出。这是第一个大坑。OpenMV的强项是图像预处理和特征提取,弱项是硬实时控制。它跑的是MicroPython,帧率受分辨率、算法复杂度影响极大,一旦图像处理耗时波动,控制周期就跟着抖。
正确的分工是:OpenMV只做三件事——识别赛道元素、输出偏差量、标记特殊区域。偏差量用整数或浮点都行,但必须带时间戳或帧序号,让STM32知道这帧数据是“新鲜”的还是“过期”的。特殊区域比如十字、环岛、坡道,输出一个标志位即可,不要让它做决策。
# OpenMV端:只做线检测和偏差计算,不做控制 import sensor, image, time from pyb import UART sensor.reset() sensor.set_pixformat(sensor.GRAYSCALE) sensor.set_framesize(sensor.QQVGA) # 160x120,帧率优先 sensor.skip_frames(time=2000) uart = UART(3, 115200) while True: img = sensor.snapshot() # 只取图像下半部分做ROI,减少计算量 roi = (0, 60, 160, 60) line = img.get_regression([(0, 50)], roi=roi, robust=True) if line: # 计算中线偏差:图像中心x=80,line.x()是拟合线在底部的x error = line.x() - 80 # 打包:帧头0xAA + 偏差 + 帧尾0x55 uart.write(bytearray([0xAA, error & 0xFF, 0x55])) else: uart.write(bytearray([0xAA, 0x00, 0x55])) time.sleep_ms(5)这段代码的关键参数:QQVGA分辨率是为了保帧率,roi只取下半部分是因为巡线主要看近处,get_regression比find_blobs更适合连续线条。偏差直接传原始值,不做任何滤波——滤波放在STM32端做,因为STM32的定时器节拍更稳。
2.2 控制层:STM32上的任务调度与数据融合
STM32端要解决三个问题:串口数据解析、控制周期固定、异常状态处理。很多队伍用HAL_UART_Receive阻塞接收,结果主循环被串口拖死。正确做法是串口中断+环形缓冲区,主循环按固定周期取最新帧。
控制周期建议1ms~5ms,用定时器中断触发。PID计算放在定时器中断里,串口解析放在主循环或DMA中断里。这样即使OpenMV偶尔丢帧,控制周期也不会乱。
// STM32端:串口中断接收 + 环形缓冲 + 定时器控制 #define BUF_SIZE 64 uint8_t rx_buf[BUF_SIZE]; volatile uint16_t rx_head = 0, rx_tail = 0; void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart) { // 每收到一个字节存入环形缓冲 uint16_t next = (rx_head + 1) % BUF_SIZE; if (next != rx_tail) { rx_buf[rx_head] = rx_byte; rx_head = next; } HAL_UART_Receive_IT(huart, &rx_byte, 1); } // 定时器中断里调用,周期2ms void Control_Loop(void) { static int16_t last_error = 0; int16_t error = Get_Latest_Error(); // 从环形缓冲解析最新偏差 int16_t diff = error - last_error; int16_t output = KP * error + KD * diff; Set_Motor_PWM(output); last_error = error; }Get_Latest_Error要从环形缓冲里找最新的完整帧,而不是读最老的。如果超过一定时间没收到新帧(比如50ms),要进入降级模式:减速或停车,而不是继续用旧数据猛跑。这是很多队伍翻车的地方——OpenMV一卡,小车直接冲出赛道。
2.3 执行层:电机驱动与舵机响应的匹配
执行层最容易被忽视的是响应延迟。电机从收到PWM到实际转速变化有几十毫秒延迟,舵机转向也有死区。如果PID参数按“理想执行器”调,实际跑起来就会振荡。
工程上的做法是:先测执行器的阶跃响应。给电机一个固定PWM,用编码器或视觉反馈记录速度变化曲线,算出等效时间常数。然后PID的微分项不要太大,积分项要加限幅。舵机转向建议用位置式PID+死区补偿,死区大小通过实验测出来,在输出上叠加一个固定偏置。
提示:执行器延迟是巡线小车“直道画龙”的主要原因,先测延迟再调参,比盲目试参数快得多。
3. 高频考点:五个必考的工程问题与答题模板
3.1 考点一:图像二值化阈值怎么定,才能不惧光照变化
真题回顾:赛道有阴影、有反光,固定阈值二值化直接失效。面试官或评委常问:“你怎么保证不同光照下都能稳定提取中线?”
答题思路:不要用固定阈值。用大津法(Otsu)做自适应二值化,或者用动态阈值+局部对比度增强。OpenMV的get_regression支持thresholds参数,可以传一个范围而不是固定值。更稳的做法是:先对图像做histeq均衡化,再取中间灰度作为阈值。
# 自适应阈值:用Otsu自动计算 img = sensor.snapshot() hist = img.get_histogram() threshold = hist.get_threshold().value() # Otsu阈值 line = img.get_regression([(threshold, 255)], roi=roi, robust=True)如果光照变化剧烈,再加一步背景减除:在比赛前先拍一张纯赛道背景,运行时做差分。但注意背景减除对动态场景不友好,适合固定场地。
易错点:很多人用find_blobs找黑色块,但赛道线条是连续的,find_blobs会把线条切成碎块,拟合出来的中线跳变严重。巡线优先用get_regression。
3.2 考点二:PID参数怎么调,才能直道不抖弯道不冲
真题回顾:评委常问:“你的PID参数怎么来的?为什么Kp是这个值?”如果回答“试出来的”,基本就凉了。
答题思路:按内环外环分离的思路答。内环是速度环(电机编码器反馈),外环是位置环(视觉偏差)。先调内环让速度响应快且无静差,再调外环。外环的Kp从临界振荡的60%开始,Kd用来抑制超调,Ki用来消除弯道稳态误差。给出具体数值范围:Kp在0.5~2.0之间,Kd在0.1~0.5之间,Ki在0.01~0.1之间,具体看电机减速比和车重。
答题模板:“我先用阶跃响应测了电机时间常数约30ms,然后按内环带宽是外环5倍的原则整定。Kp取临界值的0.6,Kd取Kp的1/4,Ki取Kp的1/20。弯道入口用前馈补偿,提前减速。”
3.3 考点三:十字路口和环岛怎么识别与决策
真题回顾:H题赛道通常包含十字、环岛、坡道。评委问:“你怎么区分十字和环岛?”
答题思路:靠特征组合,不靠单一特征。十字的特征是:左右两边都有线,且中间区域全白或全黑。环岛的特征是:一侧有弧线,另一侧直线,且弧线曲率逐渐变化。用OpenMV的find_line_segments找线段,统计线段数量和角度分布。
# 简单十字判断:ROI内左右两侧都有黑色像素 left_roi = (0, 60, 40, 60) right_roi = (120, 60, 40, 60) left_blob = img.find_blobs([(0, 50)], roi=left_roi) right_blob = img.find_blobs([(0, 50)], roi=right_roi) if left_blob and right_blob: # 进一步判断中间是否空白 center_roi = (60, 60, 40, 60) center_blob = img.find_blobs([(0, 50)], roi=center_roi) if not center_blob: state = CROSSROAD环岛则用弧线拟合:对左侧或右侧ROI做get_regression,如果拟合线的角度持续偏转超过阈值,判定为环岛入口。
易错点:不要用延时判断环岛,比如“看到弧线后延时500ms转向”。比赛场地速度变化时,延时策略直接失效。要用状态机+编码器里程做决策。
3.4 考点四:丢线怎么办,如何设计降级与恢复策略
真题回顾:评委必问:“如果OpenMV突然丢线,你的车会怎样?”
答题思路:分三级降级。一级:丢线小于50ms,保持上次偏差,PID继续算,但积分项冻结。二级:丢线50~200ms,减速到50%,偏差置零,直行。三级:丢线超过200ms,停车,蜂鸣器报警,等待人工复位。
答题模板:“我在STM32里设了一个丢线计数器,每收到一帧有效数据清零,否则累加。超过阈值就切状态。恢复时不是立刻全速,而是先低速确认视觉恢复,再逐步提速。”
3.5 考点五:如何证明你的方案是“完整工程”而不是“拼凑代码”
真题回顾:答辩时评委问:“你的工程文件怎么组织的?有没有版本管理?”
答题思路:展示模块化分层。OpenMV端一个文件夹,STM32端按Drivers、App、Config分。关键参数放在config.h里,方便现场调整。用Git做版本管理,每次修改有commit记录。评委听到“参数集中管理”和“版本可回溯”,基本就认可工程完整性。
注意:不要把所有代码塞在
main.c里。评委翻代码看到几千行main函数,印象分直接扣光。
4. 易错点:五个让队伍从一等奖掉到成功参赛的坑
4.1 错把OpenMV当实时控制器
错在哪:让OpenMV直接输出PWM或PID结果,STM32只做转发。为什么错:OpenMV的MicroPython解释器执行时间不确定,图像复杂时单帧耗时可能从10ms跳到50ms,控制周期完全失控。怎么答对:OpenMV只输出偏差和标志位,控制闭环全部在STM32定时器中断里完成。面试时主动说“视觉和控制解耦”,这是加分项。
4.2 串口通信不做校验和超时
错在哪:STM32收到什么用什么,不检查帧头帧尾,也不判断数据新旧。为什么错:串口受电机干扰容易丢字节或错位,一帧错位后续全乱。旧数据被反复使用,小车会“幻觉巡线”。怎么答对:加帧头帧尾校验,加时间戳或帧序号,超时未更新就降级。代码里体现if (HAL_GetTick() - last_frame_time > 50) { degrade(); }。
4.3 PID积分项不限幅
错在哪:弯道长时间偏差大,积分项累积到很大,出弯后小车猛冲。为什么错:积分饱和是经典问题,但很多队伍只在论文里写,代码里没做。怎么答对:积分项加限幅,比如if (integral > INTEGRAL_MAX) integral = INTEGRAL_MAX;。同时用积分分离:偏差大于阈值时暂时不积分。
4.4 电机驱动不加死区补偿
错在哪:PID输出很小的时候电机不转,输出稍大直接猛转,形成“台阶响应”。为什么错:电机有静摩擦和驱动芯片死区,小占空比无效。怎么答对:在PWM输出上叠加死区补偿值,或者用if (abs(output) < DEADZONE) output = 0; else output += sign(output)*DEADZONE;。死区值通过实验测:从0慢慢加PWM,记录电机开始转的值。
4.5 现场调试不留可调参数接口
错在哪:所有参数写死在代码里,现场改一个参数要重新编译下载。为什么错:比赛现场光照、场地摩擦和实验室不同,必须现场微调。怎么答对:用按键+OLED做参数调整菜单,或者用蓝牙串口发指令改参数。至少把Kp、Kd、Ki、速度上限做成全局变量,通过串口可写。
5. 答题技巧:从工程文件到答辩话术的最后一公里
5.1 工程文件怎么组织,评委翻代码时看什么
评委翻代码的时间通常不超过3分钟。他们看三样东西:有没有分层、关键参数是否集中、有没有异常处理。建议目录结构:
Project/ ├── OpenMV/ │ ├── main.py # 只做视觉,输出偏差 │ └── config.py # 阈值、ROI参数 ├── STM32/ │ ├── Core/ │ │ ├── main.c # 初始化+主循环 │ │ └── control.c # PID+状态机 │ ├── Drivers/ │ │ ├── uart_ring.c # 环形缓冲 │ │ └── motor.c # 电机驱动+死区补偿 │ └── Config/ │ └── config.h # 所有可调参数 └── README.md # 接线图+参数说明config.h里把Kp、Kd、Ki、速度上限、丢线阈值、死区值全部宏定义。评委看到这个文件,就知道你考虑过现场调试。
5.2 答辩话术:三个必问题的标准答法
问题一:“你们的创新点是什么?”不要答“用了OpenMV”或“用了PID”,这些是标配。答工程化改进:比如“我们做了视觉帧超时降级,丢线200ms内自动减速直行,恢复后渐进提速,实测比直接停车快1.5秒完赛。”
问题二:“如果光照突变怎么办?”答:“OpenMV端用Otsu自适应阈值,STM32端有偏差突变检测,连续三帧偏差跳变超过阈值就切到保守PID参数,同时降低速度。等偏差稳定后再恢复。”
问题三:“你们的分工是怎样的?”答:“一人负责OpenMV视觉算法和阈值调试,一人负责STM32控制与电机驱动,一人负责整体联调和答辩文档。每天合并代码一次,用Git管理。”
5.3 押题思路:今年可能怎么变
2024年H题巡线小车已经考了多元素识别和稳定性。如果今年换题,大概率还是视觉+运动控制框架,但可能增加动态障碍或多车协同。准备方向:把OpenMV的识别对象从“线”扩展到“色块+线”,STM32端加一个简单的避障状态机。核心不变:视觉输出特征,控制闭环在MCU,异常必须降级。
提示:不管题目怎么变,评委最想听的是“你怎么保证稳定”,而不是“你用了什么高级算法”。稳定压倒一切。
从那以后我每次准备控制类赛题,都强制走一遍“视觉输出协议→控制周期→异常降级→现场可调”这四个检查点,少一个都不敢上赛场。希望帮到你。
本文还有配套的精品资源,点击获取