单片机补充案例--I2C和AD使用PCF8591
程序员文章站
2024-02-24 09:06:01
...
效果如下所示,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();
}
接下来两步即可:
- sdcc -mmcs51 adtest.c
- 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;
}