懒猫爱飞

Cortex-M3学习日志(六) -- ADC实验

0
阅读(2770)

上一次简单的总结了一下DAC方面的知识,好吧,这次再来总结一下ADC方面的东东。ADCAnalog-to-Digital Converter的缩写,指模/数转换器或者模拟/数字转换器。现实世界是由模拟信号组成的,关于为什么要用模数转换器,这大概与现在数字存储技术有关吧,例如温度、压力、声音或者图像等只有转换成数字量才能方便的存储在硬盘、U盘等数码存储介质中,或许某天我们的技术发展了,数字存储可以用某些模拟量存储,也许我们就用不着这么麻烦的转来换去了。好了,闲话不多扯,来简单总结一下ADC的原理。模拟信号转换为数字信号,一般分为四个步骤进行,即取样、保持、量化和编码。前两个步骤在取样-保持电路中完成,后两步骤则在ADC中完成。关于它的原理,主要有以下几种模型:

1、积分型ADC(如TLC7135

积分型ADC工作原理是将输入电压转换成时间(脉冲宽度信号)或频率(脉冲频率),然后由定时器/计数器获得数字值。其优点是用简单电路就能获得高分辨率,但缺点是由于转换精度依赖于积分时间,因此转换速率极低。初期的单片AD转换器大多采用积分型,现在逐次比较型已逐步成为主流。

2、逐次比较型ADC(如TLC0831

逐次比较型AD由一个比较器和DA转换器通过逐次比较逻辑构成,从MSB开始,顺序地对每一位将输入电压与内置DA转换器输出进行比较,经n次比较而输出数字值。其电路规模属于中等。其优点是速度较高、功耗低,在分辨率较低(<12位)时价格便宜,但高精度(>12位)时价格很高。

3、并行比较型/串并行比较型ADC(如TLC5510

并行比较型AD采用多个比较器,仅作一次比较而实行转换,又称Flash(快速)型。由于转换速率极高,n位的转换需要2n-1个比较器,因此电路规模也极大,价格也高,只适用于视频AD转换器等速度特别高的领域。

串并行比较型AD结构上介于并行型和逐次比较型之间,最典型的是由2n/2位的并行型AD转换器配合DA转换器组成,用两次比较实行转换,所以称为Half flash(半快速)型。还有分成三步或更多步实现AD转换的叫做分级(Multistep/Subrangling)型AD,而从转换时序角度又可称为流水线(Pipelined)型AD,现代的分级型AD中还加入了对多次转换结果作数字运算而修正特性等功能。这类AD速度比逐次比较型高,电路规模比并行型小。

4Σ-Δ(Sigma?/FONT>delta)调制型ADC(如AD7705

Σ-ΔAD由积分器、比较器、1DA转换器和数字滤波器等组成。原理上近似于积分型,将输入电压转换成时间(脉冲宽度)信号,用数字滤波器处理后得到数字值。电路的数字部分基本上容易单片化,因此容易做到高分辨率。主要用于音频和测量。

5、电容阵列逐次比较型ADC

电容阵列逐次比较型AD在内置DA转换器中采用电容矩阵方式,也可称为电荷再分配型。一般的电阻阵列DA转换器中多数电阻的值必须一致,在单芯片上生成高精度的电阻并不容易。如果用电容阵列取代电阻阵列,可以用低廉成本制成高精度单片AD转换器。最近的逐次比较型AD转换器大多为电容阵列式的。

6、压频变换型ADC(如AD650

压频变换型(Voltage-Frequency Converter)是通过间接转换方式实现模数转换的。其原理是首先将输入的模拟信号转换成频率,然后用计数器将频率转换成数字量。从理论上讲这种AD的分辨率几乎可以无限增加,只要采样的时间能够满足输出频率分辨率要求的累积脉冲个数的宽度。其优点是分辨率高、功耗低、价格低,但是需要外部计数电路共同完成AD转换。

DAC的内部电路构成无太大差异,一般按输出是电流还是电压、能否作乘法运算等进行分类。大多数DAC由电阻阵列和多个电流开关(或电压开关)构成。按数字输入值切换开关,产生比例于输入的电流(或电压) 。此外,也有为了改善精度而把恒流源放入器件内部的。DAC分为电压型和电流型两大类,电压型DAC有权电阻网络、T型电阻网络和树形开关网络等;电流型DAC有权电流型电阻网络和倒T型电阻网络等。

1 电压输出型(TLC5620) 。电压输出型DAC虽有直接从电阻阵列输出电压的,但一般采用内置输出放大器以低阻抗输出。直接输出电压的器件仅用于高阻抗负载,由于无输出放大器部分的延迟,故常作为高速DAC使用。

2 电流输出型(THS5661A ) 。电流输出型DAC很少直接利用电流输出,大多外接电流- 电压转换电路得到电压输出,后者有两种方法:一是只在输出引脚上接负载电阻而进行电流- 电压转换,二是外接运算放大器。

3 乘算型(AD7533) DAC中有使用恒定基准电压的,也有在基准电压输入上加交流信号的,后者由于能得到数字输入和基准电压输入相乘的结果而输出,因而称为乘算型DAC。乘算型DAC一般不仅可以进行乘法运算,而且可以作为使输入信号数字化地衰减的衰减器及对输入信号进行调制的调制器使用。

4 一位DAC。一位DAC与前述转换方式全然不同,它将数字值转换为脉冲宽度调制或频率调制的输出,然后用数字滤波器作平均化而得到一般的电压输出,用于音频等场合。

选用ADC时要注意这几个参数:取样与保持时间、量化与编码方式、分辨率、转换误差、转换时间、绝对精准度、相对精准度等。

1、取样与保持

由于取样时间极短,取样输出为一串断续的窄脉冲。要把每个取样的窄脉冲信号数字化,是需要一定的时间。 因此在两次取样之间,应将取样的模拟信号暂时储存到下个取样脉冲到来,这个动作称之为保持。在模拟电路设计上,因此需要增加一个取样-保持电路。为了保证有正确转换,模拟电路要保留着还未转换的数据。 一个取样-保持电路可保证模拟电路中取样时,取样时间的稳定并储存,通常使用电容组件来储存电荷。根据数字信号处理的基本原理,奈奎斯特(Nyquist)取样定理(懒猫记得这个定理应该是在《信号与线性系统》这本课上学的^_^),若要能正确且忠实地呈现所撷取的模拟信号,必须取样频率至少高于最大频率的2倍。例如,若是输入一个100Hz的正弦波的话,最小的取样频率至少要2倍,即是200Hz。虽说理论值是如此,但真正在应用时,最好是接近10倍才会有不错的还原效果(因取样点越多)。若针对多信道的A/D转换器来说,就必须乘上信道数,这样平均下去,每一个通道才不会有失真的情况产生。

2、量化与编码

量化与编码 电路是A/D转换器的核心组成的部分,一般对取样值的量化方式有下列两种:

1)只舍去不进位

 首先取一最小量化单位Δ=U/2nU是输入模拟电压的最大值,n是输出数字数值的位数。 当输入模拟电压U0~Δ之间,则归入0Δ,当U在Δ~2Δ之间,则归入1Δ。透过这样的量化方法产生的最大量化误差为Δ/2,而且量化误差总是为正,+1/2LSB

2)有舍去有进位

 如果量化单位Δ=2U/(2 n+11),当输入电压U0~Δ/2之间,归入0Δ,当U在Δ/2~3/2Δ之间的话,就要归入1Δ。这种量化方法产生的最大量化误差为Δ/2,而且量化误差有正,有负,为±1/2LSB。 量化结果也造成了所谓的量化误差。

 3、解析度

A/D转换器所能分辨的最小模拟输入量。通常用转换成数字量的位数来表示,如8-bit10-bit12-bit16-bit等。位数越高,分辨率越高。若小于最小变化量的输入模拟电压的任何变化,将不会引起输出数字值的变化。采用12-bit AD574,若是满刻度为10V的话,分辨率即为10V / 212 = 2.44mV。而常用的8-bit ADC0804,若是满刻度为5V的话,分辨率即为5V / 28 = 19.53mV。 选择适用的A/D转换器是相当重要的,并不是分辨率越高越好。不需要分辨率高的场合,所撷取到的大多是噪声。分辨率太低,会有无法取样到所需的信号。

4、转换误差

通常以相对误差的形式输出,其表示A/D转换器实际输出数字值与理想输出数字值的差别,并用最低有效位LSB的倍数表示。

5、转换时间

转换时间是A/D转换完成一次所需的时间。从启动信号开始到转换结束并得到稳定的数字输出值为止的时间间隔。转换时间越短则转换速度就越快。

6、精准度

对于A/D转换器,精准度指的是在输出端产生所设定的数字数值,其实际需要的模拟输入值与理论上要求的模拟输入值之差。精确度依计算方式不同,可以区分为:绝对精确度与相对精确度。所谓的绝对精确度是指实际输出值与理论输出值的接近程度,其相关的关系是如下式子所列:

 

 

相对精准度指的是满刻度值校准以后,任意数字输出所对应的实际模拟输入值(中间值)与理论值(中间值)之差。对于线性A/D转换器,相对精准度就是它的线性程度。由于电路制作上影响,会产生像是非线性误差,或是量化误差等减低相对精准度的因素。相对精确度是指实际输出值与理想理论满刻输出值之接近程度,其相关的关系是如下式子所列:

 

 

 基本上,一个n-bit的转换器就有n个数字输出位。这种所产生的位数值是等效于在A/D转换器的输入端的模拟大小特性值。如果外部所要输入电压或是电流量较大的话,所转换后的的位数值也就较大。 透过并列端口接口或是微处理机连接A/D转换器时,必须了解如何去控制或是驱动这颗A/D转换器的问题。因此需要了解到A/D转换器上的控制信号有哪些。

懒猫为了总结学过的东东,翻箱倒塌柜,终于找到了大学的课本,又在大学城的图书包管里面坐了几个小时,当然了懒猫不也在网上转了N久,所以呢,以上知识大部分来源网络,懒猫囫囵吞枣的咽进了肚里,但大部分也开始消化了,嘻嘻……好了,这ADC有知识先暂时总结到这,下面说一说这次实验的思路(是思路不是丝路^_^)及电路图,并简单的总结一下LPC1768内部集成的ADC

LPC1768内部集成的是12位主次逼近式的模数转换器,具有8 个通道,它的基本时钟由APB时钟提供,它还包含一个可编程的分频器,可以将APB时钟调整为主次逼近转换所需的时钟(最大可达13MHz)。与ADC相关的引脚配置包括功能配置也即配置引脚功能选择寄存器PINSEL,一般ADC是第二功能,这次实验的电路连接的是ADC0.2通道所以要把P0.25配置成ADC功能,即PINCON->PINSEL1 |= (1<<18);  /* 设置ADC有第二通道 */。还要把电源参考引脚连接上参考电源一般是接3V或与VCC电压相等。与   ADC相关的寄存器包括:

1、  外围器接口电源管理寄存器PCONP,要把这个寄存器的第12位即PCADC位置位,这一位是ADC的电源控制与时钟控制位。

2、  A/D控制寄存器ADCR,主要是控制ADC转换的一些操作如通道选择,工作模式选择等等,AD转换开始前,必须设置ADCR寄存器来选择工作模式。

3、  A/D全局数据寄存器ADGDR,它包含最近一次A/D转换的结果。

4、  A/D中断使能寄存器ADINTEN,如果使用转换完中断时,需要配置此寄存器,它包含的使能位控制每一个A/D通道的DONE标记是否用来产生中断。

5、  A/D通道n数据寄存器ADDRn,共有8个,它包含在通道n上完成的最近一次转换结果。

6、  A/D状态寄存器AD0STAT,它包含所有A/D通道的DONE标志和OVERRUN标志,以及A/D中断标志。


关于这几个寄存器的各位代表是什么意思,如何设置,这里就不总结了,芯片的数据手册上写的很清楚,如果你觉得看英文不舒服可以参考一下周公那公司翻译的中文版,应该对初学者有所帮助。下面说一下这次实验的电路图:

1-1 电位器分压电路图

实验电路很简单,就是一个电位器来分压,然后由AD转换器的通道2来采样电压,然后再把采集到的电压通过串口0 发送的到串口以便观察采样的电压值,好了,电路这块就不多费口舌了,留点笔墨说一下程序吧,下面先贴出ADC的源代码:

一、AdcFunc.c源代码(主要就是与ADC相关的一些函数)

#include"adc.h"

/********************************************************************************

 * 函数名称 :void AdcInit(void)

 * 函数功能 : ADC初始化

 * 入口参数 : 无

 * 出口参数 : 无

 * 备    注 :PCONP   -- 外围电源控制寄存器,主要是开/关外围功能的电源,以降低功耗

 *                                      此处主要设置该寄存器的第12位(ADC power/clock control bit)

 *            PINSEL1  -- 引脚功能选择寄存器,主要是配置引脚的功能的,此处主要是设置

 *                       该寄存器的第18:19位当这两位是10时,选择的是ADC0.2功能

 *            ADCR    -- AD控制寄存器

 *                       0:7   SEL       -- 选择转换通道,1 - 选择 0 - 关闭

 *                       8:15  CLKDIV    -- 选择时钟分频

 *                       16    BURST     -- 循环扫描

 *                       17:20  Reserved  -- 保留位

 *                       21    PDN       -- 转换模式选择  1 - 正常模式 0 - 掉电模式

 *                       22:23  Reserved   -- 保留位

 *                       24:26  START     -- 控制ADC什么时候开始转换

 *                       27    EDGE      -- 转换启动条件 1 - 下降沿启动转换 0 - 上升沿

 *                       28:31  Reserved  -- 保留位

 *******************************************************************************/

void AdcInit(void)

{

              SC->PCONP |= (1 << 12);                    /* 使能ADC控制器电源及时钟源 */

              PINCON->PINSEL1 |= (1<<18);                /* 设置ADC有第二通道 */

    ADC -> ADCR |= ( 1 << 2 )                     /* SEL=4,选择第2通道ADC0.2 */

                            |( ( 18000000 / ADC_CLK - 1 ) << 8 )     /* CLKDIV = Fpclk / ADC_CLK - 1 */

                            |( 0 << 16 )

                            |( 1 << 21 )                                                     /* PDN = 1,正常工作模式  */

                            |( 1 << 24)                                                      /* 设置直接启动模式 */

                            |( 0 << 27) ;                                        /* 默认值 */

}


/********************************************************************************

 * 函数名称 :void AdcInit(void)

 * 函数功能 : ADC初始化

 * 入口参数 : 无

 * 出口参数 : 无

 * 备    注 :PCONP   -- 外围电源控制寄存器,主要是开/关外围功能的电源,以降低功耗

 *                                      此处主要设置该寄存器的第12位(ADC power/clock control bit)

 *            PINSEL1  -- 引脚功能选择寄存器,主要是配置引脚的功能的,此处主要是设置

 *                       该寄存器的第18:19位当这两位是10时,选择的是ADC0.2功能

 *            ADCR    -- AD控制寄存器

 *                       0:7   SEL      -- 选择转换通道,1 - 选择 0 - 关闭

 *                       8:15  CLKDIV   -- 选择时钟分频

 *                       16    BURST    -- 循环扫描

 *                       17:20 Reserved -- 保留位

 *                       21    PDN      -- 转换模式选择  1 - 正常模式 0 - 掉电模式

 *                       22:23 Reserved -- 保留位

 *                       24:26 START    -- 控制ADC什么时候开始转换

 *                       27    EDGE     -- 转换启动条件    1 - 下降沿启动转换 0 - 上升沿

 *                       28:31 Reserved -- 保留位

 *******************************************************************************/

unsigned int AdcConver(void)

{

              unsigned int i = 0;                           /* 转换次数计数 */

              unsigned long ulADCbuf = 0;                   /* 暂时存放转换结果 */

              unsigned int  ulADCData = 0;                  /* 存储转换结果 */


              for(i=0;i<ADC_CNT;i++)

              {

                            ADC -> ADCR |= 1 << 24;                 /* 立即启动ADC转换 */

                            while (!(ADC ->ADSTAT & (1 << 2)));        /* 读取AD0STAT的通道2的Done */

        ADC -> ADCR |= (1 <<24);                   /* 第一次转换结果丢弃 */

        while ((ADC ->ADSTAT & (1 << 2)) == 0);        /* 读取AD0STAT的通道2的Done */

        ulADCbuf   = ADC ->ADDR2;                /* 只有一路,则读取全局寄存器 */

        ulADCbuf   = (ulADCbuf >> 4) & 0xfff;     /* 读取转换结果 */

        ulADCData += (unsigned int)ulADCbuf;         /* 转换结果累加 */     

              }

    //ADC -> ADCR &= ~(1<<21)&(~(1<<24));           /* 停止ADC转换*/

              ulADCData = (ulADCData/ADC_CNT);             /* 采样ADC_CNT次进行虑波处理 */

              ulADCData = (ulADCData*3300)/4096;                 /* 求取采样结果 */

              return              ulADCData;                       /* 返回采样结果 */

}

这个源代码,主要就是ADC初始化与ADC采样程序,程序中我已将用到的寄存器都注释了,对于初学者来说应该不是太难了,下面贴出主程序的部分代码:

二、mian.c(主要调度函数及应用函数)

/********************************************************************************

 * 函数名称 :void AdcDisVal(unsigned int delay)

 * 函数功能 : 显示ADC转换结果

 * 入口参数 : 无

 * 出口参数 : 无

 * 备    注 :无

 *******************************************************************************/

 void AdcDisVal(void)

 {

             unsigned int adcval = 0; /* 临时存放ADC采样值 */

              unsigned int hh = 0;     /* 存放电压整数部分 */

              unsigned int hi = 0;     /* 存放电压小数第一位 */

              unsigned int ll = 0;     /* 存放电压小数第二位 */

              unsigned int lo = 0;     /* 存放电压小数第三位 */

              adcval = AdcConver();    /* 获取ADC采样值 */

              if(adcval != AdcValOld)   /* 如果本次采集的数值与上次不一样,则判断是否发送数据*/

              {

                 if(((adcval+1)!=AdcValOld)&&((adcval+2)!=AdcValOld)&&((adcval+3)!=AdcValOld)) /*如果不在+/-3范围则发送*/

                 {

                           if(((adcval-1)!=AdcValOld)&&((adcval-2)!=AdcValOld)&&((adcval-3)!=AdcValOld))

                               {

               hh = adcval/1000;     /* 求采样值整数部分 */

                         hi = adcval%1000/100; /* 求采样值小数第一位 */

                         ll = adcval%100/10;   /* 求采样值小数第二位 */

                         lo = adcval%10;      /* 求采样值小数第三位 */

                         AdcVal[38] = (unsigned char)hh + 0x30; /* 将电压值转换成字符,方便从串口发送 */

                         AdcVal[40] = (unsigned char)hi + 0x30; /* 将电压值转换成字符,方便从串口发送 */

                         AdcVal[41] = (unsigned char)ll + 0x30; /* 将电压值转换成字符,方便从串口发送 */

                         AdcVal[42] = (unsigned char)lo + 0x30; /* 将电压值转换成字符,方便从串口发送 */


                         UARTSend(0,AdcVal,50); /* 发送转换信息 */

                           

                         AdcValOld =   adcval;  /* 更新采样值 */                            

                               }                           

                 }              

              }

 }


下面是程序执行结果:

1-2 程序执行结果

这只是部分代码,详细的源代码请参考附件。这段代码是将采集到的数据转换成字符发送到串口显示,里面没有什么复杂的计算,都是烂大街的程序,因为懒猫也不是什么高手,也说不出什么高深的理论,只能总结点简单的东西,希望这些简单的实验能起到抛砖引玉的作用,让初学者尽快能迈入单片机这扇门,万事开头难,徘徊在门口的感觉最不好爱,懒猫也曾彷徨过,也曾无奈过,也曾痛苦过,也曾抱怨过,但道路虽然崎岖,只要你坚持就能爬到山顶。懒猫深知很多人面对电子有着莫大的兴趣,但却无从下手,没有高手指导,没有教材参考,这种感觉实在不好爱,所以懒猫有空便会写下自己的学习日志,尽量简单,尽量详细,俗话说师傅引进门,修行靠个人,当你熬过阵痛,跨进电子这扇门之后,剩下的事就是发挥你的聪明才智,努力的创造奇妙的未来!还是那句老话,学无难易,贵在坚持!

好了,今天先总结到这吧,最近手头上的事有点多,所以自学的时间有点紧,学习的事推了又推,但不管怎样,懒猫依然是懒猫,懒猫依然会坚持最初的梦想,一直努力的走下去……

最后再吼一下懒猫的口号:

 

 

每天进步一点点,开心多一点^_^

工程源代码:Lesson6-ADC实验.rar