☰
基于STM32与红外传感器的自动泊车系统设计与实现
2026/9/30 5:53:02 网站建设 项目流程

简介:基于C语言与STM32F103C8T6微控制器、结合红外传感器实现的自动泊车系统毕业设计资源包,主要面向嵌入式方向的高校本科生与开发者,可用于毕业设计、课程设计与项目二次开发。整套方案通过红外传感器检测车位与障碍物,主控芯片负责解析数据并控制泊车动作;源码采用模块化设计,逻辑清晰,并配有引脚对应表和硬件课设主控电路原理图,能帮助读者快速建立软硬件联调思路。压缩包共19个文件,包含C源码(7个.c、6个.h)、CubeMX配置工程、硬件主控电路原理图PDF、txt说明文档、图片素材及README等,从代码、配置到电路图形成完整交付链,整体仅127KB,轻量易用。目前已有90人下载学习,源码经过严格测试,可直接参考并在此基础上扩展更多自动泊车辅助功能。

1. 用一对红外传感器把自动泊车做到能复现

真实的课程设计答辩现场,最尴尬的不是代码报错,而是演示的时候车“停歪了”。这套基于 C 语言 + stm32f103c8t6 最小系统板 + 红外传感器模块的自动泊车系统,恰恰是少有的演示成功率较高的方案。它没有用摄像头做视觉识别,也没上激光雷达,靠两个红外传感器判断车位边界,用状态机驱动转向和进退,完成从直行寻位、倒车入库到回正车身的一整套流程。源码、硬件主控电路原理图、STM32CubeMX 工程文件(.ioc)都完整,适合毕业设计、课程设计,也适合想快速验证“感知—决策—执行”闭环的嵌入式开发者。资料里的 C 语言实现可以直接编译烧录,剩下要理解的其实是状态机拆分和传感器布置这两件事。

2. 系统架构与红外传感器选型逻辑

2.1 为什么自动泊车感知层选了红外传感器

在倒车入库场景里,车辆只需要知道两件事:车身与车位边界的距离是否进入安全范围、后方的障碍物是否接近。红外传感器模块(这里用的是数字量输出的红外避障模块)在这两个距离点上表现足够好。

和超声波模块相比,红外模块的劣势是测距范围窄、容易受环境光干扰,但它有个超声波没有的优点:响应快,反射面要求低。超声波在近距离(小于 20cm)存在盲区,红外在 2~30cm 范围内电平翻转干脆。自动泊车这类低速场景,轮速本身就慢,传感器如果漏测一次边沿,车可能已经开过整个车位。红外模块的 38kHz 调制信号在近距离触发非常稳定,反而更适合这种“只要到不到边界”的判断逻辑。和摄像头方案比,红外方案没有标定内参、亮度归一化这些额外工作量,对一个以状态机逻辑为主线的课设来说,投入产出比明显。

提示:项目里用的是数字量输出红外传感器,不是模拟量测距模块。若后续想读连续距离,需要换成带 ADC 输出的型号,代码要对应改。

2.2 整体控制链路:感知—决策—执行

系统按照三层结构组织:感知层是车位侧方的红外传感器,决策层是 STM32F103C8T6 里的 C 语言状态机,执行层是直流电机和舵机(或双电机差速)。三层之间通过 GPIO 电平变化和定时器中断衔接。

感知层不直接参与决策,而是把电平变化折算成边界事件。比方说,红外传感器从“检测到车位边沿”变成“离开边沿”的下降沿,就代表车头已经驶过车位入口,这正是状态机从“寻位”切换到“倒库”的触发条件。这样设计的价值在于:传感器只负责提供事件,不负责连续测距,决策逻辑不会绑定到具体型号上,以后换超声波、换霍尔传感器,状态机主体都不用动。

2.3 引脚分配与端口规划

资料解压后的端口对应.txt就是这份映射。以下是按项目工程整理出来的标准接线表,实际以压缩包内文档为准:

功能STM32 引脚建议 GPIO 配置说明
左侧红外传感器 OUTPA0GPIO_MODE_INPUT检测车位左边界
右侧红外传感器 OUTPA1GPIO_MODE_INPUT检测右边界/后方障碍
舵机方向控制PA8TIM1_CH1 PWM控制前轮转角
后轮电机 PWMPB0TIM3_CH3 PWM车速控制
电机方向引脚 1PB12GPIO_OUTPUT前进/后退切换
电机方向引脚 2PB13GPIO_OUTPUT差速/转向配合
串口调试 TXPA9USART1_TXprintf 日志
串口调试 RXPA10USART1_RX预留指令输入

接线的两个关键点在 CubeMX 工程里已经体现:红外传感器的 OUT 全部配成输入模式,默认下拉;PWM 引脚复用为定时器输出。方向引脚不接 PWM,只接高低电平,用 GPIO 就能完成换向。

下载包里的Core Hardware_Class_Project.ioc用 STM32CubeMX 打开后,时钟树默认是 72MHz(8MHz 外部晶振经 PLL 倍频到 72)。建议不要图省事直接用 HSI 内部时钟跑,红外信号解调本身依赖定时器时基,HSI 的温漂会让 PWM 周期跟着偏,舵机角度就不准了。

2.4 红外传感器模块的电平接口细节

常见模块是 3pin 或 4pin:VCC、GND、OUT(数字输出),有的带距离调节电位器。模块上电后,当检测区域内出现障碍物时,OUT 输出低电平,否则输出高电平。这是 5V 供电模块的常见电平,接到 3.3V 的 STM32 引脚时,5V 高电平远超 STM32 约 2.31V 的输入阈值,读起来没问题;但为了长期可靠性,我一般会串一个 1kΩ 电阻,或者在原理图上预留分压位置。

这里常见误用是把 OUT 接到 ADC 引脚上去读距离曲线。数字模块内部是比较器加了一路电位器,输出的不是距离模拟量,而是“是否超限”的逻辑量。把它接 ADC 只会读到 0 或 3.3V 两个值,没有任何中间信息,白白浪费资源。

3. 基于 C 语言的状态机实现:去抖与泊车流程

3.1 输入信号消抖:不能直接拿 GPIO 电平做跳变

红外模块输出电平在临界距离附近会抖动:小车在行进时轻微颠簸,传感器检测到边界后又短暂离开,造成十几个毫秒的高频翻转。如果状态机直接读这种电平,会在寻位和倒库两个状态之间来回跳,表现在车身上就是“刚倒一点又往前开”。

常见做法是软件消抖,在读取函数里加连续采样判断。

// 参数:连续 n 次采样一致才认为电平有效,n 由外部宏定义 uint8_t IR_Read_Debounced(GPIO_TypeDef *port, uint16_t pin, uint8_t sample_cnt) { uint8_t stable_level = 0; uint8_t i; for (i = 0; i < sample_cnt; i++) { uint8_t raw = (HAL_GPIO_ReadPin(port, pin) == GPIO_PIN_RESET) ? 1u : 0u; if (i == 0) { stable_level = raw; // 以第一次采样为基准 } else if (raw != stable_level) { stable_level = raw; // 中途出现不一致,以最后一次为准 } HAL_Delay(1); // 1ms 采样间隔 } return stable_level; }

这段代码每次调用会阻塞约 sample_cnt 毫秒。如果放在状态机主逻辑里,sample_cnt 取 5 到 8 即可保证抖动被滤掉,又不会让车辆反应迟钝。需要说明的是,这里没有做“少数服从多数”的投票,而是直接以最后一次结果覆盖,目的是让最近电平优先,车位边沿这种短促信号不会被旧的占多数电平吞掉。

如果希望不阻塞主循环,可以把采样逻辑挪到定时器中断里,用计数变量累加,直到连续 N 次一致才更新全局状态。两种做法在这个项目里都能跑,区别只在于中断版本更接近工程化写法,也方便后续扩展传感器数量。

3.2 泊车状态机:三段式倒库流程

自动泊车的核心是把驾驶逻辑拆成有限状态。项目源码里的状态定义大致如下:

typedef enum { STATE_SCAN, // 直行寻位:沿着车位边沿低速前进 STATE_ALIGN_REVERSE, // 到位后原地回正,准备倒车 STATE_REVERSE_IN, // 方向盘固定角度,倒车入库 STATE_CORRECT, // 入库过程中根据红外触发修正方向 STATE_FINISH // 到位信号触发,停车 } ParkState;

状态迁移的条件是红外边沿事件加定时器超时。以最常见的“一侧有墙、另一侧空车位”场景举例:STATE_SCAN 阶段左红外一直检测到墙,说明车身没有贴错边;右红外没有持续触发,说明右侧是空位。当右红外从“有遮挡”变成“无遮挡”,也就是经过了车位入口,系统切换到 ALIGN_REVERSE,原地把转向机构打到最大角度,开始倒车。

STATE_REVERSE_IN 阶段,车速用 PWM 控制在一个较低值,转向角度从满舵逐步减半,模拟司机回方向的过程。这里最容易被忽略的是状态迁移的退出条件:不能只看红外是否触发,还要加一个超时保护。比如倒车超过 4 秒仍然没有触发到位信号,就说明刚才的车位判断出错,需要回到 STATE_SCAN 重新找位。

3.3 状态机主循环与参数抽取

主循环用一个 10ms 时基驱动,避免在 while 里什么都不做地死等。

// 10ms 时基调用,或者主循环里用 HAL_GetTick() 做非阻塞调度 void Parking_StateMachine(void) { static uint32_t last_tick = 0; uint32_t now = HAL_GetTick(); if (now - last_tick < 10) return; last_tick = now; uint8_t left_ir = IR_Read_Debounced(GPIOA, GPIO_PIN_0, 3); uint8_t right_ir = IR_Read_Debounced(GPIOA, GPIO_PIN_1, 3); switch (cur_state) { case STATE_SCAN: Motor_SetSpeed(SCAN_SPEED); // 低速直行 if (right_ir == 0 && last_right_ir == 1) { // 右红外产生下降沿,说明车位入口经过完毕 cur_state = STATE_ALIGN_REVERSE; state_timeout_ms = 0; } break; case STATE_ALIGN_REVERSE: Steering_SetAngle(MAX_STEER_ANGLE); // 打满舵 if (state_timeout_ms >= ALIGN_TIME_MS) cur_state = STATE_REVERSE_IN; break; case STATE_REVERSE_IN: Motor_SetSpeed(REVERSE_SPEED); Motor_SetDir(DIR_BACKWARD); Steering_SetAngle(REVERSE_ANGLE); // 逐步回舵 if (left_ir == 1 && right_ir == 1) cur_state = STATE_FINISH; if (state_timeout_ms >= REVERSE_TIMEOUT_MS) cur_state = STATE_SCAN; // 超时重新找位 break; case STATE_FINISH: Motor_SetSpeed(0); Steering_SetAngle(0); break; default: cur_state = STATE_SCAN; break; } if (cur_state != last_state) { state_timeout_ms = 0; // 状态切换时清零计时 last_state = cur_state; } else { state_timeout_ms += 10; } last_right_ir = right_ir; }

这里有几个参数值得展开说:

  • SCAN_SPEED建议取最大 PWM 占空比的 25% 到 35%,寻位速度太快会导致红外边沿检测丢失,因为采样循环在阻塞消抖,单次HAL_Delay期间车可能已经过了一半车位。
  • ALIGN_TIME_MS取 800ms 比较合理,给舵机留出从 0 度打到满舵的响应时间,太短舵机还没转到位就进入倒车,轨迹会偏。
  • REVERSE_TIMEOUT_MS是容错阈值,实验场地 2 米长车道的倒库耗时一般不到 3 秒,设 5 秒的余量既能避免误判又不会卡死。

参数抽取是这套代码适合二次开发的原因:速度、转角、时间全部以宏或配置文件形式出现,没有硬编码在状态分支里。做毕业设计时,把这三组参数放进一个结构体,改成从串口透传调整,就变成了一个简单的参数自整定工具。

4. 主控电路原理图解读与硬件联调

4.1 最小系统与电源:原理图中先确认的三处

拿到硬件课设主控电路原理图.pdf,第一眼不应该去看芯片引脚,而是先看电源树。STM32F103C8T6 是 3.3V 供电,常见红外模块和舵机标称都是 5V,因此板上至少要有一个 LDO 把外部 5V 稳到 3.3V,常见的是 AMS1117-3.3。原理图里对应的滤波电容一般是 10μF 钽电容并联 100nF 陶瓷电容,这个组合在高频数字开关噪声下才够看。

第二处是 BOOT0 引脚的处理。正常烧录和运行时要拉低到 GND,如果原理图上 BOOT0 悬空,静电感应可能导致偶尔进不了下载模式。课程设计里经常出现“昨天能下今天不能下”的现象,多半就是 BOOT0 悬空或复位电路太短。

第三处是 NRST 复位电路和 SWD 调试接口。复位电容通常取 100nF,SWDIO/SWCLK 上各加一个 10kΩ 上拉。这三处都确认无误,最小系统板(也就是常说的 stm32f103c8t6 最小系统板原理图)就能稳定工作,剩下的问题只在外设连接。

4.2 红外传感器接口电路:电平匹配是坑最多的部分

红外模块的 OUT 引脚是开漏输出还是推挽输出,直接影响是否需要上拉。多数模块内部是 NPN 三极管开漏驱动,原理图上应该在 OUT 到 VCC 之间画一个 4.7kΩ 上拉电阻。如果上拉电阻没焊,且单片机内部没使能上拉,会读到随机浮空电平,现象就是状态机随机跳。

外部 5V 上拉后,OUT 高电平就是 5V。STM32F103C8T6 的绝大多数 GPIO 耐压是 5V,直接接可以工作,但更稳妥的是采用串联 1kΩ 电阻限流。需要注意,5V 上拉读成高电平没悬念,但引脚外露的耐压余量不能长期依赖,特别是实验室电源纹波大的时候,瞬时过压很容易悄悄损坏输入保护二极管。

4.3 电机和舵机的驱动:GPIO 不能直接拖

舵机信号线只需要一个 3.3V 的 PWM(50Hz,0.5ms~2.5ms 脉宽对应 0~180 度),但它的电源线在工作瞬间会有接近 1A 的电流尖峰。这个尖峰如果和单片机共用一个电源,会让 3.3V 出现跌落,红外传感器的比较器跟着误翻转,整辆车就会抽搐。原理图上的正确做法是把舵机电源和逻辑电源分开,用一个大容量的电解电容(470μF)在舵机电源入口做缓冲。

直流电机部分,单片机的 GPIO 只能提供几毫安电流,必须走驱动芯片。常见选型对比如下:

驱动方案逻辑电平峰值电流课设场景评价
L298N5V 逻辑2A模块大、发热明显,适合大扭矩电机
TB66122.7~5.5V1.2A3.3V 直连,效率高,课设首选
DRV88332.7~10.8V1.5A封装小,适合车载集成,价格略高

TB6612 在 3.3V 逻辑下可以直接配合使用,且功耗比 L298N 低。原理图里如果看到的是 L298N,注意它的逻辑供电 VSS 要接 5V,但 STM32 输出 3.3V 高电平依然在 L298N 的逻辑阈值内,连接关系不需要额外加电路。

4.4 用串口日志验证感知与执行

联调的第一步不是写满整个状态机,而是先把红外传感器的原始电平读出来,通过串口打印。项目中 USART1 已经配置在 PA9/PA10,波特率 115200。重定向 printf 到串口的常用做法:

#include <stdio.h> int fputc(int ch, FILE *f) { HAL_UART_Transmit(&huart1, (uint8_t *)&ch, 1, 10); return ch; }

然后在状态机里打印一条调试记录:

printf("[%lu] state=%d L=%d R=%d pwm=%d\r\n", HAL_GetTick(), cur_state, left_ir, right_ir, motor_pwm);

把车用手推着沿车位边界走一遍,观察串口输出里 L 和 R 的变化点是否与物理位置吻合。如果发现边沿触发位置比实际车位晚,说明传感器安装角度偏大,需要在机械固定上调整,而不是去改代码里的阈值。这个验证方法能快速区分问题是出在感知层还是决策层,省去反复烧录测试的折腾。

4.5 供电异常与电机干扰的排查顺序

如果状态机正常但车动起来后乱跳,按顺序排查:第一,舵机电源地线是否和信号线形成独立回路,关键看地线是否共用;第二,信号线走线是否过长,5cm 以上建议直接缩短;第三,电机两端是否并联续流二极管或者 RC 吸收电路,没有的话每次换向都会在电源轨上砸出毛刺,红外模块在临界状态最容易误触发。这三步走完,绝大部分干扰问题都能收敛。

5. 让泊车轨迹更稳的三个调整技巧

5.1 传感器安装角度优先于代码补偿

红外模块的检测方向不是一条直线,而是一个锥形区域。安装时把传感器朝向车位内侧稍微偏 10~15 度,经过车位边沿的哪一瞬间电平翻转点会更锐利,状态机收到的边沿事件更准。相比之下,在代码里给脉冲宽度加补偿只会挪动触发位置,不会解决“触发不稳定”的问题。调完角度后再用串口日志复测一次边沿位置,确认与上次一致再进入下一步。

5.2 停车标志改成两个条件的与运算

单靠两个红外都触发就停车,在光线反射强的地面(如白色地砖)可能提前误停。更可靠的条件是“两个红外同时触发”且“持续超过 200ms”。后面这个持续时间判断放在状态机的超时分支里即可,代码改动只有几行,却能挡住绝大多数瞬时误触发。计时用HAL_GetTick()记录首次触发时刻,持续满足才置位到位标志,效果比单纯读电平稳定得多。

5.3 参数表驱动泊车轨迹,换场地不用改逻辑

把寻位速度、倒库速度、转向角度、各阶段超时这五个参数排进一个结构体数组,每个数组元素对应该场景的泊车参数。答辩演示时如果从教室换到大厅,只需要通过串口指令切换参数组,逻辑代码一行不动。这个小改动可以让课程设计在演示环节表现出很好的适应性,也为后续移植到带速传感器的高端模型留出接口。验证时,先打印一组参数对应的完整状态轨迹,再实际跑一遍,两者吻合后继续微调下一组参数,这套闭环流程是系统从“能跑”到“稳定”的关键。

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

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

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

立即咨询