
简介这份资源是2024年全国大学生电子设计竞赛H题自动驾驶小车系统的完整工程面向备战电赛的本科生、需要项目实战练习的计算机与自动化专业学习者以及做课程设计或毕业设计的学生。包内共144个文件以84个.h头文件与38个.c源文件为核心涵盖MPU6050姿态解算、CAN通信、定时器与Flash控制等驱动模块另附原理图PDF、工程配置与烧录文件、少量图片和说明文档压缩包约10.26MB结构清晰便于按模块查阅。资源来自导师指导并通过评审的高分设计评审分99分代码完整可运行初学者也能按图索骥完成搭建与调试。目前已有2190人学习下载适合需要完整赛题方案、硬件原理参考与排错思路的读者直接复用与二次开发。1. 2024电赛H题自动驾驶小车从源代码到原理图的完整拆解2024年电赛H题要求做一辆能自主循线、识别路标、避障停车的自动驾驶小车很多队伍卡在“代码跑不通、原理图对不上、电机一给PWM就复位”这三件事上。我拿到这套“源代码原理图”的资料后第一反应不是直接烧录而是先把工程拆成三层感知层灰度/摄像头、决策层状态机、执行层电机舵机。这套方案适合正在备战电赛、想复现一辆能跑完全程的小车、或者想搞懂“为什么我的车一进弯就冲出赛道”的嵌入式新手和熟手。核心不是代码有多长而是原理图和源代码之间的引脚映射、定时器分配、中断优先级有没有对齐——这才是高分项目和“能跑就行”之间的分水岭。2. 先看懂原理图电源树、电机驱动和最小系统怎么连2.1 电源树为什么你的单片机一给电机就复位电赛小车最常见的翻车现场电机一启动单片机就重启串口疯狂打印乱码。根源不在代码在电源树。常见做法是单节锂电池3.7V升压到5V给单片机和外设电机直接吃电池电压。但电机堵转瞬间电流能到2A以上电池电压被拉低到3V以下升压芯片输入不足5V输出跟着掉单片机欠压复位。正确的电源树应该分三路电池→电机驱动VM直接供电池→5V升压→单片机/灰度/编码器5V→3.3V LDO→摄像头/OLED。关键参数升压芯片选支持2A以上峰值电流的比如TPS610885V和3.3V之间加磁珠或0Ω电阻隔离电机驱动VM脚并一个470μF以上的电解电容再并一个0.1μF陶瓷电容滤高频。电源轨典型电压供电对象关键器件注意事项VM7.4V/3.7V电机驱动电池直供并470μF电解0.1μF陶瓷5V5.0V单片机、灰度、编码器TPS61088峰值电流≥2A3.3V3.3V摄像头、OLEDAMS1117-3.3输入输出加10μF提示原理图上电机驱动VM和单片机5V之间如果有共用走线哪怕只有1cm也要割断飞线。血泪经验复位问题90%出在电源不是代码。2.2 TB6612电机驱动原理图引脚映射和PWM频率TB6612是电赛小车最常用的双路电机驱动原理图连接不复杂但引脚分配直接决定代码怎么写。典型接法STM32的TIM1_CH1/CH2接PWMA/PWMBGPIO接AIN1/AIN2/BIN1/BIN2STBY接3.3V或GPIO拉高。关键参数PWM频率建议10kHz~20kHz太低电机会啸叫太高驱动发热。ARR值按72MHz/10kHz7200-1来设。// TB6612 引脚定义以STM32F103C8T6为例 #define MOTOR_A_PWM_TIM htim1 #define MOTOR_A_PWM_CH TIM_CHANNEL_1 #define MOTOR_A_IN1_PORT GPIOA #define MOTOR_A_IN1_PIN GPIO_PIN_4 #define MOTOR_A_IN2_PORT GPIOA #define MOTOR_A_IN2_PIN GPIO_PIN_5 #define MOTOR_B_PWM_TIM htim1 #define MOTOR_B_PWM_CH TIM_CHANNEL_2 #define MOTOR_B_IN1_PORT GPIOA #define MOTOR_B_IN1_PIN GPIO_PIN_6 #define MOTOR_B_IN2_PORT GPIOA #define MOTOR_B_IN2_PIN GPIO_PIN_7 #define STBY_PORT GPIOB #define STBY_PIN GPIO_PIN_0 // 电机方向控制IN11, IN20 正转IN10, IN21 反转 void Motor_Set(int16_t left, int16_t right) { // left/right 范围 -1000 ~ 1000 if (left 0) { HAL_GPIO_WritePin(MOTOR_A_IN1_PORT, MOTOR_A_IN1_PIN, GPIO_PIN_SET); HAL_GPIO_WritePin(MOTOR_A_IN2_PORT, MOTOR_A_IN2_PIN, GPIO_PIN_RESET); } else { HAL_GPIO_WritePin(MOTOR_A_IN1_PORT, MOTOR_A_IN1_PIN, GPIO_PIN_RESET); HAL_GPIO_WritePin(MOTOR_A_IN2_PORT, MOTOR_A_IN2_PIN, GPIO_PIN_SET); left -left; } __HAL_TIM_SET_COMPARE(MOTOR_A_PWM_TIM, MOTOR_A_PWM_CH, left); // 右轮同理略 }逻辑说明先判断方向再设PWM占空比。参数说明left/right用有符号整数绝对值映射到ARR值。注意STBY必须拉高否则TB6612输出高阻电机不转。常见坑TIM1_CH1和CH2如果用了互补输出要关掉死区否则PWM波形不对。2.3 最小系统板和灰度传感器接口STM32F103C8T6最小系统板原理图里BOOT0/BOOT1跳线、复位电路、晶振电路是基础。灰度传感器常见5路或8路输出数字量或模拟量。数字量接GPIO模拟量接ADC。关键灰度传感器的供电必须和单片机同一5V轨否则电平不匹配。如果灰度输出是5VSTM32是3.3V容忍但长期跑建议加电平转换或分压。3. 源代码架构状态机、PID和中断怎么排3.1 主状态机循线、识别、避障、停车电赛H题的逻辑不复杂但状态切换要干净。常见做法是用一个enum定义状态主循环里switch-case。状态包括IDLE、LINE_FOLLOW、CROSS_DETECT、OBSTACLE_AVOID、STOP。每个状态有进入动作、运行动作、退出条件。typedef enum { STATE_IDLE, STATE_LINE_FOLLOW, STATE_CROSS_DETECT, STATE_OBSTACLE_AVOID, STATE_STOP } CarState_t; CarState_t g_state STATE_IDLE; void Car_Update(void) { switch (g_state) { case STATE_LINE_FOLLOW: // 读灰度算偏差PID输出到电机 LineFollow_Update(); // 如果检测到十字或路标切状态 if (Gray_IsCross()) { g_state STATE_CROSS_DETECT; } break; case STATE_CROSS_DETECT: // 直行通过十字计时或编码器计数 Motor_Set(600, 600); if (Encoder_GetDistance() 200) { // 200mm g_state STATE_LINE_FOLLOW; } break; case STATE_OBSTACLE_AVOID: // 超声波或红外触发转向避障 Avoid_Update(); break; case STATE_STOP: Motor_Set(0, 0); break; default: break; } }逻辑说明主循环每1ms调用一次Car_Update。参数说明Encoder_GetDistance返回累计脉冲换算的毫米数200mm是十字宽度经验值。注意状态切换时清PID积分项否则会“带记忆”冲出去。3.2 PID循线位置式还是增量式参数怎么整定灰度循线用PD就够了I项容易积分饱和。常见做法位置式PD偏差当前灰度加权位置-中心位置。P决定响应速度D抑制震荡。整定顺序先P后DP从0.5开始加直到小车能跟着线走但有点抖再加D到不抖。typedef struct { float Kp; float Kd; float last_error; float output; } PD_t; PD_t g_pd { .Kp 1.2f, .Kd 0.8f }; float LineFollow_Calc(int16_t *gray, uint8_t n) { // gray: 灰度传感器权重比如 -3,-2,-1,0,1,2,3 float error 0; float sum 0; for (uint8_t i 0; i n; i) { error gray[i] * (i - (n-1)/2.0f); sum gray[i]; } if (sum 0) return 0; // 丢线 error / sum; float d error - g_pd.last_error; g_pd.output g_pd.Kp * error g_pd.Kd * d; g_pd.last_error error; return g_pd.output; }逻辑说明加权平均算偏差PD算输出。参数说明Kp1.2Kd0.8是5路灰度的经验值8路要重新整定。注意丢线时保持上一次输出或减速不要直接归零。3.3 中断优先级编码器和串口谁先谁后编码器用外部中断或定时器编码器模式串口用DMA空闲中断。优先级编码器串口定时器。如果编码器中断被串口打断丢脉冲会导致里程计算错。常见做法编码器用TIM的编码器模式不占中断串口用DMA接收空闲中断里处理。4. 避坑与排查电赛小车最常见的5个翻车点4.1 现象电机一转OLED花屏或复位原因电源耦合电机电流冲击5V轨。 解决电机VM和5V分开走线VM并大电容5V加LC滤波。4.2 现象灰度循线直道还行弯道直接冲出去原因PWM频率太低比如1kHz电机响应滞后或者PD的D太大过弯时反向刹车。 解决PWM提到10kHz以上D减半速度降30%。4.3 现象编码器读数跳变里程忽大忽小原因编码器A/B相接线太长没屏蔽或者中断里做了浮点运算。 解决A/B相用双绞线中断里只做计数浮点放主循环。4.4 现象十字识别误判直道也停车原因灰度阈值没动态校准环境光一变就误判。 解决上电时先扫一遍地面取黑白阈值中间值或者用归一化。4.5 现象烧录后电机不转但代码能跑原因TB6612的STBY没拉高或者PWM通道没启动。 解决检查STBY电平HAL_TIM_PWM_Start必须调用。5. 进阶用编码器IMU做里程校准和弯道预测5.1 编码器里程校准轮径和脉冲数怎么标定编码器脉冲数×轮周长/每圈脉冲距离。但轮径有误差常见做法让小车直行1米读编码器脉冲反推轮径。公式轮径 1000mm × 每圈脉冲 / (脉冲数 × π)。标定一次写进代码。// 里程标定直行1000mm记录脉冲数 float wheel_diameter_mm 65.0f; // 初始值 uint32_t pulses_per_rev 13 * 4 * 30; // 编码器线数×倍频×减速比 float distance_per_pulse (3.14159f * wheel_diameter_mm) / pulses_per_rev; void Calibrate_Distance(uint32_t actual_pulses) { wheel_diameter_mm 1000.0f * pulses_per_rev / (actual_pulses * 3.14159f); distance_per_pulse (3.14159f * wheel_diameter_mm) / pulses_per_rev; }逻辑说明用实际脉冲反推轮径。参数说明pulses_per_rev根据编码器手册算13线×4倍频×30减速比1560。5.2 IMU弯道预测陀螺仪提前打角MPU6050读角速度积分得偏航角。弯道时陀螺仪比灰度早10~20ms感知到转向可以提前给舵机或差速。常见做法陀螺仪Z轴角速度阈值时PD的Kp临时加大20%。参数典型值作用陀螺仪阈值50°/s触发提前打角Kp增量20%加快入弯响应积分时间5ms陀螺仪读取周期注意陀螺仪零漂要上电校准否则直道会慢慢偏。5.3 一个具体技巧用串口波形调PID调PID最怕盲调。用串口把偏差、输出、灰度值打到上位机看波形。常见做法每10ms发一帧格式“error,output\n”上位机用匿名科创或VOFA。看波形调Kp/Kd比猜快10倍。我自己的习惯每次改完PID先用手推小车看波形再上电跑。这样能分清是代码问题还是机械问题。希望帮到你。本文还有配套的精品资源点击获取