AD和DA转换

1 AD转换及其相关背景知识

1.1 基本概念

(1)什么是AD转换?A(A,analog,模拟的,D,digital,数字的)

现实世界是模拟的,连续分布的,无法被分成有限份;
计算机世界是数字的,离散分布的,是可以被分成有限份的;
AD转换就是把一个物理量从模拟的转换成数字的。

(2)AD转换的意义?

(3)什么情况下需要AD转换。

1.2 AD转换的原理

(1)比较器,所有的AD转换芯片内部都是用比较器来实现的。

(2)和10进制转2进制有点像。

1.3 AD转换中的主要概念

(1)位数,AD转换后转出来的二进制数由几位二进制来表示。位数越多,越细腻。

(2)量程,AD转换器可以接受的模拟量的范围。

(3)精度,简单理解就是转出来到底有多准。

(4)分辨率,AD转换器转出来的二进制数,每一格表示多少。

(5)转换速率(转换时间)。

举个栗子:
输入电压范围0-5V,AD转换输出位数是10,精度是0.01V,则:量程为0-5V,
分辨率为:(5-0)/2exp(10)=0.00488V
譬如一次AD转换后得到的数据是1010101010,则对应的电压值为:3.328V,考虑精度后为3.33V

1.4 AD转换在系统中存在的方式

(1)CPU外部扩展专用AD芯片。

(2)CPU内部集成AD模块(内部外设)。

2 原理图和数据手册

2.1 原理图和接线

(1)SPI接口。
CLK接P1.0,CS接P1.1,DI接P1.2,DQ接P1.3.

(2)3种模拟电压变化原理:
AIN0靠滑动变阻器控制电压变化,AIN1靠热敏电阻NTC,AIN2靠光敏电阻GR。

AD和DA转换_第1张图片

AD和DA转换_第2张图片

2.2 数据手册

ET2046的控制字:
bit7:1
bit6-4:决定采样的是哪一路(AIN0、AIN1、AIN2、AIN3)
AIN0:001/011 X+
AIN1:101 Y+
AIN2:010 VBAT
AIN3:110 AUX
bit3:设置采样位数。0表示12bit,1表示8bit,一般都用12bit
bit2:为1表示用单端模式,为0表示差分模式。此处用单端模式
bit1-0:power down模式使能,00表示使能。

读AIN0:0b10010100 = 0x94 (实验下0xb4行不行)
读AIN1:0b11010100 = 0xD4
读AIN2:0b10100100 = 0xA4
读AIN3:0b11100100 = 0xE4

3 结合例程分析时序

3.1 时序图

AD和DA转换_第3张图片

(1)SPI变种。

(2)上升沿写入下降沿读出(之前DS1302的时候也是这样的)。

(3)读写都是高位在前(之前DS1302的时候是低位在前的)。

(4)注意写和读的交接点。

3.2 官方例程分析

(1)写指令。

(2)读12位数字值。

(3)写+读形成一个完整采样周期。

#include"XPT2046.h"

/****************************************************************************
*函数名:SPI_Write
*输  入:dat:写入数据
*输  出:无
*功  能:使用SPI写入数据
****************************************************************************/

void SPI_Write(uchar dat)
{
    uchar i;
    CLK = 0;
    for(i=0; i<8; i++)
    {
        DIN = dat >> 7;     //放置最高位
        dat <<= 1;
        CLK = 0;            //上升沿放置数据

        CLK = 1;

    }
}
/****************************************************************************
*函数名:SPI_Read
*输  入:无
*输  出:dat:读取 到的数据
*功  能:使用SPI读取数据
****************************************************************************/

uint SPI_Read(void)
{
    uint i, dat=0;
    CLK = 0;
    for(i=0; i<12; i++)     //接收12位数据
    {
        dat <<= 1;

        CLK = 1;
        CLK = 0;

        dat |= DOUT;

    }
    return dat; 
}

/****************************************************************************
*函数名:Read_AD_Data
*输  入:cmd:读取的X或者Y
*输  出:endValue:最终信号处理后返回的值
*功  能:读取触摸数据
****************************************************************************/
uint Read_AD_Data(uchar cmd)
{
    uchar i;
    uint AD_Value;
    CLK = 0;
    CS  = 0;
    SPI_Write(cmd);
    for(i=6; i>0; i--);     //延时等待转换结果
    CLK = 1;      //发送一个时钟周期,清除BUSY
    _nop_();
    _nop_();
    CLK = 0;
    _nop_();
    _nop_();
    AD_Value=SPI_Read();
    CS = 1;
    return AD_Value;    
}

4 代码实践

(1)参考例程写代码。

(2)添加串口输出调试。

(3)实践。

电位器做模拟量输入源
热敏电阻做模拟量输入源
光敏电阻做模拟量输入源

(4)串口直接显示电压值。
关键点:
直接显示电压值,而不是采样AD值;
以文本方式显示,而不是十六进制方式。

ET2046.h

#ifndef   __XPT2046_H_
#define   __XPT2046_H_

//---包含头文件---//
#include
#include

//---重定义关键词---//
#ifndef uchar
#define uchar unsigned char
#endif

#ifndef uint
#define uint  unsigned int
#endif

#ifndef ulong
#define ulong  unsigned long
#endif

//---定义使用的IO口---//
sbit CLK  = P1^0;     //时钟   
sbit CS   = P1^1;     //片选
sbit DIN  = P1^2;     //输入
sbit DOUT = P1^3;     //输出

uint Read_AD_Data(uchar cmd);

#endif

ET2046.c

#include "ET2046.h"


uint Read_AD_Data(uchar cmd)
{
    uchar i;
    uint AD_Value = 0;      // 局部变量的初始化非常重要
    CLK = 0;
    CS  = 0;

    for(i=0; i<8; i++)
    {
        DIN = cmd >> 7;     //放置最高位
        cmd <<= 1;
        CLK = 0;            //上升沿放置数据
        CLK = 1;
    }

    for(i=6; i>0; i--);     //延时等待转换结果
    CLK = 1;                //发送一个时钟周期,清除BUSY
    _nop_();
    _nop_();
    CLK = 0;
    _nop_();
    _nop_();

    for(i=0; i<12; i++)     //接收12位数据
    {
        AD_Value <<= 1;
        CLK = 1;
        CLK = 0;
        AD_Value |= DOUT;
    }
    CS = 1;

    return AD_Value;    
}

uart.h

#ifndef __UART_H__
#define __UART_H__

#include 





void uart_init(void);
void uart_send_byte(unsigned char c);
void uart_send_text(unsigned char c);
void uart_send_text2(unsigned int c);



#endif



uart.c

#include "uart.h"


// 串口设置为: 波特率9600、数据位8、停止位1、奇偶校验无
// 使用的晶振是11.0592MHz的,注意12MHz和24MHz的不行
void uart_init(void)
{
    // 波特率9600
    SCON = 0x50;    // 串口工作在模式1(8位串口)、允许接收
    PCON = 0x00;    // 波特率不加倍

    // 通信波特率相关的设置
    TMOD = 0x20;    // 设置T1为模式2
    TH1 = 253;
    TL1 = 253;      // 8位自动重装,意思就是TH1用完了之后下一个周期TL1会
                    // 自动重装到TH1去

    TR1 = 1;        // 开启T1让它开始工作
//  ES = 1;
//  EA = 1;
}

// 通过串口发送1个字节出去
void uart_send_byte(unsigned char c)
{
   // 第1步,发送一个字节
   SBUF = c;

   // 第2步,先确认串口发送部分没有在忙
   while (!TI);

   // 第3步,软件复位TI标志位
   TI = 0;
}

// 以文本方式发送c过去,意思就是要串口助手用文本方式来查看,看到的是
// 这个数字本身
void uart_send_text(unsigned char c)
{
    // 思路就是把c以十进制方式显示的几个数字,挨个变成文本发送出去
    unsigned char i;

    // 先计算得出c的最高位,然后发出去
    i = c / 100;
    uart_send_byte(i + 48);

    // 然后计算次高位
    c = c % 100;
    i = c / 10;
    uart_send_byte(i + 48);

    // 然后计算个位
    c = c % 10;
    i = c;
    uart_send_byte(i + 48);

    // 发送一个换行
    uart_send_byte('\r');
    uart_send_byte('\n');
}

// 因为我们知道电压值不会超过5000,所以只考虑显示1万以内的数值
// 这种方式限制能限制的最大值是9999
void uart_send_text2(unsigned int c)
{
    // 思路就是把c以十进制方式显示的几个数字,挨个变成文本发送出去
    unsigned char i;

    i = c / 1000;
    uart_send_byte(i + '0');

    uart_send_byte('.');

    // 先计算得出c的最高位,然后发出去
    c = c % 1000;
    i = c / 100;
    uart_send_byte(i + 48);

    // 然后计算次高位
    c = c % 100;
    i = c / 10;
    uart_send_byte(i + 48);

    // 然后计算个位
    c = c % 10;
    i = c;
    uart_send_byte(i + 48);

    uart_send_byte('V');
    // 发送一个换行
    uart_send_byte('\r');
    uart_send_byte('\n');
}

main.c

#include "ET2046.h"
#include "uart.h"


#define CMD_READ_AIN0   0x94        // 滑动变阻器
#define CMD_READ_AIN1   0xD4        // NTC
#define CMD_READ_AIN2   0xA4        // GR1
#define CMD_READ_AIN3   0xE4        // 外部输入的电压值

// AD value是12bit的,分2拨发出去
void uart_send_advalue(uint val)
{
    uart_send_byte((val >> 8) & 0xff);    // 高8位
    uart_send_byte(val & 0xff);           // 低8位
    uart_send_byte(0);  
}



void delay1s(void)   //误差 0us
{
    unsigned char a,b,c;
    for(c=167;c>0;c--)
        for(b=171;b>0;b--)
            for(a=16;a>0;a--);
    _nop_();  //if Keil,require use intrins.h
}

// 思路:用51单片机来计算,直接算出最终的电压值,然后通过串口发送显示
// 第1步:先去算出电压值(val/4096)*5000mV =1.22*val(mV)
// 第2步:串口发送出去显示。显示方法1,用二进制发送出去显示
// 方法2:用文本方式去显示.把算出来要发送的数值,转成对应的ASCII码发送
// 给串口去显示

// 以文本方式直接打印电压值
void uart_send_voltage(uint val)
{
    // 先将AD值val换算成mV为单位的电压值
    float index = 1.22;
    float voltage = index * val;
    uint vol_display = (uint)voltage;

    uart_send_text2(vol_display);
}




void main(void)
{
    uint val = 0;

    uart_init();

//  uart_send_voltage(3241);

//  while (1);

    while (1)
    {
    //  val = Read_AD_Data(CMD_READ_AIN0);      // 滑动变阻器,测试ok
    //  val = Read_AD_Data(CMD_READ_AIN1);      // NTC,测试ok
        val = Read_AD_Data(CMD_READ_AIN2);      // 光敏电阻,测试ok

        //uart_send_advalue(val);
        uart_send_voltage(val);
        delay1s();
    }
}

5 DA转换

5.1 DA转换相关概念

(1)何为DA转换。

(2)DA转换的意义和用途(函数信号发生器)。

(3)DA转换的原理。

AD和DA转换_第4张图片

5.2 原理图和案例分析

(1)运算放大器(放大+隔离)。

(2)PWM数字信号。

(3)其实不接LM358,直接用IO口连接LED实验现象也一样的。

(4)真正的DA一般是专用芯片或者CPU内置模块,给数字值输出平滑模拟量。

(5)这勉强算演示DA。

/**************************************************************************************
*                     DA-PWM输出实验                                                  *
实现现象:   具体接线操作请参考视频
            下载程序后DA模块上的指示灯呈呼吸灯效果,由暗变亮再由亮变暗
注意事项:要将AD/DA模块上的短接片将OUT和LED短接,板子丝印上有。                                                                                 
***************************************************************************************/

#include "reg52.h"           //此文件中定义了单片机的一些特殊功能寄存器

typedef unsigned int u16;     //对数据类型进行声明定义
typedef unsigned char u8;

//--定义使用的IO口--//
sbit PWM=P2^1;
bit DIR;

//--定义一个全局变量--// 
u16 count,value,timer1;

/*******************************************************************************
* 函 数 名         : Timer1Init
* 函数功能         : 定时器1初始化
* 输    入         : 无
* 输    出         : 无
*******************************************************************************/
void Timer1Init()
{
    TMOD|=0X10;//选择为定时器1模式,工作方式1,仅用TR1打开启动。

    TH1 = 0xFF; 
    TL1 = 0xff;   //1us

    ET1=1;//打开定时器1中断允许
    EA=1;//打开总中断
    TR1=1;//打开定时器           
}


/*******************************************************************************
* 函 数 名       : main
* 函数功能       : 主函数
* 输    入       : 无
* 输    出         : 无
*******************************************************************************/
void main()
{   
    Timer1Init();  //定时器1初始化
    while(1)
    {
        if(count>100)                           
        {  
            count=0;
            if(DIR==1)                     //DIR控制增加或减小
            {
              value++;
            }           
            if(DIR==0)
            {
              value--;
            }

        }

        if(value==1000)
        {
          DIR=0;
        }
        if(value==0)
        {
          DIR=1;
        }   

        if(timer1>1000)  //PWM周期为1000*1us
        {
            timer1=0;
        }
        if(timer1 
        {
            PWM=1;
        }
        else
        {
            PWM=0;
        }       
    }       
}

/*******************************************************************************
* 函 数 名       : Time1
* 函数功能       : 定时器1的中断函数
* 输    入       : 无
* 输    出       : 无
*******************************************************************************/

void Time1(void) interrupt 3    //3 为定时器1的中断号  1 定时器0的中断号 0 外部中断1 2 外部中断2  4 串口中断
{
    TH1 = 0xFF; 
    TL1 = 0xff;   //1us
    timer1++; 
    count++;

}

你可能感兴趣的:(51单片机,--------【常见外设】)