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

本文介绍了一个基于STM32的AD转换程序,利用PCF8591芯片进行模拟数字转换,并通过I2C通信将结果显示在数码管上。程序包括了I2C的起始和停止信号、数据发送与接收的实现,以及AD转换的完整流程。此外,还展示了如何在Keil环境下编译代码并转换为适用于Linux_SDCC的格式。
摘要由CSDN通过智能技术生成

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

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

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

源程序是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

附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;		
}





 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

zhangrelay

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值