本次设计利用AT89C51作为主控,利用74HC573作为数码管驱动芯片,数码管为六位共阴数码管,利用独立按键进行用户交互,实现温度/电压二者之间的转换,利用8位LED进行状态显示,利用IIC驱动PCF8591进行ADC获取数值和DAC模拟输出,通过NTC热敏电阻的特性,转换成温度值,利用ADC转换进行电压输入和模拟输出实现测量电压越高,输出电压越高。
一、硬件设计
1、PCF8591
PCF8591是一个单片集成、单独供电、低功耗、8-bit CMOS数据获取器件。PCF8591具有4个模拟输入、1个模拟输出和1个串行I²C总线接口。PCF8591的3个地址引脚A0, A1和A2可用于硬件地址编程,允许在同个I2C总线上接入8个PCF8591器件,而无需额外的硬件。在PCF8591器件上输入输出的地址、控制和数据信号都是通过双线双向I2C总线以串行的方式进行传输。相比较与ADC0808,ADC0804,PCF8591具有节省IO口,转换稳定等特性,且PCF8591有四个ADC输入,一个DAC输出口,针对本次设计的要求是完美符合的。如下
2、74HC573驱动数码管
74HC573是拥有八路输出的透明锁存器,输出为三态门,是一种高性能硅栅CMOS器件,是一种锁存芯片,74HC573一般用于扩展单片机的IO口,一般情况下驱动一个6位数码管需要大于8+6=14个IO口,但是在利用74HC573的情况下只需要8+2=10个IO口,足足节省了4个IO口。
二、软件设计
1、IIC驱动PCF8591
(1)IIC协议驱动代码
IIC驱动代码具有一般的通用性,通过IIC的读写时序可以得出以下代码,这里不过多演示IIC协议原理。
#include "delay.h"
#include "iic.h"
void delay_5us()
{
unsigned char i;
i = 12;
while (--i);
}
//金丝草;使用iic通讯方案1
//方案1:每个函数结束后未释放SDA线,且SCL=1; STOP信号均为高。起始前面处理一下
//方案2:每个函数结束后释放SDA线,且SCL=0;STOP信号均为高。后面信号处理一下。
//本次采用方案1
//1、IIC开始信号
void START_IIC(void)
{
SCL = 0;
SDA = 1;
delay_5us();
SCL = 1;
delay_5us();
SDA = 0;
delay_5us();
}
//2、IIC结束信号
void STOP_IIC(void)
{
SCL = 0;
SDA = 0;
delay_5us();
SCL = 1;
delay_5us();
SDA = 1;
delay_5us();
}
//器件的地址共计7位,第8位为0表示要写数据给设备
//为1表示要读取设备的数据
//3、IIC写数据
void write_1_byte_IIC(unsigned char datax)
{
unsigned char i;
for (i = 0x80;i != 0;i >>= 1)
{
SCL = 0;
delay_5us();
if ((datax & i) == i) //马来逼,这个地方原始数据为datax&i==1 &的结果为16进制应该与i比较,并且&没有==优先级高
SDA = 1;
else
SDA = 0;
delay_5us();
SCL = 1;
delay_5us();
}
}
//4、IIC读数据
unsigned char read_1_byte_IIC(void) //读取1字节数据
{
unsigned char i;
unsigned char datax = 0;
for (i = 0x80;i != 0;i >>= 1)
{
SCL = 0;
SDA = 1; //释放SDA线
delay_5us();
SCL = 1;
delay_5us();
if (SDA)
datax |= i;//读取SDA线上的数据
delay_5us();
}
return(datax);
}
//5、读取接收ACK应答信号
bit read_ACK_IIC(void)
{
bit ACK = 0;
SCL = 0;
SDA = 1;
delay_5us();
SCL = 1;
delay_5us();
ACK = SDA;
return(ACK);//如果ACK=1,则未收到应答 //如果ACK=0,则收到应答
}
//6、发送应答ACK信号
void write_ACK_IIC(bit ACK)
{
SCL = 0;
SDA = ACK; //ACK=1,表示不发送应答,=0表示发送应答
delay_5us();
SCL = 1;
delay_5us();
}
(2)PCF8591驱动代码
PCF8591驱动主要是利用IIC协议进行PCF8591寄存器的读写操作,一般情况下,遵循PCF8591的一般读写就可以完成功能,这里不过多解释。如下:
//PCF8591 iic通讯时序还没有AT24C02快呢,iic器件遵守iic通讯协议,故,在这里就使用AT24C02器件的iic通讯协议了
//PCF8591芯片地址为1001 xxxy最后4位看原理图,在本单片机里芯片地址位1001 000y 即0x9?其中y位表示是读取器件还是向器件写入
//读器件使用0x91,向器件写入数据使用0x90
//PCF8591使用逐次逼近型A/D转换,转换时间us级。
// SCL = P2_1;//时钟连接P2.1口
// SDA = P2_0;//时钟连接P2.0口
/*高位 地位
0 x x x 0 x x x
bit7位默认为0
bit6位是否允许模拟电压输出,在DA转换时设置1,AD转换设置0或1均可。
bit5和bit4位是选择模拟电压输出方式,一般位00单端输入方式。
bit3位默认为0
bit2位第是自动增量使能位,如果自动增量(auto-increment)标志置1,每次A/D 转换后通道号将自动增加。
bit1和bit0位是在AD转换时选择哪一个通道输入的电压转换位数字量 00表示打开通道AIN0 01表示打开通道AIN1 10表示打开通道AIN2 11表示打开通带AIN3
*/
//AIN0通道连接光敏电阻,AIN1通道连接热敏电阻,AIN2通道连接模拟信号输入(滑动电阻器),AIN3未连接其他器件
#include "iic.h"
//本代码加入了读取ACK问题,使用了goto语句
//AD转换,读取com通道的8位数据,0x00/0x40表示打开通道AIN0 0x01/0x41表示打开通道AIN1 0x02/0x42表示打开通道AIN2 0x03/0x43表示打开通带AIN3
unsigned char read_AD_data(unsigned char com)
{
unsigned char dat = 0;
unsigned char i = 0;
read_ad_data:
START_IIC();
write_1_byte_IIC(0x90);//准备向pcf8591器件写入数据,此器件的地址为1001 A0 A1 A2 R/W
if (!read_ACK_IIC())//若接收到ACK则与器件握手成功
{
write_1_byte_IIC(com);//向器件写入使用哪个AD通到开始采集数据,0x00/0x40表示通道AIN0 0x01/0x41表示通道AIN1 0x02/0x42表示通道AIN2 0x03/0x43表示通带AIN3
if (!read_ACK_IIC())
{
START_IIC();//和at24c02 读取数据有点类似 马来币之前没有写 手册英文没有看到这个地方如何读取的,参考别人写的代码
write_1_byte_IIC(0x91);//准备读取pcf8591发来的数据
if (!read_ACK_IIC())
{
dat = read_1_byte_IIC();
write_ACK_IIC(1);//主机接收到数据向从设备不发送应答信号
STOP_IIC(); //结束本次通讯
return dat;
}
else
{
STOP_IIC();
goto read_ad_data;
}
}
else
{
STOP_IIC();
goto read_ad_data;
}
}
else
{
i++;
if (i == 255)
{
return i;//如果握手失败255次则直接结束本函数,并向函数返回运行结果255;
}
STOP_IIC();
goto read_ad_data;
}
}
//DA转换,输出模拟量通过芯片的AOUT端口输出
unsigned char write_DA_data(unsigned char dat)//0~255对应0V~5V
{
unsigned char i = 0;
write_da_data:
START_IIC();
write_1_byte_IIC(0x90);
if (read_ACK_IIC() == 0)
{
write_1_byte_IIC(0x40);//允许器件模拟输出,并且不自动增加输出端口
if (!read_ACK_IIC())
{
write_1_byte_IIC(dat);
if (!read_ACK_IIC())
{
STOP_IIC();
return i;//如果握手失败255次则直接结束本函数,并向函数返回运行结果255;
}
else
{
STOP_IIC();
goto write_da_data;
}
}
else
{
STOP_IIC();
goto write_da_data;
}
}
else
{
i++;
if (i == 255)
{
return i;//如果握手失败255次则直接结束本函数,并向函数返回运行结果255;
}
STOP_IIC();
goto write_da_data;
}
}
//数字量转换成模拟电压公式如下
unsigned char convert_data(unsigned char DA)
{
unsigned char i = 0;
i = (unsigned char)(DA * 5 / 256.0 + 0.0005);//pcf8591内部数字量转化为模拟量的计算公式
return i;
}
2、74HC573驱动数码管
74HC595驱动一般写入时序是,将数据送入并行总线,LE引脚拉低,操作简单,本次设计利用6位共阴数码管,本次设计进行函数的深层次封装,得出如下代码:
sbit wei = P2^7;
sbit duan = P2^6;
unsigned duan_[]={0x3f,0x06,0x5b,0x4f,0x66,0x6d,0x7d,0x07,0x7f,0x6f};
void nixie(u8 pos, u8 num, u8 dot)
{
wei=1;
P0 = ~(0x01 << pos-1);
wei=0;
duan=1;
if(dot)P0 = duan_[num]| 0x80;
else P0 = duan_[num];
duan=0;
delay(1);
P0=0xFF;
}
三、功能演示
1、初始状态
程序初始运行,显示温度值,由于本次设计的温度下限是30,温度上限是40,所以此时灯光是绿灯。
2、温度过高或者过低
温度过高或者过低的时候,都会报警,当温度低于下限的时候,黄灯会进行闪烁,当温度高于上限的时候,红灯会闪烁,本次设计的上限是40,下限是30.演示如下图:
3、电压测量模式
显示屏上一般会显示两个电压值,分别是左电阻,一个是右电阻,通过拉动滑动变阻器,电压值会进行改变,此时电压处在正常区间,绿灯时亮起的。
4、电压过高或过低
电压过高或者过低都会触发报警,上限时4v,下限是2v,任意一个电阻高于4V时,红灯会进行闪烁,当没有带你呀高于4v时,且有任意一个电压低于2V,此时黄灯进行闪烁,如下图:
5、DAC输出
本次设计有DAC输出功能,系统测量第一个电阻的电压,通过第一个电阻的电压值,调整DAC输出,电压越高,led灯越亮,如下图:
四、项目总结
本次设计利用AT89C51借助PCF8591进行三路ADC的测量,第一路ADC为热敏电阻,通过热敏电阻特性得到温度值,其他两路使用ADC模数转换进行测量电压,同时集成温度报警、电压报警,按键模式切换等。
详情请参考我的bilibili