
简介一套基于STM32的WiFi小车控制课程设计源码包覆盖下位机驱动、无线通信与上层控制逻辑适合自动化、电子信息、计算机等相关专业学生用于毕设、课设或项目初期演示。资源共1099个文件包含585个C源码、277个头文件以及汇编启动文件、链接脚本、编译器生成文件、库文件与应用工程文件并附可直接烧录的hex/axf产物压缩包整体约25.84MBKeil、IAR与STM32CubeMX工程配置均在内目录划分清晰便于定位源码、构建配置与外设移植。已有764人学习下载且代码已通过功能测试适合作为入门到进阶的实战参考。核心模块包含STM32底层初始化、WiFi指令解析与电机控制逻辑同时基于MPU6050实现姿态数据读取并集成DSP数学库可快速扩展循迹、避障或手机遥控等玩法对课程设计、毕业设计或项目立项演示均有直接参考价值。1. 课程设计级别的 STM32 WiFi 小车先想清楚再动手如果你在毕业设计或课程设计里选了“基于 STM32 的 WiFi 小车控制”大概率已经被三件事卡住WiFi 模块连不上、电机转速不一致、代码拿到手不知道从哪看起。这个标题里的三个关键词——stm32、WIFI、小车控制——正好对应着嵌入式开发里三条独立的技术线单片机外设驱动、无线通信协议、运动控制算法。任何一个环节掉链子小车都无法跑直线而课程设计恰恰只看最终能不能动、能不能通过 WiFi 控制方向。很多初学者把精力花在“抄代码”上这是最危险的路径。WiFi 小车的核心难点不在代码量而在代码背后那套“数据怎么从手机走到电机”的链路。这条链路包括协议栈的选择AT 指令还是 TCP Socket、数据帧的编解码、PWM 占空比到轮速的映射、差速转向的数学模型。本文不打算复述某个具体源码包的逻辑——毕竟每个人的硬件配置都不一样——而是按课程设计最常见的技术路线把从原理图到能跑的完整方案拆开讲清楚包括参数怎么设、坑在哪里、代码怎么改。你能拿着这份文档对着手头的代码包逐行验证而不是对着屏幕发呆。适合谁看正在做课设或毕设的学生以及想快速复现 WiFi 小车做二次开发的硬件爱好者。前提是学过 STM32 的基本外设操作GPIO、定时器、串口否则需要先补基础。接下来从芯片选型开始逐步展开。2. 选型与原理芯片、电机驱动、WiFi 模块怎么搭才算合理2.1 STM32 选型不是越贵越好看外设够不够课程设计最常见的开发板是 STM32F103C8T6蓝板和 STM32F407VET6前者 64KB Flash、20KB RAM后者 512KB Flash、192KB RAM。对于 WiFi 小车来说F103C8T6 完全够用因为控制逻辑不复杂两路 PWM 输出、一个 UART 接 WiFi 模块、几个 GPIO 接编码器或者使能引脚。但如果你的源码包里使用了 FreeRTOS 或比较复杂的状态机F103 的 20KB RAM 会显得局促此时换 F407 是合理的。另一个需要考虑的点是定时器资源。PWM 输出需要定时器通道假设小车是两轮差速结构至少需要 2 路 PWM左轮、右轮。F103 的 TIM2、TIM3、TIM4 都可以输出多路 PWM注意确认 GPIO 的复用功能映射表用错引脚会导致输出无信号。开发板FlashRAM适用场景成本STM32F103C8T664KB20KB基础两轮/四轮小车低STM32F103ZET6512KB64KB需要屏幕显示或复杂逻辑中STM32F407VET6512KB192KB带编码器 PID 或摄像头高选型经验课程设计优先用 F103C8T6资料多、出问题好搜。等移植现成代码时重点检查源码里的启动文件startup_stm32f10x_md.s和芯片型号是否匹配不匹配直接编译报错。2.2 电机驱动板TB6612 与 L298N 的选择差异电机驱动是 WiFi 小车最容易烧毁的部分。L298N 是经典方案价格便宜、驱动能力强但压降大约 2V发热严重而且逻辑电平是 5V需要和 STM32 的 3.3V GPIO 做电平匹配。TB6612FNG 压降只有 0.5V 左右逻辑电平兼容 3.3V可以直接接 STM32 的引脚还自带过流保护是课程设计里的更优解。接线时要注意TB6612 的 PWMA/PWMB 接 STM32 的定时器 PWM 输出脚AIN1/AIN2、BIN1/BIN2 接普通 GPIO 控制方向STBY 引脚必须拉高否则驱动芯片不工作。很多人的小车不动不是代码问题而是 STBY 悬空或者接错。2.3 WiFi 模块ESP8266 还是 ESP32协议选择要提前定WiFi 模块目前课程设计里用得最多的是 ESP8266如 ESP-01S因为它便宜且资料多。ESP8266 支持两种工作模式AT 指令模式和透传模式。AT 指令模式适合简单的“手机发指令、小车执行”透传模式适合持续的数据流传输。如果你的源码包是串口中断接收 解析指令大概率用的是 AT 指令模式。ESP32 作为替代方案优势是自带蓝牙和更强的处理能力但开发复杂度也更高通常需要跑 MicroPython 或者 ESP-IDF。课程设计里如果没有明确的额外需求选 ESP8266 更稳妥。注意 ESP8266 需要 3.3V 供电且启动瞬间电流约 300mA不能直接从 STM32 的 3.3V 引脚取电要外接 AMS1117 稳压模块或单独供电否则会不断重启。3. 源码阅读路径一个典型的 WiFi 小车代码包应该从哪个文件看起3.1 先看 main.c别急着翻驱动拿到课程设计的源码包不要先打开某个外设驱动文件而是先看 main.c 里的初始化顺序。一个规范的代码包main.c 通常做这几件事初始化系统时钟、初始化 GPIO、初始化定时器 PWM、初始化串口、初始化 WiFi 模块、进入主循环。你只需要确认调用的顺序是否符合硬件接线如果源码里的初始化顺序和你手里的开发板不一致比如某引脚被配置成了别的功能小车肯定跑不起来。下面给出一段典型的主循环结构不是某个特定源码包的原文而是这类课程设计最常见的设计方式int main(void) { // 1. 系统时钟初始化F103默认使用HSI这里切换到HSE外部晶振 SystemInit(); // 2. 配置LED、电机方向控制引脚为输出模式 GPIO_Init(); // 3. 配置定时器TIM2输出PWM频率1kHz初始占空比0 TIM2_PWM_Init(999, 71); // 72MHz / (9991) / (711) 1kHz // 4. 初始化串口1用于WiFi模块通信波特率115200 UART1_Init(115200); // 5. 发送AT指令重置ESP8266 WiFi_Reset(); while(1) { // 循环中处理串口收到的指令 WiFi_ProcessCommand(); // 非阻塞轮询接收缓冲区 Delay_ms(10); // 每10ms检查一次 } }这段代码的逻辑说明PWM 频率设定为 1kHzTIM2_PWM_Init(999, 71)里的两个参数分别是自动重载值和预分频值实际计算方式是 72MHz 时钟经过预分频和计数得到 1kHz 的频率。WiFi_ProcessCommand不是阻塞等待而是检查环形缓冲区里有没有完整的数据帧有就解析执行没有就跳过这样才能保证小车在待机状态下不会卡死。3.2 串口接收中断 环形队列是标配WiFi 小车的数据入口是串口ESP8266 把手机端发来的数据通过 TX 引脚送给 STM32 的 RX 引脚。串口接收必须采用中断方式否则主循环里轮询会漏数据。环形队列是一种固定大小的循环缓冲区可以在中断里写、主循环里读两边互不影响。volatile uint8_t uart_rx_buf[256]; // 接收缓冲区 volatile uint8_t head 0, tail 0; void USART1_IRQHandler(void) { if (USART_GetITStatus(USART1, USART_IT_RXNE) ! RESET) { uint8_t data USART_ReceiveData(USART1); // 判断环形队列是否已满避免覆盖未处理的数据 if ((head 1) % 256 ! tail) { uart_rx_buf[head] data; head (head 1) % 256; } } }环形队列写入时会判断(head 1) % 256 ! tail这个条件是队列是否满的标准。如果头部追上尾部就说明缓冲区已满此时丢弃新数据而不是覆盖旧数据这是串口接收不丢帧的关键。主循环里读取时从头取出一个字节直到head tail表示队列空。3.3 指令解析协议格式决定了代码的健壮性源码包里最值得仔细看的部分是指令解析函数。比较常见的协议设计有两种单字符指令比如 F 表示前进B 表示后退和帧格式指令帧头 数据 校验。单字符简单直接适合演示但如果你需要传输速度值比如左轮占空比 60%、右轮占空比 40%单字符就无法表达需要帧格式。帧格式通常这么设计帧头(0xAA) 数据长度(1byte) 指令类型(1byte) 参数(2bytes) 校验和(1byte)但更常见且更简单的还属带分隔符的文本协议手机发送L60R40\n其中 L 后面跟左轮速度R 后面跟右轮速度\n 表示一帧结束。代码接收时以 \n 为标志将缓冲区里的一整段数据提取出来解析。这种协议的优点是调试方便直接在串口助手和 WiFi 调试工具里就能发出指令不需要计算十六进制格式。源码包的解析函数如果是按这个思路写的它的状态机一般是这样的typedef enum { STATE_IDLE, STATE_RECEIVING } FrameState; FrameState state STATE_IDLE; uint8_t parse_one_frame(uint8_t *buf, uint16_t len) { // 查找帧尾\n找到就返回该帧长度 for (uint16_t i 0; i len; i) { if (buf[i] \n) return i 1; // 包含换行符的完整帧长 } return 0; // 未找到需要继续累积 }解析的状态机一般配合两级缓冲先将串口中断收到的数据暂存到一个大数组等出现 \n 后再整体处理。这样做的好处是避免一帧数据被拆成多次处理而出错。判断一帧是否完整、是否包含非法字符这些边界条件恰恰是源码包里容易被删掉、但真正决定程序质量的部分。4. 运动控制的参数整定PWM、差速转向、死区补偿4.1 PWM 占空比与占空比映射关系PWM 占空比决定了电机的平均电压从而决定转速。课程设计里常用的电机是 N20 减速电机带减速箱转速低扭矩大或 TT 马达转速高、便宜但一致性差。N20 齿轮箱减速比常见有 1:30、1:150 等1:150 的更适合小车低速时扭矩大。但无论哪种电机两个电机的减速比都会有误差这就导致同一个占空比下两个轮子的实际转速不同小车不能走直线。这就是为什么纯开环控制的小车必然跑偏。要解决有两个层级的措施第一简单的手动补偿——在代码里设定左右轮的 PWM 映射系数左轮用 80% 占空比、右轮用 78% 占空比来校正第二接入编码器做闭环 PID 控制。前者好实现且不需要额外硬件课程设计多采用这种方式。// 速度映射表手机端传0~100的速度值映射到PWM比较寄存器数值 uint16_t speed_to_ccr(uint8_t speed_percent) { // TIM2的ARR是999PWM比较寄存器的值范围0~999 // 限制输入范围0~100 if (speed_percent 100) speed_percent 100; // 直接按百分比折算到占空比 return (uint16_t)(speed_percent * 10 - 1); // 100%对应999 }这段映射说明speed_to_ccr函数把 0-100 的整数映射为定时器的比较寄存器值。占空比 CCR / (ARR 1)所以 100% 对应 999。如果你发现小车在低占空比比如 10%时电机不动这不是代码问题而是电机启动需要的最低电压。直流电机的死区电压一般在 5%-15% 占空比之间不同电机差异很大。此时可以在映射函数里加一个最小占空比输出if (speed_percent 0 speed_percent 20) { speed_percent 20; // 低于20%时强制提到20%保证电机能启动 }注意这样做会让低速控制不平滑但可以保证只要指令不是 0轮子就一定会转。4.2 差速转向的数学模型左右轮速度怎么算两轮差速小车依靠左右轮的速度差实现转弯。最基本的转向策略是前进转向时左轮减速右轮加速但两者平均值保持原来的速度。假设设定速度为 V转向量为 D取值 -100 到 100负数表示左转正数表示右转那么左轮速度 V - D 右轮速度 V D如果 D 50则左轮速度为 V - 50右轮为 V 50。原地转圈则是左轮 -V、右轮 V。这个过程看似简单有一个隐藏问题当 V 较小而 D 较大时计算出的某个轮速会变成负数这意味着该轮需要反转你必须在电机驱动芯片上把 IN1/IN2 的状态切到反向。如果代码不处理负值PWM 寄存器会收到一个负数或错误的符号导致电机不受控。下面给出一个考虑负速度的驱动函数void set_motor_speed(int16_t speed_left, int16_t speed_right) { // 按转向和速度值同时控制电机方向与占空比 motor_set_direction(MOTOR_LEFT, speed_left 0 ? DIR_FORWARD : DIR_BACKWARD); motor_set_direction(MOTOR_RIGHT, speed_right 0 ? DIR_FORWARD : DIR_BACKWARD); // 取绝对值后作为PWM占空比限制在0~100 uint8_t duty_left abs(speed_left) 100 ? 100 : abs(speed_left); uint8_t duty_right abs(speed_right) 100 ? 100 : abs(speed_right); TIM_SetCompare1(TIM2, duty_left * 10 - 1); // 对应占空比 TIM_SetCompare2(TIM2, duty_right * 10 - 1); // 对应占空比 }用abs(speed_left)取绝对值再限定在 0-100是为了避免 PWM 比较寄存器设置负值。方向则由电机驱动芯片的两个方向引脚控制和 PWM 是独立的两路信号。课程设计里很多“转向失灵”的问题其实都出在这里速度差没有处理好负值的情况或者方向引脚和 PWM 引脚的配合时序错了。4.3 直行校正手动标定与动态补偿手动标定是让小车在固定占空比下走直线测量偏差后写入补偿参数。常规操作是在地板上画一条 2 米直线让小车以 60% 占空比前进记录偏转方向。如果车向右偏说明右轮快了就调低右轮的 PWM 映射。把这个补偿值写到 flash 的一个参数区域下次上电自动加载。除了静态标定还可以做动态补偿利用 MPU6050 陀螺仪测出车体的偏航角速度通过比例控制实时修正左右轮速度。这个思路的代码不复杂但课程设计里如果没要求可以先不引入等基础功能稳了再加。动态补偿的公式可以简单理解为error target_yaw_rate - measured_yaw_rate correction Kp * error speed_left base_speed - correction speed_right base_speed correction这里的 Kp 需要实验调整一般取 0.5 到 2.0 之间。Kp 太小修正弱车还是偏Kp 太大车体会来回摆动导致振荡。5. 通信链路排错手机到小车的数据通路逐个节点查5.1 TCP 客户端模式与 UDP 协议的取舍ESP8266 在 WiFi 小车方案里有几种工作模式作为 TCP 客户端连接手机的 TCP 服务器、作为 TCP 服务器等手机来连接、或者用 UDP 直接发送。课程设计里最常见的做法是让 ESP8266 作为 TCP 客户端手机或者 PC 上的网络调试助手作为服务器。为什么是这个方向而不是相反因为手机往往不方便开热点让模块连接而模块通过 AT 指令连接手机热点、再连上手机上运行的 TCP 服务器整个流程比较顺。UDP 是另一个合理选择特点是无需建立连接手机往模块的 IP 和端口发数据即可。但 UDP 不保证数据不丢在室内 WiFi 信号干扰大的场景偶尔丢一帧指令会导致电机停一下或者不受控。所以建议用 TCP代价是每次上电要建立连接如果手机端的 TCP 服务器没有启动模块会一直重连。ESP8266 的配置代码大致如下const char *ssid TP-LINK_XX; // WiFi热点名 const char *password 12345678; // 热点密码 const char *tcp_server_ip 192.168.1.100; // 手机在路由器下的IP const char *tcp_server_port 8080; // 依次执行AT指令配置ESP8266 send_at_cmd(ATCWMODE1\r\n, OK); // Station模式 send_at_cmd(ATCWJAP\TP-LINK_XX\,\12345678\\r\n, WIFI GOT IP); send_at_cmd(ATCIPMUX0\r\n, OK); // 单连接模式 send_at_cmd(ATCIPSTART\TCP\,\192.168.1.100\,8080\r\n, CONNECT); send_at_cmd(ATCIPMODE1\r\n, OK); // 透传模式 send_at_cmd(ATCIPSEND\r\n, ); // 开始透传发送这段指令里的send_at_cmd是一个工具函数发送 AT 指令后阻塞等待匹配的响应字符串超时则返回错误。ATCWJAP后等待的 WIFI GOT IP 是模块成功获取 IP 后的输出如果你看到的是 FAIL 或者 WIFI DISCONNECT说明热点名称、密码不正确或者模块供电不足。每个 AT 指令的响应时间不一样等待超时要给足尤其连接 WiFi 需要 3~10 秒不能设成 1 秒就超时重发。有一个容易忽略的细节ATCIPMODE1是进入透传模式成功后模块会把收到的所有网络数据直接通过串口发给 STM32同时 STM32 发给模块的串口数据也会直接发给网络。这会让串口调试变得很迷惑——你以为发送 AT 指令没回应实际上模块已经不在 AT 模式了。验证方法是在透传模式下发送不加回车模块会退出透传回到 AT 模式。5.2 三个常见到网上一搜一大片的 WiFi 连接失败原因第一个是供电问题。ESP8266 启动瞬态电流能到 300-400mA如果你用了 STM32 开发板上的 3.3V 引脚直接供电电压会被拉低模块表现为上电后串口无响应、或者连接 WiFi 时反复重启。解决方法是给 ESP8266 单独供电或者用 5V 转 3.3V 的稳压模块且输出电流至少 500mA。第二个是波特率不一致。ESP8266 出厂默认波特率 115200但也可能被改成 9600 或者 74880这个波特率是 ESP8266 上电时 ROM 输出的调试波特率。如果你的代码里初始化的串口波特率是 9600而模块实际是 115200收数据必然乱码。检测方法是把 ESP8266 的 TX 接到 USB 转 TTL 的 RX用串口助手发 AT观察返回是不是OK。如果返回乱码逐个波特率试。第三个是网络配置问题涉及热点的频段和加密方式。ESP8266 不支持 5GHz WiFi只支持 2.4GHz。如果你手机开热点时选了“5GHz 频段”模块根本扫描不到。另外热点加密方式建议选 WPA/WPA2-PSK不要选“WPA3”或“无密码”。WPA3 目前 ESP8266 老固件不支持无密码热点也能连上但有些路由器会开启 AP 隔离导致连上后无法访问同一局域网内的设备。5.3 手机端控制界面的调试技巧许多课程设计代码包会附带一个手机端 App 的源码或者是用微信小程序做控制又或者用网上的“蓝牙串口”、“WiFi 调试助手”直接发命令。如果你手里的代码包只有下位机源码没有上位机不要慌课设里最常用的做法是用手机安装一个网络调试助手 App在 TCP Client 模式下连上 ESP8266在发送框里输入指令。关键技巧是“协议与下位机严格匹配”比如你下位机解析的是一整行以换行结尾的命令手机端发送时就一定要勾选“发送新行”或者手动在末尾加\n少一个字符解析函数都收不到完整数据。课程设计答辩时最常见的演示设备不是手机而是电脑上的一款网络调试工具或串口助手。但请注意电脑上开启 TCP 服务器时防火墙可能拦截连接手机连的是路由器 WiFi电脑也连同一路由器的 WiFi才能直接通信。如果手机开热点手机和 ESP8266 都在热点的局域网内而电脑连的是另一个网络数据不通。6. 代码包移植到自己的开发板时这几类坑十年老油条也会踩6.1 “Error: no STM32 target found” 的排查优先级这个报错在热词里反复出现确实是 STM32 开发里最容易让新手崩溃的信息。它发生在下载程序时意味着仿真器ST-Link 或 J-Link没有找到目标芯片。排查优先级应该是先看仿真器的接线——SWDIO、SWCLK、GND 三根线是否牢固再看供电——目标板必须上电但到底是用仿真器给板子供电还是独立供电要看板子的跳线如果板子有电源指示灯亮着说明供电没问题最后检查软件设置——Keil 里 Options - Debug 是否选对了仿真器型号ST-Link 的信息是否被识别。如果以上都正常却依然报同样的错检查目标板是否进入了低功耗模式或者芯片的 BOOT0 引脚是否被拉高。课程设计里偶尔会出现这样的乌龙上一组同学把 BOOT0 跳线帽插到了 1 位置系统存储器启动下一组同学没拔直接下载当然找不到 target。6.2 WiFi 模块串口被占用导致的“所有指令无响应”如果源码包里用的是 USART1 接 WiFi 模块而你的开发板 USART1 默认连接了板上自带的 ST-Link 虚拟串口那么下载完程序后插上 USB 线模块的 TX 和 ST-Link 的 TX 会互相冲突。表现是程序运行正常但串口助手收到的数据完全错乱。解决方法很简单确认开发板的串口 1 是否通过跳线帽连接到了 ST-Link 或者板上 USB 转串口芯片如果是拔掉跳线帽改用另外的串口如 USART2 或 USART3接 WiFi 模块并同步修改代码里的串口初始化函数和中断服务函数。USART2 对应的引脚通常是 PA2TX和 PA3RX需要 在GPIO_Init里把这两个引脚配置为复用推挽输出TX和浮空输入RX。不同型号的板子引脚略有不同查芯片数据手册的最快捷径是看原理图里丝印上的引脚编号。6.3 串口助手看到乱码但程序正常运行时的处理方式乱码分为两种情况一种是软件里设置波特率不对串口助手的波特率和 STM32 串口初始化里的不一致这种调整波特率就能解决。另一种是 TX/RX 接反模块的 TX 必须接 STM32 的 RX模块的 RX 接 STM32 的 TX接反的表现是“发送 AT 完全无响应但示波器能看到信号”而不是乱码。比较隐蔽的是第三种情况共地问题。USB 转 TTL 和 STM32 板子各自有独立的电源和地如果不把两者的 GND 连在一起信号电平根本没有参考点串口接收就表现为随机乱码。课程设计里很多人使用笔记本电脑 USB 供电USB 转 TTL 插在电脑上STM32 板子也插在电脑的另一个 USB 口它们的地通过电脑主板通常是连通的但如果一个用充电宝供电就出问题了。6.4 一个让小车稳定跑起来的终极验证流程最后给出一套排除法按顺序操作基本能区分是哪个环节的问题第一先断开 WiFi 模块只留 STM32用串口线接 PA9/PA10 到电脑通过串口助手直接向 STM32 发指令验证下位机控制逻辑是否正常。如果直接发L60R60小车能走说明电机驱动和 PWM 都没有问题此时问题一定出在 WiFi 模块或链路。第二接上 ESP8266但只测 ATM 模式。先上电等 2 秒发AT收到OK再发ATCWJAP连接热点。如果AT都没反应直接认为模块供电或串口连接有问题。第三连接 TCP 后手机端发送指令看串口调试助手上是否收到了相同的字符串。如果在手机端和调试助手都看到数据但小车不动问题在下位机的解析代码如果调试助手里看不到数据问题在 WiFi 链路。第四如果一切正常却不愿每次都手动连 WiFi可以把ATCWJAP和ATCIPSTART的指令串拼接成一个WiFi_AutoConnect函数放在初始化末尾然后加一个连接状态的判断每 5 秒重试一次最多重试 3 次。这个逻辑虽然基础却能让演示过程顺利不少。顺带提一个容易在会上翻车的点如果小车在演示的时候突然失控大概率是 WiFi 信号被干扰导致丢包或延迟。稳妥做法是在主循环里加一个看门狗或超时逻辑——如果超过 2 秒没有收到新的控制指令自动停车。这个安全策略在代码包里通常会被省略但答辩现场能救你一命。实现起来就是在解析指令的函数里更新一个时间戳然后在主循环里检查当前时间与时间戳的差值是否超过阈值超过就调用停车函数。本文还有配套的精品资源点击获取