单片机——交通灯(中断与状态机设计) 1. 交通灯控制系统的核心设计思路第一次用51单片机做交通灯项目时我像大多数人一样用了最直接的顺序编程法——在main函数里写满delay()和if-else。结果代码越写越乱当老师要求增加夜间模式时我不得不重写80%的代码。这次惨痛教训让我明白用状态机中断才是单片机开发的正确打开方式。状态机State Machine的本质是把复杂流程拆解成多个独立状态。比如交通灯最基本的四个状态红灯亮等待通行绿灯亮允许通行黄灯闪烁过渡警告全红状态安全缓冲每个状态就像交通系统的记忆卡片系统只需要记住当前处在哪个状态并根据条件切换到下一个状态。这种设计带来三个显著优势代码可读性提升每个状态的处理逻辑完全独立功能扩展方便新增状态不影响原有逻辑维护成本降低bug定位精确到具体状态2. 硬件设计的关键细节2.1 最小系统搭建我的开发板上有这些核心部件AT89C51芯片老古董但教学常用12MHz晶振比11.0592MHz更常见4个共阳数码管显示倒计时12个LED红黄绿各4个模拟十字路口3个独立按键紧急模式/时间调整特别注意LED驱动方式我最初直接用IO口驱动结果亮度不够还烧了个IO口。后来改用74HC245缓冲器电流提升到15mA后问题解决。硬件连接有个易错点P0口必须接10K上拉电阻否则数码管显示会乱码。2.2 定时器配置技巧定时器0的初始化代码看似简单但有几个魔鬼细节void Timer0_Init() { TMOD 0xF0; // 清除T0配置位 TMOD | 0x01; // 模式1(16位定时器) TH0 0x3C; // 50ms定时初值(12MHz) TL0 0xB0; ET0 1; // 使能T0中断 TR0 1; // 启动定时器 }这里TMOD寄存器的操作必须用与或的方式否则会意外修改定时器1的配置。我曾在调试时因为直接赋值TMOD0x01导致串口通信异常花了三小时才找到原因。3. 状态机的软件实现3.1 状态枚举与转换用枚举定义状态比用数字更直观typedef enum { STATE_ALL_RED, // 全红状态 STATE_NS_GREEN, // 南北绿灯 STATE_NS_YELLOW, // 南北黄灯 STATE_EW_GREEN, // 东西绿灯 STATE_EW_YELLOW, // 东西黄灯 STATE_EMERGENCY // 紧急模式 } TrafficState;状态转换通过二维数组实现清晰展示跳转关系const TrafficState nextState[6] { STATE_NS_GREEN, // 全红→南北绿 STATE_NS_YELLOW, // 南北绿→南北黄 STATE_ALL_RED, // 南北黄→全红 STATE_EW_GREEN, // 全红→东西绿 STATE_EW_YELLOW, // 东西绿→东西黄 STATE_ALL_RED // 东西黄→全红 };3.2 中断服务程序定时器中断里不要做耗时操作这是我的中断服务程序框架void Timer0_ISR() interrupt 1 { TH0 0x3C; // 重装初值 TL0 0xB0; static uint8_t cnt50ms 0; if(cnt50ms 20) { // 1秒到达 cnt50ms 0; TimeCount--; // 状态倒计时 } }曾经在这里做过数码管扫描结果导致定时不准。后来改用状态标志位在主循环中处理显示和按键扫描。4. 高级功能扩展实战4.1 紧急模式实现通过外部中断实现紧急控制void INT0_ISR() interrupt 0 { currentState STATE_EMERGENCY; P1 0xF6; // 南北红灯东西红灯 EA 0; // 关总中断 }注意要关闭中断防止冲突我遇到过紧急模式下按键失灵的问题就是因为中断嵌套导致标志位混乱。4.2 夜间模式设计增加光敏电阻检测环境光if(ADC_Read(0) 50) { // 光线暗 nightMode 1; P1 0xDB; // 所有黄灯闪烁 } else { nightMode 0; }调试时发现ADC读数不稳定后来加了软件滤波算法连续5次采样取中间值问题解决。5. 常见问题解决方案5.1 数码管鬼影消除最初数码管显示时有残影通过两个技巧解决显示切换前先关闭位选P2 0xFF; // 关闭所有位选 P0 table[num]; // 送段码 P2 pos; // 打开指定位选增加1ms延时再切换下一位5.2 状态切换不同步当东西向和南北向状态切换不同步时我在状态机中增加了全红缓冲状态确保两个方向不会同时绿灯。实测全红状态保持2秒最合适既保证安全又不影响通行效率。6. 完整代码框架这是经过项目验证的代码结构#include reg51.h // 状态机核心代码 void Traffic_Update() { if(TimeCount 0) { currentState nextState[currentState]; switch(currentState) { case STATE_NS_GREEN: TimeCount 30; // 绿灯30秒 P1 0xDE; // 南北绿,东西红 break; // 其他状态处理... } } } void main() { System_Init(); while(1) { Traffic_Update(); Display_Scan(); // 数码管扫描 Key_Scan(); // 按键检测 } }在真实十字路口测试时发现单纯定时切换不够智能。后来我升级为动态时间调整算法通过车流量检测自动延长绿灯时间但这需要增加红外传感器或摄像头属于进阶玩法了。