ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

Proteus仿真C51单片机十字路口交通灯设计与状态机实现

Proteus仿真C51单片机十字路口交通灯设计与状态机实现 简介基于C51与Proteus的经典交通灯控制系统完整工程资源面向嵌入式初学者与单片机课程设计人群演示AT89C51控制红绿黄灯定时切换的实现思路。压缩包共24个文件约120KB包含Keil工程文件.uvproj/.uvopt、C51源码.c/.a51、编译输出.hex/.obj等及Proteus仿真工程.pdsprj/.pdsbak可直接打开查看电路与代码便于对照学习定时器初始化、中断服务与P0口位操作等知识点目录结构也便于按工程类别快速定位。资源已有360人学习适合用于课程设计、实验室练习或自学Proteus仿真流程。通过查看源码与仿真图可理解交通灯状态机设计方法并在此基础上扩展倒计时显示、按键控制等进阶功能。1. 一块开发板都没焊先把十字路口跑起来课设题目发下来只有一行字做一个十字路口交通灯。手上没有开发板没有烧录器连 LED 都没拆封。这种情况下最省事的路径不是下单等快递而是把电路先搬进 Proteus用 C51 把逻辑跑通再决定要不要落硬件。交通灯这个题看着简单真正卡人的地方在三处东西南北四个方向的相位互斥关系不能写错否则会出现南北绿和东西绿同时亮的致命 bug倒计时要和相位切换严格对齐差一秒就会看到红灯 0 秒后还亮着中断和主循环共享的变量没有处理好仿真里就会出现数字乱跳。这篇把 Proteus 仿真 51 单片机 的完整链路拆开讲端口怎么分、定时器初值怎么算、状态机怎么写、.hex 怎么送进仿真电路、出错先看哪几个地方。适合刚接触 C51、或者需要用仿真快速验证逻辑再上板的工程师。2. C51 交通灯的硬件抽象端口分配、定时器初值与 Proteus 元件选型2.1 端口分配表与 Proteus 元件清单动手画原理图之前先把信号和引脚对应关系定死。8051 的 P1、P2、P3 都是准双向口P0 是开漏口这个差别直接决定数码管段码要接在哪个口上。常见的分配方案是把方向灯放在 P1段码放 P0位选放 P2按键和串口留 P3。信号引脚Proteus 元件关键参数南北红/黄/绿P1.0 / P1.1 / P1.2LED-RED / LED-YELLOW / LED-GREEN限流 330Ω东西红/黄/绿P1.3 / P1.4 / P1.5LED-RED / LED-YELLOW / LED-GREEN限流 330Ω数码管段码 a~g,dpP0.0 ~ P0.77SEG-MPX2-CC共阴P0 接 10k 排阻上拉数码管位选P2.0 / P2.17SEG-MPX2-CC经三极管或直接驱动模式按键P3.2 (INT0)BUTTON10k 上拉晶振XTAL1 / XTAL2CRYSTAL 30pF×212MHz元件库找不到 AT89C51 的时候先确认安装的是完整版 Proteus 而不是精简包。Proteus 元件库 里 8051 家族的型号在Microprocessor ICs → 8051 Family下搜89C51或AT89C51都能命中。晶振和复位电路一个都不能省仿真器虽然不会因为缺复位电路就报错但上电初值不确定会让数码管第一帧显示乱码。2.2 定时器 T0 初值怎么算交通灯的核心是秒。8051 的定时器最长只能定到 65.536ms12MHz 晶振、方式 1所以标准做法是用 50ms 中断累计 20 次凑成 1 秒。初值公式初值 65536 - 需要计数的机器周期数 机器周期 12 / 晶振频率12MHz 晶振下机器周期为 1μs50ms 需要 50000 个机器周期于是初值 65536 - 50000 15536 0x3CB0。装入时 TH0 取高字节 0x3CTL0 取低字节 0xB0。#include reg52.h /* 方向灯低电平点亮 */ sbit NS_R P1^0; /* 南北红 */ sbit NS_Y P1^1; /* 南北黄 */ sbit NS_G P1^2; /* 南北绿 */ sbit EW_R P1^3; /* 东西红 */ sbit EW_Y P1^4; /* 东西黄 */ sbit EW_G P1^5; /* 东西绿 */ volatile unsigned char tick 0; /* 50ms 计数 */ volatile unsigned char sec 0; /* 当前相位剩余秒数 */ void Timer0_Init(void) { TMOD 0xF0; /* 清掉 T0 的配置位保留 T1 */ TMOD | 0x01; /* T0 方式116 位定时器不自动重装 */ TH0 0x3C; /* 50ms 12MHz必须每次中断重装 */ TL0 0xB0; ET0 1; /* 允许 T0 中断 */ EA 1; /* 开总中断 */ TR0 1; /* 启动 T0 */ } void Timer0_ISR(void) interrupt 1 { TH0 0x3C; /* 方式1不自动重装漏了这句周期会漂 */ TL0 0xB0; if (tick 20) { tick 0; if (sec 0) sec--; /* 每秒减一到 0 由主循环切换相位 */ } }这段代码里有两个容易被忽略的点。一是TMOD 0xF0而不是直接TMOD 0x01后者会把 T1 的配置一起清掉如果后面想加串口通信就会踩坑。二是方式 1 不自动重装中断服务函数里必须重新写 TH0/TL0忘了写周期会从 50ms 变成 65.536ms倒计时整体偏慢。想省掉重装可以改用方式 2 的 8 位自动重装但那样单次最长只有 256 个机器周期中断太频繁反而更费 CPU。2.3 灌电流点亮与限流电阻取值8051 的 P1 口输出高电平时拉电流能力很弱典型 60μA 级别输出低电平时灌电流可以到 10mA 以上。所以 LED 一定要接成单片机引脚接阴极、阳极经限流电阻接 VCC的形式也就是低电平点亮。限流电阻按目标电流算R (VCC - VF) / IVCC 取 5V红色 LED 正向压降约 1.8V目标电流取 8mAR (5 - 1.8) / 0.008 ≈ 400Ω工程上取标准值 330Ω 或 470Ω 都行。330Ω 时电流约 9.7mA离 P1 口单脚 10mA 的上限很近六个灯同时亮会超过整口的总电流限制稳妥起见用 470Ω电流约 6.8mA亮度在仿真里完全够看。数码管段码挂 P0 时必须接上拉排阻因为 P0 是开漏结构没有上拉根本输出不了高电平段会一直暗着这是新手在 Proteus 里最常见的一个灯不亮原因。3. 用相位表写状态机C51 交通灯主循环与中断服务3.1 相位表驱动的状态机交通灯的相位是有限状态机与其用一堆 if-else 判断现在是红灯还是绿灯不如把每个相位的灯状态和持续时长做成表。四个相位的定义相位编号南北方向东西方向持续时长0绿灯红灯30s1黄灯红灯3s2红灯绿灯30s3红灯黄灯3s把它翻译成两个数组灯状态用位映射表示低电平有效所以最后取反输出#define PHASE_NUM 4 /* 位映射bit0NS_R bit1NS_Y bit2NS_G bit3EW_R bit4EW_Y bit5EW_G */ code unsigned char PhaseLamp[PHASE_NUM] { 0x0C, /* 0: 南北绿(bit2) 东西红(bit3) */ 0x0A, /* 1: 南北黄(bit1) 东西红(bit3) */ 0x21, /* 2: 南北红(bit0) 东西绿(bit5) */ 0x11 /* 3: 南北红(bit0) 东西黄(bit4) */ }; code unsigned char PhaseTime[PHASE_NUM] {30, 3, 30, 3}; void main(void) { unsigned char i; Timer0_Init(); while (1) { for (i 0; i PHASE_NUM; i) { P1 ~PhaseLamp[i]; /* 取反后输出低电平点亮对应灯 */ sec PhaseTime[i]; /* 装载本相位时长由中断递减 */ while (sec 0) { Display(sec); /* 循环刷新数码管不阻塞中断 */ } } } }code关键字把数组放进程序存储器而不是 RAM8051 的 RAM 只有 128 字节四个字节的相位表加上段码表很容易把 RAM 撑爆养成习惯把常量都加code。主循环里用while (sec 0)等待而不是delay(30000)是因为延时函数会把 CPU 占死中断虽然还能进但数码管就没办法在等待期间刷新倒计时会定住不动。这个主循环轮询 中断递减的结构是 8051 上最常用的软定时模式。3.2 相位切换的边界处理状态机最容易出错的是切换瞬间。设想 sec 刚好减到 0主循环马上把 P1 换成下一个相位的灯状态如果这一瞬间旧相位的绿灯和新相位的绿灯有重叠位就会看到两个方向同时绿。用查表法就不会有这个问题因为 PhaseLamp 里每个元素的位是互斥的硬件上同一时刻只有一组方向能亮。真正需要额外处理的是黄灯过渡从相位 0 切到相位 1 时南北绿必须立刻灭、黄灯立刻亮中间不能有绿黄同亮的帧。如果用的是逐位操作先NS_G 1再NS_Y 0两条指令之间就存在这个窗口。整字节写 P1 可以避免因为 8051 的字节写是一条指令完成的。如果要在切换时加一个短暂的全红作为安全间隔只需在 PhaseLamp 中间插入一个0x09南北红 东西红的相位时长给 1~2 秒。这个改法不用动状态机结构加一行表项、把 PHASE_NUM 改成 6 就行这也是查表法比硬编码 if-else 更好维护的地方。3.3 数码管倒计时的动态扫描倒计时用两位共阴数码管段码表加动态扫描函数code unsigned char SegCode[10] { 0x3F, 0x06, 0x5B, 0x4F, 0x66, 0x6D, 0x7D, 0x07, 0x7F, 0x6F }; void DelayUs(unsigned int n) { while (n--); } void Display(unsigned char n) { if (n 99) n 99; /* 防止越界访问段码表 */ P2 0x00; /* 先消影避免拖尾 */ P0 SegCode[n / 10]; /* 十位段码 */ P2 0x01; /* 选通十位 */ DelayUs(80); P2 0x00; P0 SegCode[n % 10]; /* 个位段码 */ P2 0x02; /* 选通个位 */ DelayUs(80); }消影那句P2 0x00不能省。动态扫描的原理是两位轮流点亮靠人眼余晖合成两位同时显示。如果在切换位选时不先关掉上一位上一位的段码会短暂地串到下一位上看到的效果就是数字旁边有一道虚影。DelayUs(80)决定了刷新频率两位各 80μs一轮 160μs刷新率约 6kHz远高于人眼闪烁感知阈值。如果发现数码管有轻微抖动把延时加大到 200μs 通常能压住代价是主循环响应变慢但交通灯对响应速度不敏感可以接受。4. Keil 与 Proteus 联调把 .hex 送进仿真电路4.1 Keil C51 工程配置与 hex 输出C51 的编译工具链叫 Keil C51和 STM32 用的 MDK-ARM 是两套独立安装只是共用 uVision 这个 IDE 外壳。常见做法是把两者装到同一个 Keil 根目录下的C51和ARM子目录这样新建工程时器件库里能同时看到 8051 和 Cortex-M 的型号。如果只在 Keil5 里装了 MDK 而找不到 AT89C51说明缺 C51 组件需要单独补装 C51 工具链两者不冲突。工程配置里必须勾上生成 HEX 文件否则 Proteus 没有可加载的目标文件Project → Options for Target → Output [x] Create HEX File HEX 格式选 HEX-80 Target 页 Xtal (MHz) 填 12.0 ← 必须和 Proteus 里晶振频率一致 Memory Model 选 Small ← 变量默认放片内 RAMXtal 这一栏填错是最隐蔽的 bug。Keil 里填 12MHz、Proteus 里晶振画成 11.0592MHz 的话仿真跑出来的倒计时会比实际慢 8%肉眼看不出来但用示波器量 P1 口波形就会发现周期不对。编译成功后在Objects目录下会生成.hex文件名默认跟工程名一致。4.2 Proteus 里加载 hex 与仿真参数原理图画好后双击 AT89C51 元件在Program File一栏选择刚才生成的.hexClock Frequency填 12MHz。两个地方需要额外注意一是Program File选好后不要再改动原理图里的晶振频率改了要重新确认二是如果 C51 源码里用到了中断仿真时 CPU 频率必须和 Keil 的 Xtal 一致否则中断周期会错。Proteus 的仿真速度默认是实时跑一个 30 秒的相位要真等 30 秒。调试阶段可以在System → Set Animation Options里把Animation Timestep调小或者直接用暂停/单步按钮观察。想看引脚电平变化把虚拟示波器OSCILLOSCOPE的 A、B 通道分别接到 P1.0 和 P1.3能看到两路红灯的方波严格反相占空比由各自相位时长决定。这个波形是验证相位互斥最直接的手段比盯着 LED 看可靠得多。4.3 仿真跑不起来的排查清单仿真按了运行键却毫无反应时按这个顺序排查比乱改代码快现象常见原因处理所有灯都不亮P0 口没接上拉排阻加 10k 排阻到 VCC数码管常亮一段段码接了共阳管换成 7SEG-MPX2-CC只有一只灯亮限流电阻漏画或阻值过大检查每个 LED 支路倒计时不动hex 没重新编译加载重新 Build 并重新选 hex周期整体偏慢Keil 与 Proteus 晶振不一致统一为 12MHz数码管有拖影扫描未消影位选切换前先清零还有一类问题出在 Proteus 本身元件库 装的是精简版加载 89C51 时会提示找不到模型。这种情况换一份完整安装包或者用AT89C52替代指令集完全兼容只是 Flash 大了一倍。仿真占 CPU 高、界面卡顿的时候把Animation Timestep调大、关掉Show Wire Currents之类的渲染选项能明显缓解。5. 进阶夜间黄闪、紧急全红与仿真波形验证实际路口的交通灯不止四个相位。夜里车少的时候会切到黄闪模式两个方向都是黄灯 1Hz 闪烁遇到紧急情况需要全红锁死。用状态机加一个模式变量就能覆盖不用重写主循环#define MODE_NORMAL 0 #define MODE_FLASH 1 #define MODE_EMERG 2 unsigned char mode MODE_NORMAL; sbit KEY_MODE P3^2; void FlashMode(void) { P1 ~0x0A; /* 南北黄 东西红 */ DelayMs(500); P1 ~0x00; /* 全灭 */ DelayMs(500); } void EmergencyMode(void) { P1 ~0x09; /* 南北红 东西红其余全灭 */ } void ScanKey(void) { if (KEY_MODE 0) { DelayMs(10); /* 消抖 */ if (KEY_MODE 0) { mode (mode 1) % 3; while (KEY_MODE 0); /* 等松手避免连跳 */ } } }主循环里按 mode 分派到不同处理函数ScanKey放在倒计时刷新之后调用。黄闪用的 500ms 延时是阻塞式的会短暂影响数码管刷新但因为黄闪模式下本来就不显示倒计时问题不大。全红模式要特别注意0x09这个值它同时点亮南北红和东西红正好是互斥相位的安全组合不会出现冲突。验证层面除了虚拟示波器Proteus 8 自带逻辑分析仪能一次抓 8 路信号。把 P1.0~P1.5 全部挂上去跑一个完整的 66 秒周期导出的波形图可以直接贴进课设报告里比截图 LED 亮灭有说服力。抓波形时把采样率设高一点否则 3 秒的黄灯相位可能只采到两三个点看不出脉冲宽度。另一个验证技巧是在中断服务函数里翻转一个空闲引脚比如 P3.7然后用示波器量这个引脚的周期正常应该是精确的 50ms如果偏差超过 1%基本可以确定是初值算错或者晶振频率设置不一致。本文还有配套的精品资源点击获取
返回列表