51单片机——AD\DA转换
前言:
AD/DA介绍
-
AD和DA是模拟信号和数字信号之间的转换过程。
AD,全称为模拟到数字(Analog-to-Digital),指的是将模拟信号转换为数字信号的过程。在AD转换中,模拟信号经过采样、量化和编码等步骤,被转换为离散的数字信号。这样可以更方便地处理和传输信号,在数字系统中进行数字信号的处理和分析。 -
DA,全称为数字到模拟(Digital-to-Analog),指的是将数字信号转换为模拟信号的过程。在DA转换中,数字信号经过解码和重构处理,被转换为连续的模拟信号。这样可以将数字信号转换为模拟信号,使其能够被模拟系统接收和处理。
-
AD和DA转换常见于各种电子设备和系统中,例如音频设备、通信系统、传感器等。AD转换可以将实际物理量(如声音、温度、光强)转换为数字形式进行处理,而DA转换则可以将数字信号转换为模拟形式输出到外部设备或环境中。
-
AD和DA转换的准确性和性能对于系统的精度和稳定性至关重要,因此在设计和选择AD和DA转换器时需要考虑多个因素,如分辨率、采样率、信噪比、线性度等
。
一. A/D、D/A转换中的主要概念
位数: AD转换后转出来的数由几位二进制来表示。位数越多,越细腻,精度越高。例如:10位,代表可以将模拟量分成1024份,12位,代表可以将模拟量分成4096份;
量程:AD转换器可以接受的模拟量的范围。跟芯片制造有关系,如果超过量程,超量程部分可能会被阉割,重则将芯片烧毁;
分辨率:AD转换器转出来的二进制数,每一格表示多少。例如:10位,分辨率是1/1024,12位,分辨率是1/4096;
转换速率:就是完成一次A/D,D/A转换所耗费的时间。实际应用中,转换速率越快越好,随之成本会越高。适合项目使用即可;
举例说明:
条件:电压量程范围0-5V,A/D,D/A转换位数是10位,精度是0.01V
分辨率为:(5-0)/2exp(10)=0.00488V
例如一次A转换中数字量是1010101010,计算转换中模拟量的过程如下:
1、1010101010对应的十进制数是682;
2、数字量对应的模拟电压是682 * (分辨率)0.00488v = 3.33V
二. A/D,D/A转换在系统中存在的方式
CPU外部扩展专用A/D,D/A芯片;
CPU内部集成A/D,D/A模块(内部外设)
早期的单片机基本都是在外部扩展,新的高级一些的单片机是内部集成的;
51单片机内部没有集成,所以使用的是外部扩展,使用ET2046芯片,***并用SPI接口通信***
三. A/D,D/A转换在系统中存在的方式
A/D转换原理图及ET2046芯片介绍
1、原理图
AIN0 – XP+, 对应右边感应器AD1滑动电位器。
AIN1 – YP+, 对应右边NTC1热敏传感器。
AIN2 – VBAT,对应右边GR1热敏传感器。
AIN3 – AUX,暂无对应。
2、开发板接线
(1)、SPI接线(接线引脚可随意)
CS -> P0^0; //SPI片选。
DI -> P0^1; //SPI 输入;
CLK -> P0^2; //SPI时钟;
DO -> P0^3; //SPI输出;
(2)、模拟输入量
AIN0靠滑动变阻器分压变化;
AIN1靠热敏电阻NTC分压,温度变化电阻变化,导致分压变化;
AIN2靠光敏电阻分压。
四、ET2046芯片介绍
1、主要特点
工作电压范围为 1.5V~5.25V
支持 1.5V~5.25V 的数字 I/O 口
内建 2.5V 参考电压源
电源电压测量( 0V~6V)
内建结温测量功能
触摸压力测量
采用 3 线制 SPI 通信接口
具有自动省电功能
2、引脚功能说明
ET2046分为 单端模式 和 差分模式。
-
在使用VBAT、AUX和TEMP时使用单端模式
- XP YP VBAT AUX
在作为触摸屏应用时使用差分模式
3、ET2046控制字介绍
根据ET2046的芯片,需要先向这个芯片发送一个控制字节,(类似于寻址的地址一样)。控制字节为8bit数据,各位功能分别表示如图:
数据位高——低分别是:
(1)位8, 开始位:
为1时表示一个新的控制字节的开始
为0时表示忽略PIN引脚上的数据
(2)位7 - 位 5,通道选择位: 参照单端模式输入配置图!数据如下:
AIN0 对应 +XP 数据为 : 001或011 (没错!就是2个数据)
AIN1 对应 +YP 数据为 : 101
AIN2 对应 VBAT 数据为:010
AIN3 对应 AUX 数据为 : 110
(3)位4,MODE,12/8Bit转换分辨率选择位。
1为8bit分辨率
0为12bit分辨率
(4)位3,单端模式/差分模式选择位。
1为单端模式
2为差分模式
(5)位2-位1,低功耗选择位。
为11,总处于总处于供电状态
为00,表示在低功率状态
五、ET2046芯片SPI时序分析
1、时序图
时序图分析:***类SPI时序,均为上升沿写入,下降沿读出***。
当cs由高电变低电时, 使能开始(类总开关)。
数据DIN要在DCLK时钟之前准备好(高位在前),
然后按照dclk时钟上升沿写入的规则,在每一个时钟周期上升沿
写入1位控制字节数据,共8位数据。
完后用延时一段时间检测BUSY是否在忙,这里不用检测了,
直接延时一段时间+1个时钟周期用以代替BUSY的检测就行。
ET2046 —SPI写时序代码实现:
#include "reg51.h"
#include <intrins.h>
sbit CS = P0^0; // 管脚定义
sbit DIN = P0^1;
sbit SCLK = P0^2;
sbit DOUT = P0^3;
#define u16 unsigned int
#define u8 unsigned char
u16 Read_AD_data(u8 dat)
{
u8 i = 0; //定义循环变量i
u16 value = 0; //用于保存读出的12位分辨率的数据
CS = 0;
SCLK = 0;
// 向et2046写控制字节
for(i=0; i<8; i++)
{
DIN = dat >> 7;
SCLK = 0;
SCLK = 1; //数据 在上升沿读入
dat <<= 1;
}
for(i=10; i>0; i--); //for循环用于延时,i=?要根据开发板的晶振选择!
SCLK = 1; // 这一个时钟周期用于抵消 BUSY
_nop_();
SCLK = 0;
_nop_();
// 读et2046数据
for(i=0; i<12; i++) // 控制字节的分辨率我们这里选择12bit,“0”
{
value = value << 1; // 这里需要先一移位,12bit移到最高位需要循环n-次
SCLK = 1;
SCLK = 0;
value = value | DOUT; // DOUT读出的数据也是一位一位的读,
// 因为一个时钟的下降沿数据被读出
}
CS = 1;
return value; //返回数据value;
}
程序实现——ad转换uart文本方式发送
/*********** ET2046-SPI时序 .c文件 *****************/
#include "ET2046.h"
/****** 读取et2406数据函数 ********/
u16 Read_Spi(u8 dat) // 参数是一个局部变量
{
u8 i = 0;
u16 value = 0;
CS = 0; // 时序开始
CLK = 0;
for(i=0; i<8; i++) //向ET2406芯片输入8位控制字节数据
{
DIN = dat >> 7; // 上升沿写入,锁存。下降沿读出,锁存,并且高位在先
CLK = 0;
CLK = 1; // 数据在上升沿被写入,根据时序图来看。
dat = dat << 1;
}
CLK = 0;
for(i=0; i<6; i++);// 延时
CLK = 1; // 延时一个周期消除BUSY
_nop_();
_nop_();
CLK = 0;
_nop_();
_nop_();
/* 变量value | DOUT (dout是一个时钟输出一位数据) */
for(i=0; i<12; i++) // 控制字节这里单端模式是12位分辨率选择模式,所以这里需要循环12次
{
value = value << 1; // 先读出的位为高位,每次向前位移一位让其一直为最高位
CLK = 1;
CLK = 0; // 数据在下降沿被读出
value = value | DOUT;
}
CS = 1; // CS使能结束,拉高,结束整个过程。
return value;
}
/*********** ET2046-SPI时序 .h文件 *****************/
#ifndef _ET2046_H_
#define _ET2046_H_
// 包含头文件
#include <reg51.h>
#include <intrins.h>
#ifndef u8
#define u8 unsigned char
#endif
#ifndef u16
#define u16 unsigned int
#endif
sbit CLK = P1^0;
sbit CS = P1^1;
sbit DIN = P1^2;
sbit DOUT = P1^3;
u16 Read_Spi(u8 dat);
#endif
/*********** urat.c文件 *****************/
/* 串口函数 波特率4800 */
#include "uart.h"
void uart_init(void) // 串口初始化函数
{
SCON = 0x50;
PCON = 0X80; //波特率加倍 2400mhz加倍到4800,相应的TH1\TL1的计算就要改变
//波特率计算公式为:2的smod次方/32*晶振频率*10 000 000/12/(256-TH1)=4800mhz
TMOD = 0x20;
TH1 = 243;
TL1 = 243;
TR1 = 1;
// EA = 1;
// ES = 1;
}
void uart_send_byte(uchar a) // 串口发送函数, uchar为发送一个字节数据
{
SBUF = a;
while (!TI);
TI = 0;
}
// 这里需要写一个计算电压值“输出”的函数, 发送的方式是以文本的方式发送,需要对应ASSIIC码表
// 表中十进制显示数字0开始,是从48开始的!
void uart_send_voltage(uint a) // uint a的范围在65536 2的16次方
{
uchar i = 0;
i = a / 1000; // 高位计算(千位)
uart_send_byte(i + '0'); // ‘0’的意思就是+48
uart_send_byte('.');// mv转化v 小数点需要向前进位3个位
a = a % 1000; // 次高位的计算(百位)
i = a / 100;
uart_send_byte(i + 48);
a = a % 100; // 次低位的计算(十位)
i = a / 10;
uart_send_byte(i + 48);
i = a % 10; // 低位的计算(个位)
uart_send_byte(i + 48);
uart_send_byte('V'); // 增加‘电压’ 字节符号的显示
uart_send_byte('\r');// '\r'+'\n' = 空格
uart_send_byte('\n');
}
/*********** urat.h文件 *****************/
#ifndef _uart_H_
#define _uart_H_
#include "reg51.h"
#ifndef uchar
#define uchar unsigned char
#endif
#ifndef uint
#define uint unsigned int
#endif
void uart_init(void);
void uart_send_byte(uchar a);
void uart_send_voltage(uint a);
#endif
/*********** main.c文件 *****************/
#include "ET2046.h"
#include "uart.h"
#define u16 unsigned int
#define u8 unsigned char
#define AD1 0X94
#define NTC1 0XD4
#define GR1 0XA4
// 延时函数用于间隔发送的时间
void delay1s(void) //误差 0us
{
unsigned char a,b,c;
for(c=167;c>0;c--)
//for(b=71;b>0;b--)
for(b=171;b>0;b--)
for(a=16;a>0;a--);
_nop_(); //if Keil,require use intrins.h
}
/*
定义uart函数发送前8位和后8位读取的et2046数据
void uart_send_davalue(u16 val)
{
uart_send_byte( (val >> 8) & 0xff );
uart_send_byte( val & 0xff );
//uart_send_byte( 0 );
}
*/
// 定义一个函数计算电压值!
void AD_voltage_value(u16 dat)
{
// 先计算分辨率、(dat/4096)*5000mv=1.22*dat(mv)
float resolution_ratio = 1.22;
// 计算电压值(mv)
float voltage = resolution_ratio * dat;
// 用于存储voltage电压值的强制类型转换 舍弃小数部分的整数值
u16 vol_display = (u16)voltage;
// 用文本发送函数发送出去 调用void uart_send_voltage(uint vol)函数
uart_send_voltage(vol_display);
}
//实验刚开始是 滑动变阻器的电压值随着转动,一直显示整数的电压值,考虑时电压值函数计算出来问题
// bug在于uart_send_voltage(vol_display);函数内的变量赋值,更改后ad显示正常1.xxx样式的读数
// 主函数;主程序调用;
void main(void)
{
u16 val = 0;
uart_init();
while(1)
{
//val = Read_Spi(AD1); // 滑动变阻——电压
//val = Read_Spi(NTC1); // 热敏电阻
val = Read_Spi(GR1); // 光敏电阻
//uart_send_davalue(val);
AD_voltage_value(val);
delay1s();
}
}
/* ad转换总结
1.(ad写入)根据et2046芯片时序设计程序,其中时序部分类似spi时序,但注意是高位在前(读/写)。时序前部分为向et2046写入“8bit控制字节”数据分别对应
滑动变阻器,光敏电阻以及热敏电阻,相对的控制字节(16进制)的数据也会不一样,芯片资料都会查到。
2.(读出)读出时高位在前,程序设计上与写入程序为一个程序。最后需要返回一个value数值。
3.串口通信的加入 选用4800mhz(2400加倍至4800)。函数部分需要加入一个《发送文本》的函数,作用是
计算ad转换出的数据并按照“千百十个位”的方式依次发送出来。(计算出的是十进制数,这里需要对应ASSIIC码表,当需要输出数字时,
ASSIIC码表显示是在第48位显示为数字0,所以void uart_send_voltage(uint a)函数在计算“千百十个”位 时需要 +48).
4.main函数的编写,main函数中需要注意!需要定义一个函数来计算电压值!!!! 如本文中的void AD_voltage_value(u16 dat)函数。
(5000mv需要unsigned int变量类型来定义,其最大为65536,满足实际应用)。根据公式(dat/4096)*5000mv=1.22*dat(mv)计算出电压值,
然后调用void uart_send_voltage(uint a)将uint类型数据传入被调函数中然后串口发送十进制电压值在串口助手上。
*/