学51单片机-基于PCF8591的AD采样和DA输出
- 1、下载文档前请自行甄别文档内容的完整性,平台不提供额外的编辑、内容补充、找答案等附加服务。
- 2、"仅部分预览"的文档,不可在线预览部分如存在完整性等问题,可反馈申请退款(可完整预览的文档不适用该条件!)。
- 3、如文档侵犯您的权益,请联系客服反馈,我们会尽快为您处理(人工客服工作时间:9:00-18:30)。
学51单片机-基于PCF8591的AD采样和DA输出
首先思考一个问题,我们的世界是数字的还是模拟的?
当然是模拟的了,所有的量都是在一定范围内连续变化的。
我们为了能够更加方便的描述这些量,对它们进行了数字化。
而数字量就不一样了,它是分立的的几个值。
举个例子,我们形容一个人的身高,模拟的说法是一米七到一米七五之间,数字的说法就是一米七三。
接下来说AD转换器,它的出现也是为了让我们能更方便、更直接的描述电压的高低。
AD转换器,英文全称为Analog-to-Digital Converter,是模拟量到数字量的一个转换过程,主要用于电压的采集。
它的出现就如同有了一把尺子,很容易就能量出电压的高低。
在电子设备中,经常要检测各种模拟量:温度、压力、速度、流量、重力加速度等等,这些模拟量都被相应的传感器转换为电压信号,我们只需要测量电压的高低,就能得到相应参数。
AD的主要参数有哪些?
1、AD的位数:表明这个AD共有2^n个刻度,8位AD,输出的刻度是0~255.
2、分辨率:就是AD能够分辨的最小的模拟量变化,假设5.10V的系统用8位的AD采样,那么它能分辨的最小电压就是5.10/255=0.02V。
3、INL:Interger NONliner积分非线性度,表示了ADC器件在所有的数值点上对应的模拟值,和真实值之间误差最大的那一点的误差值。
也就是,输出数值偏离线性最大的距离。
单位是LSB(即最低位所表示的量)。
比如12位ADC:TLC2543,INL值为1LSB。
那么,如果基准4.095V,测某电压得的转换结果是1000,那么,真实电压值可能分布在0.999~1.001V之间。
4、DNL:Differencial NonLiner-差分非线性度,理论上说,模数器件相邻量个数据之间,模拟量的差值都是一样的。
就相一把疏密均匀的尺子。
但实际并不如此。
一把分辨率1毫米的尺子,相邻两刻度之间也不可能都是1毫米整。
那么,ADC相邻两刻度之间最大的差异就叫差分非线性值(Differencial NonLiner)。
DNL值如果大于1,那么这个ADC甚至不能保证是单调的,输入电压增大,在某个点数值反而会减小。
这种现象在SAR(逐位比较)型ADC中很常见。
5、基准源:有内部基准源、外部基准源等等。
6、转换速率:也就是转换周期的倒数,转换周期就是完成一次AD转换所需的时间。
今天要用到的器件是PCF8591,为什么选它?太多的开发板上用它做演示了,而且还是IIC总线通信的。
既学习了AD采样,又学习了IIC总线。
先上应用电路:
如上图所示,PCF8591的9脚和10脚,一个是数据线SDA,一个是时钟线SCL。
分别接到单片机的P2.0,P2.1上面。
为什么选这两个引脚?因为51单片机上没有IIC总线接口,需要用普通的IO模拟,所以它随便选了两个IO接上就行。
VREF是什么?基准电压,也是它能测量的最大电压。
如何控制?今天先不说IIC总线,只说控制流程。
看器件手册可以知道:
分四步:
1、发送地址字节,选择该器件。
2、发送控制字节,选择相应通道。
//
3、重新发送地址字节,选择该器件。
4、接收目标通道的数据。
这次的程序流程是:AD采样,串口发送,循环执行。
下面是AD采样源代码:
/**********************51单片机学习例程************************
*平台:Keil U4+STC89C52
*名称:AD采样+串口发送
*编写:起航
*晶体:11.0592MHZ
******************************************************************/ #include<reg52.h>
#include<intrins.h>
typedef unsigned char uint8;
typedef unsigned int uint16;
#define SLAVEADDR0x90//定义器件地址
#define nops()do{_nop_();_nop_();_nop_();_nop_();_nop_();}while(0)//定义空指令sbit SCL=P2^1;//I2C时钟
sbit SDA=P2^0;//I2C数据
void delay(uint16n)
{
while(n--);
}
/**
*函数:i2c_start()
*功能:启动i2c起始信号
*/
void i2c_start()
{
SCL=1;
nops();
SDA=1;
nops();
SDA=0;
nops();
SCL=0;
}
/**
*函数:i2c_stop()
*功能:停止i2c
*/
void i2c_stop()
{
SCL=0;
nops();
SDA=0;
nops();
SCL=1;
nops();
SDA=1;
nops();
/**
*函数:i2c_ACK(bit ck)
*功能:ck为1时发送应答信号ACK, *ck为0时不发送ACK
*/
void i2c_ACK(bit ck)
{
if(ck)
SDA=0;
else
SDA=1;
nops();
SCL=1;
nops();
SCL=0;
nops();
SDA=1;
nops();
}
/**
*函数:i2c_waitACK()
*功能:返回为0时收到ACK
*返回为1时没收到ACK
*/
bit i2c_waitACK()
{
SDA=1;
nops();
SCL=1;
nops();
if(SDA)
{
SCL=0;
i2c_stop();
return1;
}
else
{
SCL=0;
return0;
}
}
*函数:i2c_sendbyte(uint8bt)
*功能:将输入的一字节数据bt发送*/
void i2c_sendbyte(uint8bt)
{
uint8i;
for(i=0;i<8;i++)
{
if(bt&0x80)
SDA=1;
else
SDA=0;
nops();
SCL=1;
bt<<=1;
nops();
SCL=0;
}
}
/**
*函数:i2c_recbyte()
*功能:从总线上接收1字节数据
*/
uint8i2c_recbyte()
{
uint8dee,i;
for(i=0;i<8;i++)
{
SCL=1;
nops();
dee<<=1;
if(SDA)
dee=dee|0x01;
SCL=0;
nops();
}
return dee;
}
/**
*函数:i2c_readbyte
*输入:addr
*功能:读出一字节数据
*返回值:0->成功1->失败
*/
bit i2c_readbyte(uint8com,uint8*dat)
{
i2c_start();
i2c_sendbyte(SLAVEADDR);//地址
if(i2c_waitACK())
return1;
i2c_sendbyte(com);//控制字节
if(i2c_waitACK())
return1;
i2c_start();
i2c_sendbyte(SLAVEADDR+1);//地址
if(i2c_waitACK())
return1;
*dat=i2c_recbyte();//读数据
i2c_ACK(0);//因为只读一字节数据,不发送ACK信号i2c_stop();
return0;
}
/**
*UART初始化
*波特率:9600
*/
void uart_init(void)
{
ET1=0;
TMOD=0x21;//定时器1工作在方式2(自动重装)SCON=0x50;//10位uart,允许串行接受
TH1=0xFD;
TL1=0xFD;
TR1=1;
}
/**
*UART发送一字节
*/
void UART_Send_Byte(uint8dat)
{
SBUF=dat;
while(TI==0);
TI=0;
}
main()
{
uint8ans;
uart_init();
while(1)
{
i2c_readbyte(0x43,&ans);
UART_Send_Byte(ans);
delay(50000);
}
}
输出:
下面介绍PCF8591的DA
忽然发现,已经写到AD/DA这里来了。
严格来说,已经不是51单片机的内容了,而是周边应用电路的一些东西。
这些东西涉及的知识面比较广,什么都有可能提到。
关于AD/DA,或者其它设备,我的学习思路是先模仿,再深究。
因为无论是课本也好,器件手册也好,大部分讲的都是原理或者寄存器,起到的是一个工具书的作用,类似于语文课上用的字典。
但是这就出现了一个问题,很多人想通过看课本或者看器件手册的方式来掌握这些设备。
这个思路有问题吗?没有问题吗?
还记得我刚才说的话么,它们就类似于语文课上用的字典,但是,有谁是通过看字典学会说话的!!!
我们都是通过模仿别人学会说话的,遇到不认识的字才去查字典!但是很多人或者很多学校都把这两件事的顺序搞反了。
记得之前我在英飞凌官网进行芯片选型,网页都翻烂了,找不到合适的。
因为英飞凌不是我家开的,我不能保证每次都能顺利的找到我想要的东西。
但是,我同事参加了一次电子展,在展会上遇到了英飞凌的展台,然后问他们,他们一听我们的需求,马上找出一堆能满足我们要求的芯片。
这就是思路的问题!
扯远了,说回到DA控制。
DA转换(Digital to Analog),是将数字量变成模拟量的一个过程。
AD与DA 刚好是相反的两个过程,AD是把模拟信号变成单片机可识别的数字信号;DA是把单片机可识别的数字信号变成连续变化的模拟量。
这两种功能的应用范围都非常广泛!
主要参数如下,具体什么意思就不讲了,大家可以百度一下。
(因为我编不出来了...)
1)分辩率(Resolution)
2)转换速率(Conversion Rate)
3)量化误差(Quantizing Error)
4)偏移误差(Offset Error)
5)满刻度误差(Full Scale Error)
6)线性度(Linearity)
其他指标还有:绝对精度(Absolute Accuracy),相对精度(Relative Accuracy),微分非线性,单调性和无错码,总谐波失真(Total Harmonic Distotortion缩写THD)和积分非线性。
看到这么多参数,是不是很晕?
搞了这些年电子,感触最深的有一点是:无论做什么,先求有,再求好!
不要总想一口吃个胖子,没那么多天才。
参数是很多,但是没要求你一下子全都记住,甚至你可以只记一两个。
先把大致的应用流程跑一遍,跑下来,你才对这个设备有一个整体的概念,然后针对你的要求,比对相应的参数,进行修改、调试。
哪怕是在工作中,也不一定会考虑全部的参数。
例如转换时间,我到现在也没认真看PIC内部的AD采样转换时间有多久,因为有些设备对实时性要求很低,速度慢一些也没事。
然后是控制流程,认真看器件手册的,或者看了昨天日志的,都知道是怎样一个流程:
第一步:写器件地址;
第二步:写控制位。
第三步:写入数据。
好了,上程序。
通过DA输出渐变电压控制LED,形成呼吸灯的效果。
里面有个警告:***WARNING L16:UNCALLED SEGMENT,IGNORED FOR OVERLAY PROCESS 大家可以研究下,如何消除警告。
程序源码如下:
/**********************51单片机学习例程************************
*平台:Keil U34+STC89C52RD
*名称:IIC协议PCF8591ADDA转换,此程序通过IIC协议对DAAD芯片操作,并输出模拟量,用LED亮度渐变指示
*编写:起航
*晶振:11.0592MHZ
******************************************************************/
#include<reg52.h>//包含头文件,一般情况不需要改动,头文件包含特殊功能寄存器的定义
#include<intrins.h>//包含NOP空指令函数_nop_();
#define AddWr0x90//写数据地址
#define AddRd0x91//读数据地址
sbit RST=P2^4;//关掉时钟芯片输出
sbit Sda=P2^0;//定义总线连接端口
sbit Scl=P2^1;
sbit Fm=P2^3;//FM
sbit dula=P2^6;
sbit wela=P2^7;
//bit ADFlag;//定义AD采样标志位
unsigned char code
Datatab[]={0x3f,0x06,0x5b,0x4f,0x66,0x6d,0x7d,0x07,0x7f,0x6f};//7段数共阴码管段码表
data unsigned char Display[8];//定义临时存放数码管数值
/*------------------------------------------------
延时程序
------------------------------------------------*/
void mDelay(unsigned char j)
{
unsigned int i;
for(;j>0;j--)
{
for(i=0;i<125;i++)
{;}
}
}
/*------------------------------------------------
初始化定时器1
------------------------------------------------*/
void Init_Timer1(void)
{
TMOD|=0x10;
TH1=0xff;/*Init value*/
TL1=0x00;
//PT1=1;/*优先级*/
EA=1;/*interupt enable*/
ET1=1;/*enable timer1interrupt*/
TR1=1;
}
/*------------------------------------------------启动IIC总线
------------------------------------------------*/ void Start(void)
{
Sda=1;
_nop_();
Scl=1;
_nop_();
Sda=0;
_nop_();
Scl=0;
}
/*------------------------------------------------停止IIC总线
------------------------------------------------*/ void Stop(void)
{
Sda=0;
_nop_();
Scl=1;
_nop_();
Sda=1;
_nop_();
Scl=0;
}
/*------------------------------------------------应答IIC总线
------------------------------------------------*/ void Ack(void)
{
Sda=0;
_nop_();
Scl=1;
_nop_();
Scl=0;
_nop_();
}
/*------------------------------------------------
发送一个字节
------------------------------------------------*/ void Send(unsigned char Data)
{
unsigned char BitCounter=8;
unsigned char temp;
do
{
temp=Data;
Scl=0;
_nop_();
if((temp&0x80)==0x80)
Sda=1;
else
Sda=0;
Scl=1;
temp=Data<<1;
Data=temp;
BitCounter--;
}
while(BitCounter);
Scl=0;
}
/*------------------------------------------------
写入DA数模转换值
------------------------------------------------*/ void DAC(unsigned char Data)
{
Start();
Send(AddWr);//写入芯片地址
Ack();
Send(0x40);//写入控制位,使能DAC输出Ack();
Send(Data);//写数据
Ack();
Stop();
}
void fmg(void)//fm关
{
Fm=1;//关fm
}
void cmg(void)//数码管锁存函数关时钟DS1302
{
dula=1;
P0=0x00;
dula=0;
wela=1;
P0=0x00;
wela=0;
RST=0;//关时钟DS1302
}
/*------------------------------------------------
主程序
------------------------------------------------*/
void main()
{
unsigned char num;//DA数模输出变量
Init_Timer1();
cmg();//数码管锁存
fmg();
while(1)
{
DAC(num);//DA输出,可以用LED模拟电压变化
num++;//累加,到256后溢出变为0,往复循环。
显示在LED上亮度逐渐变化mDelay(20);//延时用于清晰看出变化
}
}。