参加蓝桥杯单片机竞赛的同学都知道,IAP15F2K61S2这颗国产芯片是比赛指定的主控MCU。第一次拿到开发板时,我也被上面密密麻麻的外设芯片吓到了 - DS18B20温度传感器、DS1302时钟芯片、PCF8591模数转换器、AT24C02存储器、NE555频率发生器...这么多芯片要怎么驱动?难道要把每个的代码都背下来吗?
其实完全不用!我参加比赛时最大的心得就是:学会看芯片手册比死记硬背重要100倍。官方提供的芯片资料里藏着所有秘密,只要掌握正确的查阅方法,你会发现驱动这些外设就像搭积木一样简单。举个例子,DS18B20的单总线时序要求延时必须扩大12倍,这个关键信息就写在手册的"电气特性"章节里。
刚开始我总想走捷径,直接在网上找现成代码复制粘贴。结果比赛时遇到代码跑不通就傻眼了 - 因为根本不知道为什么要那样写。后来我强迫自己先看手册再写代码,虽然前期慢点,但后期调试效率反而更高。比如PCF8591的I2C地址是0x90,这个值不是随便定的,而是由芯片的A0-A2引脚电平决定的,手册里写得清清楚楚。
提示:STC官网可以下载IAP15F2K61S2的完整资料包,里面包含所有外设的参考手册,备赛时一定要放在手边随时查阅。
DS18B20用的是单总线协议,所有通信只用一根数据线完成。刚开始我觉得这很神奇,直到看了手册才明白原理 - 它通过精确的时序来区分0和1。写代码时最容易出错的就是延时时间,因为IAP15F2K61S2的时钟周期和51标准不同,必须把延时扩大12倍。
这里有个实用技巧:用示波器抓取DQ引脚波形。当我第一次看到真实的通信波形时,瞬间理解了为什么初始化要先把总线拉低480μs。手册上这个时间标注在"复位脉冲时序"图里,实际调试时如果发现设备无响应,第一个要检查的就是这个复位脉冲是否达标。
下面是我优化后的初始化函数,加入了超时判断:
c复制bit init_ds18b20(void) {
bit initflag = 1;
unsigned char retry = 0;
DQ = 1;
Delay_OneWire(12);
while(DQ && retry<200) { // 等待总线释放
retry++;
Delay_OneWire(1);
}
if(retry >= 200) return 1;
DQ = 0;
Delay_OneWire(80); // 精确480μs复位脉冲
DQ = 1;
Delay_OneWire(10); // 等待15-60μs
initflag = DQ; // 0表示存在设备
Delay_OneWire(5);
return initflag;
}
读取温度值时最容易忽略的是转换等待时间。DS18B20需要最多750ms来完成温度转换,如果没等够就读数据,会得到前一次的值。我的做法是在启动转换后加个循环检测:
c复制float get_temp() {
init_ds18b20();
Write_DS18B20(0xCC); // 跳过ROM
Write_DS18B20(0x44); // 启动转换
while(!DQ); // 等待转换完成
init_ds18b20();
Write_DS18B20(0xCC);
Write_DS18B20(0xBE); // 读暂存器
low = Read_DS18B20();
high = Read_DS18B20();
return (high<<8 | low) * 0.0625;
}
比赛时遇到过更棘手的问题 - 多个DS18B20并联。这时需要用搜索ROM命令来区分设备,代码复杂度直线上升。建议新手先用单个设备练手,掌握了基本时序再挑战高级应用。
DS1302有个很隐蔽的坑:上电时写保护位默认是开启的。如果不先关闭写保护,所有设置时间操作都会失败。这个细节在手册的第7页有说明,但字体很小容易被忽略。我的初始化流程是这样的:
实际调试发现,DS1302对时序要求比DS18B20宽松很多,但要注意SCK时钟频率不能超过2MHz。有个队友因为把延时函数写错,导致SCK频率超标,结果芯片完全不响应。
DS1302存储的时间是BCD格式,需要转换成十进制才能显示。这里有个优化技巧:用查表代替除法运算,特别适合51这种没有硬件除法的平台:
c复制// BCD转十进制查表法
const unsigned char bcd2dec[] = {
0,1,2,3,4,5,6,7,8,9,
10,11,12,13,14,15,16,17,18,19,
... // 省略部分数据
90,91,92,93,94,95,96,97,98,99
};
void read_time(unsigned char *time) {
time[0] = bcd2dec[Read_Ds1302_Byte(0x85)]; // 秒
time[1] = bcd2dec[Read_Ds1302_Byte(0x83)]; // 分
time[2] = bcd2dec[Read_Ds1302_Byte(0x81)]; // 时
}
PCF8591用的是I2C协议,相比单总线复杂不少。最关键的是理解起始条件、停止条件和应答位。我刚开始总是搞混SDA和SCL的时序关系,后来发现用示波器看波形特别管用 - 起始条件是SCL高电平时SDA出现下降沿,这个画面看一次就记住了。
手册第8页的时序图给出了关键参数:
调试时如果遇到设备无应答,建议按这个顺序检查:
PCF8591有4路模拟输入,最常用的是AN1接光敏电阻,AN3接滑动变阻器。这里有个重要细节:每次转换后要丢弃第一个读数,因为芯片内部需要稳定时间。我的采集函数是这样写的:
c复制unsigned char read_analog(unsigned char channel) {
unsigned char val;
pcf8591_write(0x40 | (channel<<4)); // 控制字
val = pcf8591_read(); // 丢弃第一次
return pcf8591_read(); // 返回稳定值
}
比赛时经常需要把模拟量转换成百分比显示。注意不要简单除以255,因为传感器输出往往不是线性的。更好的做法是采集最小最大值后做映射:
c复制// 校准后的光强百分比
unsigned char light_percent() {
static unsigned char min=255, max=0;
unsigned char val = read_analog(1);
if(val < min) min = val;
if(val > max) max = val;
return (val - min) * 100 / (max - min);
}
AT24C02虽然是I2C设备,但它的地址和PCF8591不同(0xA0写,0xA1读)。最需要小心的是页写入限制 - 每次最多写8字节,超过会自动回卷。我有次不小心连续写了10字节,结果前2字节被后2字节覆盖了。
可靠的写入方法应该是:
c复制void safe_write(unsigned char addr, unsigned char *data, unsigned char len) {
unsigned char i;
for(i=0; i<len; i++) {
at24c02_write(addr+i, data[i]);
Delay_OneWire(500); // 等待5ms写入周期
}
}
用NE555测频率需要两个定时器配合:
这里有个精度提升技巧:延长测量时间可以降低误差。比如测10秒再除以10,分辨率就能提高10倍。我的中断服务程序是这样实现的:
c复制void Timer1_Isr() interrupt 3 {
static unsigned int cnt=0;
TL1 = 0x18; // 重装初值
TH1 = 0xFC;
if(++cnt >= 10000) { // 10秒测量周期
freq = (TH0<<8 | TL0) / 10;
TH0 = TL0 = 0;
cnt = 0;
}
}
调试时发现NE555的输出频率会受电源电压影响。如果要求高精度,应该先用稳压源供电,或者增加软件校准环节。比赛中我们通过测量已知频率来建立校正公式,最终把误差控制在0.1%以内。