第五讲 单片机C语言之AD转换

前面我在另一篇博客<<初学电子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个数的平均值,并将其作为最终的真实值。

好了本文到此为止,多谢查看。资料上传有些不便,如果你需要,可以给我留言,留下你的邮箱,我会发给你需要的资料。有问题请及时联系。







  • 20
    点赞
  • 104
    收藏
    觉得还不错? 一键收藏
  • 6
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 6
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值