蓝桥杯单片机学习11——PCF8591A/D&D/A转换芯片

上期我们学习了DS1302实时时钟的基本使用,现在我们来学习PCF8591A/D&D/A转换芯片的相关内容

蓝桥杯单片机学习11——PCF8591A/D&D/A转换芯片

  • PCF8591A/D&D/A转换芯片
  • 引脚图
  • 原理图
  • PCF8591的读写地址
  • 控制指令
  • AD转换(模拟量->数字量)
  • DA转换
  • IIC协议
  • 起始时序&结束时序
  • 等待应答&发送应答
  • 写时序
  • 读时序
  • PCF8591A/D&D/A的基本控制方法&代码
  • 实践部分
  • 1.任务要求
  • 2.实现思路
  • 3.代码展示
  • 3.1.main.c
  • 3.2.数码管显示函数
  • 3.3.官方提供的iic文件
  • 总结
  • PCF8591A/D&D/A转换芯片

    PCF8591是具有I2C 总线接口的8 位A/D 及D/A 转换器。具有以下特点:
    【1】 单电源供电
    【2】 工作电压2.5~6.0V
    【3】 低待机电流(低功耗)
    【4】 使用IIC通信接口
    【5】 具有三个可编程地址引脚
    【6】 4个模拟输入通道可控制为单端输入或差分输入
    【6】 可控制自动递增通道选择
    【7】 模拟输出电压范围VSS~VDD
    【8】 八位逐次逼近AD转换
    【9】 一路模拟输出DA转换

    引脚图


    【1】 AIN0~3 :四路模拟输入,用于AD转换
    【2】 A0~A2:可编程地址
    【3】 VSS/VDD:负/正电压,采用双电源供电,模拟输出电压范围VSS~VDD
    【4】 SDA:IIC接口数据线
    【5】 SCL:IIC接口时钟线
    【6】 OSC:时钟输入/输出引脚,外部时钟输入,内部时钟输出。
    【7】 EXT:时钟输入输出开关,使用内部时钟时接地(为低电平)
    【8】AGND:模拟信号的地
    【9】VREF:基准电压,原理图上直接接的是VCC(5V)
    【10】VOUT:模拟输出引脚

    原理图

    以上是PCF8591的硬件原理图,下面是4个模拟输入和模拟输出的产生方式。

    1.AIN0/AOUT:直接接到排针J3上面,可以提供直接提供电压模拟输入&直接测量电压

    2.AIN1:通过光敏电阻分压模拟输入

    3.AIN2:先做保留,下次介绍

    4.AIN3:通过一个继电器分压输入。

    PCF8591的读写地址

    PCF8591使用的是IIC通信协议,IIC可用于多机通信,因此在通信之前需要寻址,以下是PCF8591的读写地址。

  • 高四位在出厂之前就已经确定,属于不可修改部分
  • A0~A2是可编程部分,通过电路将对应的引脚拉高/拉低来确定。
  • 第八位是读写控制位,为1时表示通信方向为,PCF写数据,单片机读数据。为0时则相反。
  • 控制指令

    在通信寻址成功之后,我们需要对PCF8591写入控制指令,设置芯片的工作模式。

    其中:

  • 第4位和第八位固定为0,不可更改。
  • 第7位为模拟输出使能位,为1时芯片工作为DA转换模式,通过IIC接口写入0~255之间的值,可以输出对应的模拟电压值。(具体计算公式在后面介绍)
  • 第5、6位可以选择模拟输入的方式,单端输入、双端输入、单端+双端输入
  • 第3位为自动增量标志,为1时,每完成一次AD转换,输入通道自动切换到下一个通道(0->1->2->3->0往复循环),为0时不会自增
  • 第1、2位共同控制AD转换的通道,具体对应关系如上图。
  • AD转换(模拟量->数字量)

    AD转换是将时间连续和幅值连续的模拟量转换为时间离散、幅值也离散的数字量。使输出的数字量与输入的模拟量成正比。模拟量与输出量的关系如下图

    1.单端输入

    其中:

  • V-REF为基准电压,电路图上连接到VCC,理论值为5V,
  • V-AGND为模拟地,电路图上连接到GND,电压值为0V,
  • V-LSB为微分非线性dnl,即芯片可以区分最小模拟电压值,
  • 举个栗子:
    芯片使用通道0进行AD转换,在通道0输入一个1.0V的电压,那么对应的数字量 = 256*(V-AIN – V-AGND)/ (V-REF – V-AGND) = 256 * 1.0/5.0 = 51 = 0x33
    特别的:当输入电压>(V-REF – V-AGND)以上时,输出为255不变

    2.差分输入

    原理和单端输入相似,不做介绍。

    DA转换

    DA转换则是将数字量转换为模拟量进行输出,与AD转换相反。

    计算公式如图,特别的,当V-AGND = 0V 时,V-AOUT= 256*数字量/V-REF
    注意:输入数字量的取值为0~255, 输出模拟量的范围是V-AGND~V-REF。

    IIC协议

    I2C 总线在物理连接上非常简单,分别由SDA(串行数据线)和SCL(串行时钟线)及上拉电阻组成。通信原理是通过对SCL和SDA线高低电平时序的控制,来产生I2C总线协议所需要的信号进行数据的传递。在总线空闲状态时,这两根线一般被上面所接的上拉电阻拉高,保持着高电平。
    规定:在传输过程在,在SCL为低电平时,SDA允许改变,在SCL为高电平时读取SDA的电平,SDA不允许改变。

    起始时序&结束时序

    1.起始信号:SCL为高电平时,SDA产生下降沿,为开始信号
    2.结束信号,SCL为高电平时,SDA产生上升沿,为结束信号

    官方提供代码如下:

    //总线启动条件
    void IIC_Start(void)
    {
        SDA = 1;        
        SCL = 1;
        IIC_Delay(DELAY_TIME);
        SDA = 0;        //SCL为高电平时,SDA产生下降沿,为开始信号
        IIC_Delay(DELAY_TIME);
        SCL = 0;	    
    }
    
    //总线停止条件
    void IIC_Stop(void)
    {
        SDA = 0;
        SCL = 1;
        IIC_Delay(DELAY_TIME);
        SDA = 1;        //SCL为高电平时,SDA产生上升沿,为结束信号
        IIC_Delay(DELAY_TIME);
    }
    

    等待应答&发送应答

    在IIC协议中,主机发送一个字节数据后,从机需要产生应答位(Ack),为0时表示应答成功,此时主机可以选择继续发送或结束本次通信;为1时表示应答失败,此时从机将不再介绍来自主机的数据。
    当通信方向为单片机->PCF8591时,单片机每发送一个字节数据,需要等待PCF8591应答,反之则需要单片机发送应答。
    应答的规则如下:

    【1】 一个字节发送完毕,主机释放数据线(将SDA置为1)
    【2】 从机应答,将SDA置为0
    【3】 等待SCL为高电平读取应答位,
    【4】 读取完毕,从机释放总线
    【5】 主机的到应答,选择继续发送数据或结束通信

    官方提供代码如下:

    //发送应答
    void IIC_SendAck(bit ackbit)
    {
        SCL = 0;            //将SCL拉低,写入应答位
        SDA = ackbit;  					// 0:应答,1:非应答
        IIC_Delay(DELAY_TIME);
        SCL = 1;                        //将SCL拉高,从机读取应答位  
        IIC_Delay(DELAY_TIME);
        SCL = 0; 
        SDA = 1;                //主机释放SDA,结束应答
        IIC_Delay(DELAY_TIME);
    }
    
    //等待应答
    bit IIC_WaitAck(void)
    {
        bit ackbit;
    	
        SCL  = 1;   //主机释放SCL,等待从机拉低SCL,发送应答     
        IIC_Delay(DELAY_TIME);
        ackbit = SDA;       //从机发送应答后,释放SCL   
        SCL = 0;            //主机再次拉低SCL,为下一次发送数据或者结束通信做准备
        IIC_Delay(DELAY_TIME);
        return ackbit;
    }
    

    写时序


    官方提供代码如下:

    //通过I2C总线发送数据
    void IIC_SendByte(unsigned char byt)
    {
        unsigned char i;
    
        for(i=0; i<8; i++)  
        {
            SCL  = 0;       //SCL拉低
            IIC_Delay(DELAY_TIME);
            if(byt & 0x80) SDA  = 1;    //主机SDA写入数据位1/0
            else SDA  = 0;
            IIC_Delay(DELAY_TIME);
            SCL = 1;                    //SCL拉高,从机读取SDA电平
            byt <<= 1;                  
            IIC_Delay(DELAY_TIME);
        }               //重复八次,完成一个字节的发送
        SCL  = 0;       //将SCL拉低
    }
    
    

    读时序


    官方提供代码如下:

    //从I2C总线上接收数据
    unsigned char IIC_RecByte(void)
    {
        unsigned char i, da;
        for(i=0; i<8; i++)      //
        {   
        SCL = 1;            //将SCL拉高,
    	IIC_Delay(DELAY_TIME);
    	da <<= 1;           
    	if(SDA) da |= 1;    //主机读取SDA的状态
    	SCL = 0;            //将SDA拉低,从机写入SDA状态
    	IIC_Delay(DELAY_TIME);
        }               //循环八次,完成一个字节数据的读取
        return da;      //返回读取的字节
    }
    
    

    PCF8591A/D&D/A的基本控制方法&代码

    1.AD转换的具体流程
    【1】 单片机发送开始通信信号
    【2】 单片机寻址,与PCF8591通信(方向:单片机-> PCF8591)
    【3】 等待PCF8591应答,
    【4】 应答成功,发送控制指令(AD转换,单端输入,通道为channel)
    【5】 等待PCF8591应答,
    【6】 应答成功,单片机结束通信
    【7】 单片机发送开始通信信号
    【8】 单片机寻址,与PCF8591通信(方向: PCF8591->单片机)
    【9】 等待PCF8591应答,
    【10】 PCF8591发送AD转换后的数字量
    【11】 单片机读取数据,并且不应答
    【12】 单片机结束通信
    【13】 单片机对读取的数据进行处理,

    代码如下:

    //PCF8591AD转换函数,channel为模拟输入的通道
    unsigned char  PCF8591_AD_Conversion(unsigned char channel )
    {
        unsigned char dat = 0;
        IIC_Start();        //iic通信开始
        IIC_SendByte(PCF8591_Write_Addr);   //写入从机PCF8591的写地址,
        if(IIC_WaitAck())           //等待从机应答,为1表示不应答,为0表示应答
        {   
            IIC_Stop();         //如果不应答,则结束通信
            return 0;
        }
        IIC_SendByte(0x00 | channel);   //写入控制指令,单端输入,AD转换,选择通道channel
        IIC_WaitAck();      //这一步不能少,否则读出来一直是255
        IIC_Stop();         //结束本次通信,改变收发方向
        
        IIC_Start();        //iic通信开始
        IIC_SendByte(PCF8591_Read_Addr);    //写入从机PCF8591的读地址,
        if(IIC_WaitAck())           //等待应答
        {
            IIC_Stop();          //如果不应答,则结束通信
            return 0;
        }
        dat = IIC_RecByte();    //读取AD转换出的数字量 
        IIC_SendAck(1);         //不应答
    
        IIC_Stop();             //结束通信
        SEG_Arr[0] = dat/100%10;    //计算数字量的百位
        SEG_Arr[1] = dat/10%10;     //计算数字量的十位
        SEG_Arr[2] = dat%10;        //计算数字量的各位
        
        return dat;
    }
    

    2.DA转换的具体流程
    【1】 单片机发送开始通信信号
    【2】 单片机寻址,与PCF8591通信(方向:单片机-> PCF8591)
    【3】 等待PCF8591应答,
    【4】 应答成功,发送控制指令(DA转换)
    【5】 等待PCF8591应答,
    【6】 应答成功,单片机继续发送DA转换的数字量
    【7】 等待PCF8591应答,
    【8】 应答成功,PCF8591进行DA转换
    【9】 单片机结束通信

    代码如下:

    //PCF8591DA转换函数,dat为输入的数字量
    void PCF8591_DA_Conversion(unsigned  char dat)
    {
        IIC_Start();        //iic通信开始
        IIC_SendByte(PCF8591_Write_Addr);   //写入PCF8591的写地址
        if(IIC_WaitAck())           //等待应答
        {
            IIC_Stop();             //如果不应答,就结束通信
        }
        else
        {
            IIC_SendByte(0x40);     //写入控制指令,DA转换
            IIC_WaitAck();      //等待应答
            IIC_SendByte(dat);      //写入数字量
            IIC_WaitAck();      //等待应答
            IIC_Stop();         //结束通信
        }
    }
    

    实践部分

    1.任务要求

    通过官方提供的iic代码,完成与PCF8591之间的通信,具体要求如下:

  • 使用通道0和通道3模拟输入,进行AD转换,并将输出的数字量通过数码管显示出来
  • 通过按键S5控制模拟输入的通道,每次按下按键,通道在0和3之间跳变,并通过数码管显示当前模拟输入的通道数
  • 将输出的数字量进行DA转换,模拟输出
  • 测量OUT引脚的电压,判断模拟输出是否正确
    数码管显示格式如下 – 通道 – [空] [空] 数字量
  • 2.实现思路

    3.代码展示

    3.1.main.c

    #include <STC15F2K60S2.H>
    #include "LS138.h"
    #include "iic.h"
    #define  PCF8591_Write_Addr   0x90  //PCF8591写地址
    #define  PCF8591_Read_Addr    0x91  //PCF8591读地址
    unsigned char SEG_Arr[3];       //数码管显示数字
    unsigned char dat = 0;          //存放PCF8591读取的内容
    unsigned int INT0_Count = 0;
    unsigned char channel = 3;
    void IT0_Init()
    {
        IT0= 1;
        EX0 =1;
        EA =1;
    }
    //PCF8591AD转换函数,channel为模拟输入的通道
    unsigned char  PCF8591_AD_Conversion(unsigned char channel )
    {
        unsigned char dat = 0;
        IIC_Start();        //iic通信开始
        IIC_SendByte(PCF8591_Write_Addr);   //写入从机PCF8591的写地址,
        if(IIC_WaitAck())           //等待从机应答,为1表示不应答,为0表示应答
        {   
            IIC_Stop();         //如果不应答,则结束通信
            return 0;
        }
        IIC_SendByte(0x00 | channel);   //写入控制指令,单端输入,AD转换,选择通道channel
        IIC_WaitAck();      //这一步不能少,否则读出来一直是255
        IIC_Stop();         //结束本次通信,改变收发方向
        
        IIC_Start();        //iic通信开始
        IIC_SendByte(PCF8591_Read_Addr);    //写入从机PCF8591的读地址,
        if(IIC_WaitAck())           //等待应答
        {
            IIC_Stop();          //如果不应答,则结束通信
            return 0;
        }
        dat = IIC_RecByte();    //读取AD转换出的数字量 
        IIC_SendAck(1);         //不应答
    
        IIC_Stop();             //结束通信
        SEG_Arr[0] = dat/100%10;    //计算数字量的百位
        SEG_Arr[1] = dat/10%10;     //计算数字量的十位
        SEG_Arr[2] = dat%10;        //计算数字量的各位
        
        return dat;
    }
    //PCF8591DA转换函数,dat为输入的数字量
    void PCF8591_DA_Conversion(unsigned  char dat)
    {
        IIC_Start();        //iic通信开始
        IIC_SendByte(PCF8591_Write_Addr);   //写入PCF8591的写地址
        if(IIC_WaitAck())           //等待应答
        {
            IIC_Stop();             //如果不应答,就结束通信
        }
        else
        {
            IIC_SendByte(0x40);     //写入控制指令,DA转换
            IIC_WaitAck();      //等待应答
            IIC_SendByte(dat);      //写入数字量
            IIC_WaitAck();      //等待应答
            IIC_Stop();         //结束通信
        }
    }
    void SEG_Show()
    {
            SEG_Write(0,10);
            SEG_Write(1,channel);
            SEG_Write(2,10);
            SEG_Write(5,SEG_Arr[0]);
            SEG_Write(6,SEG_Arr[1]);
            SEG_Write(7,SEG_Arr[2]);
    }
    void main()     
    {
        LS138_Init();       //LS138初始化
        IT0_Init();
    	while(1)
    	{
             dat = PCF8591_AD_Conversion(channel);       //选择通道3输入,通过电阻分压,模拟输入
             PCF8591_DA_Conversion(dat);                 //将读取的数字量通过DA转换,输出
             SEG_Show();
    
    	}
    }
    
    void External_Hander0() interrupt 0
    {
        INT0_Count++;
        if(INT0_Count % 2)
        {
            channel =3;
        }
        else
        {
           channel =1;
        }
    }
    

    3.2.数码管显示函数

                                       /*0    1   2     3    4    5    6   7     8    9 */
    unsigned char code SEG_index[] = {0xc0,0xf9,0xa4,0xb0,0x99,0x92,0x82,0xf8,0x80,0x90,
                                       0xBF  }; //写0点亮
    
    
    void SEG_Write(unsigned char pos, unsigned char dat)
    {
        LS138_Clear();
        P0 = 0x00;
        LS138_Set(7);
        P0 = SEG_index[dat];
        LS138_Clear();
        P0 = 0x00;
        LS138_Set(6);
        P0 = 0x01 << pos;
        LS138_Clear();
        Delayxms(1);
    
    }
    
    void SEG_Show()
    {
            SEG_Write(0,10);
            SEG_Write(1,channel);
            SEG_Write(2,10);
            SEG_Write(5,SEG_Arr[0]);
            SEG_Write(6,SEG_Arr[1]);
            SEG_Write(7,SEG_Arr[2]);
    }
    
    

    3.3.官方提供的iic文件

    /*
      程序说明: IIC总线驱动程序
      软件环境: Keil uVision 4.10 
      硬件环境: CT107单片机综合实训平台 8051,12MHz
      日    期: 2011-8-9
    */
    
    
    #include "iic.h"
    #define DELAY_TIME 5
    
    #define SlaveAddrW 0xA0
    #define SlaveAddrR 0xA1
    
    //总线引脚定义
    sbit SDA = P2^1;  /* 数据线 */
    sbit SCL = P2^0;  /* 时钟线 */
    
    void IIC_Delay(unsigned char i)
    {
        do{_nop_();}
        while(i--);        
    }
    //总线启动条件
    void IIC_Start(void)
    {
        SDA = 1;        
        SCL = 1;
        IIC_Delay(DELAY_TIME);
        SDA = 0;        //SCL为高电平时,SDA产生下降沿,为开始信号
        IIC_Delay(DELAY_TIME);
        SCL = 0;	    
    }
    
    //总线停止条件
    void IIC_Stop(void)
    {
        SDA = 0;
        SCL = 1;
        IIC_Delay(DELAY_TIME);
        SDA = 1;        //SCL为高电平时,SDA产生上升沿,为结束信号
        IIC_Delay(DELAY_TIME);
    }
    
    //发送应答
    void IIC_SendAck(bit ackbit)
    {
        SCL = 0;            //将SCL拉低,写入应答位
        SDA = ackbit;  					// 0:应答,1:非应答
        IIC_Delay(DELAY_TIME);
        SCL = 1;                        //将SCL拉高,从机读取应答位  
        IIC_Delay(DELAY_TIME);
        SCL = 0; 
        SDA = 1;                //主机释放SDA,结束应答
        IIC_Delay(DELAY_TIME);
    }
    
    //等待应答
    bit IIC_WaitAck(void)
    {
        bit ackbit;
    	
        SCL  = 1;   //主机释放SCL,等待从机拉低SCL,发送应答     
        IIC_Delay(DELAY_TIME);
        ackbit = SDA;       //从机发送应答后,释放SCL   
        SCL = 0;            //主机再次拉低SCL,为下一次发送数据或者结束通信做准备
        IIC_Delay(DELAY_TIME);
        return ackbit;
    }
    
    //通过I2C总线发送数据
    void IIC_SendByte(unsigned char byt)
    {
        unsigned char i;
    
        for(i=0; i<8; i++)  
        {
            SCL  = 0;       //SCL拉低
            IIC_Delay(DELAY_TIME);
            if(byt & 0x80) SDA  = 1;    //主机SDA写入数据位1/0
            else SDA  = 0;
            IIC_Delay(DELAY_TIME);
            SCL = 1;                    //SCL拉高,从机读取SDA电平
            byt <<= 1;                  
            IIC_Delay(DELAY_TIME);
        }               //重复八次,完成一个字节的发送
        SCL  = 0;       //将SCL拉低
    }
    
    //从I2C总线上接收数据
    unsigned char IIC_RecByte(void)
    {
        unsigned char i, da;
        for(i=0; i<8; i++)      //
        {   
        SCL = 1;            //将SCL拉高,
    	IIC_Delay(DELAY_TIME);
    	da <<= 1;           
    	if(SDA) da |= 1;    //主机读取SDA的状态
    	SCL = 0;            //将SDA拉低,从机写入SDA状态
    	IIC_Delay(DELAY_TIME);
        }               //循环八次,完成一个字节数据的读取
        return da;      //返回读取的字节
    }
    

    总结

    在蓝桥杯单片机的板子上,PCF8591的基准电压为VCC,理论上是5V,但实际上会与5V存在差距,在本次实验中,我测得的VCC电压为4.53V,那么,如果假设输入数字量为100,进行DA转换之后的电压,就不是5100/255 = 1.96V 而因该是 4.53100/255 = 1.77V这一点需要注意。

    如图:

    物联沃分享整理
    物联沃-IOTWORD物联网 » 蓝桥杯单片机学习11——PCF8591A/D&D/A转换芯片

    发表评论