【自学嵌入式:51单片机】AD模数转换/DA数模转换(含SPI通信)
AD/DA
- AD(Analog to Digital):模拟-数字转换,将模拟信号转换为计算机可操作的数字信号;
- DA(Digital to Analog):数字-模拟转换,将计算机输出的信号转换为模拟信号;
AD/DA转换打开了计算机与模拟信号的大门,极大的提高了计算机系统的应用范围,也为模拟信号数字化提供了可能。

硬件电路模型

- AD转换通常有多个输入通道,用多路选择开关连接至AD转换器,以实现AD多路复用的目的,提高硬件利用率(AD一般检测电压的变化,比如图中检测可变电阻的电压,真实的0-5V被映射成8位二进制一个字节0-255);
- AD/DA与单片机数据传送可使用并口(速度快、原理简单),也可以使用串口(接线少、使用方便)
- 可将AD/DA模块直接集成在单片机内,这样直接写入/读出寄存器就可进行AD/DA转换,单片机的IO口可直接复用AD/DA的通道(一般来说用到DA的地方,也可以用PWM来实现)。

DA用的很少,一般用低通滤波直接转换为模拟信号。
【注】低通滤波就像个 “信号筛子”:让变化慢的低频信号(比如平稳的声音、图像里的大色块)顺利通过,把变化快的高频信号(比如杂音、图像噪点)挡在外面,帮信号 “去噪” 变干净。
图中的AD是个触摸屏芯片,内部原理比较闭塞,封装起来了,不方便理解。
硬件电路


图中的AD/DA芯片比较老,下图的PCF8591芯片常用,它用的是I2C总线进行通信

运算放大器
运算放大器(简称“运放”)是具有很高放大倍数额放大电路单元。内部集成了差分放大器、电压放大器、功率放大器三级放大电路,是一个性能完备、功能强大的通用放大电路单元,由于其应用十分广泛,现已作为基本的电路元件出现在电路图中。
运算放大器可构成的电路有:电压比较器、反相放大器、同相放大器、电压跟随器、加法器、积分器、微分器等。
运算放大器的分析方法:虚短、虚断(负反馈条件下)

运算放大器(运放):可以理解为“超级信号放大器”,放大能力特别强,是一个现成的、功能完善的放大电路模块。
差分放大器:运放内部的第一级电路,像“信号比较器”,主要负责比较两个输入信号的差异。
电压放大器:运放内部的第二级电路,专门“增强信号的电压大小”。
功率放大器:运放内部的第三级电路,负责“增强信号的功率(可以理解为信号的‘力量’)”。
三级放大电路:运放内部由差分放大器、电压放大器、功率放大器按顺序组成的“放大流水线”,一步步把信号放大。
通用放大电路单元:运放是个“多面手”,能适应各种放大需求,是电路里的基础元件。
电压比较器:运放构成的“信号对比器”,用来比较两个电压谁大谁小。
反相放大器:运放构成的放大器,输出信号和输入信号“方向相反”(比如输入正信号,输出负信号)。
同相放大器:运放构成的放大器,输出信号和输入信号“方向相同”(输入正信号,输出也正信号)。
电压跟随器:运放构成的电路,输出信号和输入信号“一模一样”,不放大但能让信号更稳定。
加法器:运放构成的“信号计算器”,能把多个输入信号的大小加起来。
积分器:运放构成的电路,能算出“信号随时间的累积效果”(类似算一段时间的总量)。
微分器:运放构成的电路,能算出“信号变化的快慢”(类似算速度)。

通过接个电阻构成负反馈电路,通过调整电阻来调整电压倍数
运放电路
电压比较器

反向放大器

输入正,然后通过不断地正负电压变化最后达到一个稳态

最后就会形成一个状态称之为虚短,就是电路负反馈的状态下,负极的电压和正极的电压相等,虚断同理类似两端断开。
同向放大器

电压跟随器

电压跟随器能提高电路的驱动能力(提高功率)
DA原理

下面两根线都接地,方便上面分流分压
PWM型DA转换器

一个电阻和一个电容构成一个低通滤波器

后面跟着电压跟随器增大电路驱动能力
它的好处是节省IO口,精度高;但是占用单片机。
AD原理

AD/DA性能指标
- 分辨率:指AD/DA数字量的精细程度,通常用位数表示。例如,对于5V电源系统来说,8位的AD可将5V等分为256份,即数字量变化最小一个单位时,模拟量变化5V/256=0.01953125V,所以,8位AD的电压分辨率为0.01953125V,AD/DA的位数越高,分辨率就越高。
- 转换速度:表示AD/DA的最大采样/简历频率,通常用转换频率或者转换时间来表示,对于采样/输出高速信号,应注意AD/DA的转换速度。
SPI通信
(转自爱上半导体)

SPI是一主多从模式的通信,主机只能有一个,从机有多个,SPI通信需要4条信号线:SS,SCK,MOSI,MISO
SS:片选信号线,单片机通过给片选信号线高低电平来确定和哪一个从机通讯,一般这根线为低电时,片选才有效(也有例外,比如93C46 EEPROM的数据手册说是高电平有效);
SCK:时钟信号线,由主设备产生信号,其信号如下:

MOSI:发送信号线,主设备通过这条线发送数据,从机通过这条线接收数据。
MISO:接收信号线,主设备通过这根线接收数据
写数据:


时钟信号上升沿,数据信号才会被采样

这是根据要传输的存储器的对象所决定的,SPI还有按时钟下降沿采集信号,还有时钟空闲为高电平的时候(上拉电阻,开漏输出,我个人理解,欢迎大佬指正)

SPI通讯对一帧有多少位没有规定,主要看芯片的设定
XPT2046
XPT2046 是一款4线制电阻式触摸屏控制器,内含 12 位分辨率 125KHz 转换速率逐步逼近型 A/D 转换器。XPT2046支持从1.5V到5.25V的低电压 I/0接口。XPT2046 能通过执行两次 A/D转换查出被按的屏幕位置,除此之外,还可以测量加在触摸屏上的压力。内部自带 2.5V 参考电压,可以作为辅助输入、温度测量和电池监测之用,电池监测的电压范围可以从 OV 到 6V。XPT2046 片内集成有一个温度传感器。 在2.7V的典型工作状态下,关闭参考电压,功耗可小于0.75mW。XPT2046采用微小的封装形式:TSSOP-16,QFN-16和 VFBGA-48。工作温度范围为-40℃~+85℃。与ADS7846、TSC2046、AK4182A 完全兼容
时序

它使用SPI通信,每个设备的CS片选信号都是不同的线,其他都接在总线上,它最基本的时序就是首先片选,上升沿输入,下降沿输出

连续读两个字节16位,因为是12位模式,只取前12位,后面都用0填充,调用这个时序,就读入单片机
写命令字
手册内容:
起始位————第一位,即S位。控制字的首位必须是1,即S=1。在XPT2046的DIN引脚检测到起始位前,所有的输入将被忽略。
地址————接下来的3位(A2、A1和A0)选择多路选择器的现行通道(通道决定读哪一路的AD),触摸屏驱动和参考源输入。
MODE————模式选择位,用于设置ADC的分辨率。MODE=0,下一次的转换将是12位模式;MODE=1,下一次的转换是8位模式。

电路中X-接地了,只能单端模式,SER给1


我们用的是外部参考电压,5V的



XP就是X+

由电路可知,X+连着的是可调电阻,Y+连着的是热敏电阻,VBAT连着的是光敏电阻
AD模数转换代码实现
分别读可调电阻ADJ,光敏电阻RG和热敏电阻NTC的值
XPT2046读AD

CS默认在1,然后变成0
XPT2046.h
#ifndef __XPT2046_H__
#define __XPT2046_H__
#define XPT2046_XP_8 0x9C//测X+,1001 1100,先来8位的精度
#define XPT2046_YP_8 0xDC //测Y+
#define XPT2046_VBAT_8 0xAC//A2A1A0 010
#define XPT2046_AUX_8 0xEC//A2A1A0 111
//12位
#define XPT2046_XP_12 0x94//测X+
#define XPT2046_YP_12 0xD4 //测Y+
#define XPT2046_VBAT_12 0xA4//A2A1A0 010
#define XPT2046_AUX_12 0xE4//A2A1A0 111
unsigned int XPT2046_ReadAD(unsigned char Command); //读数据字
#endif
XPT2046.c
#include <REGX52.H>
#include <intrins.h> // 延时空语句
sbit XPT2046_CS = P3^5;
sbit XPT2046_DCLK = P3^6;
sbit XPT2046_DIN = P3^4;
sbit XPT2046_DOUT = P3^7;
void XPT2046_Delay1ms() //@11.0592MHz
{
unsigned char i, j;
_nop_();
i = 2;
j = 199;
do
{
while (--j);
} while (--i);
}
//读数据字
unsigned int XPT2046_ReadAD(unsigned char Command)
{
unsigned char i;
unsigned int ADValue = 0;
XPT2046_DCLK = 0;
XPT2046_CS = 0;
//把8位连续的发出去
for(i=0;i<8;i++)
{
XPT2046_DIN = Command & (0x80>>i); //依次送DIN
XPT2046_DCLK = 1; //变高电平,上升沿,把最高位发出去了
XPT2046_DCLK = 0; //下降沿什么也不做,满足时序要求,都是纳秒级别
}
//这几个执行下来满足了1.5微秒,不用延时也行
//连续读两个字节
for(i=0;i<16;i++)
{
//下降沿是读,所以先给高再给低,形成下降沿
XPT2046_DCLK = 1;
XPT2046_Delay1ms(); //给下降沿1ms延时
XPT2046_DCLK = 0;
if(XPT2046_DOUT)
{
ADValue |= (0x8000>>i);
}
}
//结束时序
XPT2046_CS = 1;
//判断是8位还是12位
if(Command & 0x08) //0000 1000 ,低4位第4位1代表读的是8位的电压,0是12位
{
return ADValue>>8;
}
else
{
return ADValue>>4;
}
}
main.c
#include <REGX52.H>
#include "LCD1602.h"
#include "XPT2046.h"
unsigned int ADValue = 0;
void Delay(unsigned int xms) //@11.0592MHz
{
unsigned char i, j;
while(xms--)
{
i = 2;
j = 199;
do
{
while (--j);
} while (--i);
}
}
void main()
{
LCD1602_Init();
LCD1602_ShowString(0,0,"ADJ NTC RG");
while(1)
{
ADValue = XPT2046_ReadAD(XPT2046_XP_8);
LCD1602_ShowNum(1,0,ADValue,3);
ADValue = XPT2046_ReadAD(XPT2046_YP_8);
LCD1602_ShowNum(1,4,ADValue,3);
ADValue = XPT2046_ReadAD(XPT2046_VBAT_8);
LCD1602_ShowNum(1,8,ADValue,3);
Delay(4);
}
}
LCD1602部分
LCD1602部分我用的上一篇博客的版本,不是我gitee仓库的版本,文章地址:https://www.cnblogs.com/qinruiqian/p/19020925
显示效果

DA模数转换代码实现
其实就是PWM

IO口是P2_1,电路是二倍放大,所以呼吸灯没有I/O口的LED灯呼吸的快,它就是魔改PWM电机调速的代码,只不过硬件上加了一个低通滤波,DA应用范围没有AD广,因为很多地方用I/O口的呼吸灯就够用了
#include <REGX52.H>
sbit DA = P2^1; //DAC(PWM)模块IO口
unsigned char Counter, Compare; //Counter计数器,Compare比较值设置越大,低电平(亮的)时间越长
unsigned char i; //循环变量
void Delay(unsigned int xms) //@11.0592MHz
{
unsigned char i, j;
while(xms--)
{
i = 2;
j = 199;
do
{
while (--j);
} while (--i);
}
}
//初始化定时器,100微秒为周期
void initTimer0(void)
{
TMOD = 0x01; //只开启定时器0
TL0 = 0xA4; //设置定时初值
TH0 = 0xFF; //设置定时初值
TF0 = 0; //清除TF0标志
TR0 = 1; //定时器0开始计时
EA = 1; //允许中断
ET0 = 1; //允许定时器0中断
}
//每隔100微秒中断一次
void Timer0_Routine() interrupt 1
{
TL0 = 0xA4; //设置定时初值
TH0 = 0xFF; //设置定时初值
Counter++;
Counter %= 100; //到100的时候自动变0
//计数变量到比较值的时候自动变低电平
if(Counter < Compare)
{
DA = 1;
}
else
{
DA = 0;
}
}
void main()
{
initTimer0();
while(1)
{
for(i=0; i<100; i++)
{
Compare = i;
Delay(10);
}
for(i=100; i>0; i--)
{
Compare = i;
Delay(10);
}
}
}
浙公网安备 33010602011771号