The BIT.SH1001 Smart Traffic Light Theme Expansion Board integrates three 10mm red/yellow/green LEDs for simulating traffic signals, a six‑segment LED running‑light array, a single‑digit display directly connected to I/O pins, a two‑digit display with register, an 8×8 dot‑matrix screen, a 1W audio amplifier (with onboard speaker), and two tactile buttons. When used with BIT.U1 or BIT.S3 development boards, it enables progressive learning from basic LED‑blinking programs to running‑light sequences, countdown timers, button‑based quiz games, electronic dice, rock‑paper‑scissors, music playback, and finally a fully integrated smart traffic‑light project—allowing users to build skills intuitively through hands‑on projects. Designed for use alongside Bit‑Tree’s companion textbook *Project‑Based Creative Programming*, the card features a front panel of black light‑transmitting acrylic and a fully transparent rear acrylic panel for easy observation. It is supplied in an eco‑friendly PP plastic case for storage and protection and includes a detailed product manual with complete specifications and interface descriptions.
//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行数据为禁止图标对应行
}
}