STC51入门笔记(郭天祥C语言)---第五节:A/D和D/A工作原理
作者:sumjess
如温度、压力、位移、图像等都是模拟量,电子线路中模拟量通常包括模拟电压和模拟电流,生活用电220V交流正弦波就属于模拟电压,随着负载大小的变化,其电流大小也跟着变化,这里的电流信号也属于模拟电流,图5.1.1和图5.1.2所表示的信号就属于模拟量。
像图5.1.1和图5.1.2所示信号的幅值随着时间变化而连续变化的量就是模拟量,模拟量有可能是标准的正弦波,有可能是不规则的任何波形,也有可能是规则的方波、三角波等,当我们用数值表示其大小时,通常用十进制数表示,如2.3V,5A,47N等。
单片机系统内部运算时用的全部是数字量,即0和1,因此对单片机系统而言,我们无法直接操作模拟量,必须将模拟量转换成数字量。所谓数字量,就是用一系列0和1组成的二进制代码表示某个信号大小的量。用数字量表示同一个模拟量时,数字位数可以多也可以少,位数越多则表示的精度越高,位数越少表示的精度就越低。比如对图5.1.2中的正弦波模拟量,我们可以用一个00000~11111的5位二进制数字量来表示它,5位二进制数最多只能有32种组合形式,因此需把这个正弦波最大值与最小值之间分成32等分,每一等分用一组5位二进制数来表示。很显然,如果用32等分数字量表示这个模拟量的话,任意两相邻等分之间的模拟量我们便无法表示出来,唯有增加等分,也就是再增加数字量的位数才可表示出来。因此,若要用数字量完全表示一个模拟量的话,其数字量位数就为无穷多位。但若要设计出这样的硬件,当今的技术还无法实现。
单片机在采集模拟信号时,通常都需要在前端加上模拟量/数字量转换器,简称模/数转换器,即常说的A/D(AnalogtoDigital) 芯片。当单片机在输出模拟信号时,通常在输出级要加上数字量/模拟量转换器,简称数/模转换器,即常说的D/A(DigitaltoAnalog) 芯片,下面几节分别讲解A/D和D/A的原理。
二、A/D 转换原理及参数指标:在A/D转换器中,因为输入的模拟信号在时间上是连续的,而输出的数字信号代码是离散的,所以A/D转换器在进行转换时,必须在一系列选定的瞬间(时间坐标轴上的一些规定点上)对输入的模拟信号采样,然后再把这些采样值转换为数字量。因此,一般的A/D转换过程是通过采样保持、量化和编码这三个步骤完成的,即首先对输入的模拟电压信号采样,采样结束后进入保持时间,在这段时间内将采样的电压量转化为数字量,并按一定的编码形式给出转换结果,然后开始下一次采样。图5.2.1给出模拟量到数字量转换过程的框图。
1、采样定理:
可以证明, 为了正确无误地用图 5.2.2中所示的采样信号 Vs 表示模拟信号 VI,必须满足:
式中,fs为采样频率,fimax为输入信号VI的最高频率分量的频率。上式就是所谓的采样定理。
在满足采样定理的条件下,可以用一个低通滤波器将信号Vs还原为VI,这个低通滤波器的电压传输系数IA(f)I在低于fimax的范围内应保持不变,而在fs-fimax以前应迅速下降为零,如图5.2.3所示。因此,采样定理规定了A/D转换的频率下限。
因此,A/D转换器工作时的采样频率必须高于式(5.2.1) 所规定的频率。采样频率提高以后,留给A/D转换器每次进行转换的时间也相应缩短了,这就要求转换电路必须具备更快的工作速度。因此,不能无限制地提高采样频率,通常取fs=(3~5)fimax已经能够满足要求。
因为每次把采样电压转换为相应的数字量都需要一定的时间,所以在每次采样以后,必须把采样电压保持一段时间。可见,进行A/D转换时所用的输入电压,实际上是每次采样结束时的VI值。
2、量化和编码:
我们知道,数字信号不仅在时间上是离散的,而且数值上的变化也不是连续的。这就是说,任何一个数字量的大小,都是以某个最小数量单位的整倍数来表示的。因此,在用数字量表示采样电压时,也必须把它化成这个最小数量单位的整倍数,这个转化过程就叫做量化。所规定的最小数量单位叫做量化单位,用△表示。显然,数字信号最低有效位中的1表示的数量大小,就等于△。把量化的数值用二进制代码表示,称为编码。这个二进制代码就是A/D转换的输出信号。
既然模拟电压是连续的,那么它就不一定能被△整除,因而不可避免地会引入误差,我们把这种误差称为量化误差。在把模拟信号划分为不同的量化等级时,用不同的划分方法可以得到不同的量化误差。
假定需要把0+1v的模拟电压信号转换成三位二进制代码,这时便可以取△=(1/8)V,并规定凡数值在0(1/8)V之间的模拟电压都当做0x△,.看待,用二进制的000表示;凡数值在(1/8)~(2/8)V之间的模拟电压都当做1X△看待,用二进制的001表示,……,如图5.2.4(a)所示。不难看出,最大的量化误差可达△,即(1/8)V。
为了减少量化误差,通常采用图5.2.4(b)所示的划分方法,取量化单位△= (2/15)V,并将000代码所对应的模拟电压规定为0~ (1/15)v, 即0~△/2。这时,最大量化误差将减少为△/2=(1/15)V。这个道理不难理解,因为现在把每个二进制代码所代表的模拟电压值规定为它所对应的模拟电压范围的中点,所以最大的量化误差自然就缩小为△/2了。
(1)电路组成及工作原理
N沟道MOS管T作为采样开关用。
当控制信号VL为高电平时,T导通,输入信号VI经电阻Ri和T向电容Ch充电。若取Ri=Rf,则充电结束后Vo=-VI=Vc。
当控制信号返回低电平,T截止。由于Ch无放电回路,所以Vo的数值被保存下来。
缺点:采样 过程中需要通过Ri和T向Ch充电,所以使采样速度受到了限制。同时,Ri的数值又不允许取得很小,否则会进一步降低采样电路的输入电阻。
(2)改进电路及其工作原理
图5.2.6是单片集成采样-保持电路LE198的电路原理图及符号,它是一个经过改进的采样-保持电路。图5.2.6中AI,A2是两个运算放大器,S是电子开关,L是开关的驱动电路,当逻辑输入VL为1,即VL为高电平时,S闭合;VL为0,即低电平时,S断开。
当S闭合时,A1,A2均工作在单位增益的电压跟随器状态,所以vo=v’o=vI。如果将电容Ch接到R2的引出端和地之间,则电容上的电压也等于VI。当VL返回低电平以后,虽然S断开了,但由于Ch上的电压不变,所以输出电压Vo的数值得以保持下来。
在S再次闭合以前的这段时间里,如果VI发生变化,V’o可能变化非常大,甚至会超过开关电路所能承受的电压,因此需要增加D1和D2构成保护电路。当v’o比Vo所保持的电压高(或低)一个二极管的压降时,D1(或D2)导通,从而将v’o限制在叩+vI+VD以内。而在开关S闭合的情况下,v’o和Vo相等,故D1和D2均不导通,保护电路不起作用。
3、直接A/D转换器:
直接A/D转换器能把输入的模拟电压直接转换成输出的数字量而不需要经过中间变量。常用的电路有并行比较型和反馈比较型两类。
(1)并行比较型A/D转换器:
三位并行比较型A/D转换原理电路如图5.2.7所示,它由电压比较器、寄存器和代码转换器三部分组成。电压比较器中量化电平的划分采用图5.2.4(b)所示的方式,用电阻链把参考电压VREF分压,得到从(1/15)*VREF(13/15)*VREF之间7个比较电平,量化单位△=(2/15)*VREF。然后,把这7个比较电平分别接到7个比较器C1C7的输入端作为比较基准。同时将要输入的模拟电压同时加到每个比较器的另一个输入端上,与这7个比较基准进行比较。
单片集成并行比较型A/D转换器的产品较多,如AD公司AD9012 (8位)、AD9002(8位)AD9020(10位)等。
并行AD转换器具有如下特点:
① 由于转换是并行的,其转换时间只受比较器、触发器和编码电路延迟时间限制,因此转换速度快。② 随着分辨率的提高,元件数目要按几何级数增加。一个n位转换器,所用的比较器个数为2n-1,如8位的并行A/D转换器就需要28-1=255个比较器。由于位数愈多,电路愈复杂,因此制成分辨率较高的集成并行A/D转换器是比较困难的。③ 使用这种含有寄存器的并行A/D转换电路时,可以不用附加采样-保持电路,因为比较器和寄存器这两部分也兼有采样-保持功能,这也是该电路的一个优点。
并行比较型A/D转换器的缺点:需 要用很多的电压比较器和触发器。从图5.2.7得知,输出为n位二进制代码的转换器中应当有2n-1个电压比较器和2n-1个触发器。电路的规模随着输出代码位数的增加而急剧增加。如果输出为10位二进制代码,则需要用2^10-1=1023个比较器和1023个触发器以及一个规模相当大的代码转换电路。
(2)反馈比较型A/D转换器:
反馈比较型A/D转换器的构思是:取一个数字量加到D/A转换器上,于是得到一个对应的输出模拟电压,将这个模拟电压和输入的模拟电压信号比较,如果两者不相等,则调整所取的数字量,直到两个模拟电压相等为止,最后所取的这个数字量就是所求的转换结果。
在反馈比较型A/D转换器中经常采用的有计数型和逐次比较型两种方案。
图5.2.8 为计数型 A/D 转换器原理框图。转换电路由比较器 C、D/A 转换器、计数器、脉冲源、控制门G 以及输出寄存器等几部分组成。
转换开始前先用复位信号将计数器置0,而且转换信号应停留在VL=0的状态,这时门G被封锁,计数器不工作。计数器加给D/A转换器的是全0信号,所以D/A转换器输出的模拟电压vo=0。如果VI为正电压信号,比较器的输出电压为1。依同样方法比较完DA的全部位数。
因为在转换过程中计数器中的数字不停地变化,所以不宜将计数器的状态直接作为输出信号,为此在输出端设置了输出寄存器,在每次转换完成以后,用转换控制信号的下降沿将计数器输出的数字置入输出寄存器中,而以寄存器的状态作为最终的输出信号。这个方案的明显问题是:转换时间长,当输出为n位二进制数码时,最长的转换时间可达到2^n-1倍的时钟信号周期,因此这种方法只能用在对转换速度要求不高的场合。然而由于它的电路非常简单,所以在对转换速度没有严格要求时仍是一种可取的方案。
为了提高转换速度,在计数型A/D转换的基础上又产生了逐次比较型A/D转换器。逐次逼近转换过程与用天平称物重非常相似。按照天平称重的思路,逐次比较型A/D转换器,就是将输入模拟信号与不同的参考电压做多次比较,使转换所得的数字量在数值上逐次逼近输入模拟量的对应值。
三位逐次比较型A/D转换器的逻辑电路如图5.2.9所示。这是一个输出为三位二进制数的逐次比较型A/D转换器。图5.2.9中C为电压比较器,当vI>=vo时,比较器的输出为0;反过来,输出为1。FFA,FFB,FFc三个触发器组成了三位数码寄存器,触发器FF1—FF5和门电路G1~G9组成控制逻辑电路。
由以上分析可见,逐次比较型A/D转换器完成一次转换所需时间与其位数和时钟脉冲频率有关,位数愈少,时钟频率越高,转换所需时间越短。这种A/D转换器具有转换速度快,精度高的特点。
集成逐次比较型A/D转换器有ADC0804/0808/0809系列(8)位、AD575(10位)、AD574A(12位)等。
(3)间接A/D转换器:
目前使用的间接A/D转换器多半都属于电压-时间变换型(V-T变换型)和电压-频率变换型(V-F变换型)两类。
在V-T变换型A/D转换器中,首先把输入的模拟电压信号转换成与之成正比的时间宽度信号,然后在这个时间宽度里对固定频率的时钟脉冲计数,计数的结果就是正比于输入模拟电压的数字信号。
在V-F变换型A/D转换器中,则首先把输入的模拟电压信号转换成与之成正比的频率信号,然后在一个固定的时间间隔里对得到的频率信号计数,所得到的结果就是正比于输入模拟电压的数字量。
下面给出V-T变换型和V-F变换型两种A/D的结构框图,大家了解即可。
(4)A/D转换器的参数指标:
① 分辨率—它说明A/D转换器对输入信号的分辨能力。
A/D转换器的分辨率以输出二进制数的位数表示。从理论上讲,n 位输出的A/D转换器能区分2n个不同等级的输入模拟电压,能区分输入电压的最小值为满量程输入的1/2n,在最大输入电压一定时,输出位数愈多,量化单位愈小,分辨率愈高。常用的有8, 10,12,16,24,32位等。例如,A/D转换器输出为8位二进制数,输入信号最大值为5V, 那么这个转换器应能区分输入信号的最小电压为19.53mV(5Vx1/28≈19.53mV)。再如,某A/D转换器输入模拟电压的变化范围为-10V~+10V,转换器为8位,若第一位用来表示正、负号,其余7位表示信号幅值,则最末一位数字可代表80mV模拟电压(10Vxl/27≈80mV),即转换器可以分辨的最小模拟电压为80mV。而同样情况下,用一个10位转换器能分辨的最小模拟电压为20mV(10Vx1/2^9≈20mV)。
②转换误差—表示A/D转换器实际输出的数字量与理论输出数字量之间的差别。
在理想情况下,输入模拟信号所有转换点应当在一条直线上,但实际的特性不能做到输入模拟信号所有转换点在一条直线上。转换误差是指实际的转换点偏离理想特性的误差,一般用最低有效位来表示。例如,给出相对误差<士LSB/2,这就表明实际输出的数字量和理论上应得到的输出数字量之间的误差小于最低位的一半。注意,在实际使用中当使用环境发生变化时,转换误差也将发生变化。
③转换精度—它是A/D转换器的最大量化误差和模拟部分精度的共同体现。
具有某种分辨率的转换器在量化过程中由于采用了四舍五入的方法,因此最大量化误差应为分辨率数值的一半。如上例,8位转换器最大量化误差应为40mV(80mVx0.5=40mV),全量程的相对误差则为0.4%(40mV/10Vx100%)。可见,A/D转换器数字转换的精度由最大量化误差决定。实际上,许多转换器末位数字并不可靠,实际精度还要低一些。
由于含有A/D转换器的模/数转换模块通常包括有模拟处理和数字转换两部分,因此整个转换器的精度还应考虑模拟处理部分(如积分器、比较器等)的误差。一般转换器的模拟处理误差与数字转换误差应尽量处在同一数量级,总误差则是这些误差的累加和。例如,一个10位A/D转换器用其中9位计数时的最大相对量化误差为2^9x0.5≈0.1%,若模拟部分精度也能达到0.1%,则转换器总精度可接近0.2%。
④ 转换时间—指A/D转换器从转换控制信号到来开始,到输出端得到稳定的数字信号所经过的时间。
不同类型的转换器转换速度相差甚远。其中并行比较A/D转换器转换速度最高,8位二进制输出的单片集成A/D转换器转换时间可达50μs以内。逐次比较型A/D转换器次之,它们多数转换时间在10~50µs之间,也有达几百纳秒的。间接A/D转换器的速度最慢,如双积分A/D转换器的转换时间大都在几十毫秒至几百毫秒之间。在实际应用中,应从系统数据总的位数、精度要求、输入模拟信号的范围及输入信号极性等方面综合考虑A/D转换器的选用。
【例5.2.1】某信号采集系统要求用一片A/D转换集成芯片在1s(秒)内对16个热电偶的输出电压分时进行A/D转换。已知热电偶输出电压范围为0-0.025V(对应于0~450°C温度范围),需要分辨的温度为0.1°C,试问应选择多少位的A/D转换器,其转换时间为多少?
解:对于从0-450°C温度范围,信号电压范围为0~0.025v,分辨温度为0.1°C,这相当于0.1/450=1/4500的分辨率。12位A/D转换器的分辨率为1/2^12=1/4096,所以必须至少选用13位的A/D转换器。
系统的采样速率为16次每秒,采样时间为62.5ms。对于这样慢的采样速度,任何一个A/D转换器都可以达到。可选用13位或13位以上带有采样-保持(S/H)的逐次比较型A/D转换器或不带S/H的双积分式A/D转换器均可。
① 不同的A/D转换方式具有各自的特点,在要求转换速度高的场合,选用并行A/D转换器;在要求精度高的情况下,可采用双积分A/D转换器,当然也可选高分辨率的其他形式A/D转换器,但会增加成本。由于逐次比较型A/D转换器在一定程度上兼有以上两种转换器的优点,因此得到普遍应用。
② A/D转换器和D/A转换器的主要技术参数是转换精度和转换速度,在与系统连接后,转换器的这两项指标决定了系统的精度与速度。
1、特性:
2、应用:
3、概述:
4、内部框图:
5、引脚:
6、功能描述:
6.1 地址:
6.2 控制字:
6.3 DA转换:
6.4 AD转换:
6.5 参考电压:
6.6 振荡器:
7、I2C总线特性:
7.1 位传输:
7.2开始或停止条件:
7.3 系统配置:
7.4 应答:
7.5 I2C总线协议:
8、应用消息:
四、PCF8059驱动程序:#include <reg52.h>
#include "24c02_Sum.h"
#include "pcf8591_Sum.h"
#include "1602_Sum.h"
#define PCF8591 0x90 //PCF8591 地址 该芯片的固定I2C地址 ,PDF资料上提供。
unsigned char AD_temp;
unsigned int D[1]; //定义一个数组的空间,内部有 8个字节,该程序只需使用一个字节
void main()
{
lcdinit_1602(); //1602液晶初始化
Disp_1602(1,1," PCF8591-TEST ",16); //写1602显示框架
Disp_1602(1,2," DATA: 000 ",16);
while(1)
{
ISendByte(PCF8591,0x40); // 0x40 对应AD 输入 AIN0 0x41 对应AD 输入 AIN1 以此类推。
D[1]=IRcvByte(PCF8591); //ADC3 模数转换4
AD_temp=D[1];
DACconversion(PCF8591,0x40, AD_temp); //DAC 数模转换
//*********************************************************************
write_twoline_1602(9,AD_temp); //把读出的数据送 1602显示
//*********************************************************************
}
}
/*
* 文 件 名:24c02.c
* 芯 片:24c02
* 晶 振:11.0592MHz
* 创 建 者:XK
* 创建日期:2011.9.17
* 修 改 者:
* 修改日期:
* 功能描述:24c02,读写数据函数
*/
#include <reg52.h>
#include <intrins.h>
#include "24c02_Sum.h"
#define NOP() _nop_() /*定义空指令*/
#define _Nop() _nop_() /*定义空指令*/
sbit SCL=P2^1; //I2C 时钟
sbit SDA=P2^0; //I2C 数据
bit ack; /*应答标志位*/
//AT2402的功能函数
/*******************************************************************
向有子地址器件发送多字节数据函数
函数原型: bit ISendStr(UCHAR sla,UCHAR suba,ucahr *s,UCHAR no);
功能: 从启动总线到发送地址,子地址,数据,结束总线的全过程,从器件
地址sla,子地址suba,发送内容是s指向的内容,发送no个字节。
如果返回1表示操作成功,否则操作有误。
注意: 使用前必须已结束总线。
********************************************************************/
bit ISendStr(unsigned char sla,unsigned char suba,unsigned char *s,unsigned char no)
{
unsigned char i;
Start_I2c(); /*启动总线*/
SendByte(sla); /*发送器件地址*/
if(ack==0)return(0);
SendByte(suba); /*发送器件子地址*/
if(ack==0)return(0);
for(i=0;i<no;i++)
{
SendByte(*s); /*发送数据*/
if(ack==0)return(0);
s++;
}
Stop_I2c(); /*结束总线*/
return(1);
}
/*******************************************************************
向有子地址器件读取多字节数据函数
函数原型: bit RecndStr(UCHAR sla,UCHAR suba,ucahr *s,UCHAR no);
功能: 从启动总线到发送地址,子地址,读数据,结束总线的全过程,从器件
地址sla,子地址suba,读出的内容放入s指向的存储区,读no个字节。
如果返回1表示操作成功,否则操作有误。
注意: 使用前必须已结束总线。
********************************************************************/
bit IRcvStr(unsigned char sla,unsigned char suba,unsigned char *s,unsigned char no)
{
unsigned char i;
Start_I2c(); /*启动总线*/
SendByte(sla); /*发送器件地址*/
if(ack==0)return(0);
SendByte(suba); /*发送器件子地址*/
if(ack==0)return(0);
Start_I2c(); /*重新启动总线*/
SendByte(sla+1);
if(ack==0)return(0);
for(i=0;i<no-1;i++)
{
*s=RcvByte(); /*发送数据*/
Ack_I2c(0); /*发送就答位*/
s++;
}
*s=RcvByte();
Ack_I2c(1); /*发送非应位*/
Stop_I2c(); /*结束总线*/
return(1);
}
/*******************************************************************
/*******************************************************************
起动总线函数
函数原型: void Start_I2c();
功能: 启动I2C总线,即发送I2C起始条件.
********************************************************************/
void Start_I2c()
{
SDA=1; /*发送起始条件的数据信号*/
_Nop();
SCL=1;
_Nop(); /*起始条件建立时间大于4.7us,延时*/
_Nop();
_Nop();
_Nop();
_Nop();
SDA=0; /*发送起始信号*/
_Nop(); /* 起始条件锁定时间大于4μs*/
_Nop();
_Nop();
_Nop();
_Nop();
SCL=0; /*钳住I2C总线,准备发送或接收数据 */
_Nop();
_Nop();
}
/*******************************************************************
结束总线函数
函数原型: void Stop_I2c();
功能: 结束I2C总线,即发送I2C结束条件.
********************************************************************/
void Stop_I2c()
{
SDA=0; /*发送结束条件的数据信号*/
_Nop(); /*发送结束条件的时钟信号*/
SCL=1; /*结束条件建立时间大于4μs*/
_Nop();
_Nop();
_Nop();
_Nop();
_Nop();
SDA=1; /*发送I2C总线结束信号*/
_Nop();
_Nop();
_Nop();
_Nop();
}
/*******************************************************************
字节数据发送函数
函数原型: void SendByte(UCHAR c);
功能: 将数据c发送出去,可以是地址,也可以是数据,发完后等待应答,并对
此状态位进行操作.(不应答或非应答都使ack=0)
发送数据正常,ack=1; ack=0表示被控器无应答或损坏。
********************************************************************/
void SendByte(unsigned char c)
{
unsigned char BitCnt;
for(BitCnt=0;BitCnt<8;BitCnt++) /*要传送的数据长度为8位*/
{
if((c<<BitCnt)&0x80)SDA=1; /*判断发送位*/
else SDA=0;
_Nop();
SCL=1; /*置时钟线为高,通知被控器开始接收数据位*/
_Nop();
_Nop(); /*保证时钟高电平周期大于4μs*/
_Nop();
_Nop();
_Nop();
SCL=0;
}
_Nop();
_Nop();
SDA=1; /*8位发送完后释放数据线,准备接收应答位*/
_Nop();
_Nop();
SCL=1;
_Nop();
_Nop();
_Nop();
if(SDA==1)ack=0;
else ack=1; /*判断是否接收到应答信号*/
SCL=0;
_Nop();
_Nop();
}
/*******************************************************************
字节数据接收函数
函数原型: UCHAR RcvByte();
功能: 用来接收从器件传来的数据,并判断总线错误(不发应答信号),
发完后请用应答函数应答从机。
********************************************************************/
unsigned char RcvByte()
{
unsigned char retc;
unsigned char BitCnt;
retc=0;
SDA=1; /*置数据线为输入方式*/
for(BitCnt=0;BitCnt<8;BitCnt++)
{
_Nop();
SCL=0; /*置时钟线为低,准备接收数据位*/
_Nop();
_Nop(); /*时钟低电平周期大于4.7μs*/
_Nop();
_Nop();
_Nop();
SCL=1; /*置时钟线为高使数据线上数据有效*/
_Nop();
_Nop();
retc=retc<<1;
if(SDA==1)retc=retc+1; /*读数据位,接收的数据位放入retc中 */
_Nop();
_Nop();
}
SCL=0;
_Nop();
_Nop();
return(retc);
}
/********************************************************************
应答子函数
函数原型: void Ack_I2c(bit a);
功能: 主控器进行应答信号(可以是应答或非应答信号,由位参数a决定)
********************************************************************/
void Ack_I2c(bit a)
{
if(a==0)SDA=0; /*在此发出应答或非应答信号 */
else SDA=1;
_Nop();
_Nop();
_Nop();
SCL=1;
_Nop();
_Nop(); /*时钟低电平周期大于4μs*/
_Nop();
_Nop();
_Nop();
SCL=0; /*清时钟线,钳住I2C总线以便继续接收*/
_Nop();
_Nop();
}
#include "24c02_Sum.h"
#include "pcf8591_Sum.h"
extern bit ack;
/*******************************************************************
DAC 变换, 转化函数
*******************************************************************/
bit DACconversion(unsigned char sla,unsigned char c, unsigned char Val)
{
Start_I2c(); //启动总线
SendByte(sla); //发送器件地址
if(ack==0)return(0);
SendByte(c); //发送控制字节
if(ack==0)return(0);
SendByte(Val); //发送DAC的数值
if(ack==0)return(0);
Stop_I2c(); //结束总线
return(1);
}
/*******************************************************************
ADC发送字节[命令]数据函数
*******************************************************************/
bit ISendByte(unsigned char sla,unsigned char c)
{
Start_I2c(); //启动总线
SendByte(sla); //发送器件地址
if(ack==0)return(0);
SendByte(c); //发送数据
if(ack==0)return(0);
Stop_I2c(); //结束总线
return(1);
}
/*******************************************************************
ADC读字节数据函数
*******************************************************************/
unsigned char IRcvByte(unsigned char sla)
{ unsigned char c;
Start_I2c(); //启动总线
SendByte(sla+1); //发送器件地址
if(ack==0)return(0);
c=RcvByte(); //读取数据0
Ack_I2c(1); //发送非就答位
Stop_I2c(); //结束总线
return(c);
}
//******************************************************************/
/*
* 文 件 名:1602led.c.c
* 芯 片:1602液晶
* 晶 振:11.0592MHz
* 创 建 者:XK
* 创建日期:2011.8.6
* 修 改 者:Sumjess
* 修改日期:2019.5.29
* 功能描述:1602,写数据函数
*/
#include <reg52.h>
#include "1602_Sum.h"
#include "delay_Sum.h"
#include "74hc595_Sum.h"
sbit rs_1602=P2^5; //1602rs信号
sbit rw_1602=P2^6; //1602rw信号
sbit e=P2^4;
/*
void write_zl(uchar zl)
{
rs=0;
P0=zl;
delay(5);
e=1;
delay(5);
e=0;
}
void write_sj(uchar sj)
{
rs=1;
P0=sj;
delay(5);
e=1;
delay(5);
e=0;
} */
void write_order_1602(uchar order_data)
{
e=0;
rs_1602=0;
P0=order_data;
e=1;
delay(1);
e=0;
}
void write_data_1602(uchar data_1602)
{
e=0;
rs_1602=1;
P0=data_1602;
e=1;
delay(1);
e=0;
}
/
//功能:按指定位置显示一串字符
///
//输入:
//列显示地址x_1602(取值范围1-16)
//行显示地址y_1602(取值范围1-2),
//指定字符串指针*p_1602,
//要显示的字符个数count_1602 (取值范围1-16)
///
// 子函数使用例子: Disp_1602(1,1," TEMP: . ",16); //在1602第一行第一列写16个字符,既字符串数据
/
void Disp_1602(unsigned char x_1602,unsigned char y_1602,unsigned char *p_1602,unsigned char count_1602)
{
unsigned char i;
for(i=0;i<count_1602;i++)
{
if (1 == y_1602) x_1602 |= 0x80; //当要显示第一行时地址码+0x80;
else x_1602 |= 0xC0; //在第二行显示是地址码+0xC0;
write_order_1602(x_1602-1);
write_data_1602(*p_1602);
x_1602++;
p_1602++;
}
}
void write_oneline_1602(uchar add_1602,uint date_1602)
{
uchar bai,ge,shi;
bai=date_1602/100;
shi=(date_1602%100)/10;
ge=date_1602%10;
write_order_1602(0x80+add_1602-1);
write_data_1602(0x30+bai);
write_order_1602(0x80+add_1602);
write_data_1602(0x30+shi);
write_order_1602(0x80+add_1602+1);
write_data_1602(0x30+ge);//在1602上写时间函数 即在1602第二行指定位置上写显示
//在1602上写时间函数 即在1602第二行指定位置上写显示
}
void write_twoline_1602(uchar add_1602,uint date_1602)
{
uchar bai,ge,shi;
bai=date_1602/100;
shi=(date_1602%100)/10;
ge=date_1602%10;
write_order_1602(0x80+0x40+add_1602-1);
write_data_1602(0x30+bai);
write_order_1602(0x80+0x40+add_1602);
write_data_1602(0x30+shi);
write_order_1602(0x80+0x40+add_1602+1);
write_data_1602(0x30+ge);//在1602上写时间函数 即在1602第二行指定位置上写显示
}
void write_oneline_DS18b20_1602(uchar add_1602,uint date_1602)
{
uchar qian,bai,shi,ge;
qian=date_1602/1000;
bai=(date_1602%1000)/100;
shi=(date_1602%100)/10;
ge=date_1602%10;
write_order_1602(0x40+0x40+add_1602-1);
write_data_1602(0x30+qian);
write_order_1602(0x40+0x40+add_1602);
write_data_1602(0x30+bai);
write_order_1602(0x40+0x40+add_1602+1);
write_data_1602(0x30+shi);
write_order_1602(0x40+0x40+add_1602+3);
write_data_1602(0x30+ge); //在1602上写时间函数 即在1602第二行指定位置上写显示
}
void lcdinit_1602()
{
rw_1602=0;
e=0;
write_byte_74hc595(0X02);//关流水灯使能 关蜂鸣器 光继电器 打开1602背光打开
write_order_1602(0x38); //液晶设置不判忙模式
write_order_1602(0x0c); //开显示 不显示光标
write_order_1602(0x06); //当写一个字符是,地址指针加 1
write_order_1602(0x01); //显示清屏
}
版权声明
本文仅代表作者观点,不代表博信信息网立场。