• 【单片机】15-AD和DA转换


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

    1.基本概念

    1.什么是AD转换?

    A(A,analog,模拟的,D,digital,数字的)

    现实世界是模拟的,连续分布的,无法被分成有限份;

    计算机世界是数字的,离散分布的,可以被分成有限份的

    AD转换就是把一个物理量从模拟的转换成数字的。

    2.AD转换的意义

    想要计算机来实现现实世界

    3.什么情况下需要AD转换

    CPU是数字的【要准确的0V或者5V】

    2.AD转换的原理

    1.比较器

    将差一点的电压转换为准确的二进制

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

    2.和十进制转二进制有点像

    使用除法

    3.AD转换中的主要概念

    1.位数

    AD转换后转出来的二进制数由几位二进制来表示。【实际结果是一样大】

    位数越多,越细腻。【精度越高】

    2.量程

    AD转换器可以接受的模拟量的范围

    3.精度

    精度==准确度

    简单理解就是转出来有多准

    【精度越小可靠率越高】

    4.分辨率

    AD转换器转出来的二进制数,每一格表示多少

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

    时间越短,速率越大

    6.例子

    输入电压范围:0-5V,AD转换输出位数是10,精度是0.01V

    量程:0-5V

    分辨率:(5-0)/2exp(10)=0.00488V

    比如一次AD转换后得到数据为:【1010101010】,对应电压值:1010101010-->十进制:682,电压=682*0.00488=3.328V,考虑精度后为=3.33V

    4.AD转换在系统中存在的方式

    1.CPU外部扩展专用AD芯片

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

    2.原理图和数据手册

    https://www.semiee.com/file/ETEK/ETEK-ET2046.pdf

    1.原理图

    2.数据手册

     AIN0-AIN3:不能同时采集,同一时间只能采集一路

    3.SPI接线

    CLK接P1.0

    CS接P1.1

    DI接P1.2

    DO接P1.3

    4.3种模拟电压变化原理

    AIN0靠滑动变阻器控制电压变化

    AIN1靠热敏电阻NTC

    AIN2靠光敏电阻

    5.ET2046控制字

    bit7:起始位【高表示开始传输】:1

    bit6-bit4:决定采样哪一路(AIN1,AIN0,AINT2,AINT3):

    AIN0:001/011         X+

    AIN1:101                Y+

    AIN2:010                VBAT

    AIN3:110                VBAT

    bit3:设置ADC精度:【1:使用bit8位】【0:使用bit12位--一般使用这个】:0

    bit2:【1:表示用单端模式】【0:表示差分模式(触摸屏)】:1

    bit1:power down模式使能,00表示使能

     读AIN0:0b 1001 0100=0x94

    读AIN1:0b 1101 0100=0xd4

    读AIN2;0b 1010 0100=0xa4
    读AIN3:0b 1110 0100=0xe4

    AIN0:001/011         X+

    AIN1:101                Y+

    AIN2:010                VBAT

    AIN3:110                VBAT

    3.分析时序

    1.时序图

    1.SPI变种

    回顾SPI知识点:【单片机】13-实时时钟DS1302-CSDN博客

    有CS,DCLK,I/O

    2.上升沿写入下降沿读出

    之前DS1302(SPI)的时候也是这样

    上升沿写入:当CLK为上升沿的时候,数据通过DI从SPI主设备写入到SPI从设备

    下降沿读出:当CLK为下降沿的时候,数据通过DO从SPI从设备读入到SPI主设备

    3.读写都是高位在前

    之前DS1302(SPI)的时候是低位在前

    4.注意写和读的交界点

    2.官方例程分析

    1.ET2046写数据

    1. /*******************************************************************************
    2. * 函 数 名 : xpt2046_wirte_data
    3. * 函数功能 : XPT2046写数据
    4. * 输 入 : dat:写入的数据
    5. * 输 出 : 无
    6. *******************************************************************************/
    7. void xpt2046_wirte_data(u8 dat)
    8. {
    9. u8 i;
    10. CLK = 0;//可以忽略的
    11. _nop_();
    12. for(i=0;i<8;i++)//循环8次,每次传输一位,共一个字节
    13. {
    14. //先准备好数据,在置CLK=0
    15. DIN = dat >> 7;//先传高位再传低位
    16. dat <<= 1;//将低位移到高位
    17. CLK = 0;//CLK由低到高产生一个上升沿,从而写入数据
    18. _nop_();
    19. CLK = 1;
    20. _nop_();
    21. }
    22. }

    2.ET2046读数据

    1. /*******************************************************************************
    2. * 函 数 名 : xpt2046_read_data
    3. * 函数功能 : XPT2046读数据
    4. * 输 入 : 无
    5. * 输 出 : XPT2046返回12位数据
    6. *******************************************************************************/
    7. u16 xpt2046_read_data(void)
    8. {
    9. u8 i;
    10. u16 dat=0;
    11. CLK = 0;
    12. _nop_();
    13. for(i=0;i<12;i++)//循环12次,每次读取一位,大于一个字节数,所以返回值类型是u16
    14. {
    15. dat <<= 1;
    16. CLK = 1;
    17. _nop_();
    18. CLK = 0; //CLK由高到低产生一个下降沿,从而读取数据
    19. _nop_();
    20. dat |= DOUT;//先读取高位,再读取低位。
    21. }
    22. return dat;
    23. }

    3.ET2046返回AD值

    1. /*******************************************************************************
    2. * 函 数 名 : xpt2046_read_adc_value
    3. * 函数功能 : XPT2046读AD数据
    4. * 输 入 : cmd:指令
    5. * 输 出 : XPT2046返回AD值
    6. *******************************************************************************/
    7. u16 xpt2046_read_adc_value(u8 cmd)
    8. {
    9. u8 i;
    10. u16 adc_value=0;
    11. CLK = 0;//先拉低时钟
    12. CS = 0;//使能XPT2046
    13. xpt2046_wirte_data(cmd);//发送命令字
    14. for(i=6; i>0; i--);//延时等待转换结果,这个时候进行AD转换
    15. CLK = 1;//发送应该
    16. _nop_();
    17. CLK = 0;//发送一个时钟,清除BUSY
    18. _nop_();
    19. adc_value=xpt2046_read_data();
    20. CS = 1;//关闭XPT2046
    21. return adc_value;
    22. }

    4.main函数

    1. /*******************************************************************************
    2. * 函 数 名 : main
    3. * 函数功能 : 主函数
    4. * 输 入 : 无
    5. * 输 出 : 无
    6. *******************************************************************************/
    7. void main()
    8. {
    9. u16 adc_value=0;
    10. float adc_vol;//ADC电压值
    11. u8 adc_buf[3];
    12. while(1)
    13. {
    14. //0x94:对应AINT0--0b 1001 1100
    15. adc_value=xpt2046_read_adc_value(0x94);//测量电位器
    16. adc_vol=5.0*adc_value/4096;//将读取的AD值转换为电压
    17. adc_value=adc_vol*10;//放大10倍,即保留小数点后一位
    18. adc_buf[0]=gsmg_code[adc_value/10]|0x80;
    19. adc_buf[1]=gsmg_code[adc_value%10];
    20. adc_buf[2]=0x3e;//显示单位V
    21. smg_display(adc_buf,6);
    22. }
    23. }

    4.代码实践【AD转换】

    1.将12bit位的数值分2次输出

    1. //AD value是12bit的,分2波出去【因为一次只能输出8位】
    2. void uart_send_advalue(u16 val){
    3. uart_send_byte((val>>8)&0xff); //高8位
    4. uart_send_byte(val&0xff); //低8位
    5. uart_send_byte(0);//分割符
    6. }

    2. 计算电压值

    3.读取AD数值:

    ET2046.c

    1. #include"ET2046.h"
    2. /*******************************************************************************
    3. * 函 数 名 : xpt2046_read_adc_value
    4. * 函数功能 : XPT2046读AD数据
    5. * 输 入 : cmd:指令
    6. * 输 出 : XPT2046返回AD值
    7. *******************************************************************************/
    8. u16 xpt2046_read_adc_value(u8 cmd)
    9. {
    10. u8 i;
    11. u16 adc_value=0; //局部变量的初始化非常重要
    12. CLK = 0;//先拉低时钟
    13. CS = 0;//使能XPT2046
    14. //写入数据
    15. for(i=0;i<8;i++)//循环8次,每次传输一位,共一个字节
    16. {
    17. //先准备好数据,在置CLK=0
    18. DIN = cmd >> 7;//先传高位再传低位
    19. cmd <<= 1;//将低位移到高位
    20. CLK = 0;//CLK由低到高产生一个上升沿,从而写入数据
    21. _nop_();
    22. CLK = 1;
    23. _nop_();
    24. }
    25. for(i=6; i>0; i--);//延时等待转换结果,这个时候进行AD转换
    26. CLK = 1;//发送应该
    27. _nop_();
    28. CLK = 0;//发送一个时钟,清除BUSY
    29. _nop_();
    30. for(i=0;i<12;i++)//循环12次,每次读取一位,大于一个字节数,所以返回值类型是u16
    31. {
    32. adc_value <<= 1;
    33. CLK = 1;
    34. _nop_();
    35. CLK = 0; //CLK由高到低产生一个下降沿,从而读取数据
    36. _nop_();
    37. adc_value |= DOUT;//先读取高位,再读取低位。
    38. }
    39. CS = 1;//关闭ET2046
    40. return adc_value;
    41. }

    ET2046.h

    1. #ifndef _xpt2046_H
    2. #define _xpt2046_H
    3. #include "reg51.h"
    4. #include "intrins.h"
    5. typedef unsigned int u16; //对系统默认数据类型进行重定义
    6. typedef unsigned char u8;
    7. typedef unsigned long u32;
    8. //管脚定义
    9. sbit DOUT = P1^3; //输出
    10. sbit CLK = P1^0; //时钟
    11. sbit DIN = P1^2; //输入
    12. sbit CS = P1^1; //片选
    13. //函数声明
    14. u16 xpt2046_read_adc_value(u8 cmd);
    15. #endif

    main.c

    1. #include"ET2046.h"
    2. #include"uart.h"
    3. #define CMD_READ_AIN0 0x94 //滑动变阻器
    4. #define CMD_READ_AIN1 0xD4 //NTC--热敏电阻
    5. #define CMD_READ_AIN2 0xA4 //GR1--光敏电阻
    6. #define CMD_READ_AIN3 0xE4 //外部输入的电压值
    7. void Delay400000us() //@11.0592MHz
    8. {
    9. unsigned char i, j, k;
    10. _nop_();
    11. _nop_();
    12. i = 17;
    13. j = 208;
    14. k = 27;
    15. do
    16. {
    17. do
    18. {
    19. while (--k);
    20. } while (--j);
    21. } while (--i);
    22. }
    23. //AD value是12bit的,分2波出去【因为一次只能输出8位】
    24. void uart_send_advalue(u16 val){
    25. uart_send_byte((val>>8)&0xff); //高8位
    26. uart_send_byte(val&0xff); //低8位
    27. uart_send_byte(0);//分割符
    28. }
    29. void main(){
    30. //注意:定义一个变量要记得初始化,要不然后面可能出现问题
    31. u16 val=0;//12bit数值
    32. uart_init();
    33. while(1){
    34. val=xpt2046_read_adc_value(CMD_READ_AIN0);
    35. uart_send_advalue(val);
    36. Delay400000us();
    37. }
    38. }

    4.串口直接显示电压值

    1.关键点

    (1)直接显示电压值,而不是采样AD值

    (2)以文本方式显示,而不是十六进制方式

    2.将数值转换为十进制

    1. //以文本方式发送c过去,意思就是要串口助手用文本方式来查看,看到的是
    2. //这个数字本身
    3. void uart_send_text(unsigned char c){
    4. //思路就是把c以十进制方式显示的几个数字,挨个变成文本发出去
    5. unsigned char i;//因为c是unsigned char 范围是0-255
    6. //先计算得出c的最高位,然后发出去
    7. i=c/100;
    8. uart_send_byte(i+48);//+48是对应ASCII
    9. //计算次高位
    10. c=c%100;
    11. i=c/10;
    12. uart_send_byte(i+48);
    13. //计算个位
    14. c=c%10;
    15. i=c;
    16. uart_send_byte(i+48);
    17. //发送一个换行
    18. uart_send_byte('\r');
    19. uart_send_byte('\n');
    20. }

    因为我们这里的电压是5V,对应5000mV,明显上面的0-255不在范围内,所以我们要求传入的是unsigned int c,【0-2的16次方】才足够

    1. //因为这个函数的范围是unsigned char---->是2的8次方
    2. //但是我们最大电压值为:5V----5000mV,所以我们这里要使用unsigned int
    3. //以文本方式发送c过去,意思就是要串口助手用文本方式来查看,看到的是
    4. //这个数字本身
    5. void uart_send_text2(unsigned int c){
    6. //思路就是把c以十进制方式显示的几个数字,挨个变成文本发出去
    7. unsigned char i;//因为c是unsigned char 范围是0-255
    8. //因为我们知道电压值不会超过5000mV,所以只考虑显示1万以内的数据
    9. //先计算得出c的最高位,然后发出去
    10. i=c/1000;
    11. uart_send_byte(i+48);//+48是对应ASCII
    12. c=c%1000;
    13. i=c/100;
    14. uart_send_byte(i+48);//+48是对应ASCII
    15. //计算次高位
    16. c=c%100;
    17. i=c/10;
    18. uart_send_byte(i+48);
    19. //计算个位
    20. c=c%10;
    21. i=c;
    22. uart_send_byte(i+48);
    23. //发送一个换行
    24. uart_send_byte('\r');
    25. uart_send_byte('\n');
    26. }

    5.DA转换

    将数字转换为模拟的

    1.DA转换的原理

    为了让数字量转换成模拟量,必须将每一位代码按其权重的大小转换为相应的模拟量,然后再把这些模拟量相加。

    2.原理图和案例分析

    1.运算放大器(LM358)

    放大作用:将数字信号-----》模拟信号

    隔离作用:防止输出信号影响输入信号

    2.PWM数字信号

    当输入的PWM数字信号一直为1,则输出的模拟信号一直为高电压

    如果输入的PWM数字信号一直为0,则输出的模拟信号一直为低电压

    关键点:取决于输入的PWM信号的高低电平所占的时间。【连续变化的模拟量】

    3.LM358

    其实不接LM358,直接用IO口连接LED实现现象也一样。(说明灯的亮度只与PWM输入的电压值的大小有关)

    4.注意点

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

  • 相关阅读:
    网络问题排障专题-AF网络问题排障
    0928vue/cli脚手架,node.js
    Jenkins 设置定时任务
    centos8stream 编译安装 php-rabbit-mq模块
    矩阵类运算(运算符重载)
    539、RabbitMQ详细入门教程系列 -【100%消息投递消费(一)】 2022.08.31
    【深入理解C++】vector
    esp8266用arduino连上阿里云(图文操作,100%成功)
    充气泵方案设计-汽车打气泵PCBA
    Oracle 定时任务job实际应用
  • 原文地址:https://blog.csdn.net/m0_63077733/article/details/133472609