资讯详情

AT89C52停车场车位管理系统设计:红外检测与PWM舵机控制

📅 2026/9/16 15:14:36 | 华诺云谱 👁 阅读
AT89C52停车场车位管理系统设计:红外检测与PWM舵机控制
简介面向单片机课程设计与毕业设计的AT89C52停车场车位管理系统方案包模拟16车位管理场景通过按键模拟车辆进出、LED状态指示与LCD实时显示满位时触发报警可帮助学习者快速掌握外围器件驱动与中断/按键逻辑设计。压缩包共64个文件约77MB涵盖Keil源码C/H文件、Proteus仿真工程pdsprj、原理图与参考报告SchDoc/docx、元件清单xls及实物焊接与运行视频mp4/jpg等从代码调试到实物焊接均有配套说明。目前已吸引113人学习适合单片机入门及综合项目实训。资源特意整理出常见问题、使用前必读与PPT模板减少环境搭建与仿真运行中的踩坑成本参考报告约10500字结合143行带注释主程序可支撑设计文档撰写与答辩准备。1. 基于AT89C52单片机停车场车位管理系统的设计与实现停车场车位管理系统是51单片机课程设计和毕业设计里最常见的综合题它同时用到IO口输入、定时器中断、动态扫描显示和状态机逻辑做完一个题目就能摸清AT89C52大部分外设。有人觉得它只是“数个数、显示数字”真正动手才发现红外传感器去抖、剩余车位同步、舵机模拟道闸的PWM控制都有不少隐性坑。这篇文章按“资源分配 → 硬件接线 → C语言状态机实现 → 调试与仿真”的顺序把一个5车位模型系统完整铺开。适合刚完成单片机基础实验、想拿完整项目练手的学生也适合需要评估“用AT89C52做这套系统边界在哪”的工程师。2. 停车场车位管理系统的总体设计与AT89C52资源分配2.1 一个最小可用系统需要哪几类功能模块一套实用的停车场车位管理系统至少要完成四件事检测每个车位是否被占用、实时统计剩余车位数、在入口显示“Free: n/20”这样的信息以及当车位占满时控制入口道闸阻止车辆继续进入。为了人工干预还应该有一组按键做复位或模式切换。对AT89C52来说8K字节的Flash足够装下完整C程序256字节RAM也能放得下5个车位状态数组和显示缓冲区不需要外扩任何存储。常见方案是这样8个红外传感器接到P1口AT89C52按固定周期读取P1口的电平把每个车位占用状态存入数组主循环用差分比较更新剩余车位然后刷新LCD1602当剩余车位数降到0时输出信号控制舵机把道闸放下。这里我特意选舵机而不是直流电机是因为SG90舵机只用一根信号线在定时器中断里模拟50Hz PWM就能驱动比继电器控制电机简单在Proteus里也更容易仿真。2.2 AT89C52引脚功能与端口分配AT89C52是MCS-51系列标准单片机40引脚DIP封装4个8位IO口P0到P33个16位定时器/计数器一个全双工UART。P0口是漏极开路结构做输出时必须外部接上拉电阻P1、P2、P3口内部有上拉直接驱动LED或读取按键更方便。P3口的第二功能里P3.2和P3.3分别是外部中断0和1的输入引脚如果把入口地感线圈接到P3.2车辆压过瞬间会触发一次下降沿中断适合做快速响应。针对5车位模型我常用的端口分配如下表外设连接端口说明车位传感器1~5P1.0~P1.4低电平表示占用高电平表示空闲入口红外对管P3.2检测是否有车辆在入口等待LCD1602数据线D0~D7P0.0~P0.7必须外接10k上拉排阻LCD1602 RS、RW、ENP2.5、P2.6、P2.7控制线舵机信号线P2.450Hz周期PWM输出复位按键P2.3低电平有效长按清空计数按这个分配读5个车位状态只需要一条语句states P1 0x1F低5位就是5个车位的状态。P2口同时管LCD控制线和舵机逻辑上很清晰。需要注意P0口驱动LCD之前必须加上拉电阻否则LCD1602显示乱码是大概率事件。// 端口定义在Keil C51下编译 #include reg52.h #define SENSOR_PORT P1 // 车位传感器低5位对应1~5号车位 #define LCD_PORT P0 // LCD1602数据口 #define SENSOR_IN P3_2 // 入口红外对管输入 #define SERVO_PIN P2_4 // 舵机信号输出 #define RESET_KEY P2_3 // 人工复位按键 void gpio_init(void) { SENSOR_PORT 0xFF; // 读引脚前先把锁存器置1否则外部低电平会被拉高 SERVO_PIN 0; // 舵机信号初始为低避免上电瞬间乱转 }代码说明51单片机读外部引脚前必须向对应端口写1这是初学者最容易漏的一步。P1口本身有内部上拉但锁存器里如果存的是0读取时就会把外部高电平拉低。gpio_init里这两行是硬件上电后最先要执行的语句。2.3 传感器选型为什么优先用红外对管车位占用检测常见方案有地感线圈、红外对管、超声波和摄像头。地感线圈需要LC振荡电路对布线寄生参数敏感不适合单片机课程模型摄像头需要额外处理器AT89C52根本跑不起来超声波模块成本高多车位之间还容易串扰。红外对管是最经济的方案发射管和接收管装在车位对角线位置车辆停入时遮挡红外线接收管输出电平变化经比较器整形后直接进P1口。这里有个常见误用有人把红外接收管串联电阻直接接单片机觉得高低电平能变化就行。实际上红外管受环境光影响很大输出边沿会有抖动甚至在阈值附近来回跳变。我一般会在接收管输出端加一只10k电位器用LM393电压比较器做滞回整形输出开漏引脚再接上拉电阻到5V这样得到的是干净的TTL方波。参考电压调到2V左右可以避开大部分环境光干扰。纯Proteus仿真可以用拨码开关或按键代替传感器但做真机时比较器这级不能省。3. AT89C52最小系统与车位传感器、LCD硬件电路实现3.1 最小系统关键参数AT89C52要跑起来需要晶振电路、复位电路和电源去耦。晶振我习惯用12MHz因为12MHz下一条普通指令正好是1us软件延时和定时器初值都容易心算同时串口波特率设置成4800或9600时重装值刚好是整数。晶振两个引脚各接22pF电容到地配合12MHz基音晶振起振可靠。复位电路用10uF电解电容正极接VCC、负极接RST10k电阻把RST下拉到地上电后RST保持高电平时间约100ms满足51单片机复位要求。电源去耦容易被忽略。AT89C52本身功耗不大但舵机启动瞬间电流能到500mA以上如果VCC和GND之间没有足够的去耦电容会造成瞬间电压跌落表现是LCD闪烁、单片机复位重启。我习惯在电源入口放一个10uF钽电容并联一个0.1uF陶瓷电容分别吸收低频和高频噪声。舵机必须单独供电只在逻辑上共地这一点在第5章还会再强调。3.2 红外传感器输入电路与LM393整形每个车位传感器由一对红外发射管和接收管组成。发射管串一个220欧姆限流电阻接5V接收管按反偏方式接在VCC和地之间。无车时接收管截止节点电压被上拉电阻拉到高电平有车遮挡红外光线时接收管导通节点电压被拉低。问题是这个节点信号边沿很缓直接接单片机可能造成连续触发所以要加LM393比较器整形。LM393是一颗开漏输出的双路比较器单电源就能工作。参考电压由10k电位器从VCC分压得到输入接比较器同相端传感器信号接反相端。输出端必须外接4.7k上拉电阻到VCC否则开漏输出无法输出高电平。比较器输入阻抗很高功耗很小5个车位用两片LM393四路比较器就够。每个车位的接线都按照下表连接引脚功能连接对象说明LM393 VCC/GND5V/地用10uF0.1uF就近去耦同相输入端10k电位器抽头参考电压约2V反相输入端红外接收管输出节点无车为高有车为低输出端4.7k上拉到VCC后接P1.x开漏输出需要上拉3.3 LCD1602与P0口上拉电阻的接线要点显示剩余车位用LCD1602比数码管信息量更大能直接显示“Free: 12/20”这样的文本。8条数据线接P0口RS接P2.5RW接P2.6EN接P2.7。RW是读写选择脚只写不读可以接地但那样会失去读忙标志的能力所以我仍然让RW接单片机写到LCD前先查忙。查忙本身就是一个很好的51引脚读操作练习先给P0写0xFF再让RS0、RW1拉高EN后读DB7。#include reg52.h #define LCD_DATA P0 sbit LCD_RS P2^5; sbit LCD_RW P2^6; sbit LCD_EN P2^7; void lcd_delay(unsigned int t) { while (--t); } void lcd_busy_wait(void) { unsigned char temp 0x80; LCD_DATA 0xFF; // 读之前先把总线置1 LCD_RS 0; LCD_RW 1; while (temp 0x80) { LCD_EN 1; temp LCD_DATA; // 读DB7忙标志 LCD_EN 0; } } void lcd_write_cmd(unsigned char cmd) { lcd_busy_wait(); LCD_RS 0; LCD_RW 0; LCD_DATA cmd; LCD_EN 1; lcd_delay(10); LCD_EN 0; } void lcd_write_data(unsigned char dat) { lcd_busy_wait(); LCD_RS 1; LCD_RW 0; LCD_DATA dat; LCD_EN 1; lcd_delay(10); LCD_EN 0; }代码说明lcd_busy_wait是典型的“写1后读引脚”操作51单片机P0口没有内部上拉所以在读之前要先把0xFF写入锁存器。LCD_EN产生一个高脉冲在脉冲期间读取P0数据。如果是8位总线模式一次就能读到DB7初始化时要先调用lcd_write_cmd(0x38)把模块设为8位模式、2行显示、16字宽。4. 车位计数状态机与道闸PWM控制的C语言实现4.1 主程序框架防抖、计数、显示三步循环主程序不复杂初始化之后进入死循环循环里做四件事用防抖状态机更新所有传感器稳定值、根据车位稳定值计算剩余车位、刷新LCD、根据剩余车位数控制舵机。这里的关键是不要直接在读到电平变化的瞬间就加减计数必须先用状态机做软件防抖否则车位传感器边沿抖动会导致剩余车位乱跳。剩余车位的初值在系统启动时由全部5个车位传感器重新扫描一次得到而不是简单写死为5。这样如果某些车位预先被占用上电后统计也是准确的。主循环每次读到新的稳定值后与上一轮状态比较状态从空闲变成占用时free_cnt--从占用变成空闲时free_cnt同时都要做上下限保护。unsigned char cur[5] {0}; // 当前车位稳定状态 unsigned char old[5] {0}; // 上一轮车位状态 unsigned char free_cnt 5; // 剩余车位数 #define TOTAL_PLACES 5 void system_loop(void) { unsigned char i; unsigned char s SENSOR_PORT 0x1F; // 只取低5位 for (i 0; i TOTAL_PLACES; i) { cur[i] (s i) 0x01; // 1空闲 0占用 if (cur[i] ! old[i]) { if (cur[i] 0) { if (free_cnt 0) free_cnt--; } else { if (free_cnt TOTAL_PLACES) free_cnt; } old[i] cur[i]; } } display_free_count(free_cnt); if (free_cnt 0) set_servo_angle(0); // 占满道闸放下 else set_servo_angle(1); // 有车位道闸抬起 }参数说明这里约定传感器低电平表示车位被占用对应cur[i]0。差分比较只处理状态变化的那一位不会重复计数。display_free_count把free_cnt格式化成“Free: n”字符串刷新到LCD第二行。舵机角度通过set_servo_angle控制0和1分别对应放行和拦截两个位置具体PWM宽度在第4.2节实现。4.2 用定时器1模拟PWM驱动舵机AT89C52没有硬件PWM模块SG90舵机的控制信号需要自己用定时器模拟。舵机控制信号是50Hz周期即20ms一个周期高电平宽度在0.5ms到2.0ms之间对应0到180度。我们只需要两个角度所以用一个0.1ms中断就能覆盖精度要求。12MHz晶振下机器周期为1us定时器1工作在方式1产生0.1ms中断的初值是65536-1000xFF9C。在中断里维护一个200次的计数器每0.1ms加一次到200就清0正好是20ms周期。中断里把计数器值和高电平宽度计数值比较小于设定值就输出1否则输出0。高电平宽度由pwm_high决定例如2.0ms对应20次0.1ms。unsigned char pwm_cnt 0; unsigned char pwm_high 20; // 默认2.0ms void timer1_init(void) { TMOD 0x0F; // T1用方式116位定时器 TMOD | 0x10; TH1 0xFF; // 初值0xFF9C溢出周期0.1ms TL1 0x9C; ET1 1; EA 1; TR1 1; } void timer1_isr(void) interrupt 3 { TH1 0xFF; // 重新载入初始值 TL1 0x9C; if (pwm_cnt pwm_high) SERVO_PIN 1; else SERVO_PIN 0; pwm_cnt; if (pwm_cnt 200) pwm_cnt 0; // 200*0.1ms20ms } void set_servo_angle(unsigned char angle) { if (angle 0) pwm_high 5; // 0.5ms道闸放下 else pwm_high 20; // 2.0ms道闸抬起 }参数说明pwm_high是中断周期计数不是毫秒数。0.1ms一次中断5对应0.5ms20对应2.0ms。实际舵机在5V供电下不同品牌SG90的响应角度略有差异如果发现道闸没有完全抬起或放下微调这两个数值即可不用改中断频率。注意不要把pwm_high设到小于2或大于25否则舵机会堵转并持续大电流。4.3 通用输入防抖状态机红外传感器信号经过LM393整形后仍然可能存在机械性抖动特别是车辆慢速压过时。单纯延时消抖在5路传感器同时工作时不现实所以我写了一个通用防抖状态机给每个输入分配一个结构体在主循环里周期调用。状态机的思路是原始电平连续10次采样都确认变化才认为状态翻转。struct debounce { unsigned char state; // 4个状态 unsigned char cnt; // 连续采样计数 unsigned char stable; // 稳定输出值 }; void debounce_update(struct debounce *p, unsigned char raw) { switch (p-state) { case 0: // 稳定低电平 if (raw) { p-cnt 0; p-state 1; } break; case 1: // 检测到可能的上升沿 if (raw) { if (p-cnt 10) { p-stable 1; p-state 2; } } else { p-state 0; } break; case 2: // 稳定高电平 if (!raw) { p-cnt 0; p-state 3; } break; case 3: // 检测到可能的下降沿 if (!raw) { if (p-cnt 10) { p-stable 0; p-state 0; } } else { p-state 2; } break; } }状态说明表状态段原始输入连续确认稳定输出0→00-00→11未满10次01→21已满10次12→21-12→30未满10次13→00已满10次0这个状态机每个输入只占3字节RAM5个车位加入口、出口共7路消耗21字节AT89C52的256字节RAM完全够用。主循环里把原始传感器值交给它之后读取stable作为稳定状态4.1节的差分比较就可以直接使用这个稳定值不用再单独写延时。4.4 多路车位状态与道闸控制的时序协调主循环里的车位状态更新和PWM中断是并行在跑的需要注意一个时序问题free_cnt是全局变量主循环会修改它舵机中断不会改它所以在主循环里修改free_cnt时不需要关中断。但如果以后扩展成“入口传感器触发计数”就必须考虑中断和主循环同时访问free_cnt导致的数据不一致。常见做法是在修改前EA0修改后EA1或者用一个临时变量累加最后统一写回。车位状态更新和LCD刷新之间也有时序约束。LCD每次写入最多需要40us但lcd_busy_wait在模块忙时可能持续几百微秒这期间级别低的中断不受影响但PWM中断会持续触发属于正常现象。主循环对实时性的要求并不高几十毫秒哪怕几百毫秒的LCD刷新延迟都不影响系统功能。5. Proteus仿真、真机调试与剩余车位同步技巧5.1 在Proteus里搭一个可验证的最小系统先用Proteus放置AT89C52双击芯片设置Clock Frequency为12MHz。晶振和复位电路在仿真里通常可以省略但为了保持硬件一致性建议画上去。车位传感器用按键模拟按键按下表示有车占用释放表示空闲。LCD1602连接到P0和P2口注意P0要放一个RESPACK-8上拉排阻。舵机在Proteus里可以用一个标准舵机模型但需要单独给它设置电源并且确认信号引脚接在P2.4。把Keil编译生成的hex文件加载到AT89C52里运行后观察LCD数字随按键变化同时观察舵机旋转角度。仿真中如果不放舵机用一颗LED代替道闸状态也行free_cnt为0时LED点亮表示道闸放下。Proteus的舵机对PWM周期要求较高如果发现舵机抖动检查定时器初值是否按照0.1ms计算以及中断里有没有正确重装TH1和TL1。5.2 真机调试最常见的3个坑第一个坑是P0口上拉电阻没接LCD1602显示乱码。这是最经典的硬件错误因为AT89C52的P0口无法主动输出高电平必须依靠外部上拉。可以先在P0口对地量电压空闲时应为高电平如果为低说明上拉电阻开路或接法有误。第二个坑是舵机供电和单片机共用一个5V电源。舵机堵转时电流会拉低整条电源线导致AT89C52复位。正确做法是舵机电源直接接5V适配器或额外一路电源只把GND和单片机的GND相连信号线上串一个100~470欧姆电阻做保护。第三个坑是传感器抖动导致剩余车位乱跳。LM393整形后边沿已经很干净但长线传输会引入感应噪声软件防抖不能省。可以把防抖状态机的连续确认次数从10改成50即约50次主循环采样约几十毫秒避免车辆抖动产生误动作。5.3 用同步校正函数修复累计误差即使有防抖长时间运行后free_cnt仍可能因为上电位、异常脉冲等原因产生累计误差。我习惯在主循环里加一个周期性的同步校正当所有输入状态机都处于“稳定”区间时直接用传感器重新扫描一次把剩余车位重新计算为TOTAL_PLACES - 占用数。这样即使之前漏算或多算了一两次也会在几百毫秒内自动修复。void resync_free_cnt(void) { unsigned char i, occupied 0; unsigned char s SENSOR_PORT 0x1F; for (i 0; i TOTAL_PLACES; i) { if (((s i) 0x01) 0) occupied; } free_cnt TOTAL_PLACES - occupied; }调用时机是主循环空闲段并且入口红外传感器状态为“无车”。如果车辆正处于入口传感器上暂不执行同步校正避免把刚刚腾出的车位误算成占用。这个函数放到主循环while(1)末尾加一个软计数器每隔500次循环调用一次就能让剩余车位永远跟随真实传感器状态。本文还有配套的精品资源点击获取
📝

华诺云谱内容团队

资深建站顾问 · 行业研究员

10年+企业数字化服务经验,专注智能建站、SEO优化与品牌营销,持续输出建站技巧、行业洞察与营销干货,已帮助5000+企业实现数字化增长。

你可能需要的服务

订阅华诺云谱资讯周报

每周一封,精选建站技巧、SEO与营销干货,直达邮箱。已有 8,000+ 企业主订阅,助你少走弯路。