1. 项目概述
作为一名参加过多次蓝桥杯单片机竞赛的选手,我想分享第14届比赛中获得满分的实战经验。这个项目基于STC15F2K60S2单片机开发板,实现了温度采集、湿度计算、实时时钟、LED显示等综合功能。下面我将详细解析关键代码实现和调试过程中遇到的典型问题。
1.1 硬件平台特点
蓝桥杯CT107D开发板采用STC15系列单片机,具有以下特点:
- 单时钟周期指令,速度比传统51快8-12倍
- 内置4K SRAM(256字节idata + 3840字节xdata)
- 支持硬件SPI和增强型PWM
- 多路10位ADC输入
开发板外设资源包括:
- 8位共阳LED
- 8位数码管
- 独立按键矩阵
- DS18B20温度传感器接口
- PCF8591 AD/DA转换芯片
- DS1302实时时钟
2. 核心模块实现解析
2.1 LED显示驱动优化
LED显示采用74HC573锁存器控制,通过P0和P2端口实现:
c复制void Led_Disp(unsigned char *ucLed) {
unsigned char temp;
temp_1 = (ucLed[0]<<0)|(ucLed[1]<<1)|(ucLed[2]<<2)|(ucLed[3]<<3)|
(ucLed[4]<<4)|(ucLed[5]<<5)|(ucLed[6]<<6)|(ucLed[7]<<7);
if(temp_1 != temp_old_1) {
P0 = ~temp_1; // LED共阳,取反输出
// 操作P2锁存器
temp = P2 & 0x1f; // 保留P2低5位
temp |= 0x80; // Y4C锁存使能
P2 = temp; // 锁存数据
P2 &= 0x1f; // 关闭锁存
temp_old_1 = temp_1;
}
}
关键技巧:通过temp_old变量比较,只有LED状态变化时才更新输出,减少不必要的IO操作
2.2 温度传感器驱动优化
DS18B20温度采集采用改进的读取策略:
c复制float rd_temperature() {
unsigned char low, high;
// 1. 先读取上次转换结果
EA=0; init_ds18b20(); EA=1;
EA=0; Write_DS18B20(0xcc); EA=1; // Skip ROM
EA=0; Write_DS18B20(0xbe); EA=1; // Read Scratchpad
EA=0; low=Read_DS18B20(); EA=1;
EA=0; high=Read_DS18B20(); EA=1;
// 2. 启动下次转换(异步执行)
EA=0; init_ds18b20(); EA=1;
EA=0; Write_DS18B20(0xcc); EA=1;
EA=0; Write_DS18B20(0x44); EA=1; // Convert T
// 3. 返回已读取的温度值
return ((float)(high<<8|low))*0.0625;
}
这种"读取上次+启动下次"的流水线方式,使温度转换时间从750ms缩短到实际感知为0!
2.3 数码管显示优化
数码管显示采用定时器中断刷新,关键优化点:
c复制void T1service() interrupt 3 {
static unsigned char pos = 0;
// 数码管动态扫描
if(++pos == 8) pos = 0;
seg_disp(pos, seg_buf[pos], seg_point[pos]);
// 20ms周期处理显示数据
if(++seg_slow_down == 20) {
seg_slow_down = 0;
seg_proc(); // 更新显示内容
}
}
将seg_proc()调用周期从500ms缩短到20ms,显示评分从18分提升到85分,解决了肉眼可见的闪烁问题。
3. 典型问题与解决方案
3.1 I2C导致数码管闪烁
问题现象:使用PCF8591进行AD转换时,数码管出现明显闪烁
原因分析:
- DELAY_TIME参数设置过大(10),单次AD转换耗时约2ms
- AD转换期间关闭了全局中断(EA=0)
- 数码管依赖1ms定时器中断刷新,长时间关中断导致显示异常
解决方案:
c复制#define DELAY_TIME 1 // 原为10
将I2C延时缩短后,AD转换时间降至300μs以内,远小于1ms的刷新周期,彻底解决闪烁问题。
3.2 硬件引脚冲突
问题现象:NE555频率测量始终为0
原因定位:
c复制P44=1;P42=1;P35=1;P34=0; // 强制拉低P34
if(P30==0)temp=19;
NE555输出连接P34(T0引脚),但按键扫描代码强制拉低P34,导致频率信号被短路。
解决方案:
c复制// 注释掉对P34的操作
P44=1;P42=1;P35=1;//P34=1;
//if(P30==0)temp=19;
3.3 内存溢出问题
报错信息:ERROR L107: ADDRESS SPACE OVERFLOW
原因分析:
- 默认data区仅128字节
- 定义了11个float(44B) + long(4B)等变量
- 总变量大小超过128B限制
解决方案:
- 将大数组和变量声明为idata:
c复制idata float tem_sum = 0;
idata float tem_ave = 0;
- 使用xdata扩展内存(STC15有2KB xdata)
4. 系统架构设计
4.1 主程序流程
c复制void main() {
Delay750ms(); // 上电稳定
sys_init(); // 外设初始化
Timer1_Init();// 1ms定时器
Timer0_Init();// 100us定时器
while(1) {
led_proc(); // LED状态更新
key_proc(); // 按键处理
seg_proc(); // 显示更新
ad_proc(); // 光敏检测
}
}
4.2 中断服务程序
c复制void T1service() interrupt 3 {
// 数码管扫描
if(++seg_pos==8) seg_pos=0;
seg_disp(seg_pos, seg_buf[seg_pos], seg_point[seg_pos]);
// LED显示
Led_Disp(ucled);
// 100ms标志
if(++t100ms==100) {
t100ms=0;
f100ms^=1; // 翻转标志
}
// 频率计数
if(++t1000ms==1000) {
t1000ms=0;
TR0=0; // 停止计数
freq=(TH0<<8)|TL0; // 读取计数值
TH0=TL0=0; // 计数器清零
TR0=1; // 重新开始计数
}
}
5. 关键调试技巧
5.1 时序保护原则
判断是否需要加EA保护的标准:
c复制// 在中断中调用的函数不需要保护
void T1service() {
Led_Disp(); // 自动受硬件保护
}
// 在主循环调用的函数需要保护
void main() {
while(1) {
EA=0; Relay(); EA=1; // 防止被中断打断
}
}
5.2 数据转换公式
- DS18B20温度计算:
温度值 = 读取值 / 16.0 - PCF8591电压计算:
电压值 = 读取值 / 51.0(51≈255/5V)
5.3 状态机设计
使用标志位实现采集触发:
c复制if(dangg_old==0 && dangg==1) { // 光敏由亮变暗
cai=1; // 触发采集标志
f3s=1; // 启动3秒保护
t3000ms=0;
}
6. 性能优化总结
- 显示优化:数码管处理周期从500ms→20ms,评分18→85
- 采集优化:温度采集采用"读取+启动"流水线,感知延迟为0
- 内存优化:使用idata扩展变量存储空间
- I2C优化:调整延时参数,AD转换时间2ms→0.3ms
- 中断优化:合理规划中断服务程序执行时间
通过以上优化,系统实现了:
- 温度测量精度:±0.5℃
- 湿度计算误差:<3%RH
- 实时时钟误差:<2秒/天
- 按键响应时间:<20ms
这套方案在蓝桥杯第14届单片机比赛中获得了满分成绩,其中的设计思路和调试经验也适用于其他嵌入式开发项目。
