前面我在另一篇博客<<初学电子ADC模块应用的一些问题>>里面详细介绍过ADC的基本原理,那这讲便直接来看如何应用ADC。首先单片机可以使用专门的外设AD转换芯片来读取模拟数据,其次也可以通过片内ADC来实现读取。因此我们分两部分来进行。
基础概念
还是回顾一下基础概念,之前的单片机学习都是关于某个IO口输出或接收到低电平或者高电平,在51里是0或5V,但是如果想接收一些模拟信号呢?什么是模拟信号,两个特点,一是随时间连续变化,譬如温度,每时每刻都在变化,而是可以有无数个取值,就是说你在任何一个时间点都能找到一个温度值。现在如果我们要测量这个模拟信号,比如温度信号,可以怎么做呢?一种方法是使用特定的芯片如TLC0831或者TLC0832等等,这些芯片有输入引脚可以直接连接温度传感器,然后当温度变化的时候,传感器电路会发出电压值,通过该引脚捕获这个电压值,芯片便会输出一个数字量。通常温度与传感器的电压会成一定比例关系,因此我们也可以构建数字量与传感器电压之间的关系,进而得出了数字量与温度之间的关系。另一种方法则是使用片内的ADC了,一般51单片机的P1口(具体哪些口要看芯片数据手册)都具有ADC功能。不管哪种方法,AD转换器的技术指标都是一样的。一般我们只关注3个指标。
(1)ADC的位数:ADC转换芯片输出的数字信号线的根数。(用N表示)常见的有8 bit, 10bit, 12bit, 24bit...
(2)ADC的分辨率:能够引起AD转换输出端数字量发生一个台阶变化所对应的输入模拟量。(Vmin)
(3)参考电压(VREF):所有的ADC芯片必须满足关系式,Vmin=VREF/2的n次方。
一般来说我们在实际项目中,如果要求提高分辨率的话,可以考虑减小参考电压或者增加位数。但是建小了参考电压的话,量程又减小了。你比如一个传感器输出的电压是5V,但是你的AD转换器参考电压3.3V,那么就无法捕捉到其余部分了。所以这种情况下还是考虑选用高位数的AD转换器。另外如果有负压出现,比如有的温度传感器输出负电压时候对应的是负温度值,则要考虑引入负参考电压。
既然现在有了数字量了,只需要将其标定为电压模拟信号,然后再由模拟信号计算出对应的实际模拟量的值。
AD转换芯片
好了,现在我们就来尝试用TLC0381芯片读取0-5V的电压。
先看我们的硬件电路部分。
MCU的三个引脚如高亮部分接到TLC0831芯片:
再给TLC0831的模拟口输入一个由电位器调节的电压,另外P19处,VREF应当接到5v的参考电压,这里不再附图了:
TLC0831是一款8位的AD转换器,即如果我们读到的数字量应该是一个介于0-255之间的数,当电位器电阻0时,对应数字量0,电压为0V,同理电位器电阻调到最大,应该对应255,电压为5V。好了我们直接上程序吧。
main.c
#include "reg51.h"
#include "12864.h"
#include "tlc0831.h"
u8 ad;
void main()
{
float v;
LCD_Init();
Show_String(0x80,"TLC0831");
while(1)
{
ad=Read_tcl0831(); //读取数字量
v=0.0195*ad; //将数字量标定为电压值 5.00V/256=0.0195
Show_Number(0x90,ad);
Show_Float(0x88,v);
}
}
tlc0831.c
#include "tlc0831.h"
sbit tlc0831_ck=P3^3;
sbit tlc0831_do=P1^5;
sbit tlc0831_cs=P2^3;
u8 Read_tcl0831()
{
u8 ad,i;
tlc0831_ck=0;
tlc0831_cs=0;
delay(1);
tlc0831_ck=1;
tlc0831_ck=0;
delay(1);
for(i=0;i<8;i++)
{
ad<<=1;
tlc0831_ck=1;
delay(1);
tlc0831_ck=0;
delay(1);
if(tlc0831_do)
{
ad|=0x01;
}
}
tlc0831_ck=0;
tlc0831_cs=1;
return ad;
}
tlc0831.h
#ifndef _tlc0831_
#define _tlc0831_
#include "reg51.h"
#define u8 unsigned char
#define u16 unsigned int
u8 Read_tcl0831();
extern void delay(u16 x);
#endif
12864.c
#include "12864.h"
#include "stdio.h"
sbit LCDRS_CS=P3^5; //-->4
sbit LCDWR_SID=P3^6; //5
sbit LCDE_CLK=P3^7; //6PIN
/*
0XF8 µØÖ·£¬ÃüÁî
0XFA Êý¾Ý
*/
code u8 Addmap[]={0X80,0X90,0X88,0X98};
void delay(u16 x)
{
while(x--);
}
void LCD_SendByte(u8 d) //ÏòLCD·¢ËÍÒ»¸ö×Ö½Ú
{
u8 i;
for(i=0;i<8;i++)
{
LCDE_CLK=0;
if(d&0x80)
{
LCDWR_SID=1;
}
else
{
LCDWR_SID=0;
}
LCDE_CLK=1;
d<<=1;
}
}
void Set_Text() //ʹLCD½øÈëÎı¾Ä£Ê½
{
Write_cmd(0x30);
}
void Set_DrawON() //ʹLCD½øÈë»æÍ¼Ä£Ê½
{
Write_cmd(0x36);
}
void Set_DrawOFF() //ʹLCD½øÈë»æÍ¼Ä£Ê½
{
Write_cmd(0x34);
}
void Set_TextAdd(u8 add) //ÉèÖÃÎı¾Ä£Ê½µØÖ·
{
LCDE_CLK=0;
LCDRS_CS=1;
LCD_SendByte(0XF8);
LCD_SendByte(add&0xf0); //7654 0000
LCD_SendByte(add<<4); //3210 0000
LCDE_CLK=0;
LCDRS_CS=0;
}
void Write_data(u8 dat) //ÏòLCDдÈëÏÔʾÊý¾Ý
{
LCDE_CLK=0;
LCDRS_CS=1;
LCD_SendByte(0XFa);
LCD_SendByte(dat&0xf0); //7654 0000
LCD_SendByte(dat<<4); //3210 0000
LCDE_CLK=0;
LCDRS_CS=0;
}
void Write_cmd(u8 cmd) //ÏòLCDдÈëÒ»ÌõÃüÁî
{
LCDE_CLK=0;
LCDRS_CS=1;
LCD_SendByte(0XF8);
LCD_SendByte(cmd&0xf0); //7654 0000
LCD_SendByte(cmd<<4); //3210 0000
LCDE_CLK=0;
LCDRS_CS=0;
}
void LCD_Init()
{
Set_Text(); //ʹLCD½øÈëÎı¾Ä£Ê½
Write_cmd(0x01);//Çå³ýÎı¾Ä£Ê½
delay(5000);
Write_cmd(0x0c); //
delay(100);
Draw_Clean();
}
void Show_chinese(u8 add,u8 qh,u8 ql) //ÔÚLCDÉÏÏÔʾһ¸öÖÐÎÄ
{
Set_Text();
Set_TextAdd(add);
Write_data(qh);
Write_data(ql);
}
void Show_String(u8 add,u8 *str) //ÔÚLCDÉÏÏÔʾ×Ö·û´®
{
Set_Text();
Set_TextAdd(add);
while(*str)
{
Write_data(*str++);
}
}
void Show_Number(u8 add,u16 n)
{
char s[6];
sprintf(s,"%05d",n);
Show_String(add,s);
}
void Show_Float(u8 add,float n) //ÏÔʾ¸¡µãÊý
{
char s[6];
sprintf(s,"%03.2f",n);
Show_String(add,s);
}
void Set_GDRAMAdd(u8 add) //ÉèÖÃGDRAMµÄµØÖ·
{
add|=0x80;
LCDE_CLK=0;
LCDRS_CS=1;
LCD_SendByte(0XF8);
LCD_SendByte(add&0xf0); //7654 0000
LCD_SendByte(add<<4); //3210 0000
LCDE_CLK=0;
LCDRS_CS=0;
}
void Show_Group(u8 x,u8 y,u16 dat) //ÏÔʾ»æÍ¼Ä£Ê½µÄÒ»×é
{ // x=0-7 y=0-63
u8 x0, y0; //LCDµÄÕæÊµ×é×ø±ê
if(y>31) //ÒªÏÔʾµÄ×éÔÚϰëÆÁ
{
y0=y-32;
x0=x+8;
}
else //ÒªÏÔʾµÄ×éÔÚÉϰëÆÁ
{
y0=y;
x0=x;
}
Set_DrawOFF() ;
Set_GDRAMAdd(y0);
Set_GDRAMAdd(x0);
Write_data(dat>>8);
Write_data(dat&0xff);
Set_DrawON();
}
void Show_GroupX(u8 x,u8 y,u16 dat) //ÏÔʾ»æÍ¼Ä£Ê½µÄÒ»×é
{ // x=0-7 y=0-63
u8 x0, y0; //LCDµÄÕæÊµ×é×ø±ê
if(y>31) //ÒªÏÔʾµÄ×éÔÚϰëÆÁ
{
y0=y-32;
x0=x+8;
}
else //ÒªÏÔʾµÄ×éÔÚÉϰëÆÁ
{
y0=y;
x0=x;
}
// Set_DrawOFF() ;
Set_GDRAMAdd(y0);
Set_GDRAMAdd(x0);
Write_data(dat>>8);
Write_data(dat&0xff);
// Set_DrawON();
}
void Draw_Clean() //½«»æÍ¼Ä£Ê½µÄ×éÈ«²¿Çå0
{
u8 x,y;
Set_DrawOFF() ;
for(y=0;y<64;y++)
{
for(x=0;x<8;x++)
{
Show_GroupX(x,y,0x0000);
}
}
Set_DrawON() ;
}
void Showpic_128x64(u16 *tp) //ÏÔʾһ·ù128x64µÄͼƬ
{
u8 x,y;
Set_DrawOFF() ;
for(y=0;y<64;y++)
{
for(x=0;x<8;x++)
{
Show_GroupX(x,y,*tp++);
}
}
Set_DrawON() ;
}
void Highlinght_line(u8 h,u8 w) //½«Ä³Ò»ÐÐÎı¾Ëù¶ÔÓ¦µÄ»æÍ¼Çø¸ßÁÁ»òÈ¡Ïû¸ßÁÁ
{
u16 dat;
u8 sy,y,x;
if(w==1)
{
dat=0xffff;
}
else
{
dat=0x0000;
}
sy=h*16;
for(x=0;x<8;x++)
{
for(y=sy;y<sy+16;y++)
{
Show_Group(x,y,dat);
}
}
}
void Dis_string(u8 h,u8 *str,u8 w) //ÏÔʾһ¸ö×Ö·û´®£¬´ø¸ßÁÁ¿ØÖÆ
{ //w=1 ¸ßÁÁÏÔʾ£¬w=0Õý³£ÏÔʾ
//h=0-3
Show_String(Addmap[h],str);
Highlinght_line(h,w);
}
void Highlinght_TextArea(u8 x,u8 y,u8 w) //¸ßÁÁÏÔʾһ¸öÎı¾Çø
{
u8 sy,i;
sy=y*16;
for(i=0;i<16;i++)
{
if(w==1)
{
Show_Group(x,sy+i,0xffff);
}
else
{
Show_Group(x,sy+i,0x0000);
}
}
}
12864.h
#ifndef _12864_
#define _12864_
#include "reg51.h"
#define u8 unsigned char
#define u16 unsigned int
void delay(u16 x);
void SLCD_Init();et_Text(); //ʹLCD½øÈëÎı¾Ä£Ê½
void Set_DrawON(); //ʹLCD½øÈë»æÍ¼Ä£Ê½
void Set_DrawOFF(); //ʹLCD½øÈë»æÍ¼Ä£Ê½
void Set_TextAdd(u8 add); //ÉèÖÃÎı¾Ä£Ê½µØÖ·
void Write_data(u8 dat); //ÏòLCDдÈëÏÔʾÊý¾Ý
void Write_cmd(u8 cmd); //ÏòLCDдÈëÒ»ÌõÃüÁî
void LCD_Init();
void Show_chinese(u8 add,u8 qh,u8 ql);
void Show_String(u8 add,u8 *str); //ÔÚLCDÉÏÏÔʾ×Ö·û´®
void Show_Number(u8 add,u16 n);
void Show_Float(u8 add,float n); //ÏÔʾ¸¡µãÊý
void Show_Group(u8 x,u8 y,u16 dat); //ÏÔʾ»æÍ¼Ä£Ê½µÄÒ»×é
void Draw_Clean(); //½«»æÍ¼Ä£Ê½µÄ×éÈ«²¿Çå0
void Dis_string(u8 h,u8 *str,u8 w); //ÏÔʾһ¸ö×Ö·û´®£¬´ø¸ßÁÁ¿ØÖÆ
void Highlinght_line(u8 h,u8 w); //½«Ä³Ò»ÐÐÎı¾Ëù¶ÔÓ¦µÄ»æÍ¼Çø¸ßÁÁ»òÈ¡Ïû¸ßÁÁ
void Highlinght_TextArea(u8 x,u8 y,u8 w);
#endif
实验部分12864液晶的程序还是跟原先的一样,有兴趣可以去阅读之前的文章,这里就不再赘述。我们重点看tlc0831.c驱动文件。程序是根据读取时序来的。
首先在clk为0时,拉低cs。延时tsu,之后clk先拉高,再拉低。准备工作就做好了,现在开始读取8个位,从高到低。首先拉高clk,再拉低clk,读取第7位,并重复7次(总共8次)。最后便是在clk为0的时候,拉高cs便结束了。
STC片内AD转换
关于stc的片内ADC,主要有以下4点:
(1)有8路10位的ADC。如上图P1口的8个引脚分别作为8路adc使用。10位也就是读取的数字量最大1023。
(2)参考电压等于芯片的供电电压。
(3)分辨率 5V/1023=0.00488V
(4)使用过程:选择模拟输入通道,启动ADC转换,等待ADC转换完成,读取ADC结果。
好了,现在我们要进行一个实验,实验是用温度传感器LM35来读取室内温度。我们选用P1.5作为温度传感器的输入,直接连接P1.5口到端子排P17的1脚即可。
直接上程序吧(12864.c,12864.h同上,这里不再给出)。
main.c
#include "12864.h"
#include "stc_adc.h"
#include "filter.h"
u16 ad5;
float v5;
float t;
void main()
{
LCD_Init();
Show_String(0x80,"stc_adc");
Stcadc_Init();
while(1)
{
ad5=Get_Adc();
v5=ad5*0.00488; //v
t=(v5*1000)/10;
Show_Number(0x90,ad5);
Show_Float(0x88,v5);
Show_Float(0x98,t);
}
}
stc-adc.c
#include "stc_adc.h"
/*将P1.5 1.6 1.7配置成ADC的模拟信号输入口*/
sfr P1M1=0x91;
sfr P1M0=0x92;
sfr P1ASF=0x9D;
sfr ADC_CONTR=0XBC;
sfr ADC_RES=0XBD;
sfr ADC_RESL=0XBE;
sfr AUXR1=0XA2;
void Stcadc_Init()
{
P1M1|=((1<<5)|(1<<6)|(1<<7));
P1M0&=~((1<<5)|(1<<6)|(1<<7)); //P1.5-P1.7输入模式
P1ASF=(1<<5)|(1<<6)|(1<<7);
ADC_CONTR=0XA0; // 1010 0000
AUXR1|=(1<<2); //ADRJ=1
}
u16 Read_Stcadc(u8 ch) //读取STC单片机某一通道ADC的数字量
{
u16 ad,temp;
ADC_CONTR=0xA0|ch; //ch~0-7|1010ccc 选择输入通道
delay(1);
ADC_CONTR|=(1<<3); // 启动ADC
while((ADC_CONTR&(1<<4))==0); //等待ADC转换完成
ADC_CONTR&=~(1<<4);//完成后置1
temp=ADC_RES; //读取ADC结果
temp<<=8;
ad=temp|ADC_RESL;
return ad;
}
stc-adc.h
#ifndef _stc_adc_
#define _stc_adc_
#include "reg51.h"
#define u8 unsigned char
#define u16 unsigned int
void delay(u16 x);
void Stcadc_Init();
u16 Read_Stcadc(u8 ch);
#endif
filter.c
#include "filter.h"
#include "stc_adc.h"
void BubbleSort(u16 *arr, int n)
{
int i = 0, j =0;
for(i = 0; i < n; i++)
for(j = 0; j < n - 1 - i; j++)
{
if(arr[j] > arr[j + 1])
{
arr[j] = arr[j] ^ arr[j+1];
arr[j+1] = arr[j] ^ arr[j+1];
arr[j] = arr[j] ^ arr[j+1];
}
}
}
u16 Get_Adc() //获取当前真实的ADC值,具备抗干扰的能力
{
#define count 10
xdata u16 Sam[count];
u16 ad;
u8 i;
for(i=0;i<count;i++)
{
Sam[i]=Read_Stcadc(5);
}
BubbleSort(Sam, count);
ad=0;
for(i=1;i<(count-1);i++)
{
ad+=Sam[i];
}
ad=ad>>3;//右移动3位相当于 ad/8
return ad;
}
filter.h
#ifndef _filter_
#define _filter_
#include "reg51.h"
#define u8 unsigned char
#define u16 unsigned int
void delay(u16 x);
u16 Get_Adc();
void BubbleSort(int *arr, int n);
#endif
这里我们重点看看stc-adc.c驱动和filter.c滤波文件。驱动文件里,我们首先要声明各个寄存器的地址,因为A51启动文件没有这些寄存器的声明。初始化函数中,首先通过P1M1和P1M0设置P1.7,P1.6,P1.5位高阻输入口,其次再将P1ASF寄存器中对应p1.7-1.5置1,表示该3个通道最为AD使用。再将ADC-CONTR寄存器上电并选定中速360时钟周期转换一次,最后将AUXR1的第二位置1,表示转换结果低八位装在ADC-RESL中,剩余2位装在ADC-RES中,按低位开始右边对齐。读取函数中要先设置ADC-CONTR寄存器,选定输入通道5,延时,开启ADC转换。(具体请参考STC12C5A60S2数据手册)
另外在filter.c中我们使用了平均值滤波法。滤波的好处是使读取的数值显示更加稳定,因为AD转换的分辨率为0.00488V,约为5mv,也就是只要有5mv的增加或减少,数字量便会加1或减1,我们知道传感器本身就是有很多干扰另外加上空气中电磁波干扰,数据会不停的跳动,这些都可以称为噪声。因此要滤波,即滤出噪声。我们这里的思路是每次取十个数字量,然后对其进行排序(排序的方法值得看看,采用异或操作),去除最小的和最大的,再求出剩余8个数的平均值,并将其作为最终的真实值。
好了本文到此为止,多谢查看。资料上传有些不便,如果你需要,可以给我留言,留下你的邮箱,我会发给你需要的资料。有问题请及时联系。