//Arduino智能交通灯演示程序
#include <SPI.h> // 引入SPI通信库,用于串行外设接口通信
#include <LedControl.h> // 引入MAX7219点阵屏控制库,用于驱动8x8点阵屏
/* ---------------------- 引脚定义 ---------------------- */
const int dataPin = A3; // 数码管数据引脚(连接到移位寄存器的数据输入)
const int clockPin = A4; // 数码管时钟引脚(控制数据移位时钟)
const int latchPin = A5; // 数码管锁存引脚(锁存显示数据到寄存器)
int numToShow = 12; // 倒计时显示数值(初始为红灯倒计时12秒)
int redLight = A0; // 红灯引脚(连接红色LED)
int yellowLight = A1; // 黄灯引脚(连接黄色LED)
int greenLight = A2; // 绿灯引脚(连接绿色LED)
int buzzer = 2; // 喇叭引脚(连接蜂鸣器)
// MAX7219点阵屏引脚定义(用于显示行人动画和禁止图标)
const int maxDinPin = 11; // 点阵屏DIN引脚(串行数据输入)
const int maxCsPin = 10; // 点阵屏CS引脚(片选信号)
const int maxClkPin = 13; // 点阵屏CLK引脚(串行时钟)
// 创建MAX7219控制器对象(参数:DIN, CLK, CS, 设备数量)
LedControl lc = LedControl(maxDinPin, maxClkPin, maxCsPin, 1);
/* ---------------------- 点阵屏显示数据定义 ---------------------- */
// 行人走路动画帧(2帧循环,每帧8行数据,16进制表示点阵列)
const byte MAN_WALKING[2][8] = {
{0x00, 0x1C, 0x1C, 0x1C, 0x28, 0x0A, 0x14, 0x22}, // 第1帧:左腿抬起(二进制0b00011100...)
{0x00, 0x1C, 0x1C, 0x1C, 0x0A, 0x28, 0x08, 0x1C} // 第2帧:右腿抬起(交换左右腿位置)
};
// 禁止通行图标(红色叉号,8行数据构成对角线交叉)
const byte NO_ENTRY[8] = {0x00,0x42,0x24,0x18,0x18,0x24,0x42,0x00};
// 数码管0-9显示编码(共阴数码管,LSB对应最低位LED)
const byte NUM[] = { // 每个元素对应7段数码管的段码(D0-D6分别对应a-g段)
0xFC, // 0b11111100 → a/b/c/d/e/f段亮,g段灭(显示数字0)
0x60, // 0b01100000 → b/c段亮(显示数字1)
0xDA, // 0b11011010 → a/b/d/e/g段亮(显示数字2)
0xF2, // 0b11110010 → a/b/c/d/g段亮(显示数字3)
0x66, // 0b01100110 → b/c/f/g段亮(显示数字4)
0xB6, // 0b10110110 → a/c/d/f/g段亮(显示数字5)
0xBE, // 0b10111110 → a/c/d/e/f/g段亮(显示数字6)
0xE0, // 0b11100000 → a/b/c段亮(显示数字7)
0xFE, // 0b11111110 → 所有段亮(显示数字8)
0xF6, // 0b11110110 → a/b/c/d/f/g段亮(显示数字9)
0x01 // 0b00000001 → 仅小数点亮(用于熄灭显示)
};
/* ---------------------- 全局变量定义 ---------------------- */
// 行人动画控制变量
unsigned long manTime = 0; // 记录上一次动画更新时间
const unsigned long MAN_DELAY = 250; // 动画切换间隔(250毫秒)
int currentFrame = 0; // 当前动画帧索引(0或1)
// 数码管动态扫描控制变量
unsigned long digitRefreshTime = 0; // 记录上一次数码管刷新时间
const unsigned long DIGIT_REFRESH_INTERVAL = 2; // 刷新间隔(2毫秒,确保视觉无闪烁)
void setup() {
// 初始化数码管控制引脚为输出模式
pinMode(dataPin, OUTPUT);
pinMode(clockPin, OUTPUT);
pinMode(latchPin, OUTPUT);
// 初始化交通灯引脚和蜂鸣器引脚为输出模式
pinMode(redLight, OUTPUT);
pinMode(yellowLight, OUTPUT);
pinMode(greenLight, OUTPUT);
pinMode(buzzer, OUTPUT);
// 初始化为低电平(确保移位寄存器初始状态稳定)
digitalWrite(clockPin, LOW);
digitalWrite(latchPin, LOW);
// 初始化MAX7219点阵屏
lc.shutdown(0, false); // 唤醒第0号设备(禁止掉电模式)
lc.setIntensity(0, 8); // 设置亮度等级(0-15,8为中等亮度)
lc.clearDisplay(0); // 清除点阵屏显示
// 初始状态:启动蜂鸣器提示音,点亮红灯,显示禁止图标
tone(buzzer, 200, 80); // 发出200Hz声音,持续80毫秒
digitalWrite(redLight, HIGH); // 点亮红灯
showNoEntry(); // 显示禁止通行图标(点阵屏显示红色叉号)
digitRefreshTime = millis(); // 初始化数码管刷新计时器
}
// 状态控制变量(用于记录当前激活的交通灯状态)
unsigned long digitTime = 0; // 倒计时数值更新时间戳
unsigned long lightTime = 0; // 灯光闪烁时间戳
unsigned long buzzerTime = 0; // 蜂鸣器频率切换时间戳
bool On_Off = false; // 灯光闪烁开关
bool buzzer_on_off = false; // 蜂鸣器声音开关
bool red = true; // 红灯激活状态(true表示当前为红灯模式)
bool green = false; // 绿灯激活状态
bool yellow = false; // 黄灯激活状态
int currentDigit = 0; // 当前显示的数码管位(0为个位,1为十位)
void loop() {
// 核心功能:定期刷新数码管显示(动态扫描实现双位显示)
if (millis() - digitRefreshTime >= DIGIT_REFRESH_INTERVAL) {
digitRefreshTime = millis(); // 更新时间戳
refreshDisplay(); // 执行数码管刷新
}
// 根据当前激活状态执行对应逻辑
if (red) { // 红灯状态
displayRedLight(); // 处理红灯倒计时和灯光控制
showNoEntry(); // 点阵屏显示禁止图标
} else if (green) { // 绿灯状态
displayGreenLight(); // 处理绿灯倒计时和行人动画
animateMan(); // 点阵屏显示行人走路动画
} else if (yellow) { // 黄灯状态
displayYellowLight(); // 处理黄灯倒计时和蜂鸣器提示
showNoEntry(); // 点阵屏显示禁止图标
}
}
/* ---------------------- 状态处理函数 ---------------------- */
// 红灯状态处理函数
void displayRedLight() {
// 每秒更新一次倒计时数值
if (millis() - digitTime >= 1000) {
digitTime = millis(); // 更新时间戳
numToShow--; // 倒计时减1
// 当剩余时间>6秒时,持续点亮红灯,蜂鸣器低频提示
if (numToShow > 6) {
tone(buzzer, 200, 80); // 200Hz声音
digitalWrite(redLight, HIGH); // 红灯常亮
} else { // 剩余时间≤6秒时,蜂鸣器高频提示
tone(buzzer, 500, 80); // 500Hz声音
}
}
// 每200毫秒切换红灯闪烁状态(仅在剩余时间≤6秒时闪烁)
if (millis() - lightTime >= 200) {
lightTime = millis(); // 更新时间戳
On_Off = !On_Off; // 切换开关状态
if (numToShow <= 6) { // 剩余时间≤6秒时执行闪烁
digitalWrite(redLight, On_Off ? HIGH : LOW); // 红灯闪烁
}
}
// 倒计时结束后切换到绿灯状态
if (numToShow < 0) {
red = false; // 关闭红灯状态
green = true; // 激活绿灯状态
numToShow = 10; // 绿灯倒计时初始值10秒
tone(buzzer, 160, 80); // 切换状态提示音
digitalWrite(greenLight, HIGH); // 点亮绿灯
digitalWrite(redLight, LOW); // 关闭红灯
manTime = millis(); // 重置行人动画计时器
}
}
// 绿灯状态处理函数
void displayGreenLight() {
// 每秒更新一次倒计时数值
if (millis() - digitTime >= 1000) {
digitTime = millis(); // 更新时间戳
numToShow--; // 倒计时减1
}
// 根据剩余时间执行不同提示逻辑
if (numToShow > 6) { // 剩余时间>6秒时,绿灯常亮,蜂鸣器短音提示
digitalWrite(greenLight, HIGH); // 绿灯常亮
if (millis() - lightTime >= 50) { // 每50毫秒切换一次提示音
lightTime = millis(); // 更新时间戳
On_Off = !On_Off; // 切换开关状态
if (On_Off) tone(buzzer, 160, 30); // 短促提示音
}
} else { // 剩余时间≤6秒时,绿灯开始闪烁,蜂鸣器长音提示
if (millis() - lightTime >= 200) { // 每200毫秒切换闪烁状态
lightTime = millis(); // 更新时间戳
On_Off = !On_Off; // 切换开关状态
digitalWrite(greenLight, On_Off ? HIGH : LOW); // 绿灯闪烁
if (On_Off) tone(buzzer, 160, 100); // 长提示音
}
}
// 倒计时结束后切换到黄灯状态
if (numToShow < 0) {
yellow = true; // 激活黄灯状态
green = false; // 关闭绿灯状态
numToShow = 5; // 黄灯倒计时初始值5秒
tone(buzzer, 350, 80); // 切换状态提示音
digitalWrite(yellowLight, HIGH); // 点亮黄灯
digitalWrite(greenLight, LOW); // 关闭绿灯
lc.clearDisplay(0); // 清除点阵屏
showNoEntry(); // 显示禁止图标
}
}
// 黄灯状态处理函数
void displayYellowLight() {
// 每秒更新一次倒计时数值,黄灯常亮
if (millis() - digitTime >= 1000) {
digitTime = millis(); // 更新时间戳
numToShow--; // 倒计时减1
digitalWrite(yellowLight, HIGH); // 黄灯常亮
}
// 每500毫秒切换蜂鸣器声音频率
if (millis() - buzzerTime >= 500) {
buzzerTime = millis(); // 更新时间戳
buzzer_on_off = !buzzer_on_off; // 切换声音模式
tone(buzzer, buzzer_on_off ? 300 : 100, 100); // 高低频交替
}
// 倒计时结束后切换回红灯状态
if (numToShow < 0) {
yellow = false; // 关闭黄灯状态
red = true; // 激活红灯状态
numToShow = 12; // 红灯倒计时初始值12秒
tone(buzzer, 200, 80); // 切换状态提示音
digitalWrite(redLight, HIGH); // 点亮红灯
digitalWrite(yellowLight, LOW); // 关闭黄灯
showNoEntry(); // 显示禁止图标
}
}
/* ---------------------- 显示控制函数 ---------------------- */
// 数码管动态扫描刷新函数(实现双位数码管显示)
void refreshDisplay() {
// 分解数值为个位和十位(例如12→个位2,十位1)
int digits[2] = {numToShow % 10, numToShow / 10}; // [个位, 十位]
// 消隐处理:先关闭所有段显示,避免残影
shiftOut(dataPin, clockPin, MSBFIRST, 0x00); // 位选信号全灭(0x00表示不选中任何位)
shiftOut(dataPin, clockPin, LSBFIRST, 0x00); // 段选信号全灭(0x00表示所有段熄灭)
digitalWrite(latchPin, HIGH); // 锁存消隐信号
digitalWrite(latchPin, LOW);
// 显示当前位(0为个位,1为十位)
byte val = 0;
bitSet(val, currentDigit); // 设置当前位选信号(例如currentDigit=0→0b00000001)
// 发送位选信号(MSB优先,最高位对应高位数码管)
shiftOut(dataPin, clockPin, MSBFIRST, val); // 选择显示位(个位或十位)
// 发送段选信号(LSB优先,最低位对应数码管a段)
shiftOut(dataPin, clockPin, LSBFIRST, NUM[digits[currentDigit]]); // 显示对应数字的段码
// 锁存显示数据
digitalWrite(latchPin, HIGH); // 上升沿锁存数据
digitalWrite(latchPin, LOW);
// 切换到下一位(个位→十位→个位循环)
currentDigit = (currentDigit + 1) % 2;
}
// 行人走路动画函数(在点阵屏循环显示两帧动画)
void animateMan() {
if (millis() - manTime >= MAN_DELAY) { // 达到动画切换间隔时更新
manTime = millis(); // 更新时间戳
lc.clearDisplay(0); // 清除点阵屏显示(避免残留上一帧)
// 显示当前动画帧(逐行设置点阵数据)
for (int row = 0; row < 8; row++) {
lc.setRow(0, row, MAN_WALKING[currentFrame][row]); // 设置第0号设备的第row行数据
}
// 切换到下一帧(0→1→0循环)
currentFrame = (currentFrame + 1) % 2;
}
}
// 显示禁止图标函数(在点阵屏显示红色叉号)
void showNoEntry() {
// 逐行设置禁止图标数据(8行组成对角线交叉图案)
for (int row = 0; row < 8; row++) {
lc.setRow(0, row, NO_ENTRY[row]); // 设置第0号设备的第row行数据为禁止图标对应行
}
}