1,AD转换基本概念
51 单片机系统内部运算时用的全部是数字量,即0 和1,因此对单片机系统而言,无法直接操作模拟量,必须将模拟量转换成数字量。所谓数字量,就是用一系列0 和1 组成的二进制代码表示某个信号大小的量。用数字量表示同一个模拟量时,数字位数可以多也可以少,位数越多则表示的精度越高,位数越少表示的精度就越低。
ADC(analog to digital converter)也称为模数转换器,是指一个将模拟信号转变为数字信号。单片机在采集模拟信号时,通常都需要在前端加上A/D 芯片。
A(A,analog,模拟的,D,digital,数字的)现实世界是模拟的,连续分布的,无法被分成有限份;计算机世界是数字的,离散分布的,是可以被分成有限份的;AD转换就是把一个物理量从模拟的转换成数字的。
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 。
AD转换在系统中存在的方式:
(1)CPU外部扩展专用AD芯片;
(2)CPU内部集成AD模块(内部外设);
电池单体的电压采集芯片有一种叫AFE的(Anlog Front End),即是一种AD转换模块,采集单体的电压转换为数字量发给MCU。
2,AD转换原理图和数据手册
ET2046 AD转换模块通过AIN0/AIN1/AIN2分别连接滑线变阻器、热敏电阻、光敏电阻。与单片机连接的接口为CS(使能接口,低有效)、CLK(时钟信号)、DI(数据输入,从单片机到AD转换模块)、DO(数据输出、从AD转换模块到单片机)。
X+、Y+、VBAT 和AUX 模拟信号经过片内的控制寄存器选择后进入ADC,ADC 可以配置为单端或差分模式。选择VBAT和AUX 时应该配置为单端模式;作为触摸屏应用时,应该配置为差分模式。
单片机在对AD转换模块进行控制时,控制字节由DIN 输入的控制字命令格式如下所示:
Bit7为开始位,为1表示一个新的控制字节到来,为0则忽略PIN引脚上的数据;
A2-A0为通道选择位,表示选择哪个通道的输入电压进行AD转换;
MODE为12/8位转换分辨率选择位,为1选择8位转换分辨率,为0选择12位分辨率;
SER/DFR:单端输入方式/差分输入方式选择,为1是单端输入方式,为0是差分输入方式;
PD1-PD0为低功耗模式选择位,若为11,器件总处于供电状态,若为00,器件在转换之间处于低功耗模式。
单端模式时采集通道的选择如下表所示(通过上述控制字节的A2-A1进行选择):
选择X+通道、12位分辨率、单端模式、低功耗模式的控制字节命令:0b1001 0100 = 0x94。
AD转换模块的时序图如下所示:
从时序图上可见,转换模块进入工作状态时,CS为低,DCLK为低,DIN和DOUT不用关注;首先通过DIN数据线从单片机发送控制字节到AD转换模块,在DCLK的上升沿AD转换模块读入数据(从高到低进行读入),当8位控制字节命令发送完成后,转换模块进入busy状态,即转换模块开始进行AD转换,此时间可从数据手册获取,一般程序中通过延迟一段时间来进行处理;然后单片机在DOUT数据线读取转换模块发出的数据,每个DCLK的下降沿转换模块会将一位数据发送到DOUT数据线上,仍然是从高到低的顺序。
可见AD转换和单片机的通讯方式类似于SPI通讯。
3,AD转换代码
AD采样转换代码包括单片机和AD转换模块写入命令和读取数据的底层时序代码,通过串口显示采样数据代码,以及main文件。
ET2046.c底层时序代码:
#include "ET2046.h"
#include <intrins.h>
void delay10us(void) //误差 0us
{
unsigned char a,b;
for(b=1;b>0;b--)
for(a=2;a>0;a--);
}
unsigned int read_AD_value(unsigned char cmd)
{
unsigned char i = 0;
unsigned int AD_Value = 0;
CS = 0;
SCLK = 0;
for(i = 0;i < 8;i++)
{
DIN = cmd >> 7;
cmd <<= 1; //注意将一个数据移位后再赋给本身的运算符位 <<=
SCLK = 1;
_nop_();
SCLK = 0;
_nop_();
}
delay10us();
SCLK = 1; //发送一个时钟周期,清除BUSY
_nop_();
_nop_();
SCLK = 0;
_nop_();
_nop_();
for(i = 0;i<12;i++)
{
AD_Value <<= 1;
SCLK = 1;
_nop_();
SCLK = 0;
_nop_();
AD_Value |= DOUT;
}
CS = 1;
return AD_Value;
}
ET2046.h
#ifndef __ET2046_H__
#define __ET2046_H__
#include <reg51.h>
sbit SCLK = P1^0;
sbit CS = P1^1;
sbit DIN = P1^2;
sbit DOUT = P1^3;
unsigned int read_AD_value(unsigned char cmd);
#endif
串口调试代码:
#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;
}
void uart_send_adVal(unsigned int val)
{
uart_send_byte(val>>8); //AD采样的数据为12位的,首先左移8位串口输出高4位
uart_send_byte(val); //再输出低8位
}
注意:因为AD采样的数据是12位的数据,串口每次只能发送8位数据,需要分两次将12位数据进行发送;
#ifndef __UART_H__
#define __UART_H__
#include <reg51.h>
void uart_init(void);
void uart_send_byte(unsigned char c);
void uart_send_adVal(unsigned int val);
#endif
main.c函数
#include "ET2046.h"
#include "uart.h"
#define AIN0 0x94 //滑动变阻器
#define AIN1 0xd4 //热敏电阻
#define AIN2 0xa4 //光敏电阻
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--);
}
void main()
{
unsigned int ad_val = 0;
uart_init();
//uart_send_adVal(0xabc); //测试代码
//uart_send_byte(0x0d);
//uart_send_byte(0x0a);
//while(1);
while(1)
{
ad_val= read_AD_value(AIN2);
uart_send_adVal(ad_val);
uart_send_byte(0); //发送数据0区分每次采样数值
delay1s(); //如何实现在串口调试助手中发送一次采样数据后换行?
}
}
思考:上述代码中,main函数是通过while()不断采样和发送AD转换的数据,如何通过中断来采样和发送AD转换数据?
4,AD转换代码-直接在串口显示电压值
串口助手中看到的数据以16进制显示或以对应字符形式来显示,因此在显示AD转换的电压时不直观,为了直观显示采集到的电压值,通过对采集到的电压值根据ASCii表对每一个十进制数转化为对应的数字符号,如下图所示,入药显示电压值中的数字5,只需要发送5+48的十进制数,在串口助手中就可以看到对应的符号5。
代码如下:
5,DA转换
待完善