AD/DA介绍
AD和DA是模拟信号和数字信号之间的转换过程。
AD,全称为模拟到数字(Analog-to-Digital),指的是将模拟信号转换为数字信号的过程。在AD转换中,模拟信号经过采样、量化和编码等步骤,被转换为离散的数字信号。这样可以更方便地处理和传输信号,在数字系统中进行数字信号的处理和分析。
DA,全称为数字到模拟(Digital-to-Analog),指的是将数字信号转换为模拟信号的过程。在DA转换中,数字信号经过解码和重构处理,被转换为连续的模拟信号。这样可以将数字信号转换为模拟信号,使其能够被模拟系统接收和处理。
AD和DA转换常见于各种电子设备和系统中,例如音频设备、通信系统、传感器等。AD转换可以将实际物理量(如声音、温度、光强)转换为数字形式进行处理,而DA转换则可以将数字信号转换为模拟形式输出到外部设备或环境中。
AD和DA转换的准确性和性能对于系统的精度和稳定性至关重要,因此在设计和选择AD和DA转换器时需要考虑多个因素,如分辨率、采样率、信噪比、线性度等。
硬件电路模型
AD转换电路:
1.采样电路:根据采样信号,对要转换的模拟量进行抽样,通常使用开关电容电路或采样保持电路实现。
2.模拟信号输入电路:将要转换的模拟信号经过滤波、放大等处理后输入到采样电路。
该电路通常包括输入缓冲器、低通滤波器和增益放大器等。
3.A/D转换器(ADC):采样电路输出的模拟信号通过ADC芯片进行转换。ADC芯片可能采用逐次逼近法、积分法或其他转换方法。
4.数字信号输出:转换后的数字信号通过总线或其他方式传输给单片机。
DA转换电路:
1.数字信号输入:单片机通过总线或其他方式将要转换的数字信号输入到DA转换电路。
2.D/A转换器(DAC):数字信号经过DAC芯片进行转换,DAC芯片可能采用R-2R网络、串行接口或其他转换方法。
3.模拟信号输出电路:转换后的模拟信号经过滤波、放大等处理后输出到外部设备或环境.
举个例子,麦克风接收声音:通过声音的大小、音调、音色等因素转换为高低不同的电压,转换后的电压值就是数字信号;然后用扩音器放出声音:单片机或者芯片通过总线将存储在寄存器上的数字信号传入到DAC中;DAC将数字信号转换为对应的声音,最后进行输出。
AD原理
这是单片机上的ADC模块原理图;
与单片机芯片连接的引脚:
DIN:串行数据输入;
CS;片选信号;这是一种控制信号,用于选择要与主设备进行通信的从属设备。
DLCK:串行时钟;
DOUT:串行数据输出;
与外部电阻连接的引脚:
AIN0:连接着分压电阻;
AIN1:连接着热敏电阻;
AIN2:连接着光敏电阻;
下面是AD转换器的内部结构:逐次逼近型AD转换器
它使用二分搜索的方法逼近输入的模拟信号,并将其转换为数字输出;
逐次逼近型AD转换器通常由以下几个主要组成部分构成:
1.比较器(Comparator):用于比较输入模拟信号和DAC输出的数字量,产生一个比较结果。
2.数字-模拟转换器(Digital-to-Analog Converter,DAC):将数字量转换为模拟信号输出。在逐次逼近型AD转换器中,DAC通常是一个逐位逼近型DAC,可以根据比较结果生成合适的模拟电压输出。
3.控制逻辑(Control Logic):用于控制转换过程,包括初始化、逐位逼近、对比和停止等步骤。控制逻辑会根据比较结果调整DAC的输出,直到得到一个与输入模拟信号尽可能接近的数字输出。
4.计数器(Counter):用于计数和记录逼近过程中的比特位。逐次逼近型AD转换器从高位(MSB)开始逼近,通过逐步调整DAC输出的比特位。计数器会在每一次逼近过程中更新。
DA原理
与单片机相连的引脚:P21;通过放大器将数字信号转换为模拟信号到DA1(LED灯上);
T型电阻网络DA转换器:
T型电阻网络DAC通常由一个或多个可变电阻和固定比例的电阻组成。其中,可变电阻用于控制输出模拟信号的大小,而固定比例的电阻则用于分割参考电压以确定输出模拟信号的范围。
在T型电阻网络DAC中,输入的数字信号首先被编码为N位二进制数。每个二进制位对应一个电阻。根据输入二进制数中1的个数,相应的开关会连接或断开对应的电阻。通过这样的连接和断开过程,可变电阻的总阻值可以被调整,从而实现不同模拟输出电压的生成。当电阻网络的总阻值变化时,输出模拟电压也会相应地变化。
需要注意的是,T型电阻网络DAC的精度受限于电阻的精度和线性度。此外,由于电阻切换过程中可能引入的开关跳变,可能产生瞬态误差(glitch),导致输出信号的非理想性。
PWM型DA转换器:
PWM型DA转换器(Pulse Width Modulation)是一种常见的数字模拟转换器,通过调节脉冲的宽度来实现模拟输出信号的调节。
PWM型DA转换器基于脉冲信号,其输出信号的模拟值由脉冲的占空比决定。PWM型DA转换器通常包含一个计数器和一个比较器。计数器用于生成一个周期性的计数序列,比较器用于将计数值与给定的参考值进行比较产生脉冲信号。
工作原理如下:通过改变计数器的计数范围,以及在每个计数值处进行比较的阈值,可以控制脉冲信号的占空比。占空比表示脉冲高电平的时间与周期的比例,反映了输出模拟信号的幅度。
在PWM型DA转换器中,**输入的数字信号首先被编码为N位二进制数。这个二进制数通常对应了脉冲信号的占空比调节值。**因此,输入数字信号越大,对应的脉冲信号的高电平时间也越长,从而模拟输出信号的幅度也会相应增加。
运算放大器
运算放大器(Operational Amplifier,简称运放或OP-AMP)是一种具有很高放大倍数的电路单元。它常用于模拟电路中,可以将输入信号进行放大、加、减、微分、积分等数学运算,并输出相应的结果。
运放通常由集成电路或分立元件组成,在现代电子行业广泛应用。它的工作原理是利用多级放大电路实现非常高的增益,同时通过外部反馈网络来控制其响应和特性。运放具有差分输入和单端输出的特点,其中两个输入端分别为非反相输入端和反相输入端,输出端则与输入端之间存在一个差分增益。
运放电路
1.运算放大器的正相输入端和反向输入端分别连接到待比较的两个电压源。
2.运算放大器的输出端与反馈电阻组成反馈网络,将输出信号通过反馈电阻返回到运放的正相输入端。
3.参考电压源为基准,通常连接到运放的正相输入端。
当运放的非反相输入端(+)所对应的电压高于反相输入端(-)时,运算放大器的输出会趋向于最大正饱和电压。相反地,当非反相输入端(+)的电压低于反相输入端(-)时,输出会趋向于最大负饱和电压。
反向放大器的原理如下:
- 输入信号通过输入电阻连接到运放的反相输入端(-)。
- 反馈电阻将运放的输出信号反馈到反相输入端(-)。
- 运放的非反相输入端(+)通常连接到地或参考电压源。
根据反馈原理,在稳定工作状态下,反向放大器的负反馈将使输入电流接近于零。根据欧姆定律,输入电流通过输入电阻产生的电压与输出电压成比例。
根据虚短和虚断点理想条件,可以得出反向放大器的放大倍数为:
放大倍数(Av) = -(Rf / Rin)
其中,Rf为反馈电阻,Rin为输入电阻。
由于反向放大器的特性,输出信号与输入信号之间存在180度的相位差,且放大倍数为负值。当输入信号为正时,输出信号为负;当输入信号为负时,输出信号为正。
反向放大器常用于信号放大、滤波、调节增益等应用。通过调整反馈电阻和输入电阻的比例,可以实现不同的放大倍数,并控制输出信号的幅度。
同向放大器是一种电子设备,常用于放大电信号。它的工作原理基于放大器中的晶体管或其他增益元件。
同向放大器的主要原理是将输入信号通过增益元件进行放大,并输出放大后的信号。增益元件通常是一个晶体管,例如双极性晶体管(BJT)或场效应晶体管(FET)。
在同向放大器中,输入信号被传送到晶体管的基极(对于BJT)或栅极(对于FET)。晶体管被偏置,以确保它在其工作区域内正常工作。当输入信号施加在晶体管上时,它会引起晶体管中的电流和电压的变化。
晶体管中的电流和电压变化会导致输出信号的放大。通过调整晶体管的工作点,可以控制输出信号的幅度和偏置。同向放大器通常采用反馈网络来提高线性度和稳定性。
电压跟随器(Voltage Follower)是一种放大器电路,它具有高输入阻抗、低输出阻抗和1倍的电压增益。它的主要功能是将输入电压信号复制到输出端,同时提供更低的输出阻抗。
电压跟随器的原理很简单:输入信号被连接到放大器的非反馈输入端,而输出信号则从放大器的输出端获取。由于没有反馈网络,所以该电路没有电压增益,信号只是通过放大器进行缓冲传递。这使得输出信号与输入信号具有相同的幅度,但输出阻抗远低于输入阻抗。
XPT2046
简介:XPT2046 是一款 4 线制电阻式触摸屏控制器,内含 12 位分辨率 125KHz 转换速率逐步逼近型 A/D 转换器。
XPT2046 支持从 1.5V 到 5.25V 的低电压 I/O 接口。XPT2046 能通过执行两次 A/D 转换查出被按的屏幕位置, 除此之外,还可以测量加在触摸屏上的压力。内部自带 2.5V 参考电压,可以作为辅助输入、温度测量和电池监测之用,电池监测的电压范围可以从 0V 到 6V。
XPT2046 片内集成有一个温度传感器。 在 2.7V 的典型工作状态下,关闭参考电压,功耗可小于 0.75mW。
XPT2046 采用微小的封装形式:TSSOP-16,QFN-16 和 VFBGA-48。 工作温度范围为-40℃~+85℃。与 ADS7846、TSC2046、AK4182A 完全兼容。
在我们单片机上,已经内置了XPT2046控制器,我们只需要根据它的时序结构,就可以实现模数转换;
XPT2046时序
XPT2046 数据接口是串行接口,其典型工作时序如图 12 所示,图中展示的信号来自带有基本串行接口的单片机或数据信号处理器。(SPI通信)
片选信号CS保持低电平;对于DLCK,上升沿表示输入,下降沿表示输出;然后进行数据输入到DIN,最后将信号进行读出DOUT;
代码:
#include <REGX52.H> #include<INTRINS.H> //引脚定义 sbit XPT2046_DIN=P3^4; sbit XPT2046_CS=P3^5; sbit XPT2046_DLCK=P3^6; sbit XPT2046_DOUT=P3^7; //读出信号 unsigned int XPT2046_ReadAD(unsigned char Command) { unsigned char i; unsigned int Data=0; XPT2046_DLCK=0;//串行时钟置于低电平 XPT2046_CS=0;//片选信号置于低电平 for(i=0;i<8;i++) { XPT2046_DIN=Command&(0x80>>i);//信号输入 XPT2046_DLCK=1;//上升沿输入 XPT2046_DLCK=0; } //这是一个12位的AD转换器,可输出12位的分辨率 for(i=0;i<16;i++) { XPT2046_DLCK=1; XPT2046_DLCK=0;//下降沿输出 //需要通过不断的上升下降表示接受不同的数据位 if(XPT2046_DOUT)Data|=0x8000>>i; } XPT2046_CS=1;//片选置于高电平 return Data>>8;//返回8位的数字信号 }
这是从高位开始输入的;位7选择1;位6-4表示通道的选择,需要参考对应表;位3我们选择8位的转换分辨率的;位2选择单端输入方式;位1与位0选择低电平即可,不用用到总处于供电状态;
单端模式简单,在采样过程完成后,转换过程中可以关闭驱动开关,降低功耗。但这种模式的缺点是精度
直接受参考电压源的精度限制,同时由于内部驱动开关的导通电阻存在,导通电阻与触摸屏电阻的分压作用,也会带来测量误差
差分模式的优点是: +REF 和-REF 的输入分别直接接到 YP、 YN 上,可消除由于驱动开关的导通电阻引入的坐标测量误差。缺点是:无论是采样还是转换过程中,驱动开关都需要接通,相对单端模式而言,功耗增加了。
那么我们就有这样的信号模板
0xxx 1100
我们根据原理图,选出对应的输出配置:
#define XPT2046_VBAT 0xAC #define XPT2046_AUX 0xEC #define XPT2046_XP 0x9C #define XPT2046_YP 0xDC
AD模数转换代码:
将分压电阻,热敏电阻,光敏电阻转换为对应的数字信号并显示于屏幕上;
XPT2046.h:
#ifndef __XPT2046_H__ #define __XPT2046_H__ //输入端口地址的宏定义 #define XPT2046_VBAT 0xAC #define XPT2046_AUX 0xEC #define XPT2046_XP 0x9C #define XPT2046_YP 0xDC unsigned int XPT2046_ReadAD(unsigned char Command); #endif
XPT2046.c
#include <REGX52.H> #include<INTRINS.H> //引脚定义 sbit XPT2046_DIN=P3^4; sbit XPT2046_CS=P3^5; sbit XPT2046_DLCK=P3^6; sbit XPT2046_DOUT=P3^7; //读出信号 unsigned int XPT2046_ReadAD(unsigned char Command) { unsigned char i; unsigned int Data=0; XPT2046_DLCK=0; XPT2046_CS=0; for(i=0;i<8;i++) { XPT2046_DIN=Command&(0x80>>i); XPT2046_DLCK=1; XPT2046_DLCK=0; } for(i=0;i<16;i++) { XPT2046_DLCK=1; XPT2046_DLCK=0; if(XPT2046_DOUT)Data|=0x8000>>i; } XPT2046_CS=1; return Data>>8; }
Delay.h
#ifndef __DELAY_H__ #define __DELAY_H__ void Delayms(unsigned int x); #endif
Delay.c
void Delayms(unsigned int x) //@11.0592MHz { unsigned char i, j; while(x--) { i = 2; j = 199; do { while (--j); } while (--i); } }
LCD1602.c
#include <REGX52.H> //引脚配置: sbit LCD_RS=P2^6; sbit LCD_RW=P2^5; sbit LCD_EN=P2^7; #define LCD_DataPort P0 //函数定义: /** * @brief LCD1602延时函数,12MHz调用可延时1ms * @param 无 * @retval 无 */ void LCD_Delay() { unsigned char i, j; i = 2; j = 239; do { while (--j); } while (--i); } /** * @brief LCD1602写命令 * @param Command 要写入的命令 * @retval 无 */ void LCD_WriteCommand(unsigned char Command) { LCD_RS=0; LCD_RW=0; LCD_DataPort=Command; LCD_EN=1; LCD_Delay(); LCD_EN=0; LCD_Delay(); } /** * @brief LCD1602写数据 * @param Data 要写入的数据 * @retval 无 */ void LCD_WriteData(unsigned char Data) { LCD_RS=1; LCD_RW=0; LCD_DataPort=Data; LCD_EN=1; LCD_Delay(); LCD_EN=0; LCD_Delay(); } /** * @brief LCD1602设置光标位置 * @param Line 行位置,范围:1~2 * @param Column 列位置,范围:1~16 * @retval 无 */ void LCD_SetCursor(unsigned char Line,unsigned char Column) { if(Line==1) { LCD_WriteCommand(0x80|(Column-1)); } else if(Line==2) { LCD_WriteCommand(0x80|(Column-1+0x40)); } } /** * @brief LCD1602初始化函数 * @param 无 * @retval 无 */ void LCD_Init() { LCD_WriteCommand(0x38);//八位数据接口,两行显示,5*7点阵 LCD_WriteCommand(0x0c);//显示开,光标关,闪烁关 LCD_WriteCommand(0x06);//数据读写操作后,光标自动加一,画面不动 LCD_WriteCommand(0x01);//光标复位,清屏 } /** * @brief 在LCD1602指定位置上显示一个字符 * @param Line 行位置,范围:1~2 * @param Column 列位置,范围:1~16 * @param Char 要显示的字符 * @retval 无 */ void LCD_ShowChar(unsigned char Line,unsigned char Column,char Char) { LCD_SetCursor(Line,Column); LCD_WriteData(Char); } /** * @brief 在LCD1602指定位置开始显示所给字符串 * @param Line 起始行位置,范围:1~2 * @param Column 起始列位置,范围:1~16 * @param String 要显示的字符串 * @retval 无 */ void LCD_ShowString(unsigned char Line,unsigned char Column,char *String) { unsigned char i; LCD_SetCursor(Line,Column); for(i=0;String[i]!='\0';i++) { LCD_WriteData(String[i]); } } /** * @brief 返回值=X的Y次方 */ int LCD_Pow(int X,int Y) { unsigned char i; int Result=1; for(i=0;i<Y;i++) { Result*=X; } return Result; } /** * @brief 在LCD1602指定位置开始显示所给数字 * @param Line 起始行位置,范围:1~2 * @param Column 起始列位置,范围:1~16 * @param Number 要显示的数字,范围:0~65535 * @param Length 要显示数字的长度,范围:1~5 * @retval 无 */ void LCD_ShowNum(unsigned char Line,unsigned char Column,unsigned int Number,unsigned char Length) { unsigned char i; LCD_SetCursor(Line,Column); for(i=Length;i>0;i--) { LCD_WriteData(Number/LCD_Pow(10,i-1)%10+'0'); } } /** * @brief 在LCD1602指定位置开始以有符号十进制显示所给数字 * @param Line 起始行位置,范围:1~2 * @param Column 起始列位置,范围:1~16 * @param Number 要显示的数字,范围:-32768~32767 * @param Length 要显示数字的长度,范围:1~5 * @retval 无 */ void LCD_ShowSignedNum(unsigned char Line,unsigned char Column,int Number,unsigned char Length) { unsigned char i; unsigned int Number1; LCD_SetCursor(Line,Column); if(Number>=0) { LCD_WriteData('+'); Number1=Number; } else { LCD_WriteData('-'); Number1=-Number; } for(i=Length;i>0;i--) { LCD_WriteData(Number1/LCD_Pow(10,i-1)%10+'0'); } } /** * @brief 在LCD1602指定位置开始以十六进制显示所给数字 * @param Line 起始行位置,范围:1~2 * @param Column 起始列位置,范围:1~16 * @param Number 要显示的数字,范围:0~0xFFFF * @param Length 要显示数字的长度,范围:1~4 * @retval 无 */ void LCD_ShowHexNum(unsigned char Line,unsigned char Column,unsigned int Number,unsigned char Length) { unsigned char i,SingleNumber; LCD_SetCursor(Line,Column); for(i=Length;i>0;i--) { SingleNumber=Number/LCD_Pow(16,i-1)%16; if(SingleNumber<10) { LCD_WriteData(SingleNumber+'0'); } else { LCD_WriteData(SingleNumber-10+'A'); } } } /** * @brief 在LCD1602指定位置开始以二进制显示所给数字 * @param Line 起始行位置,范围:1~2 * @param Column 起始列位置,范围:1~16 * @param Number 要显示的数字,范围:0~1111 1111 1111 1111 * @param Length 要显示数字的长度,范围:1~16 * @retval 无 */ void LCD_ShowBinNum(unsigned char Line,unsigned char Column,unsigned int Number,unsigned char Length) { unsigned char i; LCD_SetCursor(Line,Column); for(i=Length;i>0;i--) { LCD_WriteData(Number/LCD_Pow(2,i-1)%2+'0'); } }
LCD1602.h
#ifndef __LCD1602_H__ #define __LCD1602_H__ //用户调用函数: void LCD_Init(); void LCD_ShowChar(unsigned char Line,unsigned char Column,char Char); void LCD_ShowString(unsigned char Line,unsigned char Column,char *String); void LCD_ShowNum(unsigned char Line,unsigned char Column,unsigned int Number,unsigned char Length); void LCD_ShowSignedNum(unsigned char Line,unsigned char Column,int Number,unsigned char Length); void LCD_ShowHexNum(unsigned char Line,unsigned char Column,unsigned int Number,unsigned char Length); void LCD_ShowBinNum(unsigned char Line,unsigned char Column,unsigned int Number,unsigned char Length); #endif
main.c
#include <REGX52.H> #include"Delay.h" #include"LCD1602.h" #include"XPT2046.h" unsigned int ADValue; void main() { LCD_Init(); LCD_ShowString(1,1,"ADJ NTC GR"); while(1) { ADValue=XPT2046_ReadAD(XPT2046_XP); //读取AIN0,可调电阻 LCD_ShowNum(2,1,ADValue,3); ADValue=XPT2046_ReadAD(XPT2046_YP); //读取AIN1,热敏电阻 LCD_ShowNum(2,6,ADValue,3); ADValue=XPT2046_ReadAD(XPT2046_VBAT); //读取AIN2,光敏电阻 LCD_ShowNum(2,11,ADValue,3); Delayms(100);//延迟100ms,会根据实况不断刷新数据 } }
AD的实例代码
产生PWM的方法
在这里,我们利用软件程序进行实现PWM,就是利用计数器和某一个值进行比较,根据比较结果输出一个对应的高低电平;像图中的,只要比较值大于计数器的值,那么就输出0,比较值大于等于计数器的值,那么就输出1;
DA信号输出的呼吸灯代码
Timer0.c
#include <REGX52.H> /** * @brief 定时器0初始化 * @param 无 * @reval 无 */ void Timer0Init(void) //100us @11.0592MHz { TMOD &= 0xF0; //设置定时器模式 TMOD |=0x01; TL0 = 0xA4; //设置定时初值 TH0 = 0xFF; //设置定时初值 TF0 = 0; //清除TF0标志 TR0 = 1; //定时器0开始计时 ET0=1; //允许T0中断 EA=1; //CPU开放总中断 PT0=0; //定时器0中断优先级 }
Timer0.h
#ifndef __TIMER0_H__ #define __TIMER0_H__ void Timer0Init(); #endif
Delay.h
#ifndef __DELAY_H__ #define __DELAY_H__ void Delay(unsigned int xms); #endif
Delay.c
void Delay(unsigned int xms) { unsigned char i, j; while(xms--) { i = 2; j = 239; do { while (--j); } while (--i); } }
main.c
#include <REGX52.H> #include"Delay.h" #include"Timer0.h" sbit DA=P2^1; unsigned char Counter,Compare;//计数值和比较值 unsigned char i; void main() { Timer0Init(); while(1) { for(i=0;i<100;i++)//利用循环不断增加 { Compare=i; Delay(10);//延长周期,不然LED闪烁过快 } for(i=100;i>0;i--) { Compare=i; Delay(10); } } } //通过100us中断一次,将计数值与比较值比较,赋上对应的值 void Timer0_Routine() interrupt 1 { TL0 = 0xA4; TH0 = 0xFF; Counter++; Counter%=100; if(Counter<Compare) { DA=1; } else { DA=0; } }