简介这是一套基于51单片机与Proteus仿真完成的楼道人数计数器设计资料适用于电子/嵌入式相关专业学生进行课程设计或项目实践。项目以AT89C51为主控利用外部中断实现人数加/减计数通过数码管显示0-999并提供了完整的仿真、代码与论文。压缩包共11个文件、约153KB其中.dsn与.DBK为Proteus仿真电路.c与.uv2为Keil源码工程.hex为烧录文件.docx为设计论文另有图片和说明文档可对照学习电路搭建与编程逻辑。目前已有94人学习下载适合需要快速理解51单片机计数、中断、显示等知识并完成相似任务的用户。通过这套资料可直观查看仿真图的连线方式阅读源代码掌握中断处理思路参考论文梳理设计流程能为课程答辩或项目开发提供有力支持。1. 楼道人数计数器避开器件坑用51单片机把0~999的数数清楚楼道人数计数器是课程设计和入门项目里出镜率最高的一类题目两个信号进来一个加一一个减一显示在数码管上。听起来简单但真要做到从仿真到实物一次跑通要处理外部中断的边沿触发、机械按键抖动、数码管动态扫描消隐以及人数小于0和超过999这两种边界情况。这套基于AT89C51的Proteus仿真工程自带可编译的C源码、完整的电路仿真图还有配套设计论文适合正在做单片机课设的学生也适合想快速验证中断驱动计数逻辑的工程师。下面按方案、代码、仿真、排障四个层次拆开讲。2. 方案定型为什么是AT89C51、外部中断与传感器信号模型2.1 主控选型AT89C51在计数场景的不可替代性选择AT89C51而不是STM32或者Arduino首先是课程设计的要求但站在工程角度这个选择本身也合理。楼道人数计数器本质上是一个“事件计数”系统外部产生进入/离开信号单片机对信号边沿做出响应更新一个显示值。AT89C51的指令周期虽然比现代MCU慢很多但两个外部中断INT0和INT1恰好对应“进”和“出”两路信号中断响应时间在微秒级而人体经过传感器的间隔至少是几十毫秒响应速度完全够用。更重要的是Proteus对51系列的仿真支持非常成熟。AT89C51的仿真模型包含了完整的内部Flash、定时器、串口和中断系统不需要像STM32那样依赖外部固件库。直接把Keil编译出来的HEX文件加载进去点击运行就能看到引脚电平变化。相比之下Proteus仿真STM32时外设模型并不完整尤其是外部中断的时序行为经常和实际芯片有偏差。对于“仿真图源代码论文”这种课程设计交付形式AT89C51是整个开发链路里风险最低的一环。2.2 进入和离开的检测逻辑双传感器A/B判定方向真实楼道场景里最常见的方案是两对红外对射传感器前后排列。传感器A在前传感器B在后间距一般为10到30厘米。当人从外往里走时先遮挡A再遮挡B两个信号先后产生下降沿离开时顺序相反。这个设计需要两路独立的外部中断输入但不需要单片机去判断相位差只需要把A给INT0、B给INT1在INT0中断里“人数加1”在INT1中断里“人数减1”。在Proteus仿真中没有真实红外对管模型常见做法是用两个按键或者两个脉冲源来模拟传感器输出。按键按下一次相当于一个人经过传感器一次。需要说明的是按键存在机械抖动按下一次会在引脚上产生多个下降沿如果不处理计数器会一次性跳好几下。这个问题在仿真中同样存在所以软件里的去抖逻辑不能省。另一种更贴近实测的模拟方式是用Proteus的信号发生器产生一个单脉冲用脉冲的下降沿触发中断这样可以完全避免抖动干扰也更接近红外对管经过整形电路后的输出波形。2.3 硬件引脚分配与数码管驱动方式本设计的核心外设是三位数码管用来显示0到999。AT89C51的P0口驱动能力较弱且是开漏输出直接接共阴数码管的段选脚时高电平输出可能不足以点亮段码所以一般会加10kΩ排阻上拉到VCCP2口作为位选控制负责轮流选通个位、十位、百位三只数码管。下面这张表格整理出AT89C51的引脚分配和连接目标仿真图里也是按照这个关系连线的单片机引脚连接目标功能说明P3.2 / INT0进入传感器输出或按键下降沿触发人数加1P3.3 / INT1离开传感器输出或按键下降沿触发人数减1P0.0 ~ P0.7数码管段码 a ~ g, dp输出字形码需上拉排阻P2.0个位位选三极管基极低电平选通个位P2.1十位位选三极管基极低电平选通十位P2.2百位位选三极管基极低电平选通百位XTAL1 / XTAL212MHz晶振 2×33pF电容提供时钟RST10μF电容 10kΩ电阻上电复位如果使用共阳数码管段码驱动方式要反过来P0输出低电平点亮段码位选用高电平选通。仿真工程里必须保持器件类型和代码中的段码表一致否则显示会完全错乱。2.4 计数边界0~999的溢出与下溢处理思路技术人员拿到需求后第一眼要关注的就是“计数范围0到999”。这句话意味着两件事第一计数值最大到999再加1不能变1000应该停留在999第二人数最小到0再减1不能变成65535或者负数应该停留在0。如果用16位的unsigned int作为计数器自增到1000并不会溢出但显示模块只设计了三位数码管1000会让数码管显示干位数据位选逻辑也要跟着改成四位这超出了原设计范围。更隐蔽的是下溢问题。一旦计数变量定义成无符号类型0减1会得到65535显示出来是“655”或乱码。所以必须在中断服务函数里做边界判断count 999才允许加1count 0才允许减1。这个判断看起来简单但它决定了整个系统在最极端的人流情况下是否能保持数据可信。3. C51源码实现外部中断服务函数、动态扫描与防抖3.1 Keil C51工程文件组成与初始化源码包里的counter.c是唯一的C源文件工程基于Keil C51创建输出目标芯片设置为Atmel AT89C51。工程文件结构很标准counter.Uv2是Keil工程文件counter.hex是编译产物仿真时由Proteus直接加载。程序初始化分三部分设置外部中断触发方式、开启总中断和外部中断、清空计数值。选择下降沿触发方式需要把TCON寄存器的IT0和IT1置1。这样当P3.2或P3.3从高电平跳到低电平时硬件自动把中断标志位IE0或IE1置1CPU响应后跳转到对应的中断服务函数。下面是初始化代码#include reg51.h unsigned int count 0; // 当前楼道人数范围 0 ~ 999 unsigned char timer_tick 0; // 动态扫描专用变量 void delay_ms(unsigned int ms) { unsigned int i, j; for (i 0; i ms; i) for (j 0; j 123; j); // 12MHz 下延时约 1ms } void system_init(void) { IT0 1; // INT0 下降沿触发 IT1 1; // INT1 下降沿触发 EX0 1; // 使能 INT0 外部中断 EX1 1; // 使能 INT1 外部中断 EA 1; // 开启总中断 count 0; // 初始人数为 0 }这段代码里IT0和IT1是TCON寄存器的位定义Keil的reg51.h头文件里已经声明好了直接操作位名称即可。EX0和EX1是IE寄存器中的外部中断使能位EA是总开关。如果EA没有打开后面两个中断永远进不去这是初学者最容易漏掉的一处。3.2 外部中断服务函数加1与减1的边界保护外部中断服务函数的写法是关键。按照需求INT0对应人进入人数加1INT1对应人离开人数减1。为了防止0~999边界被突破函数内部必须加两个范围判断void ext0_isr(void) interrupt 0 { if (count 999) // 到达上限后不再叠加 count; } void ext1_isr(void) interrupt 1 { if (count 0) // 到达下限后不再递减 count--; }注意中断号interrupt 0对应外部中断0interrupt 2对应外部中断1interrupt 1是定时器0不要搞混。如果写错中断号函数会被挂到错误的中断向量上仿真时表现就是死活不进入中断。去抖逻辑在这个项目里要分两种场景讨论。如果仿真中用按键模拟传感器信号必须在中断服务函数内做10到20毫秒的延时等电平稳定后再判断一次如果使用信号发生器产生标准脉冲抖动问题不存在这段延时反而会拖慢中断响应。我一般会把去抖放在外面在中断里只置一个标志位在主循环中检测到标志后再做延时和二次确认这样既能防抖又不阻塞中断。3.3 三位数码管动态扫描与消隐三位数码管动态扫描的基本思路是任何时候只点亮一位循环切换个位、十位、百位利用人眼视觉暂留效应让三个数字看起来同时点亮。每个位码通过P2.0到P2.2控制段码由P0输出。显示数字之前要把数字拆成三个十进制位再查表得到段码。unsigned char code seg_table[] { 0x3f, // 0 0x06, // 1 0x5b, // 2 0x4f, // 3 0x66, // 4 0x6d, // 5 0x7d, // 6 0x07, // 7 0x7f, // 8 0x6f // 9 }; void display_number(unsigned int num) { unsigned char d0, d1, d2; unsigned char h0, h1, h2; d0 num % 10; // 个位 d1 (num / 10) % 10; // 十位 d2 num / 100; // 百位 // 个位 P2 0xFE; // 1111 1110选通个位 P0 seg_table[d0]; delay_ms(2); // 十位 P2 0xFD; // 1111 1101选通十位 P0 seg_table[d1]; delay_ms(2); // 百位 P2 0xFB; // 1111 1011选通百位 P0 seg_table[d2]; delay_ms(2); }这段代码在仿真中可以直接跑但有一个细节必须修正切换位选时前一位的段码会残留在P0上造成一位数字“拖影”到另一位上。正确做法是先把P0全部清零也就是让所有段码熄灭再切换P2位选再输出新的段码。消隐语句就两行先P0 0x00;再改P2最后输出段码。这个现象在仿真中看得不明显但实物板子上非常明显很多课设答辩时被问倒的地方就在这里。3.4 主循环调度计数与显示职责分离主循环的结构直接影响系统的实时性能。外部中断和显示刷新是两个不同频率的事件人数变化是随机的而数码管扫描需要固定周期。如果把人数累加逻辑放在主循环里查询引脚电平一旦主循环被显示延时阻塞就可能漏掉脉冲反之把动态扫描放在中断里又会让中断服务函数占用太长时间。所以本设计采用“中断计数、主循环显示”的职责分离方式void main(void) { system_init(); while (1) { display_number(count); // 循环刷新三位数码管 } }主循环不做任何按键检测和人数加减只负责读取count的当前值并显示。count被中断服务函数修改时主循环可能正在读取它。由于51单片机是8位机unsigned int占两个字节读取高字节和低字节之间如果恰好发生中断更新可能读到不一致的临时值。严格来说应该先关闭中断再读取count读取完再恢复但这会增加代码复杂度。对楼道人数计数器这个场景显示瞬时的错误数值只持续一个扫描周期肉眼不可见工程上可以接受。4. Proteus仿真搭建元件放置、加载hex与中断信号调试4.1 Proteus元件清单与网络连接打开压缩包里的仿真工程时会看到两个文件counter.DSN和counter.pdsprj。DSN是Proteus 7时代的工程格式pdsprj是Proteus 8之后的主格式。如果电脑装的是Proteus 8 Professional直接打开pdsprj文件最省事如果遇到版本过低打不开用文本编辑器查看DSN头部的版本号再去装对应的Proteus版本。如果需要从零开始重新搭建仿真电路下面是完整的元件清单Proteus 关键字元件名数量用途AT89C51AT89C51 单片机1主控制器7SEG-MPX3-CC三位共阴数码管1显示 0~999BUTTON轻触按键2模拟进入/离开传感器RES10kΩ 电阻8P0口上拉排阻RESPACK-88脚排阻1简化P0上拉连接CRYSTAL12MHz 晶振1时钟源CAP33pF 电容2晶振负载电容CAP-ELEC10μF 电解电容1复位电路PNP或NPN三极管2N22223位选驱动数码管选择7SEG-MPX3-CC代表三位共阴数码管如果选择CA版本段码表和位选极性都要反过来。电路连接时P0口接排阻公共端再连到数码管段码输入P2.0到P2.2经过三极管接到数码管的公共端。中断引脚P3.2和P3.3各接一个按键按键另一端接地常态保持高电平按下时产生下降沿。4.2 编译生成counter.hex并导入仿真文件Keil C51端只需要确保Output选项卡里勾选了Create HEX File再重新编译一次就会覆盖生成counter.hex。如果代码有改动Hex文件不会自动更新必须在Keil里先Rebuild再去Proteus里重新加载否则仿真的还是旧程序。在Proteus中加载hex文件的路径是双击AT89C51元件打开Edit Component对话框在Program File一栏点击文件夹图标选中counter.hex然后把Clock Frequency改成12MHz点击OK生效。有一点很容易被忽略Program File填了路径之后必须确认对话框底部的External Model这个选项没有被意外勾选否则仿真器不会运行Keil编译出来的程序而是走外部模型通道。4.3 用探针和逻辑分析仪验证外部中断捕获Proteus默认只用绿灯红灯表示引脚电平排查时序问题时信息量不够。建议在P3.2和P3.3上各放一个电压探针再把数码管的公共端接到虚拟逻辑分析仪上。点击仿真运行按下进入按键可以看到P3.2电压先是从高变低数码管个位数值加1松开按键后引脚恢复高电平。如果在一次按键过程中数字跳了好几次说明按键抖动触发了多次中断需要回去检查去抖代码。4.4 仿真中容易踩的三个坑第一个坑是P0口没有接上拉电阻。P0口内部结构特殊作为普通IO输出高电平时驱动能力很弱仿真中虽然能看到波形变化但数码管段码亮度不均匀放到实物上直接点不亮。第二个坑是复位电路参数不对。Proteus仿真会自动初始化单片机但实物板如果没有RC复位电路上电时程序可能从不确定状态开始跑。第三个坑是中断引脚悬空。P3.2和P3.3在不接传感器时不要悬空用10kΩ电阻上拉到VCC否则电场干扰会让单片机频繁误触发中断计数器自动乱跳。5. 进阶技巧边界计数验证、消隐调优与串口上报扩展5.1 用一组测试序列快速验证计数逻辑拿到一份别人写的源码第一件事不是看注释而是构造测试序列验证边界。推荐按“0→正常加数→到999→正常减数→回到0”的顺序测试。具体操作是在仿真中连续按进入按键到999再继续按几次看是否仍停在999然后连续按离开按键到0再继续按看是否停在0。下面这个表格记录了预期的六步行为操作序列初始值预期结果连续按进入键10次0显示10连续按进入键990次10显示999继续按进入键5次999仍显示999连续按离开键5次999显示994连续按离开键994次994显示0继续按离开键3次0仍显示0如果最后一步显示变成了65535或者655说明下溢保护没生效。另外在Proteus中连续按990次按键非常累可以改用信号发生器设置频率为10Hz的脉冲串直接接到P3.2脚用示波器数脉冲数量对比计数结果能一次性验证上百次计数没有丢步。5.2 消隐与刷新率调整动态扫描刷新率太低会看到闪烁太高则占用CPU时间。三位数码管建议单个位停留2ms一轮扫描约6ms对应刷新频率约160Hz远高于人眼闪烁阈值。如果显示亮度不均常见原因是某一位的位选延时不相等比如中途插入了其它延时函数。解决办法是把延时收敛到一个公共函数并且在切换位选前统一执行消隐P0 0x00; // 消隐关闭所有段码 P2 0xFB; // 改写位选 P0 seg_table[d2]; // 输出百位段码 delay_ms(2);这里必须先改变P2再输出P0顺序反过来会有一个短暂时间把上一段的段码显示到新位选上。5.3 中断重入导致计数丢失的处理默认情况下51单片机外部中断0优先级高于外部中断1且同级中断不能互相打断。如果人流量极大进入信号到达时CPU正在处理离开中断新的中断会被挂起但不会丢真正会丢的是两个外部中断同时到达的情况。硬件层面可以给两个中断设置不同优先级软件层面建议在中断服务函数中只更新标志位把加减操作放到主循环里执行。bit entry_flag 0; bit exit_flag 0; void ext0_isr(void) interrupt 0 { entry_flag 1; // 仅记录事件 } void ext1_isr(void) interrupt 1 { exit_flag 1; }主循环中检查flag并合并处理实现上相当于把中断服务函数缩短到几个微秒大幅降低事件丢失概率。这是从课程设计代码过渡到真实工程代码的关键一步。5.4 从仿真到实物传感器信号整形与上电显示初始值仿真里的按键不会产生模拟噪声但红外对管输出的模拟信号必须经过比较器整形才能进入单片机引脚。常见做法是使用LM393比较器配合电位器调节阈值输出端加上拉电阻到VCC产生标准的TTL电平信号。上电瞬间如果楼道已经有人计数器从0开始统计会低于真实人数更稳妥的做法是在初始化时把count设为一个可配置的初值或者提供两个按键手动设定初始人数。这套工程里paper文档对硬件部分写得很详细做课设答辩时把这几条改进话术带上比单纯复述源码更能体现工程理解。本文还有配套的精品资源点击获取