欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

单片机补充案例--I2C和AD使用PCF8591

程序员文章站 2024-02-24 09:06:01
...

效果如下所示,AD转换结果用数码管显示:

上电位器:从大变小!(2.49-1.64-0.42-0.33)另一个不变(0.01附近)

单片机补充案例--I2C和AD使用PCF8591

下电位器:从小变大!(0.01-0.93-1.26-1.97-2.12)另一个不变(2.49)

单片机补充案例--I2C和AD使用PCF8591

源程序是keil,转为Linux_SDCC,如下:

adtest.c

#include <8052.h>

unsigned char flag1s = 1;  //1s定时标志
unsigned char T0RH = 0;    //T0重载值的高字节
unsigned char T0RL = 0;    //T0重载值的低字节
unsigned char LedBuff[4] ={0xFF,0xFF,0xFF,0xFF}; //显示缓冲区
unsigned __code char smgcode[]={
	0xC0, 0xF9, 0xA4, 0xB0, 0x99, 0x92, 0x82, 0xF8,
	0x80, 0x90, 0x88, 0x83, 0xC6, 0xA1, 0x86, 0x8E
	};

#define LSA  P1_5  //LED位选译码地址引脚A
#define LSB  P1_6  //LED位选译码地址引脚B
#define LSC  P1_7  //LED位选译码地址引脚C
#define SCL  P3_7
#define SDA  P3_6
void I2cDelay() 
{
    ;
}

void Timer0() __interrupt 1;
void ConfigTimer0(unsigned int ms);
void ValueToBuff(unsigned char val);


/*******************************************************************************
* 函数名         : I2cStart()
* 函数功能	: 起始信号:在SCL时钟信号在高电平期间SDA信号产生一个下降沿
* 输入           : 无
* 输出         	: 无
* 备注           : 起始之后SDA和SCL都为0
*******************************************************************************/

void I2cStart()
{
	SDA = 1;
	SCL = 1;
	I2cDelay();	
	SDA = 0;		//先拉低SDA
	I2cDelay();		
	SCL = 0;		//再拉低SCL		
	I2cDelay();			
}
/*******************************************************************************
* 函数名         : I2cStop()
* 函数功能	: 终止信号:在SCL时钟信号高电平期间SDA信号产生一个上升沿
* 输入           : 无
* 输出         	: 无
* 备注           : 结束之后保持SDA和SCL都为1;表示总线空闲
*******************************************************************************/
void I2cStop()
{
	SDA = 0;
	SCL = 0;
	I2cDelay();
	SCL = 1;	  	//先拉高SCL
	I2cDelay();
	SDA = 1;	 	//再拉高SDA
	I2cDelay();		
}
/*******************************************************************************
* 函数名         : I2cWriteByte(unsigned char dat)
* 函数功能	: 通过I2C发送一个字节。在SCL时钟信号高电平期间,保持发送信号SDA保持稳定
* 输入           : dat
* 输出         	: 从机的应答值
* 备注           : 发送完一个字节SCL=0,SDA=1
*******************************************************************************/

unsigned char I2cWriteByte(unsigned char dat)
{
	unsigned char i = 0;		
	unsigned char ack;
	for(i = 0;i < 8;i++)	//要发送8位,从最高位开始
	{
		SDA = dat >> 7;	//起始信号之后SCL=0,所以可以直接改变SDA信号
		dat = dat << 1;
		I2cDelay();
		SCL = 1;	//拉高SCL
		I2cDelay();		
		SCL = 0;	//再拉低SCL,完成一个位周期
		I2cDelay();		
	}
	SDA = 1;		//8位数据发送完以后主机释放SDA,以检测从机应答
	I2cDelay();
	SCL = 1;		//拉高SCL
	ack = SDA;		//读取此时的SDA值,即为从机的应答值
	I2cDelay();
	SCL = 0;		//再拉低SCL完成应答位,并保持住总线
 	return ack;		//应答位取反以符合逻辑习惯:0=不存在
				//或忙或失败,1=存在且空闲或写入成功
}
/*******************************************************************************
* 函数名         : I2cReadByte()
* 函数功能	: 使用I2c读取一个字节
* 输入           : ack,1:发送无应答信号,0:发送应答信号
* 输出         	: dat
* 备注           : 接收完一个字节SCL=0,SDA=1.
*******************************************************************************/

unsigned char I2cReadByte(unsigned char ack)
{
	unsigned char i = 0,dat = 0;
	SDA = 1;					//起始和发送一个字节之后SCL都是0
	I2cDelay();
	for(i = 0;i < 8;i++)	//从高位到地位接收8位
	{
		SCL = 1;
		I2cDelay();
		dat <<= 1;
		dat |= SDA;
		I2cDelay();
		SCL = 0;
		I2cDelay();
	}
	
	SDA = ack;		//8位数据发送完以后,发送应答或非应答信号
	I2cDelay();
	SCL = 1;		//拉高SCL
	I2cDelay();
	SCL = 0;		//再拉低SCL完成应答或非应答位,并保持住总线

	return dat;		
}

 /*A/D转换程序*/
unsigned char GetADCValue(unsigned char chn) 
{
	unsigned char val;

	I2cStart();
	if(I2cWriteByte(0x48<<1)!=0) 	//寻址PCF8591,若未应答,则停止操作并返回0
	{
		I2cStop();
		return 0;
	}
	I2cWriteByte(0x40 | chn);	//写控制字节,选择转换通道
	I2cStart();
	I2cWriteByte(0x48<<1 | 0x01);	//寻址PCF8591,指定后续为读操作
	I2cReadByte(0);			//先空读一个字节,提供采样转换时间
	val = I2cReadByte(1);		//读取刚刚转换的值	
	I2cStop();
	return val;
}

void main()
{
    unsigned char val;  	//AD转换后的数字量
	unsigned char channel = 0; 
	
	EA = 1;            	//开总中断
    ConfigTimer0(2);  	        //配置T0定时2ms
    
    while (1)
    {
        if (flag1s)
        {
            flag1s = 0;                      //显示通道0的电压
            val = GetADCValue(channel++);   //获取ADC通道0的转换值
            ValueToBuff(val);  		    //转为字符串格式的电压值
	    channel %= 2;		    //保证channel为0或1   
        
        }
    }
}
/* 取出数字量的百十个位,保存到显示缓冲区 */
void  ValueToBuff(unsigned char val)
{
	val = (val*250)/255;		//val放大100倍
	LedBuff[0] = smgcode[(val%10)];	//取个位数字
	LedBuff[1] = smgcode[(val/10)%10];//取十位数字
	LedBuff[2] = smgcode[(val/100)]; //取百位数字
	LedBuff[2] &= 0x7F;		//小数点设置在百位,再缩小100倍
}
/* 配置并启动T0,ms:T0定时时间 */
void ConfigTimer0(unsigned int ms)
{
    unsigned long tmp;  			//临时变量
    
    tmp = 11059200 / 12;      			//定时器计数频率
    tmp = (tmp * ms) / 1000;  			//计算所需的计数值
    tmp = 65536 - tmp;        			//计算定时器重载值
    tmp = tmp + 18;           			//补偿中断响应延时造成的误差
    T0RH = (unsigned char)(tmp>>8);  	        //定时器重载值拆分为高低字节
    T0RL = (unsigned char)tmp;
    TMOD &= 0xF0;   					//清零T0的控制位
    TMOD |= 0x01;   					//配置T0为模式1
    TH0 = T0RH;     					//加载T0重载值
    TL0 = T0RL;
    ET0 = 1;        					//使能T0中断
    TR0 = 1;        					//启动T0
}

/* 数码管动态扫描刷新函数,需在定时中断中调用 */
void LedScan()
{
    static unsigned char i = 0;  		//动态扫描的索引
    
    P0 = 0xFF;   				//显示消隐
    switch (i)
    {
        case 0: LSC=0; LSB=0; LSA=1; i++; P0=LedBuff[0]; break;
        case 1: LSC=0; LSB=1; LSA=0; i++; P0=LedBuff[1]; break;
        case 2: LSC=0; LSB=1; LSA=1; i++; P0=LedBuff[2]; break;
        case 3: LSC=1; LSB=0; LSA=0; i=0; P0=LedBuff[3]; break;
        default: break;
    }
}
/* T0中断服务函数,执行LED动态显示和1s计时 */
void Timer0() __interrupt 1
{
    static unsigned int tmr1s = 0;
    
    TH0 = T0RH;  	//重新加载重载值
    TL0 = T0RL;
    tmr1s++;
    if (tmr1s >= 500)  	//定时1s,2us计了500次
    {
        tmr1s = 0;
        flag1s = 1;
    }
    LedScan();
}

接下来两步即可:

  1. sdcc -mmcs51 adtest.c
  2. stcgal -P stc89 adtest.ihx

单片机补充案例--I2C和AD使用PCF8591

附keil程序,注意对比差异性!

main.c


#include <reg52.h>
#include "PCF8591.h"

bit flag1s = 1;       		//1s定时标志
unsigned char T0RH = 0;  	//T0重载值的高字节
unsigned char T0RL = 0;  	//T0重载值的低字节
unsigned char LedBuff[4] ={0xFF,0xFF,0xFF,0xFF};//显示缓冲区
unsigned char code smgcode[]={
	0xC0, 0xF9, 0xA4, 0xB0, 0x99, 0x92, 0x82, 0xF8,
	0x80, 0x90, 0x88, 0x83, 0xC6, 0xA1, 0x86, 0x8E
	};

sbit LSA = P1^5;  			//LED位选译码地址引脚A
sbit LSB = P1^6;  			//LED位选译码地址引脚B
sbit LSC = P1^7;  			//LED位选译码地址引脚C

void ConfigTimer0(unsigned int ms);
void ValueToBuff(unsigned char val);

void main()
{
    unsigned char val;  	//AD转换后的数字量
	unsigned char channel = 0; 
	
	EA = 1;            	//开总中断
    ConfigTimer0(2);  	//配置T0定时2ms
    
    while (1)
    {
        if (flag1s)
        {
            flag1s = 0;
            //显示通道0的电压
            val = GetADCValue(channel++);   //获取ADC通道0的转换值
            ValueToBuff(val);  		//转为字符串格式的电压值
			channel %= 2;			//保证channel为0或1   
        
        }
    }
}
/* 取出数字量的百十个位,保存到显示缓冲区 */
void  ValueToBuff(unsigned char val)
{
	val = (val*250)/255;			//val放大100倍
	LedBuff[0] = smgcode[(val%10)];	//取个位数字
	LedBuff[1] = smgcode[(val/10)%10];//取十位数字
	LedBuff[2] = smgcode[(val/100)]; //	取百位数字
	LedBuff[2] &= 0x7F;				 //小数点设置在百位,再缩小100倍
}
/* 配置并启动T0,ms:T0定时时间 */
void ConfigTimer0(unsigned int ms)
{
    unsigned long tmp;  				//临时变量
    
    tmp = 11059200 / 12;      			//定时器计数频率
    tmp = (tmp * ms) / 1000;  			//计算所需的计数值
    tmp = 65536 - tmp;        			//计算定时器重载值
    tmp = tmp + 18;           			//补偿中断响应延时造成的误差
    T0RH = (unsigned char)(tmp>>8);  	//定时器重载值拆分为高低字节
    T0RL = (unsigned char)tmp;
    TMOD &= 0xF0;   					//清零T0的控制位
    TMOD |= 0x01;   					//配置T0为模式1
    TH0 = T0RH;     					//加载T0重载值
    TL0 = T0RL;
    ET0 = 1;        					//使能T0中断
    TR0 = 1;        					//启动T0
}

/* 数码管动态扫描刷新函数,需在定时中断中调用 */
void LedScan()
{
    static unsigned char i = 0;  		//动态扫描的索引
    
    P0 = 0xFF;   						//显示消隐
    switch (i)
    {
        case 0: LSC=0; LSB=0; LSA=1; i++; P0=LedBuff[0]; break;
        case 1: LSC=0; LSB=1; LSA=0; i++; P0=LedBuff[1]; break;
        case 2: LSC=0; LSB=1; LSA=1; i++; P0=LedBuff[2]; break;
        case 3: LSC=1; LSB=0; LSA=0; i=0; P0=LedBuff[3]; break;
        default: break;
    }
}
/* T0中断服务函数,执行LED动态显示和1s计时 */
void InterruptTimer0() interrupt 1
{
    static unsigned int tmr1s = 0;
    
    TH0 = T0RH;  		//重新加载重载值
    TL0 = T0RL;
    tmr1s++;
    if (tmr1s >= 500)  	//定时1s,2us计了500次
    {
        tmr1s = 0;
        flag1s = 1;
    }
    LedScan();
}

PCF8591.h

#ifndef __PCF8591_H_
#define __PCF8591_H_

unsigned char GetADCValue(unsigned char chn);
 
#endif

PCF8591.c

#include "PCF8591.h"
#include "i2c.h"

 /*A/D转换程序*/
unsigned char GetADCValue(unsigned char chn) 
{
	unsigned char val;

	I2cStart();
	if(!I2cWriteByte(0x48<<1)) 			//寻址PCF8591,若未应答,则停止操作并返回0
	{
		I2cStop();
		return 0;
	}
	I2cWriteByte(0x40 | chn);			//写控制字节,选择转换通道
	I2cStart();
	I2cWriteByte(0x48<<1 | 0x01);		//寻址PCF8591,指定后续为读操作
	I2cReadByte(0);						//先空读一个字节,提供采样转换时间
	val = I2cReadByte(1);				//读取刚刚转换的值	
	I2cStop();
	return val;
}

I2C.h

#ifndef __I2C_H_
#define __I2C_H_

#include <reg52.h>

sbit SCL = P3^7;
sbit SDA = P3^6;

void I2cStart();
void I2cStop();
bit I2cWriteByte(unsigned char dat);
unsigned char I2cReadByte(bit ACK);

#endif

I2C.c

#include"i2c.h"
#include <intrins.h>

#define I2cDelay() {_nop_();_nop_();_nop_();_nop_();}


/*******************************************************************************
* 函数名         : I2cStart()
* 函数功能		 : 起始信号:在SCL时钟信号在高电平期间SDA信号产生一个下降沿
* 输入           : 无
* 输出         	 : 无
* 备注           : 起始之后SDA和SCL都为0
*******************************************************************************/

void I2cStart()
{
	SDA = 1;
	SCL = 1;
	I2cDelay();	
	SDA = 0;		//先拉低SDA
	I2cDelay();		
	SCL = 0;		//再拉低SCL		
	I2cDelay();			
}
/*******************************************************************************
* 函数名         : I2cStop()
* 函数功能		 : 终止信号:在SCL时钟信号高电平期间SDA信号产生一个上升沿
* 输入           : 无
* 输出         	 : 无
* 备注           : 结束之后保持SDA和SCL都为1;表示总线空闲
*******************************************************************************/
void I2cStop()
{
	SDA = 0;
	SCL = 0;
	I2cDelay();
	SCL = 1;	  	//先拉高SCL
	I2cDelay();
	SDA = 1;	 	//再拉高SDA
	I2cDelay();		
}
/*******************************************************************************
* 函数名         : I2cWriteByte(unsigned char dat)
* 函数功能		 : 通过I2C发送一个字节。在SCL时钟信号高电平期间,保持发送信号SDA保持稳定
* 输入           : dat
* 输出         	 : 从机的应答值
* 备注           : 发送完一个字节SCL=0,SDA=1
*******************************************************************************/

bit I2cWriteByte(unsigned char dat)
{
	unsigned char i = 0;		
	bit ack;
	for(i = 0;i < 8;i++)	//要发送8位,从最高位开始
	{
		SDA = dat >> 7;	//起始信号之后SCL=0,所以可以直接改变SDA信号
		dat = dat << 1;
		I2cDelay();
		SCL = 1;			//拉高SCL
		I2cDelay();		
		SCL = 0;			//再拉低SCL,完成一个位周期
		I2cDelay();		
	}
	SDA = 1;				//8位数据发送完以后主机释放SDA,以检测从机应答
	I2cDelay();
	SCL = 1;				//拉高SCL
	ack = SDA;				//读取此时的SDA值,即为从机的应答值
	I2cDelay();
	SCL = 0;				//再拉低SCL完成应答位,并保持住总线
 	return ~ack;			//应答位取反以符合逻辑习惯:0=不存在
							//或忙或失败,1=存在且空闲或写入成功
}
/*******************************************************************************
* 函数名         : I2cReadByte()
* 函数功能		 : 使用I2c读取一个字节
* 输入           : ack,1:发送无应答信号,0:发送应答信号
* 输出         	 : dat
* 备注           : 接收完一个字节SCL=0,SDA=1.
*******************************************************************************/

unsigned char I2cReadByte(bit ack)
{
	unsigned char i = 0,dat = 0;
	SDA = 1;					//起始和发送一个字节之后SCL都是0
	I2cDelay();
	for(i = 0;i < 8;i++)		//从高位到地位接收8位
	{
		SCL = 1;
		I2cDelay();
		dat <<= 1;
		dat |= SDA;
		I2cDelay();
		SCL = 0;
		I2cDelay();
	}
	
	SDA = ack;					//8位数据发送完以后,发送应答或非应答信号
	I2cDelay();
	SCL = 1;					//拉高SCL
	I2cDelay();
	SCL = 0;					//再拉低SCL完成应答或非应答位,并保持住总线

	return dat;		
}