1. 项目概述
红绿灯控制系统是城市交通管理的基础设施,也是嵌入式系统开发的经典练手项目。用单片机实现红绿灯控制,不仅能学习硬件电路设计、定时器编程等核心技能,还能深入理解状态机在实际工程中的应用。这个项目特别适合电子类专业学生和嵌入式开发新手作为第一个完整的硬件开发实践。
我在大学期间就做过类似的课程设计,后来在工作中又多次改进过这个方案。从最基础的51单片机到STM32,从固定时序控制到加入传感器反馈,这个看似简单的项目其实蕴含着嵌入式开发的许多精髓。下面我就结合自己踩过的坑,分享一个稳定可靠的单片机红绿灯设计方案。
2. 硬件设计解析
2.1 核心器件选型
主控芯片的选择直接影响系统成本和复杂度。对于教学和入门项目,我推荐以下三种方案:
- STC89C52:经典51内核,价格约3-5元,适合纯定时器控制方案
- STM32F103C8T6:Cortex-M3内核,价格10-15元,适合需要复杂逻辑或传感器扩展
- ESP8266:Wi-Fi功能加持,价格15-20元,适合需要联网控制的场景
提示:新手建议从STC89C52开始,其开发环境(Keil)配置简单,调试方便。我在带学生做毕设时发现,约70%的硬件问题都出在电路连接上,而非芯片本身。
2.2 电路设计要点
红绿灯系统的硬件电路主要包含三个部分:
-
电源模块:
- 采用AMS1117-3.3V稳压芯片为单片机供电
- 输入电压7-12V,通过DC插座接入
- 必须加装100μF电解电容和0.1μF陶瓷电容滤波
-
LED驱动电路:
- 每个LED串联220Ω限流电阻
- 采用ULN2003达林顿阵列驱动高亮度LED
- 共阳极接法,单片机输出低电平点亮
-
显示模块:
- 增加4位数码管显示倒计时
- 使用74HC595移位寄存器节省IO口
- 数码管建议选用共阳型,与LED驱动一致
c复制// 典型LED控制代码(51单片机)
sbit RED = P1^0;
sbit YELLOW = P1^1;
sbit GREEN = P1^2;
void light_control(unsigned char state) {
RED = (state & 0x01);
YELLOW = (state & 0x02);
GREEN = (state & 0x04);
}
2.3 PCB设计注意事项
- 电源走线宽度不小于0.5mm
- LED驱动部分与其他电路保持2mm以上间距
- 在单片机每个电源引脚附近放置0.1μF去耦电容
- 预留ISP下载接口和串口调试接口
- 为每个IO口预留测试点
3. 软件设计实现
3.1 状态机建模
红绿灯系统的核心是一个典型的状态机,通常包含以下几个状态:
| 状态编号 | 名称 | 红灯 | 黄灯 | 绿灯 | 持续时间 |
|---|---|---|---|---|---|
| 0 | 主路通行 | OFF | OFF | ON | 30s |
| 1 | 主路警告 | OFF | ON | OFF | 3s |
| 2 | 支路通行 | ON | OFF | OFF | 20s |
| 3 | 支路警告 | ON | ON | OFF | 3s |
c复制// 状态机实现示例
enum TrafficLightState {
MAIN_GREEN,
MAIN_YELLOW,
SIDE_GREEN,
SIDE_YELLOW
};
void state_machine() {
static enum TrafficLightState current = MAIN_GREEN;
static unsigned int counter = 0;
counter++;
switch(current) {
case MAIN_GREEN:
if(counter >= 300) { // 30s
current = MAIN_YELLOW;
counter = 0;
}
break;
// 其他状态类似...
}
}
3.2 定时器配置
精确的定时控制是红绿灯系统的关键。以STC89C52为例,推荐配置定时器0为16位自动重装模式:
- 晶振频率选择11.0592MHz(便于产生标准波特率)
- 定时器每10ms中断一次
- 中断服务程序维护全局计时变量
c复制// 定时器初始化代码
void timer0_init() {
TMOD &= 0xF0; // 设置定时器模式
TMOD |= 0x01; // 16位定时器模式
TH0 = 0xDC; // 10ms定时初值
TL0 = 0x00;
ET0 = 1; // 允许定时器0中断
EA = 1; // 开总中断
TR0 = 1; // 启动定时器0
}
// 中断服务程序
void timer0_isr() interrupt 1 {
TH0 = 0xDC; // 重装初值
TL0 = 0x00;
time_counter++; // 全局时间计数器
}
3.3 倒计时显示实现
数码管显示倒计时需要考虑两个问题:
- 动态扫描防止闪烁
- 数值到段码的转换
建议采用定时中断刷新显示,以下为参考代码:
c复制unsigned char code segment_map[] = {
0xC0, 0xF9, 0xA4, 0xB0, 0x99, 0x92, 0x82, 0xF8, 0x80, 0x90
};
void display_number(unsigned int num) {
unsigned char digits[4];
digits[0] = num / 1000;
digits[1] = (num % 1000) / 100;
digits[2] = (num % 100) / 10;
digits[3] = num % 10;
for(int i=0; i<4; i++) {
P2 = ~(1 << (7-i)); // 位选
P0 = segment_map[digits[i]]; // 段选
delay_ms(2); // 保持显示
}
}
4. 系统优化与扩展
4.1 增加手动控制
通过按键实现特殊场景控制:
- 紧急车辆优先通行
- 行人过街请求
- 时段模式切换(高峰/平峰)
c复制// 按键检测逻辑
if(emergency_btn == 0) {
delay_ms(10); // 消抖
if(emergency_btn == 0) {
current_state = EMERGENCY_MODE;
counter = 0;
}
}
4.2 联网功能扩展
使用ESP8266模块可实现:
- 远程状态监控
- 配时参数无线更新
- 多路口协同控制
注意:Wi-Fi模块需要单独供电,建议使用AMS1117-3.3V稳压芯片,与主控共用电源时需确保电流足够。
4.3 车流量检测
增加红外或超声波传感器:
- 统计车流量自动调整配时
- 检测紧急车辆自动切换信号
- 故障车辆检测与报警
c复制// 超声波测距示例
float get_distance() {
TRIG = 1;
delay_us(10);
TRIG = 0;
while(ECHO == 0);
start_time = TIMER;
while(ECHO == 1);
end_time = TIMER;
return (end_time - start_time) * 0.017; // cm
}
5. 常见问题与解决方案
5.1 LED亮度不足
可能原因及解决:
- 限流电阻过大 → 减小电阻值(不低于100Ω)
- 驱动电流不足 → 改用达林顿管或MOSFET驱动
- 电源功率不够 → 检查电源额定电流(建议≥1A)
5.2 定时不准确
调试步骤:
- 检查晶振频率设置
- 验证定时器初值计算
- 测量实际中断间隔(可用示波器观察IO口翻转)
- 避免在中断服务程序中执行耗时操作
5.3 数码管显示异常
排查方法:
- 确认共阳/共阴类型匹配
- 检查段选线连接顺序
- 增加位选驱动能力(如用三极管扩流)
- 调整扫描频率(建议100-200Hz)
5.4 系统死机
预防措施:
- 增加看门狗定时器
- 关键变量使用volatile声明
- 堆栈空间预留充足
- 避免浮点运算(特别是51单片机)
c复制// 看门狗初始化(STM32示例)
IWDG_WriteAccessCmd(IWDG_WriteAccess_Enable);
IWDG_SetPrescaler(IWDG_Prescaler_32); // 约1s超时
IWDG_SetReload(0xFFF);
IWDG_Enable();
6. 项目进阶方向
完成基础版本后,可以考虑以下扩展:
- 多相位控制:实现左转专用相位
- 自适应控制:根据车流自动调整配时
- 故障自检:LED损坏检测与报警
- 太阳能供电:加入电池管理电路
- V2X应用:与车载设备通信
我在实际道路工程中见过最复杂的路口有12组信号灯,采用STM32F407作为主控,通过光纤同步多个路口的信号机。虽然我们这个单片机版本简单得多,但核心原理是相通的。初学者常犯的错误是过早追求复杂功能,建议先把基础定时控制和状态机练熟,再逐步增加传感器和通信模块。