简介:这是基于STM32单片机的智能停车场毕业设计完整资料包,适合电子、自动化、计算机等相关专业学生用于毕业设计、课程设计、作业或项目初期演示,也可作为嵌入式入门者的进阶练习材料。项目源码已经过运行测试,功能稳定,覆盖车辆检测、车位状态管理及核心控制逻辑等典型环节,并保留了芯片启动文件、系统初始化配置与工程说明,便于快速理解整体架构和调试方式。压缩包共69个文件,以C语言头文件(.h)和源文件(.c)为主,另有Keil工程配置文件(.uvprojx/uvoptx)、链接映射表(.map)及说明文档(.md/txt)等,总大小约305KB,目录划分清晰,查找方便。当前已有312人参与学习,适合需要完整参考方案的毕设人群。配套详细设计文档可辅助梳理STM32外设调用思路,代码注释与模块划分清楚,适合在此基础上做二次开发或功能扩展,能兼顾毕设验收与入门进阶的学习需求。
1. 基于STM32的智能停车场毕设源码:先别急着写代码,想清楚题目得分点再动手
做毕业设计最怕的不是代码敲不出来,而是题目看着会,做完才发现答辩时讲不清原理。基于STM32单片机的智能停车场设计就是典型“看着简单、想高分也不简单”的题目:传感器检测车辆进出,单片机计算剩余车位,道闸自动开合,LCD实时显示状态。这套源码加文档的工程包我完整拆过一遍,走的是STM32F103系列标准外设库,配套原理图、仿真和论文材料,适合想在一两周内把硬件到软件全链路复现清楚的同学,也适合刚入门嵌入式、想用第一个综合项目串起GPIO、中断、定时器和PWM的人。方向对了,后面每一步才不会白费。
2. STM32资源分配与工程结构:先看分配表再动手,能少踩一半坑
2.1 为什么这类毕设几乎都选STM32F103C8T6
智能停车场的任务拆开看,无非是几个开关量输入、一路PWM、一块显示屏和串口打印。STM32F103C8T6是最稳的选型:64KB Flash、20KB RAM,跑这套逻辑绰绰有余;GPIO数量、USART、定时器、ADC都够用。关键是它便宜,核心板十几块钱,实验室资源也最全,网上随便搜都是参考例程。
有人会说51单片机也能做,但LCD1602的时序要自己模拟,定时器和中断资源紧张,车位检测如果上超声波,51的定时器几乎全被占满。反过来,用ESP32去跑这种题目又有点浪费,WiFi配网、下载驱动都会成为不可控变量,毕业设计答辩现场最怕的就是设备连不上路由器。STM32的确定性更好,每个外设都能讲清楚寄存器级行为,这正是答辩老师想看的能力。
这套资料你解压后如果看到stm32f10x_conf.h这类文件,说明是标准外设库(SPL)工程。SPL比HAL库老,但好处是中断入口、外设配置都非常直白,对着《STM32不完全手册》看就行。如果你只会HAL,也没关系,先找到初始化函数再动手改,不要把整个工程重写成HAL。
2.2 模块划分与引脚分配对照表
我拆完这套资料后,建议你第一件事不是打开main.c,而是先整理一个“外设-引脚-模式”对照表。下面是我在做类似毕设时常用的映射关系,具体以你手里资料包的原理图为准:
| 功能块 | 建议引脚 | 外设模式 | 说明 |
|---|---|---|---|
| 入口红外对射 | PA0 | GPIO输入 + EXTI0 | 检测有车进入,下降沿触发 |
| 出口红外对射 | PA6 | GPIO输入 + EXTI6 | 检测有车离开 |
| 超声波测距(车位检测选配) | TRIG=PA10,ECHO=PA11 | GPIO输出 / 输入捕获 | Echo脉宽换算距离 |
| 道闸舵机 | PA1(TIM2_CH2) | 复用推挽输出 / PWM | 50Hz PWM控制角度 |
| LCD1602显示屏 | RS=PB0,E=PB1,D4~D7=PA2~PA5 | 推挽输出 | 4线模式,省GPIO |
| 蜂鸣器 | PB12 | 推挽输出 | 有车时短鸣提示 |
把表抄到纸上,再对照原理图走一遍。PA0和PA1相邻,很多人会把舵机PWM和入口红外接反,上电后计数乱跳,问题往往就是这里。GPIO配置上,红外对射模块建议设为上拉输入,传感器不遮挡时输出高电平,遮挡时拉低,这样下降沿触发比较可靠。
2.3 解压后的资料包结构:源码、硬件、文档怎么对应
毕设资料包最常见的问题是“源码和文档脱节”,要么论文写一套代码写一套,要么原理图引脚和代码注释对不上。我拿到这套资料后的目录结构大概是这样的:
01_Source/ User/main.c User/stm32f10x_it.c Hardware/display.c Hardware/motor.c Hardware/detector.c 02_Schematic/ 原理图PDF Altium工程文件 Proteus仿真文件 03_Documents/ 开题报告.md 设计说明书.pdf 答辩PPT.pptx 04_Tools/ ST-Link驱动 串口驱动先看02_Schematic里的原理图,再打开01_Source对照。代码里Hardware/motor.c对应道闸控制,detector.c对应进出门传感器和车位传感器,display.c对应LCD显示。千万别直接跳到答辩PPT,PPT里为了好看经常简化流程图,最后代码实现和PPT不一致,现场被问穿就尴尬了。
3. 状态机与计数逻辑:把“进了几辆、还剩几位”算准,就够答辩了
3.1 用外部中断采集车辆,还是主循环轮询
初学者最容易写成主循环里不停读GPIO,发现信号就计数。这种做法在按键上勉强能用,但红外对射被车遮挡的瞬间可能只有几十毫秒,如果主循环里有LCD刷新或延时,很容易漏脉冲。更关键的是,轮询逻辑和显示逻辑混在一起,一旦出现误计数,排查时根本分不清是传感器抖动还是代码时序问题。
我一般会把这个题目拆成两层:中断层只负责把“有事件发生”记下来,业务层在主循环里处理防抖和状态转移。中断服务函数尽量短,只置标志位,不要在里面调Delay,也不要直接驱动LCD和舵机。原因很简单,中断服务函数越长,主循环被卡死的概率越大,现场老师拿着示波器看你程序时,这个点是高频失分项。
入口检测中断的示意代码如下:
volatile uint8_t entry_irq_flag = 0; volatile uint8_t exit_irq_flag = 0; void EXTI0_IRQHandler(void) { if (EXTI_GetITStatus(EXTI_Line0) != RESET) { entry_irq_flag = 1; EXTI_ClearITPendingBit(EXTI_Line0); } }这里entry_irq_flag只是给主循环发一个“入口可能有车”的信号,真正判断车是否进来放在主循环状态机里。如果直接在中断里做GPIO_ReadInputDataBit再计数,传感器输出的抖动会触发很多次中断,计数立刻失真。
3.2 进出车辆计数状态机:防抖、开闸、落杆一次理清
这是全项目的核心逻辑。通过入口的单路红外对射判断车辆进入,不能靠一次边沿就加数,至少要经过“触发—确认—开闸—落杆”四个阶段。我用一个枚举状态机实现,代码可以照抄到main.c里:
#define TOTAL_PARKING_SLOTS 20 #define ENTRY_CONFIRM_MS 20 #define GATE_KEEP_OPEN_MS 2000 volatile uint16_t current_cars = 0; volatile uint16_t free_slots = TOTAL_PARKING_SLOTS; typedef enum { ENTRY_IDLE, ENTRY_WAIT_CONFIRM, ENTRY_GATE_OPEN } EntryState; EntryState entry_state = ENTRY_IDLE; uint32_t entry_timer = 0; void Parking_EntryRun(void) { switch (entry_state) { case ENTRY_IDLE: if (GPIO_ReadInputDataBit(GPIOA, GPIO_Pin_0) == 0) { entry_timer = Hal_GetTick(); entry_state = ENTRY_WAIT_CONFIRM; } break; case ENTRY_WAIT_CONFIRM: if (Hal_GetTick() - entry_timer >= ENTRY_CONFIRM_MS) { if (GPIO_ReadInputDataBit(GPIOA, GPIO_Pin_0) == 0) { if (current_cars < TOTAL_PARKING_SLOTS) { current_cars++; free_slots = TOTAL_PARKING_SLOTS - current_cars; Servo_SetAngle(90); /* 抬杆 */ LCD_ShowFreeSlots(free_slots); UART_SendStatusFrame(current_cars, free_slots); } entry_state = ENTRY_GATE_OPEN; entry_timer = Hal_GetTick(); } else { entry_state = ENTRY_IDLE; /* 20ms内已恢复,视为抖动 */ } } break; case ENTRY_GATE_OPEN: if (Hal_GetTick() - entry_timer >= GATE_KEEP_OPEN_MS) { Servo_SetAngle(0); /* 落杆 */ entry_state = ENTRY_IDLE; } break; default: entry_state = ENTRY_IDLE; break; } }参数说明:ENTRY_CONFIRM_MS是防抖窗口,20ms足够滤掉传感器毛刺,又不会漏掉真实车辆;GATE_KEEP_OPEN_MS是道闸保持时间,模拟车辆通过的时间,一般设1.5到3秒。current_cars用了短整型,溢出可能性低,但注意TOTAL_PARKING_SLOTS是宏,如果改成35个车位,上限逻辑会自动匹配。
出口逻辑完全对称,把GPIO_Pin_0换成GPIO_Pin_6,计数值先判断大于0再减一,否则会出现负的剩余车位。很多资料把入口和出口写在一起,但不做“当前车辆数不为零”保护,连续刷两辆车出去后,剩余车位直接变成21个,这种低级错误一旦在答辩演示中被发现,基本就告别高分了。
3.3 车位检测与道闸控制:超声波测距和舵机PWM怎么配
如果题目要求“每个车位上检测是否空余”,常见做法是超声波测距加判断。STM32发一个10us的触发脉冲给超声波模块,模块返回一段高电平,高电平持续时间就是声音往返时间。距离计算公式为:距离(mm)= 时间(us)× 0.34 / 2。
代码上可以这样实现:
uint32_t Ultrasonic_Measure(void) { uint32_t count; GPIO_SetBits(GPIOB, GPIO_Pin_10); /* TRIG拉高 */ Delay_us(10); GPIO_ResetBits(GPIOB, GPIO_Pin_10); /* TRIG拉低 */ while (GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_11) == 0); /* 等ECHO变高 */ TIM_SetCounter(TIM2, 0); TIM_Cmd(TIM2, ENABLE); while (GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_11) == 1); /* 等ECHO变低 */ TIM_Cmd(TIM2, DISABLE); count = TIM_GetCounter(TIM2); return count * 10 / 58; /* 单位:mm,可用经验系数 */ }这段代码里的while是死等,真实项目一定要加超时保护,否则传感器断线时程序会卡住。更稳妥的做法是设置一个1ms超时,超过时间直接返回0xFFFF。判断车位是否有车,我一般把阈值设成300mm,小于阈值认为有车。要注意超声波模块的供电电压,很多模块是5V工作,和STM32共用电源时Echo输出也可能到5V,串电阻或电平转换后再进GPIO,不然长期使用会烧引脚。
舵机控制走PWM。SG90舵机的周期是20ms,脉宽0.5ms对应0度,2.5ms对应180度。定时器配置参数很关键:
TIM_TimeBaseInitTypeDef tb; TIM_OCInitTypeDef oc; RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2, ENABLE); tb.TIM_Prescaler = 72 - 1; /* 72MHz / 72 = 1MHz,计数器每1us加1 */ tb.TIM_CounterMode = TIM_CounterMode_Up; tb.TIM_Period = 2000 - 1; /* 2000us计满,PWM周期20ms */ TIM_TimeBaseInit(TIM2, &tb); oc.TIM_OCMode = TIM_OCMode_PWM1; oc.TIM_Pulse = 50; /* 脉宽0.5ms,对应0度 */ oc.TIM_OutputState = TIM_OutputState_Enable; TIM_OC2Init(TIM2, &oc); TIM_OC2PreloadConfig(TIM2, TIM_OCPreload_Enable); TIM_Cmd(TIM2, ENABLE);比较值范围是50到250,0度就是50,180度就是250。Servo_SetAngle(90)实际上就是把比较值改成50 + angle。注意angle是浮点的话还要写整数映射,我习惯写一个带限幅的函数,防止传入超过180度的值把舵机打死。
3.4 LCD1602显示和按键消抖:别让显示逻辑污染主循环
LCD1602用4线模式,初始化代码无非是“延时—写命令—清屏—设置光标”。真正容易出问题的是刷新时机,不要在车辆状态还没有更新时刷新,也不要每循环都刷。我一般只在车辆计数变化时调用LCD_ShowFreeSlots,显示模块内部再做一次缓冲区比较,内容没变就不写屏。
按键在智能停车场里一般用来模拟“手动抬杆”和“强制清零”。按键消抖不要用Delay死等,而是记录按下时间,由定时器节拍驱动。一个简单的消抖实现:
#define KEY_DEBOUNCE_MS 20 void Key_Scan(void) { static uint32_t debounce_tick = 0; static uint8_t key_last = 1; if (GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_13) == 0) { if (key_last == 1 && Hal_GetTick() - debounce_tick > KEY_DEBOUNCE_MS) { Parking_ManualLiftGate(); debounce_tick = Hal_GetTick(); } } key_last = GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_13); }key_last记录上一次按键电平,只有从高到低且超过20ms才响应。这样就不会出现按一下键道闸开合好几次的情况。主循环里每2ms调用一次Key_Scan,不要在一个大循环里跑完所有事,否则按键响应会很迟钝。
4. Keil工程还原与串口验证:把代码烧进去,比反复看论文更有用
4.1 Keil5打开工程前,三处设置不能漏
如果资料包里的源码是标准库工程,用Keil5打开时经常碰到芯片包缺失、编译报错、下载失败。我建议先检查三处:
第一,打开Options for Target -> Device,确认芯片型号是STM32F103C8。很多资料从网上转手时,工程里选的是F103RCT6,直接编译能过,但下载算法不匹配。第二,在Debug选项卡里选择ST-Link Debugger,右侧Settings里把Port设为SW,不要选JTAG,整个核心板只引出SWDIO和SWCLK两根线。第三,在Utilities -> Flash Download里勾选Reset and Run,不然每次下载后还要手动按复位键,演示时容易被人看出接线不稳定。
如果提示找不到芯片包,要么安装Keil对应的Device Family Pack,要么直接把工程文件里的旧芯片型号改成F103C8。用VSCode搭EIDE环境也能编译这套代码,但毕设场景下不建议折腾,官方Keil加ST-Link是最少出错的组合。
4.2 上电前检查BOOT引脚和供电
STM32F103C8T6核心板的BOOT0和BOOT1必须都接低电平,从主Flash启动。如果BOOT0被拉高,下载程序后一复位就进入系统引导区,现象是程序“烧进去了但跑不起来”。我自己的习惯是,动手接线前先用万用表量一下SWD的四根线,ST-Link和板子之间的3.3V、GND不能接反,SWDIO和SWCLK接反会直接导致No target connected。
上电后第一个检查点是电源指示灯和3.3V输出。舵机如果直接用板载5V供电,开闸瞬间电流冲击可能把单片机拉复位,一定要把舵机电源和MCU电源分开。最简单的办法是:STM32核心板用USB供电,舵机VCC接外部5V,但GND必须和主板共地,否则PWM参考电平不一致,舵机会乱摆。
4.3 串口打印状态帧,把黑匣子变成白盒
这套工程里通常有串口初始化代码,我建议在main.c里确认USART1配置为115200 8-N-1,PA9是TX、PA10是RX。要用printf直接打印,需要重定向fputc:
int fputc(int ch, FILE *file) { while (USART_GetFlagStatus(USART1, USART_FLAG_TXE) == RESET); USART_SendData(USART1, (uint8_t)ch); return ch; }编译时在Keil的Options里勾选Use MicroLIB,否则会引入半主机模式,程序可能卡死在printf。调试时打印的帧格式我习惯定义成P:05 F:15 Gate:OPEN,P是当前车数,F是剩余车位,Gate是道闸状态。拿USB-TTL模块接PA9、GND,串口助手就能实时看到状态。
用串口的好处是能快速确认计数逻辑是否正常。用手遮挡入口红外对射,串口输出从P:01变到P:02;松手后20ms内如果又变回来,说明防抖逻辑没生效。这一步能隔离硬件和软件问题:传感器接线坏了不会触发中断,串口根本没输出;软件逻辑错了,输出次数和遮挡次数不一致。
5. 常见问题与排坑:四条血泪经验,照着查比对着报错快
5.1 下载失败:提示 No target connected
现象:Keil点击下载后立刻弹窗,进度条完全不动,错误信息是No target connected。
原因排名前三:Debugger设置成了J-LINK但实际用的是ST-Link;SWDIO和SWCLK两根线接反;板子没单独供电,ST-Link的3.3V输出能力不够,核心板没跑起来。
解决:先确认ST-Link灯亮,然后在Keil的Debug设置里重新选ST-Link Debugger,Settings里手动扫描一次IDCODE,出现一串ID说明物理连接正常。如果没有ID,把SWDIO和SWCLK对调再试。最后检查BOOT0是不是给外设模块拉高了,拔掉所有扩展模块,只留核心板和ST-Link,往往就能解决。
5.2 LCD1602上电花屏或自动复位
现象:LCD显示一排方块,或者文字显示到一半突然清屏重来,好像在反复复位。
原因:LCD1602的背光电流不小,如果3.3V转5V模块供电能力不足,屏工作时会把电压拉低,单片机于是掉电复位。另外,Vo对比度电位器没调到位,也会显示成方块。代码时序问题则通常是E引脚没有先拉高再拉低,4线模式下数据线高低电平变化过快被误采样。
解决:先把Vo接一个10K电位器,调节到能清晰看出字符轮廓。再把LCD1602供电改到5V源,GND和单片机共地,避免和舵机共用电源。代码上检查LCD_WriteByte的实现,标准做法是先设置RS和RW,再把数据放到D4~D7,最后给E一个下降沿。如果还花屏,降低GPIO翻转速度,在每次写数据后加1us延时。
5.3 进出计数乱跳,没人经过也会加一
现象:上电后剩余车位快速减少,或者手在红外传感器旁边晃一下就加了好几辆车。用示波器看传感器输出,能看到一堆毛刺。
原因:红外对射模块输出端悬空时电平不稳定;GPIO配置成了浮空输入,外部引线一长,感应噪声就触发中断。另一个常见原因是中断服务函数里直接做了计数,没有经过20ms防抖,一个遮挡过程触发多次下降沿。
解决:GPIO初始化和EXTI配置时,把GPIO_Mode_IPD或GPIO_Mode_IPU设成上拉输入,传感器输出再接一个10K上拉电阻到3.3V。代码层面严格执行第3章的状态机,中断只置标志,状态机里连续20ms读到低电平才认为有效。出入口两个红外传感器还要避免离得太近,车头同时遮住两个传感器时,两个方向都可能触发,实际工程里出入口要拉开距离。
5.4 舵机抖动、不动或者一抬杆就复位
现象:烧录后舵机嗡嗡响,像在使劲顶住某个角度;开闸瞬间整个板子复位;或者舵机根本没有反应。
原因:第一,舵机用5V电源,但GND和单片机地没共地,PWM信号没有参考地,舵机接收不到有效脉宽。第二,SG90扭矩不够,道闸模型机械结构卡住时,堵转电流能到1A以上,把5V直接拉垮。第三,定时器PWM频率不对,不是50Hz的话舵机控制板无法解析脉宽。
解决:舵机供电单独接5V,并确保GND和STM32共地。机械结构上,舵机摇臂和道闸连杆之间的角度余量留5到10度,不要硬顶到极限。程序里把PWM周期固定在20ms,比较值限制在50到250之间。如果舵机接上后板子复位,用一个5V/2A适配器给舵机单独供电,不要动车上的USB口。
6. 让毕设从“能跑”变成“能答辩”:交叉验证与三个加分技巧
拿到这套资料后,我建议你按下面的验证清单走一遍,每项都要在串口助手和LCD上看到对应结果:
| 验证项 | 预期结果 | 异常排查方向 |
|---|---|---|
| 上电 | LCD显示剩余20个车位,串口输出P:00 F:20 | 先查供电和BOOT,再查LCD初始化 |
| 遮挡入口传感器一次 | 剩余车位变19,舵机抬杆后落杆 | 状态机是否进入GATE_OPEN,舵机角度是否合理 |
| 遮挡出口传感器一次 | 剩余车位变20,再次开闸 | 出口计数是否误把入口也算进去了 |
| 连续快速遮挡入口两次 | 剩余车位从20变19再变18 | 看串口打印次数,排查传感器抖动 |
| 把当前车数加到总车位数 | 剩余车位为0后,入口不再加数,蜂鸣器提示 | 检查current_cars < TOTAL_PARKING_SLOTS限制 |
如果所有项都通过,这套代码已经达到校级毕设的正常水平。想再加分,我给三个方向:
第一,把TOTAL_PARKING_SLOTS做成EEPROM可配置参数,掉电不丢失,答辩时现场改成25个车位,演示“配置后系统自动生效”,比背代码效果好很多。第二,加一个RTC模块,每次进出车时记录时间戳,存到Flash或EEPROM,串口解析时能输出“2025-05-20 14:33:08 入口进车”,这套数据流直接能写进论文的实验章节。第三,如果硬件资料里引出了ESP8266,可以加一段AT指令代码,把剩余车位以MQTT推送到巴法云这类物联网平台,手机端能看到车位情况,但前提是先把本地跑稳,WiFi保持常连接是一整天都要盯着的事。
我自己的习惯是,每拆一份STM32毕设源码,先把第二章的引脚映射表单独抄一遍,再对照原理图走一遍,最后才打开Keil工程。这套资料里最容易翻车的就是传感器电源、舵机电源和LCD供电三块之间的地线关系,提前把这些单独处理,后面基本就是编译、烧录、看串口日志的机械流程。希望帮到你。
本文还有配套的精品资源,点击获取