资讯详情

51单片机交通灯课程设计:从定时器到红外遥控的完整实现

📅 2026/9/17 2:22:48 | 华诺云谱 👁 阅读
51单片机交通灯课程设计:从定时器到红外遥控的完整实现
简介面向51单片机课程设计与期末大作业的完整交通灯设计方案基于STC/AT89系列等常见51内核包含源码、实验报告PDF与原理图等。项目覆盖LED数码管倒计时、按键调整、紧急模式等典型功能代码注释详细即使新手也能快速上手适合电子、自动化、嵌入式等相关专业学生参考与复用。压缩包共58个文件约1.47MB主要包含C语言源码.c/.h、Keil工程文件.uvproj/.uvopt、编译生成文件.hex/.obj/.lst等以及实验报告PDF和原理图相关文件。其中.hex可直接烧录到开发板验证PDF报告可直接用于课程设计文档参考整体结构清晰按模块划分。该资源已有405人学习被评价为满分大作业资源能直接部署使用。下载后可获得一套可运行的交通灯控制系统源码、配套实验报告和原理图等既可用于课程答辩演示也能帮助理解定时器中断、按键扫描、状态机设计等单片机核心知识点具有较高参考价值。1. 这块板子不只是一组红绿灯很多人的课程设计都栽在同一个地方功能单一只有两组灯按固定时间闪答辩老师一问“中断怎么用的”就卡住。这个基于51单片机的交通灯项目代码里除了基础的红绿黄状态切换还带了矩阵键盘调时、DS18B20温度读取、红外遥控解码和蜂鸣器驱动等于把定时器、外部中断、单总线、串行按键扫描全过了一遍。文件里给的ProgramCode.hex是直接能烧的产物实验报告PDF里含原理图和设计过程适合期末大作业、课程设计这类需要“能跑、能讲、能改”的场景。下面从工程文件拆起把每一块代码怎么工作、参数怎么调、调试时看哪里完整过一遍。2. 先看懂工程再动手文件的职责划分与硬件映射拿到压缩包先别急着打开Keil把文件清单读一遍基本就能还原整套系统的设计思路。这个工程里既有驱动层代码也有逻辑层代码还有一堆编译中间产物分清哪些是源码、哪些是生成物才不会在改代码时改错文件。2.1 从工程文件反推整体架构解压后看到的.c和.h文件对应的是各个硬件模块下面这张表把核心文件对应的功能列出来文件模块职责main.c主程序状态机调度、初始化、主循环LED.c / LED.h交通灯显示红黄绿LED点亮与熄灭控制Timer.c / Timer0定时器产生时基驱动倒计时与数码管刷新Key.c / MatrixKey.c按键输入独立按键与矩阵键盘扫描调整时间参数Int0.c / IR.c红外接收外部中断0触发NEC协议解码DS18B20.c OneWire.c温度采集单总线时序读取环境温度Buzzer.c蜂鸣器倒计时结束提示与超温报警Delay.c延时微秒级和毫秒级软件延时注意Delay.h、Timer.h这些头文件是模块对外暴露的接口而.obj、.lst、.m51、.build_log.htm都是Keil编译时自动生成的不需要手动维护。ProgramCode.hex是最终烧录文件实验报告PDF里还有原理图和端口分配表。2.2 逻辑层与驱动层的边界main.c里跑的是一套状态机它只关心“当前处于哪个通行方向、剩余多少秒”并不直接操作引脚。真正控制引脚高低电平的是LED.c这就把逻辑和硬件解耦了。比如在main.c里改一个路口方向的时间不需要动LED.c里的任何一行反过来如果把LED从P0口换到P1口也只需要改LED.c里的引脚宏定义和段码表状态机完全不受影响。这种分层习惯在单片机项目里非常重要尤其是课程设计要做功能扩展时。你后续想加一个倒计时数码管显示只需要在Timer中断里多调一个显示刷新函数不需要去main.c里找哪里在控制电平翻转。2.3 从代码反推引脚分配参考实验报告PDF里的原理图这套设计在Proteus仿真中常见接法是四组红黄绿LED分别接P0口和P2口高电平驱动或低电平驱动取决于原理图中的LED接法数码管段码接P0、位选接P2或P3口的高位矩阵键盘占用P1口低四位和高四位DS18B20数据线接P3.7或P1.0红外接收头输出接INT0P3.2蜂鸣器接P2.5之类带负载能力的引脚。拿到实物或仿真文件后第一件事就是对照原理图确认每个模块的引脚。源码里的LED.h头文件下会有类似这样的宏定义#define RED_EAST P0_0 // 东向红灯 #define YELLOW_EAST P0_1 // 东向黄灯 #define GREEN_EAST P0_2 // 东向绿灯 #define RED_WEST P0_3 // 西向红灯 #define YELLOW_WEST P0_4 // 西向黄灯 #define GREEN_WEST P0_5 // 西向绿灯这里每个宏定义就是把一个具体的单片机引脚映射成语义化的名字。好处是代码里读到GREEN_EAST 1就知道是在点亮东向绿灯而不是去记P0_2是什么。引脚分配与原理图不一致时改的是这一块宏定义而不是散落在各个函数里的电平操作。如果你的Proteus仿真图换了一个单片机型号比如从AT89C51改成STC89C52RC引脚兼容但要注意P0口默认是开漏输出必须外接上拉电阻才能得到高电平。3. 红绿灯时序的表层与底层定时器重装、状态机与倒计时刷新交通灯的核心问题只有一个如何让时间精准地走。软件延时会被按键扫描和红外解码阻塞时间一长就漂移。这个工程的做法是典型的“定时器产生时基 主循环跑状态机”结构下面拆开看。3.1 Timer0为什么配置成50毫秒中断以12MHz晶振、Timer0工作在方式116位定时器为例单次定时上限是65.535毫秒。要产生一个50ms的中断周期初值计算方式为TH0 (65536 - 50000) / 256; // 高8位 0x3C TL0 (65536 - 50000) % 256; // 低8位 0xB0因为每个机器周期是12个时钟周期即1微秒定时50000个机器周期就是50毫秒。中断服务程序里维护一个软件计数器每计20次就是1秒。void Timer0_ISR(void) interrupt 1 { TH0 0x3C; // 重新装载初值 TL0 0xB0; tick_count; if (tick_count 20) // 50ms * 20 1000ms { tick_count 0; second_flag 1; // 置秒标志主循环处理 } }interrupt 1是Keil C51里Timer0中断的固定入口号不能改。主循环检测到second_flag为1时才对状态机执行一次秒递减。把耗时操作全部放在主循环而不是中断里处理可以减少中断服务程序的执行时间避免影响下一次定时中断的响应。3.2 东西与南北直行的状态机切换交通灯的本质是一个四状态循环东西绿灯/南北红灯东西绿灯闪东西黄灯南北绿灯/东西红灯南北绿灯闪南北黄灯。用枚举定义状态用switch-case驱动状态迁移typedef enum { EW_GREEN_NS_RED, EW_GREEN_BLINK, EW_YELLOW_NS_RED, NS_GREEN_EW_RED, NS_GREEN_BLINK, NS_YELLOW_EW_RED } TrafficState; TrafficState current_state EW_GREEN_NS_RED; unsigned char second_counter 0; void traffic_state_machine(void) { switch (current_state) { case EW_GREEN_NS_RED: set_leds(1, 0, 0, 0, 1, 0); // 东西绿、南北红 if (second_counter 30) // 持续30秒 { current_state EW_GREEN_BLINK; second_counter 0; } break; case EW_GREEN_BLINK: blink_green(); // 绿灯闪3秒 if (second_counter 3) { current_state EW_YELLOW_NS_RED; second_counter 0; } break; // 其余状态类似黄灯3秒→切换南北方向 } }这里的second_counter在主循环里由second_flag驱动递增。参数30和3就是每个状态持续时长放在宏定义里可以统一修改。这个状态机的关键设计点在于每个状态的下一个状态是唯一的中间没有跳变状态所以即使按键在任何时刻修改了second_counter也不会打乱状态顺序只会提前或延后切换。3.3 倒计时显示与动态扫描数码管倒计时是这个设计里最容易被看出“有没有用心”的部分。多位共阴数码管扫描不能把所有位同时点亮需要一位一位轮流刷新靠视觉暂留形成“同时亮”的效果。code unsigned char seg_code[] { 0xC0, 0xF9, 0xA4, 0xB0, 0x99, // 0 1 2 3 4 0x92, 0x82, 0xF8, 0x80, 0x90 // 5 6 7 8 9 }; void display_number(unsigned char num) { unsigned char tens num / 10; // 十位 unsigned char ones num % 10; // 个位 P2 0x01; // 选通第一位个位 P0 seg_code[ones]; delay_short(5); // 位选保持时间 P2 0x02; // 选通第二位 P0 seg_code[tens]; delay_short(5); }0xC0是共阳数码管里数字0的段码P2控制位选P0输出段码。函数末尾没有清屏操作是因为下一个周期马上会重新选通位选短暂的过渡帧人眼分辨不出来。刷新频率不能太慢否则会闪烁实际调用方式是在Timer中断里每5到10毫秒刷新一次。注意如果原理图用的是共阴数码管段码表需要取反即~0xC0。这是移植到其他开发板时最常见的坑之一。3.4 矩阵键盘按键调时矩阵键盘部分Key.c负责检测按下MatrixKey.c负责行列扫描。三乘四矩阵键盘扫描思路是逐行拉低读列电平来判断是哪个键按下unsigned char matrix_key_scan(void) { unsigned char row, col; P1 0x0F; // 低四位拉低高四位输入 if ((P1 0x0F) 0x0F) return 0; // 无按键 for (row 0; row 4; row) { P1 ~(0x10 row); // 逐行拉低 for (col 0; col 4; col) { if (!(P1 (0x01 col))) { return row * 4 col 1; // 返回键值 } } } return 0; }P1 0x0F先把低四位作为扫描输出、高四位作为输入检测到按键后再逐行扫描定位具体位置。拿到键值后在main.c里分配给不同功能加减秒数、切换方向、进入配时模式。配时模式里修改的是EW_GREEN_TIME等常量变量状态机运行时直接读这些变量实现不停机调时。4. 扩展模块的接入方式DS18B20温度、NEC红外与蜂鸣器联动文件列表里出现DS18B20.c、IR.c和Buzzer.c说明这已经不是一个单纯的交通灯而是带环境感知的复合系统。这在课程设计答辩中属于加分项评委看到的不只是“红绿灯会跳”还能追问出传感器时序、外部中断、协议解析这些更底层的东西。4.1 OneWire单总线读DS18B20的几个时序点DS18B20走的是OneWire单总线协议一根数据线既做电源又传数据时序要求严格。复位、写0、写1、读时隙这四种操作必须满足微秒级的时间窗口。OneWire.c里的延时函数是核心常见的简化版读温度函数长这样bit ds18b20_read_bit(void) { bit dat; DATA_PIN 0; // 拉低总线启动时隙 _nop_(); DATA_PIN 1; // 释放总线 _nop_(); dat DATA_PIN; // 在15us窗口内采样 delay_us(50); return dat; }读一位的时序主机先把总线拉低至少1微秒然后释放在15微秒内读取总线电平。DS18B20返回1就输出高返回0就输出低。读取整字节就是连续读8位低位在前。代码里_nop_()是Keil C51内置的微秒级空指令。OneWire时序对延时非常敏感如果在Proteus仿真里跑要把仿真步长调小在实物上跑如果读出的温度恒定是85通常是复位时序里的延时不够把复位时拉低时间从480us加大到600us再试。4.2 基于外部中断0的NEC红外解码IR.c借助Int0.c提供的外部中断0来捕捉红外接收头的下降沿。NEC协议里引导码是9ms高电平加4.5ms低电平数据位通过高低电平的时间长短区分560us高1.69ms低表示0560us高2.25ms低表示1。解码思路是利用定时器测量两次下降沿之间的时间间隔void Int0_ISR(void) interrupt 0 { unsigned int pulse_width get_timer_value(); // 读取Timer1计数值 reset_timer(); if (pulse_width 13000) // 约9ms引导码 { ir_receiving 1; ir_bit_count 0; } else if (ir_receiving) { if (pulse_width 2000) // 约1.6~2.25ms → 数据位1 { ir_data | 1; } ir_data 1; ir_bit_count; if (ir_bit_count 32) // 地址地址反码数据数据反码 { ir_frame_done 1; ir_receiving 0; } } }pulse_width 2000这个阈值由Timer1在12MHz晶振下的计数频率决定具体数值要按实际晶振调整。32位数据里前16位是地址码和地址反码后16位是数据码和数据反码判断(ir_data 0xFF) (~(ir_data 8) 0xFF)有效才算接收成功。4.3 蜂鸣器的两种触发场景Buzzer.c里有独立的发声函数给不同场景不同的声音模式。倒计时最后5秒每秒短响一声提示行人准备DS18B20读到的温度超过设定的上限值比如40摄氏度连续长鸣报警。void buzzer_single_beep(void) { buzzer_on(); delay_ms(100); buzzer_off(); delay_ms(100); }这里驱动蜂鸣器注意一点有源蜂鸣器直接给电平就能发声无源蜂鸣器必须给特定频率的方波才能发声。这个工程里用的是有源蜂鸣器所以buzzer_on()直接拉高引脚即可。如果替换成无源蜂鸣器需要增加一个定时器翻转I/O口产生2kHz到4kHz的方波否则只会听到“咔哒”声。5. 从工程到验收编译烧录、Proteus仿真与常见故障定位这个项目到能演示状态只差两步编译出hex文件然后把hex加载进Proteus仿真或烧进实物单片机。最后一环往往卡在环境配置和连线细节上而不是代码本身。Keil里打开ProgramCode.uvproj后先检查晶振频率是不是12MHz这个值影响延时函数和定时器初值的实际效果。点魔术棒进入Options for Target在Target页确认Xtal(MHz)填的是12.0然后按F7编译。编译输出0 Error 0 Warning时在Objects文件夹里能找到ProgramCode.hex。Proteus仿真时双击原理图中的单片机芯片在Program File一栏选择这个hex文件晶振频率设为12MHz后点运行。如果仿真中LED不亮优先检查LED的方向和限流电阻。源码里如果写的是LED_on为高电平但原理图上LED接的是共阳接法另一端接VCC那点亮条件是引脚输出低电平逻辑就对不上。实际部署中还有几个高频故障数码管显示乱码。先确定是共阴还是共阳对照原理图把段码表整体取反。按键无效。矩阵键盘扫描里P1 0x0F后的短暂延时被优化掉了加几个空循环或delay_us(10)。红外遥控没反应。测量红外接收头OUT脚空闲时是不是高电平空闲不是高说明接线或供电有问题。温度始终显示85。DS18B20的DQ引脚缺少4.7k上拉电阻到VCC加上就好。答辩时如果被问“为什么用外部中断而不是轮询处理红外”可以回答红外解码要求微秒级精度轮询会被主循环中其他任务阻塞错过下降沿导致整帧数据错乱外部中断可以保证每次跳变都被记录配合定时器实现精确脉宽测量。最后有个提升演示效果的技巧把按键调时和温度显示联动起来。比如按下功能键后数码管前两位显示温度后两位显示倒计时剩余秒数这样一次答辩就能把“传感器采集”和“人机交互”都覆盖进去加分项就落在实际演示里了。本文还有配套的精品资源点击获取
📝

华诺云谱内容团队

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

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

你可能需要的服务

订阅华诺云谱资讯周报

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