☰
STM32扫地机器人控制系统:硬件可焊、软件可调、逻辑可复现
2026/11/3 8:28:03 网站建设 项目流程

简介:本资源是一套完整的STM32扫地机器人小车控制系统毕业设计资料包,面向嵌入式初学者、自动化专业学生及课程设计/毕设实践者,解决智能移动机器人底层控制与多传感器协同开发难题。压缩包含1281个文件,以C语言源码(.c/.h)、编译中间文件(.o/.d)、Keil工程配置(.uvprojx/.uvoptx/.sct)、原理图与PCB设计文件(.schdoc/.pcbdoc/.prjpcb)及PDF说明文档为主,覆盖从硬件驱动、超声波避障、红外人体感应、MPU6050姿态解算到电机PWM控制的全链路实现,48MB体积紧凑实用。已有341人学习下载,资源提供可直接编译运行的完整Keil工程、分层清晰的模块化代码结构、配套系统框图与流程图,以及电源管理、状态切换等关键功能的实现实例,便于理解分层架构思想并快速复现整机控制逻辑。

1. 这不是玩具小车:一套能真实驱动直流电机、响应红外避障、跑通A*路径规划的STM32扫地机器人控制系统,专为毕业设计卡点验证而生

你手头那套“基于STM32的扫地机器人小车控制系统设计.zip”,不是教学演示Demo,也不是只亮灯跑马灯的验证板工程。它是一套完整闭环的嵌入式机电系统资料包:从Keil MDK里可编译烧录的C源码(含FreeRTOS任务调度)、嘉立创EDA绘制的双层PCB(含L298N驱动+TB6612FNG双路替换方案)、Altium Designer兼容的原理图(标注了所有关键信号走向与滤波电容选型)、到系统级框图和主控流程图——全部对齐STM32F103C8T6最小系统。它解决的是毕业设计中最硬的三道坎:硬件可焊接、软件可调试、逻辑可复现。适合正在赶毕设进度、被导师反复要求“拿出实物验证数据”的本科生;也适合想快速搭建移动机器人底层控制框架、跳过电源噪声/电机反电动势/编码器计数抖动等玄学问题的初学者。别再花三天调不好PWM占空比,也别再为PCB上MOS管炸了查不出原因熬夜——这套资料里,每个模块都留了实测参数和对应注释。


2. 硬件设计落地:从原理图到PCB,看懂为什么这里用TB6612FNG而不是L298N,以及PCB布线如何规避电机干扰

2.1 原理图核心模块拆解:电源、驱动、传感、通信四路信号必须隔离

这套资料的原理图(.SchDoc格式,AD17及以上可打开)不是简单堆芯片,而是按信号完整性做了分区设计:

  • 电源域:明确划分为数字电源(3.3V LDO AMS1117-3.3)、电机电源(7–12V输入经二极管防反接)、传感器电源(5V LDO AMS1117-5.0),三者在GND处单点接地,避免电机电流窜入MCU地线;
  • 驱动模块:主推TB6612FNG双H桥(非L298N),原因很实在——TB6612FNG导通电阻仅0.3Ω,满载时发热低,且自带PWM频率限制(最高100kHz),适配STM32 TIM1的高级定时器输出;原理图中已预留L298N兼容焊盘(R13/R14位置),但默认不贴片,文档里明确写了“若用L298N需额外加装续流二极管D1–D4”;
  • 传感链路:红外避障(TCRT5000)输出直接接STM32 GPIO,无上拉电阻——因为芯片内部已使能上拉(代码里GPIO_PuPd_UP配置可见);超声波模块(HC-SR04)的Trig/Echo引脚分别接PA0/PA1,避开AFIO重映射冲突区;
  • 通信接口:CH340G USB转串口电路独立供电(5V取自电机电源域),TXD/RXD串联33Ω电阻限流,防止MCU UART引脚过驱动。

提示:原理图中所有电解电容(如C12、C15)均标注了耐压值(16V)和ESR要求(≤0.1Ω),这不是凑数——实测中若用普通10μF/16V电容替代,电机启停时MCU会复位。

2.2 PCB布局关键细节:为什么电机走线要加粗到0.5mm,以及如何手动修正嘉立创EDA的板框错误

PCB文件(.PcbDoc,AD17兼容)采用双层板设计,顶层走信号,底层铺完整GND平面。重点观察以下三处:

  • 电机驱动走线:TB6612FNG的OUT1/OUT2至电机端子的铜箔宽度设为0.5mm(而非默认0.2mm),在Design → Rules → Width中可查到该规则单独设置。实测中若用0.2mm线宽,带载启动瞬间压降超0.8V,导致电机堵转;
  • 晶振区域处理:8MHz外部晶振(Y1)紧邻STM32的OSC_IN/OSC_OUT引脚,周围3mm内无任何走线或过孔,且底部GND覆铜挖空——这是为减少寄生电容影响起振稳定性;
  • 嘉立创EDA导出陷阱:资料包里的Gerber文件由嘉立创EDA生成,但其自动导出的板框(Board Outline)常存在微小错位(约0.1mm)。实际打样前,我习惯在AD里用“Place → Line”工具沿机械层(Mechanical 1)重新描一遍板边,再执行Tools → Convert → Create Board Cutout from Selected Objects,否则嘉立创工厂可能拒收。

下面这段代码是PCB设计时必须核对的电气规则检查(DRC)关键项,直接复制进AD的Design → Rules → Electrical中:

// DRC Rule: Motor Power Trace Width Name: Motor_Trace_Width Condition: InLayer('TopLayer') AND (NetName = 'MOTOR_A+' OR NetName = 'MOTOR_A-' OR NetName = 'MOTOR_B+' OR NetName = 'MOTOR_B-') Minimum Width: 0.5mm // DRC Rule: Crystal Keepout Zone Name: Crystal_Keepout Condition: InLayer('TopLayer') AND (ObjectKind = 'Pad' OR ObjectKind = 'Track') Clearance: 3mm Scope: Within 3mm of Pad 'Y1-1' and 'Y1-2'

这段规则强制约束了电机走线宽度和晶振禁区,避免因疏忽导致批量打样失败。很多同学第一次打板就翻车在晶振不起振,根源往往是PCB厂按默认规则布线,把GND覆铜铺到了晶振下方。

2.3 框图与流程图:理解系统分层架构,避免写代码时陷入“全在main里塞if-else”的泥潭

系统框图(PDF格式)清晰划分了四层结构:

  1. 感知层:红外阵列(左/右/前3路)、超声波(前向测距)、编码器(左右轮各1路);
  2. 决策层:FreeRTOS中创建3个任务——vTask_SensorRead(10ms周期采样)、vTask_PathPlanning(A*算法,50ms触发)、vTask_MotorCtrl(20ms闭环PID);
  3. 执行层:TB6612FNG驱动电路 + 直流减速电机(12V/300rpm);
  4. 交互层:CH340G串口输出调试信息(含当前坐标、障碍物距离、任务状态)。

程序流程图(PNG格式)则聚焦主循环逻辑:

  • 上电后先初始化所有外设(GPIO、TIM、ADC、UART),再创建FreeRTOS任务;
  • vTask_SensorRead中,红外读数做滑动平均(5次采样取中值),超声波用HAL库HAL_GPIO_WritePin()触发后,用HAL_TIM_Base_Start_IT()启动定时器捕获Echo高电平时间;
  • vTask_PathPlanning接收红外+超声数据,构建16×16栅格地图(每个格子0.1m×0.1m),调用A*函数返回路径点数组;
  • vTask_MotorCtrl根据路径点计算左右轮差速,用TIM1_CH1/CH2输出互补PWM(死区时间200ns),并通过编码器反馈做速度闭环。

这套分层设计直接决定了代码可维护性。我见过太多毕设代码把A*算法、PID计算、串口发送全塞进一个while(1)里,结果调电机时发现路径规划卡顿——根本原因是没用RTOS做任务隔离。


3. 软件工程实战:Keil工程结构解析、FreeRTOS任务配置、A*算法移植与PID调参实录

3.1 Keil工程目录树与关键文件作用说明

打开Project.uvprojx,工程结构如下(已剔除无关中间文件):

├── Core/ // STM32标准外设库(非HAL) │ ├── inc/ │ │ ├── stm32f10x.h // 标准寄存器定义 │ │ └── system_stm32f10x.h // 系统时钟初始化 │ └── src/ │ ├── stm32f10x_gpio.c │ └── stm32f10x_tim.c // 关键:TIM1高级定时器用于PWM输出 ├── Drivers/ │ ├── inc/ │ │ ├── motor_driver.h // TB6612FNG驱动封装 │ │ └── encoder.h // 正交编码器输入捕获 │ └── src/ │ ├── motor_driver.c // 含set_speed(left, right)接口 │ └── encoder.c // 返回左右轮脉冲计数 ├── Middlewares/ │ └── FreeRTOS/ // V10.3.1,已裁剪掉stream_buffer等冗余组件 ├── User/ │ ├── main.c // 创建任务、启动调度器 │ ├── path_planning.c // A*算法实现(含open_list/close_list管理) │ └── pid_control.c // 位置式PID,Kp=0.8, Ki=0.02, Kd=0.15(实测值) └── Startup/ └── startup_stm32f10x_md.s // 启动文件,支持MD(36脚)封装

注意:所有外设初始化均使用标准库(Standard Peripheral Library),而非HAL库。这意味着你无需安装CubeMX或STM32CubeIDE,Keil uVision5 v5.30+即可直接编译。但代价是——ADC采样需手动配置ADC1->CR2 |= ADC_CR2_SWSTART触发,不能用HAL_ADC_Start()。

3.2 FreeRTOS任务创建与栈空间分配:为什么vTask_MotorCtrl必须设为configMINIMAL_STACK_SIZE * 3

main.c中任务创建代码如下(已加关键注释):

// 创建传感器采集任务:优先级3,栈大小128字(足够存5路传感器原始值+滑动平均缓冲区) xTaskCreate(vTask_SensorRead, "Sensor", 128, NULL, 3, NULL); // 创建路径规划任务:优先级2,栈大小256字(A*需动态申请open_list节点内存) xTaskCreate(vTask_PathPlanning, "PathPlan", 256, NULL, 2, NULL); // 创建电机控制任务:优先级4(最高),栈大小384字——必须放大! // 原因:PID计算涉及浮点运算(arm_math.h),且需缓存左右轮目标速度、当前速度、误差积分项 xTaskCreate(vTask_MotorCtrl, "MotorCtrl", 384, NULL, 4, NULL);

实测中若将vTask_MotorCtrl栈设为256字,运行10分钟后会触发uxTaskGetStackHighWaterMark()告警(剩余栈<32字),导致PID输出异常震荡。这是因为arm_pid_f32()函数内部调用深度达7层,且每次调用需压入12个浮点寄存器(S0–S11)。

3.3 A*算法移植要点:栅格地图构建、启发式函数选择、以及如何避免内存泄漏

path_planning.c中的A*实现并非教科书伪代码,而是针对嵌入式资源优化的版本:

  • 地图构建:不使用二维数组uint8_t map[16][16],而是用一维数组uint8_t map[256](节省编译器对多维数组的地址计算开销);
  • 节点管理:open_list用链表实现(非堆),每个节点仅含x, y, g, h, parent五个字段(共12字节),避免malloc动态分配——所有节点预分配在全局数组node_pool[64]中;
  • 启发式函数:采用曼哈顿距离(h = abs(x - goal_x) + abs(y - goal_y)),而非欧氏距离。原因:避免开方运算耗时,且在栅格地图中精度损失可接受;
  • 终止条件:当open_list为空或找到目标点时退出,必须清空node_pool中已使用的节点,否则连续规划3次后node_pool耗尽,后续路径返回空。

关键清理代码(位于A*函数末尾):

// 清空node_pool中已分配的节点,避免内存泄漏 for(uint8_t i = 0; i < NODE_POOL_SIZE; i++) { if(node_pool[i].used == 1) { node_pool[i].used = 0; // 标记为未使用 node_pool[i].parent = NULL; } }

这个细节常被忽略——很多同学移植A*后发现跑几次就卡死,根源就是node_pool未重置。

3.4 PID调参血泪经验:从“电机狂抖”到“稳停±2cm”的三步法

pid_control.c中位置式PID公式为:

output = Kp * error + Ki * integral + Kd * derivative;

但直接套用理论参数必然翻车。我的实操三步法:

  1. 先调Kp:设Ki=Kd=0,给定目标速度100rpm,观察电机响应。若超调剧烈(转过头再回退),Kp过大;若响应迟钝(爬行缓慢),Kp过小。实测Kp=0.8时,阶跃响应上升时间≈0.8s,超调量<15%;
  2. 再加Ki:Kp固定,Ki从0.005开始递增。当出现低频振荡(周期≈2s)时,说明Ki过大,回退到0.02——此时积分项能消除稳态误差(停车时速度偏差<0.5rpm);
  3. 最后加Kd:Kp/Ki固定,Kd从0.05开始加。若电机发出高频“吱吱”声,说明微分过强,最终定为0.15——它能抑制超调,但过高会放大编码器噪声。

提示:所有PID参数均定义为float类型,但STM32F103无硬件FPU,故编译时需在Options → Target中勾选“Use MicroLIB”,并启用__ARM_ARCH_6M指令集,否则浮点运算极慢。


4. 避坑指南:那些让毕设答辩前夜崩溃的5个真实故障,附带定位命令与修复方案

4.1 现象:下载程序后电机完全不转,但串口有“Init OK”打印

原因:TB6612FNG的STBY引脚悬空(原理图中R10未焊接),导致芯片处于休眠态。STBY需接高电平才能使能输出。
解决:检查PCB上R10位置(位于U3芯片旁),用烙铁将R10两端短接(即STBY→VCC),或改焊为0Ω电阻。代码中无需操作,纯硬件问题。

4.2 现象:红外避障始终返回0xFF(无遮挡时也报障碍)

原因:TCRT5000模块供电不足。原理图中红外传感器VCC接5V LDO,但实测PCB上C18(100μF滤波电容)虚焊,导致纹波超200mV,光电三极管输出失真。
解决:用万用表DC档测TCRT5000的VCC引脚,若电压低于4.7V,补焊C18;或临时将VCC改接到电机电源(12V经AMS1117-5.0稳压后)。

4.3 现象:A*规划路径后小车原地打转,不向前移动

原因:编码器A/B相接反。原理图中标注ENC_A接PA6、ENC_B接PA7,但PCB丝印误标为“ENC_A→PA7”,导致正交解码方向相反。
解决:进入encoder.c,将TIM3->CCMR1配置中的CC1S和CC2S位互换(即TIM_ICSELECTION_DIRECTTI↔TIM_ICSELECTION_INDIRECTTI),或物理交换PCB上ENC_A/ENC_B焊点。

4.4 现象:FreeRTOS任务创建失败,xTaskCreate返回pdFAIL

原因:Heap空间不足。FreeRTOSConfig.h中configTOTAL_HEAP_SIZE设为10240字节(10KB),但vTask_PathPlanning栈256字×4字节=1024字,vTask_MotorCtrl栈384字×4字节=1536字,加上内核开销,总需求超12KB。
解决:修改FreeRTOSConfig.h,将configTOTAL_HEAP_SIZE改为16384(16KB),并确认heap_4.c已加入工程(非heap_2.c)。

4.5 现象:串口调试信息乱码(如“Init OK”显示为“n OK”)

原因:USART1时钟源配置错误。STM32F103默认APB2时钟72MHz,但RCC->CFGR & RCC_CFGR_PPRE2被误设为RCC_HCLK_Div2(36MHz),导致USARTDIV计算偏差。
解决:检查system_stm32f10x.c中SystemCoreClockUpdate()函数,确保RCC_CFGR_PPRE2位为0b000(即APB2不分频),对应RCC_HCLK_Div1。实测波特率115200时,时钟必须为72MHz。


5. 系统功能测试图实操:用逻辑分析仪抓取PWM波形、用串口助手验证A*输出、以及毕业答辩必演的3个场景

5.1 PWM波形抓取:确认TIM1_CH1/CH2输出是否符合PID计算值

用Saleae Logic 8逻辑分析仪(或DSO138示波器)探头接PA8(TIM1_CH1)和PA9(TIM1_CH2),设置采样率≥10MS/s:

  • 预期波形:频率20kHz(ARR=3599, PSC=0),占空比随PID输出动态变化(如左轮目标100rpm时占空比≈45%);
  • 关键判据:两路PWM必须严格互补(死区时间200ns),即CH1高电平时CH2为低,且下降沿延迟200ns。若无死区,TB6612FNG可能直通短路;
  • 验证命令:在pid_control.c中插入printf("L_PWM:%d R_PWM:%d\r\n", left_pwm, right_pwm);,通过串口助手查看数值是否与波形占空比一致。

提示:若波形频率不对,检查TIM_TimeBaseInitTypeDef中TIM_Period和TIM_Prescaler是否匹配——72MHz / (PSC+1) / (ARR+1) = 20kHz,故PSC=0, ARR=3599。

5.2 A*路径输出验证:用串口助手实时查看栅格坐标序列

在path_planning.c的A*主循环末尾添加调试输出:

// 在return path之前插入 for(uint8_t i = 0; i < path_len && i < 20; i++) { printf("P[%d]=(%d,%d)\r\n", i, path[i].x, path[i].y); }

打开XCOM串口助手(波特率115200),发送指令$PLAN:0,0,15,15(起点0,0→终点15,15),应看到类似输出:

P[0]=(0,0) P[1]=(1,0) P[2]=(2,0) ... P[28]=(15,15)

若输出乱码或无响应,检查usart.c中USART1_IRQHandler()是否清除了USART_SR_TC标志位(否则中断卡死)。

5.3 毕业答辩必演场景:3分钟闭环演示脚本(含话术与故障预案)

场景1:基础避障(30秒)

  • 操作:小车静止,用手掌靠近左侧红外→小车右转;靠近右侧→左转;正前方→后退。
  • 话术:“本系统采用三路红外+单路超声融合判断,红外响应快(<10ms),超声测距准(±1cm),避免单一传感器盲区。”
  • 故障预案:若无反应,立即说“请看串口日志”,展示printf("IR_L:%d IR_R:%d US:%d\r\n", ir_l, ir_r, us_dist);输出值,证明传感器正常。

场景2:A*路径规划(60秒)

  • 操作:在地面用胶带标出1m×1m区域,放置纸箱作障碍物,发送$PLAN:0,0,9,9→小车自动绕行抵达。
  • 话术:“地图分辨率10cm/格,A*算法在200ms内完成100格搜索,路径长度比直线长<15%,满足实时性要求。”
  • 故障预案:若卡住,快速切换到$TEST:MOTOR:50:50(直行测试),证明驱动正常,归因为地图构建错误(可现场修改map[x][y]=1临时清除障碍)。

场景3:PID稳停测试(30秒)

  • 操作:设定目标点(5,5),小车运行至该点后,用游标卡尺测量实际停车位置,误差≤2cm。
  • 话术:“位置式PID闭环控制,编码器分辨率360PPR,经1:20减速后,理论定位精度0.87mm,实测±1.8cm满足扫地场景需求。”
  • 故障预案:若超调,立即解释“当前Kd=0.15为保守值,若需更高精度可提升至0.25,但会增加电机噪声”。

从那以后我每次帮师弟调试毕设,都强制走一遍这三步:先抓PWM波形确认底层驱动,再看串口A*输出验证算法逻辑,最后用游标卡尺量停车精度——只要这三项全过,答辩PPT一页都不用改,导师只会问“你用的什么PID参数?”。希望帮到你。

本文还有配套的精品资源,点击获取

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询