1. 项目概述:51单片机公交报站系统开发实录
这个项目是我去年给本地公交公司做的一个小型报站系统原型,用最经典的STC89C52单片机实现。相比市面上的商业方案,这个DIY版本虽然功能简单,但胜在成本低廉(整套硬件不到50元)且完全开源可控。核心功能包括:通过按键切换上下行路线、手动触发报站、实时时间显示与设置。别看功能简单,要实现稳定运行需要处理好GPIO控制、定时器中断、状态机编程等多个关键技术点。
2. 硬件设计与Proteus仿真搭建
2.1 核心元件选型与电路设计
硬件部分采用最小系统板+外设的方案:
- 主控:STC89C52RC(兼容8051指令集,8K Flash存储足够存放站名数据)
- 显示:1602 LCD(带背光,用于显示站名和时间)
- 输入:4个轻触按键(上、下、确认、返回)
- 指示:2个LED(红/绿指示当前方向)
- 音频:无源蜂鸣器(通过PWM产生不同提示音)
关键提示:Proteus仿真时务必添加LCD背光供电电路,实际项目中可用10K电位器调节对比度。我曾遇到仿真时LCD不显示的问题,最后发现是忘了接背光电源。
2.2 Proteus仿真要点
仿真电路搭建有几个易错点需要特别注意:
- 单片机时钟电路:添加11.0592MHz晶振和30pF负载电容
- LCD上拉电阻:数据线需接4.7K上拉电阻
- 蜂鸣器驱动:添加NPN三极管(如2N3904)驱动,直接IO口推不动
- 按键防抖:硬件上可并联104电容,但本项目采用软件消抖
仿真元件清单:
| 元件类型 | Proteus名称 | 参数说明 |
|---|---|---|
| 单片机 | AT89C52 | 兼容STC89C52 |
| LCD | LM016L | 16x2字符型 |
| 蜂鸣器 | SOUNDER | 接三极管驱动 |
| 按键 | BUTTON | 轻触式 |
3. 软件架构与核心代码解析
3.1 主程序流程图设计
系统采用事件驱动架构,主循环结构如下:
c复制void main() {
init_all(); // 初始化外设
while(1) {
key = key_scan(); // 扫描按键
handle_key(key); // 处理按键事件
update_display(); // 刷新显示
check_auto_play(); // 自动报站检测
}
}
3.2 状态机按键消抖实现
传统delay消抖会阻塞系统运行,这里采用状态机方案:
c复制uchar key_scan() {
static uchar key_state = 0;
uchar key_press = (P2 & 0x0f);
switch(key_state) {
case 0: // 初始状态
if(key_press) key_state = 1;
break;
case 1: // 消抖确认
if(key_press) {
key_state = 2;
return key_press; // 返回有效键值
}
else key_state = 0;
break;
case 2: // 等待释放
if(!key_press) key_state = 0;
break;
}
return 0;
}
实战经验:状态机消抖的检测周期建议设置为10-20ms,可通过定时器中断实现。我曾测试过5ms周期,发现对某些机械按键仍会出现误触发。
3.3 方向切换与站序管理
上下行使用不同的站序数组存储:
c复制code uchar up_stations[] = {"1.Start 2.Mid 3.End"};
code uchar down_stations[] = {"3.End 2.Mid 1.Start"};
方向切换时的处理逻辑:
c复制if(key == KEY_UP) {
direction = UP;
current_station = 0; // 重置为起点站
LCD_ShowString(1,9,"UP ");
LED_GREEN = 0; // 绿灯亮表示上行
}
else if(key == KEY_DOWN) {
direction = DOWN;
current_station = 0;
LCD_ShowString(1,9,"DOWN");
LED_RED = 1; // 红灯亮表示下行
}
4. 关键功能实现细节
4.1 时间设置功能实现
时间设置采用三级菜单结构(时-分-秒):
c复制void time_set() {
uchar select = 0; // 0:时 1:分 2:秒
while(1) {
// 显示时间,当前编辑位反白显示
if(select == 0) LCD_ShowReverse(1,4,2); // 时
else if(select == 1) LCD_ShowReverse(1,7,2); // 分
else LCD_ShowReverse(1,10,2); // 秒
key = key_scan();
if(key == KEY_ESC) break;
if(key == KEY_UP) adjust_time(select, 1); // 加
if(key == KEY_DOWN) adjust_time(select, -1); // 减
if(key == KEY_ENT) select = (select+1)%3; // 切换编辑位
}
}
显示技巧:1602 LCD的反白显示通过发送0x0D命令开启光标闪烁,再配合LCD_SetCursor定位,比自行实现反白显示更稳定。
4.2 报站触发与音频处理
报站触发采用查询方式(实际项目建议用外部中断):
c复制if(key == KEY_ENT) {
play_station(direction, current_station);
current_station = (current_station+1)%TOTAL_STATIONS;
}
蜂鸣器驱动使用PWM方波:
c复制void beep(uint freq, uint duration) {
uint period = 1000000 / freq; // 计算周期(us)
uint cycles = duration * 1000 / period;
for(uint i=0; i<cycles; i++) {
BEEP = ~BEEP;
delay_us(period/2);
}
}
音频优化技巧:
- 不同站点使用不同频率提示音(800Hz/1KHz交替)
- 长音(500ms)表示到站,短音(200ms)表示离站
- 实际项目中可替换为语音芯片播放录制好的站名
5. 系统优化与问题排查
5.1 定时器资源分配
本系统需要两个定时器:
- 定时器0:1ms中断,用于系统时钟基准
- 定时器1:波特率发生器(如需串口调试)
定时器0初始化代码:
c复制void timer0_init() {
TMOD &= 0xF0; // 清除T0配置位
TMOD |= 0x01; // 16位定时器模式
TH0 = 0xFC; // 1ms@11.0592MHz
TL0 = 0x66;
ET0 = 1; // 使能中断
TR0 = 1; // 启动定时器
}
5.2 常见问题与解决方案
-
LCD显示乱码:
- 检查初始化时序,确保延时足够
- 测量VO引脚电压(通常0.5-1V)
- 重新烧录程序时先断电复位
-
按键响应异常:
- 检查按键电路是否接触不良
- 调整消抖时间(10-20ms为宜)
- 确认IO口模式设置为准双向
-
蜂鸣器不响:
- 测量驱动三极管基极电压
- 尝试降低频率(500Hz-2KHz)
- 检查蜂鸣器极性(有源/无源类型)
6. 项目扩展方向
这个基础版本还可以进一步扩展:
- 增加SD卡存储,实现语音报站
- 添加GPS模块,实现自动定位报站
- 改用STC15系列单片机,支持硬件PWM
- 增加无线模块,实现远程更新站名
实际部署时需要注意:
- 车载电源处理(建议使用DC-DC稳压模块)
- 结构防震设计(减震海绵固定PCB)
- 环境防护(电路板喷涂三防漆)
