基于51单片机与红外对射的智能红绿灯车流量检测设计
2026/9/16 13:03:55 网站建设 项目流程

简介:面向单片机开发与嵌入式课程设计的智能红绿灯项目,基于C51芯片完成十字路口交通灯控制,能够通过红外检测车流量自适应调整红绿灯时长,缓解固定配时造成的拥堵。资源共332个文件,压缩包约93.95MB,主要包含C语言与A51汇编源码、Keil工程文件(uvproj/uvopt)、hex固件、原理图与PCB、文档说明、图片及演示视频等,构成一套从代码阅读、仿真调试到实物制作的完整资料。当前已有124人学习下载,适合计算机、自动化、电子信息等专业学生用作毕业设计、课程设计或竞赛初期方案演示。项目代码经过充分运行验证,评审平均分达96分,并配有操作录屏与详细说明,能帮助读者快速理解车流量检测与动态配时逻辑,并支持在此基础上二次开发。

1. 车流量检测才是智能红绿灯的核心:定时控制为什么不够用

先把结论放在前面:用 51 单片机做十字路口红绿灯,最难的不是让 LED 按时间亮灭,而是「怎么知道哪个方向的车多、该放行多久」。传统的定时红绿灯只是把时间片固定切给东西和南北两个方向,车流空的时候浪费通行时间,车流高峰的时候又排成长队。智能红绿灯要解决的,是把「定时切换」改成「依据车流量的自适应切换」,而这一切的前提,是先有一套能实时量化车流量的检测电路。本文选择的方案是基于 C51 芯片、配合红外对射传感器做脉冲计数,再用定时器产生时间基准,在状态机里动态调整绿灯时长,适合课程设计、毕设以及刚接触单片机中断与定时器综合应用的开发者。这个方案的工程文件里会看到STARTUP.A51zm.uvprojjtd.uvproj等项目源码,代码在 Keil 环境下可直接编译烧录,下面把检测原理、控制逻辑、工程配置和调试技巧逐个拆开讲。

2. 红外对射检测与脉冲计数:传感器怎么选、信号怎么处理

2.1 车流量检测方案对比:为什么用红外对射而不是摄像头

智能红绿灯项目里,车流量检测有很多种方案,常见的有电感线圈检测、摄像头视觉检测和红外对射检测。电感线圈需要在车道地面开槽埋线,检测精度高,但施工破坏路面,而且课程设计环境里很难模拟;摄像头视觉检测能统计车辆数量和排队长度,可是需要额外的图像处理芯片,对 51 单片机来说算力不够用;红外对射检测是最容易在开发板和仿真环境里复现的方案,原理简单、成本低、实时性好。C51 芯片跑 12MHz 晶振时,外部中断响应速度完全能跟上红外对射的脉冲频率,所以项目资料里选用的就是红外对射。

下面这张表把三种方案在课程设计场景下的关键差异列出来,方便理解选型思路。

方案成本检测维度环境适应性与 51 单片机集成难度
电感线圈车流量埋地,易受重车挤压需要额外调理电路,难度中
摄像头视觉车流量、排队长度受光照影响大需要图像处理,难度高
红外对射车流量受灰尘和雨雾影响开关量信号,难度低

2.2 红外对射传感器输出的信号是什么样的

红外对射传感器由发射管和接收管组成,发射管持续发出红外光,接收管在没有车辆遮挡时导通,光路被车辆遮断时接收管截止。把这路信号经过电压比较器整形后,输出就是一个标准的开关量,也就是 TTL 电平的跳变。车辆经过光路时,输出会先拉高再拉低,形成一个下降沿。这个下降沿,就是单片机外部中断最好的触发信号。

在电路接线上,一般把整形后的信号接到 C51 的P3.2(INT0)或者P3.3(INT1)。P3.2是外部中断 0 的输入引脚,设置为下降沿触发后,每当有车通过红外对射区域,单片机就会进入一次外部中断服务函数。为了保证计数不遗漏,还需要在中断服务函数里做软件去抖,因为车辆通过时可能因为车身反光或者路面颠簸产生毛刺信号,直接计数会偏高。

2.3 脉冲计数与去抖的完整代码实现

在 C51 里实现车流量脉冲计数,用外部中断 INT0 接收红外对射整形后的信号。下面这段代码同时包含去抖和计数两个功能,去抖思路是进入中断后延时 10ms 再次确认引脚电平,如果仍然处于车辆遮挡状态,才认为是有效脉冲。

#include <reg51.h> sbit IR_IN = P3^2; // 红外对射整形信号接 INT0 unsigned int car_count = 0; // 车流量计数值 unsigned char debounce_flag = 0; // 去抖状态标志 void delay_10ms(void) // 粗略延时,用于软件去抖 { unsigned char i, j; for (i = 0; i < 200; i++) for (j = 0; j < 200; j++); } void ext0_isr(void) interrupt 0 // INT0 下降沿触发 { if (!debounce_flag) { debounce_flag = 1; // 进入去抖窗口 delay_10ms(); if (IR_IN == 0) { // 延时后仍为低电平,判定为有效脉冲 car_count++; } debounce_flag = 0; } } void main(void) { IT0 = 1; // INT0 下降沿触发 EX0 = 1; // 使能外部中断 0 EA = 1; // 打开总中断 while (1) { // 主循环每隔固定周期读取 car_count 并清零 } }

这段代码里,IT0 = 1是设置 INT0 为下降沿触发,也就是检测到高电平到低电平跳变时进入中断函数ext0_isr。中断函数里先判断debounce_flag,防止上一次去抖还没结束又触发新的中断。IR_IN == 0表示 10ms 后引脚仍然被拉低,这代表车辆确实遮挡了红外光路,而不是瞬时的电磁干扰。car_count就是当前车流量统计值,主循环里可以周期性读取这个值,再根据它决定绿灯是否延长。

3. C51 核心控制逻辑:定时器时基、四状态机与绿灯延长策略

3.1 交通灯状态机设计

传统红绿灯的状态比较简单,只有南北绿灯、南北黄灯、东西绿灯、东西黄灯四个状态。智能红绿灯在这四个状态之上,增加了绿灯延长的判断逻辑。状态机是这套代码的骨架,每个状态内部要做的事情是:点亮对应方向的 LED 灯、启动定时器、按时间阈值切换到下一个状态。

我习惯把四个状态定义成宏,再用一个state变量做状态切换。状态机的流转规则是:

  • 南北绿灯期间,如果南北方向的车流量(由红外脉冲计数得到)持续大于设定阈值,绿灯时间可以延长到最大上限,否则在最短绿灯时间后切换黄灯。
  • 南北黄灯固定 3 秒,然后切换到东西绿灯。
  • 东西绿灯延长的判断逻辑与南北方向完全相同。

3.2 定时器产生 1 秒时基

51 单片机的定时器 0 工作在模式 1,也就是 16 位定时器,晶振选择 12MHz,机器周期是 1us。要产生 1 秒时基,可以先让定时器产生 50ms 中断,再在主函数里累计 20 次。之所以不用定时器直接定时 1 秒,是因为 16 位定时器的最大定时时间只有约 65.5ms,超过这个时间就需要重新装载初值。

#include <reg51.h> sbit NS_GREEN = P1^0; // 南北绿灯 sbit NS_YELLOW = P1^1; // 南北黄灯 sbit NS_RED = P1^2; // 南北红灯 sbit EW_GREEN = P1^3; // 东西绿灯 sbit EW_YELLOW = P1^4; // 东西黄灯 sbit EW_RED = P1^5; // 东西红灯 unsigned int ms_tick = 0; unsigned int second_tick = 0; unsigned char current_state = 0; #define STATE_NS_GREEN 0 #define STATE_NS_YELLOW 1 #define STATE_EW_GREEN 2 #define STATE_EW_YELLOW 3 // 绿灯最短 15 秒,最长 36 秒,黄灯 3 秒 #define MIN_GREEN_TIME 15 #define MAX_GREEN_TIME 36 #define YELLOW_TIME 3 void timer0_isr(void) interrupt 1 { TH0 = 0x4C; // 50ms 定时初值 TL0 = 0x00; ms_tick++; if (ms_tick >= 20) { // 累计 20 次 = 1 秒 ms_tick = 0; second_tick++; } }

TH0 = 0x4CTL0 = 0x00是 50ms 定时初值,这是 12MHz 晶振下常用的装载值。second_tick每次加 1 代表过了 1 秒,状态机里每个状态的剩余时间都由它来驱动。如果在别的晶振频率下运行,比如 11.0592MHz,那么初值要做相应修正,否则时间基准偏快或偏慢会在最终绿灯时长上放大误差。

3.3 主循环状态切换与绿灯延长逻辑

状态机的主体写在主循环while(1)里,每次循环先判断当前状态,再根据second_tick的变化决定是否切换。核心的自适应逻辑在于:南北绿灯状态下,只有绿灯运行时间超过最短时间,同时南北车流量大于阈值,并且绿灯总时长还没到最大上限,才继续维持绿灯。任何一个条件不满足,就立刻切换黄灯。

extern unsigned int car_count; void main(void) { unsigned int green_start = 0; unsigned int car_flow_threshold = 3; // 3 秒内检测到超过 3 辆车,视为车流繁忙 TMOD = 0x01; // 定时器 0 模式 1 TH0 = 0x4C; TL0 = 0x00; ET0 = 1; // 使能定时器 0 中断 EA = 1; TR0 = 1; // 启动定时器 while (1) { switch (current_state) { case STATE_NS_GREEN: NS_GREEN = 1; NS_RED = 0; EW_RED = 1; EW_GREEN = 0; EW_YELLOW = 0; if (second_tick - green_start >= MIN_GREEN_TIME) { if ((car_count < car_flow_threshold) || (second_tick - green_start >= MAX_GREEN_TIME)) { current_state = STATE_NS_YELLOW; second_tick = 0; } } if (current_state == STATE_NS_GREEN && car_count == 0) { // 无车时强制切换到黄灯,减少空放时间 } break; case STATE_NS_YELLOW: NS_GREEN = 0; NS_YELLOW = 1; if (second_tick >= YELLOW_TIME) { current_state = STATE_EW_GREEN; second_tick = 0; green_start = 0; car_count = 0; // 清零,重新统计下一个绿周期的车流量 } break; case STATE_EW_GREEN: // 逻辑与南北绿灯一致,方向换成东西 break; case STATE_EW_YELLOW: // 逻辑与南北黄灯一致 break; } } }

green_start记录的是绿灯刚开始时的秒数,second_tick - green_start算出绿灯已经运行了多长时间。car_count < car_flow_threshold表示当前车流不大,这个时候继续亮绿灯是浪费其他方向的时间。MAX_GREEN_TIME是硬上限,避免某个方向一直有车导致另一个方向长时间得不到放行。这里最容易被忽略的是car_count的统计周期:如果车流量一直累计不清零,阈值判断就会失效,所以要在黄灯切换完成后把计数归零。

4. Keil 工程构建与 STARTUP.A51 工程初始化细节

4.1 Keil 工程里的这些文件各是什么

下载的工程压缩包里能看到STARTUP.A51zm.uvprojjtd.uvprojzm.uvgui等文件。其中STARTUP.A51是 Keil 为 C51 自动生成的启动文件,它在 main 函数执行之前完成三件事:初始化内部数据存储器的清零、设置堆栈指针、重定位外部数据存储器变量。这个文件通常不需要修改,但它的存在会影响内存布局,理解它对后面排查变量丢失问题很有帮助。

zm.uvprojjtd.uvproj是 Keil 工程文件,.uvgui是界面布局配置文件,.bak是备份文件。实际编译时只需要打开.uvproj文件,源码会自动加载。如果电脑上同时装了 Keil 4 和 Keil 5,建议用 Keil 5 打开工程,Keil 5 兼容 C51 和 ARM 两种工具链,切换设备时不需要重装软件。

4.2 C51 和 STM32 共存安装的注意事项

很多开发者的电脑上同时需要开发 51 单片机和 STM32,安装顺序会影响 Keil 的工具链识别。正确做法是先安装 Keil 5 的基础包,再分别安装 C51 的 Device Pack 和 ARM 的 Device Pack。如果安装后新建工程时找不到 C51 芯片,可以检查 C:/Keil_v5/UV4/ 下是否存在C51目录,以及 Keil 安装路径里是否包含UV4C51两个完整目录。注意 Keil 5 的 C51 支持包版本号要对应,比如常见能稳定运行的是 C51 V9.x 版本。

用 Keil 打开工程后,在 Options for Target 界面里确认三处关键配置:一是晶振频率要填 12MHz 或与硬件一致的频率,否则延时函数全部按错误时间计算;二是 Output 选项卡里勾选 Create HEX File,这样才能生成烧录用的.hex文件;三是 Memory Model 保持默认即可,STARTUP.A51里的内存布局参数会跟随工程配置自动调整。

4.3 编译常见报错与排查路径

实际编译这个智能红绿灯工程时,最容易遇到的报错是C51 FATAL-ERROR提示文件路径包含中文或空格,Keil 对非英文字符路径兼容性较差,直接把工程目录改名成英文再打开就能解决。另外一个高频问题是undefined symbol,原因通常是car_count等全局变量在外部中断文件里定义,但主函数文件里没有用extern声明,把这个声明补上就能解决。

.hex文件烧录前建议先用仿真模式跑一遍逻辑,Keil 的 Debug 模式里打开 Peripherals 菜单下的 Port 1,可以手动翻转 P1 口电平来模拟红外传感器信号。这样做的好处是,不必每次修改代码都向开发板烧录,还能在 Debug 界面里查看定时器计数器和car_count变量的实时变化,快速排查状态机卡住的问题。

4.4 Proteus 仿真与硬件验证的差异

如果课程设计要求做 Proteus 仿真,需要注意 Proteus 里的红外对射传感器模型通常用开关代替,这与实际红外模块的响应速度有差异。仿真时用按键模拟车辆通过,每按一次按键产生一个下降沿脉冲,观察状态机是否能在最短绿灯时间后切换到黄灯。但 Proteus 不擅长模拟红外光束被遮挡的连续过程,所以仿真通过后,一定要在上板后实测红外输出的波形,波形确认是干净的下降沿再接入单片机。

5. 车道参数标定与红外阈值调试:让规则逻辑变成能部署的控制策略

参数标定处在整个项目落地阶段,目的是把「车流量大就延长绿灯」这个粗略规则,细化为可量化的阈值组合。标定对象主要是三个参数:最短绿灯时间、最长绿灯时间、单位时间车流量阈值。我在实际操作中一般通过串口打印观察数据的方法来标定,把当前绿灯运行秒数、car_count值、状态机当前状态通过串口发送到电脑,上位机用串口助手接收。

最短绿灯时间不宜太短,否则行人横穿马路时绿灯突然结束,会出现安全隐患,一般 15 到 20 秒比较合理。最长绿灯时间则取决于路口最大允许排队长度,经验做法是让最长绿灯时间内通过的车数恰好能把排队车辆清空,而不是无限延长。车流量阈值car_flow_threshold按下式估算:道路每车道饱和车头时距约 2 秒,一个绿灯周期内单车道最多通过 15 秒除以 2 约为 7 辆车,如果检测到连续两个统计周期内车数都超过这个值的 80%,即可判定为高峰。

红外传感器的安装距离和角度直接影响脉冲计数的准确性:发射管和接收管要正对安装,光轴偏差超过 5 度时接收管接收到的是散射光,车辆通过时波形会出现抖动;安装高度建议距地面 30 到 50 厘米,这个高度能同时检测到轿车和 SUV 的车身遮挡,却不至于被底盘较高的货车底盘的机械结构漏光。

最后一招是给计数数据加滑动平均滤波,避免单次异常脉冲干扰状态机判断。在主循环里维护一个长度为 5 的数组,每次读取car_count时更新数组,取平均值之后再和阈值比较。滑动平均在车流平稳时优势不明显,但在车辆刚起步、走走停停的拥堵场景里,能明显减少绿灯时长频繁跳动的问题,操作上只需要在 3.3 节代码的阈值判断前增加一个小函数。

unsigned char flow_buf[5] = {0, 0, 0, 0, 0}; unsigned char flow_index = 0; unsigned int get_avg_flow(unsigned int current_count) { unsigned int sum = 0; unsigned char i; flow_buf[flow_index] = current_count % 16; flow_index = (flow_index + 1) % 5; for (i = 0; i < 5; i++) { sum += flow_buf[i]; } return sum / 5; }

这段代码把当前car_count的低 4 位存入滑动窗口,每次取 5 个历史值的平均作为有效车流量。current_count % 16这个操作是为了防止计数溢出,实际使用中可以把car_count的原始周期计数直接传入。标定完成之后,可以把红外阈值、最长时间这些宏定义从代码里抽出来,放到工程头文件里统一管理,不同路口只需要改头文件就能重新部署,不需要动状态机主体代码。这套「检测-计数-状态机-标定」的链路完整跑通后,整个智能红绿灯项目就具备了从仿真到实物的迁移能力,你后续要增加行人按钮、倒计时显示、夜间黄灯闪烁模式,也都是在当前状态机框架上扩展新状态的事。

本文还有配套的精品资源,点击获取

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询