PCF8591是蓝桥杯单片机竞赛中常用的模数转换芯片,它通过I2C总线与单片机通信,能够将模拟信号转换为数字信号。在实际项目中,我们经常需要测量各种模拟量,比如温度、光照强度、电压等,这时候PCF8591就派上了大用场。
我刚开始接触这个芯片时,最头疼的就是理解它的工作原理。后来发现可以把它想象成一个"翻译官"——把模拟世界的连续信号(比如电压变化)翻译成单片机能够理解的数字信号。芯片内部有4个模拟输入通道,1个模拟输出通道,8位分辨率,这意味着它能把0-5V的电压分成256个等级(2^8=256)。
在蓝桥杯竞赛中,最常见的应用场景就是测量滑动变阻器的电压值。比如题目要求读取Rb2的电压并显示在数码管上,范围0.00~5.00V。这个案例看似简单,但包含了硬件连接、I2C通信、数据转换、数码管显示等多个关键技术点,非常考验选手的综合能力。
PCF8591的硬件连接有几个关键点需要注意。首先是电源连接,VDD接5V,VSS接地,这是芯片工作的基础。然后是模拟输入,AIN0-AIN3可以接各种模拟信号源,在我们的案例中,AIN3接滑动变阻器Rb2的输出端。
I2C总线的连接特别重要,SCL(时钟线)接P2^0,SDA(数据线)接P2^1。这里有个坑我踩过——忘记加上拉电阻。I2C总线需要4.7kΩ的上拉电阻,否则通信会不稳定。CT107D开发板已经内置了这些电阻,但如果是自己搭建电路,这个细节千万不能忽略。
I2C通信的底层驱动是整个项目的核心。代码中定义了总线启动、停止、发送字节、接收字节等基本操作。这些函数看起来简单,但时序要求非常严格。
比如IIC_Start()函数,它需要先拉高SDA和SCL,然后拉低SDA,最后拉低SCL。这个顺序不能错,时间间隔也要合适。DELAY_TIME定义为5,这个值需要根据单片机时钟频率调整,太快可能导致通信失败。
c复制void IIC_Start(void) {
SDA = 1;
SCL = 1;
IIC_Delay(DELAY_TIME);
SDA = 0;
IIC_Delay(DELAY_TIME);
SCL = 0;
}
在实际调试时,我建议用示波器观察SDA和SCL的波形,确保时序符合I2C规范。如果没条件用示波器,可以通过LED指示灯简单判断通信是否成功。
read_adc()函数负责从指定通道读取ADC值。它首先发送启动信号,然后发送设备地址(0x90表示写,0x91表示读),接着发送通道号(0x03对应AIN3),最后读取转换结果。
这里有个细节值得注意:PCF8591的地址字节最后一位表示读写方向,0表示写,1表示读。所以写地址是0x90,读地址是0x91。我曾经因为搞混这个导致一整天调试无果。
c复制unsigned char read_adc(unsigned char add) {
unsigned char val;
IIC_Start();
IIC_SendByte(0x90); //写命令
IIC_SendByte(add); //通道号
IIC_Start();
IIC_SendByte(0x91); //读命令
val=IIC_RecByte(); //读取转换值
IIC_Stop();
return val;
}
直接从PCF8591读取的值范围是0-255,而我们需要显示0.00-5.00V的电压值。这就需要进行数据转换。
代码中使用value=read_adc(0x03)*1.96+0.5这个公式,其中1.96≈500/255,0.5是为了四舍五入。这样就把0-255映射到了0-500,方便数码管显示时直接取百位、十位和个位作为整数和小数部分。
在实际应用中,我发现这个转换可能存在误差。为了提高精度,可以采用两点校准法:先测量0V时的ADC值(通常应该是0),再测量一个已知电压(比如用稳压源提供2.5V),然后计算斜率。这样可以消除系统误差。
数码管显示部分需要将转换后的数值分解为三个数字:百位、十位和个位。百位对应电压的整数部分,十位和个位对应小数部分。
代码中使用了dsp_code数组来存储数码管的段码,dsp_show数组存储要显示的内容。特别注意dsp_show[5]的最高位被清零(&0x7f),这是为了在百位数码管上显示小数点。
c复制dsp_show[5]=dsp_code[value/100]&0x7f; //整数部分带小数点
dsp_show[6]=dsp_code[value/10%10]; //第一位小数
dsp_show[7]=dsp_code[value%10]; //第二位小数
在main函数中,有几个优化点值得关注。首先是初始化后读取10次ADC值但不显示,这是为了等待ADC稳定。ADC芯片上电后需要一定时间才能达到稳定状态,直接读取可能导致数据不准。
然后是200ms刷新一次的机制,通过count_adc计数器实现。这个刷新率既保证了显示的实时性,又避免了过于频繁的读取影响系统性能。在实际测试中,我发现刷新率过高会导致数码管闪烁,过低则显得反应迟钝,200ms是个不错的折中。
最后是操作ADC时关闭中断(EA=0),防止中断服务程序干扰ADC读取过程。这个细节很容易被忽略,但非常重要,特别是在系统中有多个中断源时。