51单片机模数转换ADC原理与代码一
1.概述
这篇文章是模数转换的入门文章,这篇文章主要介绍模数的概念、原理、核心指标、专业术语,以及一个模数转换的实例代码实现检测电位器的数值变化。
2.ADC介绍
2.1.ADC概念
ADC(Analog-to-Digital Converter)是用于将模拟形式的连续信号转换为数字形式的离散信号的一类设备。
Analog: 直接翻译过来就是模拟,也就是说是模拟信号,这里我们就要了解一个概念了什么是模拟信号?其实并不难理解,模拟信号就是将电路模拟成信号,电信号有电压、电流等因素等等;
Digital:直接翻译过来就是数字的,也就是数字信号,那我们又要了解一个概念了,什么是数字信号?其实它比模拟信号还要容易理解,将电路的信号模拟成数字信号,通常情况下高电平就表示1,低电平就表示0;
Converter:当我们了解了前面两个概念之后,您或许有一些似懂非懂的意思了,那么这翻译过来就是模拟信号转变成数字信号呗!没错就是这样,我们通过相应的采集装置,采集到的值为电压的大小,此时我们就需要用到我们的模数转换来将它转换成数字信号了。
2.2.ADC指标
ADC有一些核心的性能指标,这些指标为我们选择ADC模块提供了参考,主要的指标如下。
-
输出位数
- AD采集共有2n个刻度,也就是说如果一个8位的AD,它最终输出的数值是在0~255之间的值。
-
分辨率
- 对输入信号变化的敏感度,用ADC转换器输出位数来表示,例如输出位数位8位,输出量的变化范围是0~255,当参考电压为5V,转换器对输入模拟电压的分辨率计算公式为 5V / 255 = 18.6mV
-
转换速度
- 完成一次AC转换所需要花费的时钟周期的倒数,也就是1个周期内可以完成多少次AD转换
-
参考电压
- 通过参考电压计算分辨率
2.3.ADC寄存器
ADC寄存器介绍
这里没有介绍ADC内部工作原理,是因为我们侧重点是偏向实操,操作ADC实现模数转换,在真实的场景应用它完成一些工作,而不是研究他的原理,因此这里不介绍过多的理论知识。
了解了ADC概念和指标之后就有了一个初步的认识,再了解ADC寄存器就可以操作ADC了。ADC寄存器的设置因型号不同而有差异,要查看实际型号对应的说明书。任何型号的AD转换器只是寄存器设计有点差异,操作方式都是一样的,因此掌握了一款型号就可以操作其他型号。
这里主要以STC12C2052AD型号的说明书做讲解。
上图中是ADC转换器内部结构图,下面详细介绍下ADC_CONTR Register 寄存器各个位如何配置。
ADC_POWER:
是单片机开启ADC 电源控制位;0:关闭ADC 电源;1:打开A/D转换器电源
1.建议进入空闲模式和掉电模式前,将ADC电源关闭,可降低功耗。
2.启动A/D转换前一定要确认A/D电源已打开,A/D转换结束后关闭A/D电源可降低功耗,也可不关闭。
3.初次打开内部A/D转换模拟电源,需适当延时,等内部模拟电源稳定后,再启动A/D转换。
SPEED1,SPEED0:
用来控制单片机ADC的转换速度
SPEED1 | SPEED0 | ADC时钟周期N个时钟转换1次 |
---|---|---|
0 | 0 | 1080 |
0 | 1 | 810 |
1 | 0 | 540 |
1 | 1 | 270 |
ADC_FLAG:
转换标志位:每次当AD转换结束完成后,这个位就会自动置"1",需要手动将这一位重新置"0"。
ADC_START:
转换启动位:每当手动将其置"1"后,AD转换开始,当AD转换结束后这个位就会自动置"0"。
CHS2/CHS1/CHS0:
选择采集信号输入端口
CHS2 | CHS1 | CHS0 | ADC输入通道 |
---|---|---|---|
0 | 0 | 0 | ADC0(P1^0) |
0 | 0 | 1 | ADC0(P1^1) |
0 | 1 | 0 | ADC0(P1^2) |
0 | 1 | 1 | ADC0(P1^3) |
1 | 0 | 0 | ADC0(P1^4) |
1 | 0 | 1 | ADC0(P1^5) |
1 | 1 | 0 | ADC0(P1^6) |
1 | 1 | 1 | ADC0(P1^7) |
P1M0、P1M1
设置单片机P1的IO口状态,P1口如果要作为AD使用需要将它设置为高阻或者开漏模式。
P1M0 | P1M1 | I/O 口模式 |
---|---|---|
0 | 0 | 准双向口(传统8051 IO口模式,弱上拉),灌电流可达20mA,拉电流为270uA,由于制造误差,实际为270uA~150uA |
0 | 1 | 推挽输出(强上拉输出,可达20mA,要加限流电阻) |
1 | 0 | 高阻输入(电流既不能流入也不能流出) |
1 | 1 | 开漏(Open Drain),内部上拉电阻断开。开漏模式既可读外部状态也可对外输出(高电平或低电平)。如要正确读外部状态或需要对外输出高电平,需外加上拉电阻,否则读不到外部状态,也对外输不出高电平。 |
中断允许寄存器IE
该寄存器用来控制单片机的各种中断。在此只介绍与ADC相关的寄存位。
EA: CPU中断开放标志
EA的作用是使中断允许形成多级控制。即各中断源首先受EA控制;其次还受各中断源自己的中断允许控制位控制。
EA=1,CPU开放中断,
EA=0,CPU屏蔽所有中断申请。
EADC_SPI : A/D转换中断允许位和SPI中断允许位
EADC_SPI的作用就是用来控制AD转换的开启与停止。
EADC_SPI=1,允许A/D转换中断和SPI中断,
EADC_SPI=0,禁止A/D转换中断和SPI中断。
ADC寄存器配置
上面把ADC寄存器所有的内容都介绍完了,了解了他们的作用可能在代码中还是不会配置,因此这里专门介绍下如何使用寄存器。
ADC_CONTR Register 寄存器共有8个位是按位寻址,将其转换成对应的十六进制数就可以按位设置。
下面举几个例子介绍使用方法,其他的参数配置方法都相同。
ADC_START设置
含义:转换启动位:每当手动将其置"1"后,AD转换开始,当AD转换结束后这个位就会自动置"0"。
开启转换:ADC_START在寄存器中从右向左数是第四位,对应十六进制就是 0x08,转换为二进制就是 0000 1000 第四位为1表示开启转换
ADC_POWER:
含义:是单片机开启ADC 电源控制位;0:关闭ADC 电源;1:打开A/D转换器电源
开启ADC电源:ADC_POWER在寄存器中从右向左数是第八位,对应十六进制就是 0x80,转换为二进制就是 1000 0000 第八位为1表示开启ADC电源
3.ADC代码
ADC代码比较简单,可以分装到函数中,使用的时候直接调用函数即可。ADC转换代码分装成两个函数
- ADC初始化函数
- 设置采集信号输入端口,例如P1.0采集数据。
- 设置采集速度
- 启动ADC电源
- ADC读取输出数据函数
- ADC转换开始
- 检测转换是否结束
- 将转换标志位置0
- 输出转换数据
初始化ADC函数
在main
函数中调用adcInit(0,ADC_SPEEDH,0x01,0x00);
ADC初始化函数,工作流程如下
- ADC_CONTR |= (inIOnum & 0x07); 传入0,设置P1.0引脚为数据采集
- ADC_CONTR = speed; 传入ADC_SPEEDH,设置中高转换速度
- P1M0 = p1m0; 传入0X01,设置P1.0引脚为高阻状态
- ADC_CONTR |= ADC_POWER; 开启ADC转换电源
/*定义ADC寄存器*/
#define ADC_POWER 0x80 //ADC 电源打开
#define ADC_FLAG 0x10 //ADC 打开转换标志位
#define ADC_START 0x08 //ADC 开始转换
#define ADC_SPEEDLL 0x00 //420 设置低速转换
#define ADC_SPEEDL 0x20 //280 设置低中速转换
#define ADC_SPEEDH 0x40 //140 设置中高速转换
#define ADC_SPEEDHH 0x60 //70 设置低高速转换
/*
函数名:8位A/D转换初始化函数
参 数:
inDate:采集数据端口(0000 0XXX 其中XXX是设置输入端口号,可用十进制0~7表示,0表示P1.0,7表示P1.7)
speed: 设置转换速度
ioMode:设置IO口模式,P1口如果要作为AD使用需要将它设置为高阻或者开漏模式。
返回值:无
功 能:开启ADC功能并设置ADC的输入端口
备 注:适用于STC12C2052AD系列单片机(必须使用STC12C2052AD.h头文件)
*/
void adcInit (unsigned char inIOnum, unsigned char speed, unsigned char p1m0, unsigned char p1m1){
ADC_CONTR |= (inIOnum & 0x07); //选择A/D当前输入信号的通道,当前选择是P1.0(P1.0~P1.7),选择ADC的8个接口中的一个(0000 0111 清0高5位)
ADC_CONTR = speed; //ADC转换的速度(0XX0 0000 其中XX控制速度,请根据数据手册设置)
P1M0 = p1m0;
P1M1 = p1m1; // 设置IO口模式,P1口如果要作为AD使用需要将它设置为高阻或者开漏模式。
_nop_();
ADC_CONTR |= ADC_POWER; //启动A/D电源
DELAY_MS(1); //使输入电压达到稳定(1ms即可)
}
获取ADC转换结果
/*
函数名:8位A/D转换函数
参 数:无
返回值:8位的ADC数据
结 果:读出指定ADC接口的A/D转换值,并返回数值
备 注:适用于STC12C2052AD系列单片机(必须使用STC12C2052AD.h头文件)
*/
unsigned char getADCResult (){
ADC_CONTR |= ADC_START; //启动A/D转换(0000 1000 令ADCS = 1)
_nop_();
_nop_();
_nop_();
_nop_();
while (!(ADC_CONTR & ADC_FLAG));//等待转换完成,ADC_FLAG为0x10转换完成
ADC_CONTR &= ~ADC_FLAG; //1111 0111 清ADC_FLAG位, 关闭A/D转换,
return ADC_DATA; //返回A/D转换结果(8位)
}
main调用ADC
void main(){
// 调用ADC初始化函数
adcInit(0,ADC_SPEEDH,0x01,0x00);
while(1){
// 获取ADC转换结果
m = getADCResult ();
printChar(5,1,m/100+0x30);//1011 0101
printChar(6,1,m/10%10+0x30);//1011 0101
printChar(7,1,m%10+0x30);//1011 0101
}
}
1602显示屏展示ADC转换效果
在51单片机4线并发IO口控制1602LCD实验上加入上面的ADC代码,通过调整电位器,在1602显示屏上显示转换的数值。
硬件电路
按照下图接线,有两点需要改动
- P1.0~P1.3 线不用接
- 电位器1脚接GND,2脚接P1.0,3脚接VCC
代码
#include <STC12C2052AD.H>
#include <string.h>
#include <intrins.h> //51基本运算(包括_nop_空函数)
typedef unsigned char uint8;
// 定义引脚
#define LCD1602_DB4_DB7 P1 // 定义高4位LCD1602的数据总线
sbit LCD1602_RS = P3 ^ 2; // 定义LCD1602的RS控制线
sbit LCD1602_RW = P3 ^ 3; // 定义LCD1602的RW控制线
sbit LCD1602_E = P3 ^ 4; // 定义LCD1602的E控制线
sbit LCD1602_Busy = P1 ^ 7; // 定义LCD1602的测忙线(与LCD1602_DB4_DB7关联)
// 定义指令集
/*设置显示模式*/
#define LCD_MODE_PIN8 0x38 // 8位数据线,两行显示
#define LCD_MODE_PIN4 0x28 // 4位数据线,两个显示
#define LCD_SCREEN_CLR 0x01 // 清屏
#define LCD_CURSOR_RET 0x02 // 光标复位
#define LCD_CURSOR_RIGHT 0x06 // 光标右移,显示不移动
#define LCD_CURSOR_LEFT 0x04 // 光标左移,显示不移动
#define LCD_DIS_MODE_LEFT 0x07 // AC自增,画面左移
#define LCD_DIS_MODE_RIGHT 0X05 // AC自增,画面右移
/*光标开关控制*/
#define LCD_DIS_CUR_BLK_ON 0x0f // 显示开,光标开,光标闪烁
#define LCD_DIS_CUR_ON 0x0e // 显示开,光标开,光标不闪烁
#define LCD_DIS_ON 0x0c // 显示开,光标关,光标不闪烁
#define LCD_DIS_OFF 0x08 // 显示关,光标关,光标不闪烁
/*光标、显示移动*/
#define LCD_CUR_MOVE_LEFT 0x10 // 光标左移
#define LCD_CUR_MOVE_RIGHT 0x14 // 光标右移
#define LCD_DIS_MOVE_LEFT 0x18 // 显示左移
#define LCD_DIS_MOVE_RIGHT 0x1c // 显示右移
/*定义ADC寄存器*/
#define ADC_POWER 0x80 //ADC 电源打开
#define ADC_FLAG 0x10 //ADC 打开转换标志位
#define ADC_START 0x08 //ADC 开始转换
#define ADC_SPEEDLL 0x00 //420 设置低速转换
#define ADC_SPEEDL 0x20 //280 设置低中速转换
#define ADC_SPEEDH 0x40 //140 设置中高速转换
#define ADC_SPEEDHH 0x60 //70 设置低高速转换
void DELAY_MS (unsigned int a){
unsigned int i;
while( --a != 0){
for(i = 0; i < 600; i++);
}
}
/**
LCD1602忙碌状态不会接收新指令,因此在发送新指令前先检测是否忙碌。
判断LCD1602_Busy变量的值为低电平则为不忙。
*/
void LCD1602_TestBusy(void){
LCD1602_DB4_DB7 = 0xf0; //高4位IO口设置为1,低4位IO口保持原态
LCD1602_RS = 0; // 指令状态
LCD1602_RW = 1; // 读状态
LCD1602_E = 1;
while(LCD1602_Busy); //读取LCD1602_Busy(P1.7)为低电平则结束循环
LCD1602_E = 0; // 关闭LCD显示器读指令
}
/********************************************************************************************
// 写指令程序 //
// 向LCD1602写命令 本函数需要1个指令集的入口参数 //
/********************************************************************************************/
void LCD1602_WriteCMD(uint8 LCD1602_command) {
LCD1602_TestBusy();
LCD1602_RS = 0;
LCD1602_RW = 0;
//输入的命令高4位赋值给LCD1602_DB4_DB7
LCD1602_DB4_DB7 = LCD1602_command;
DELAY_MS(1);
LCD1602_E = 1;
LCD1602_E = 0;
//将命令低4位移到高四位供IO口读取
LCD1602_DB4_DB7 = LCD1602_command << 4;
DELAY_MS(1);
LCD1602_E = 1;
LCD1602_E = 0;
}
/********************************************************************************************
// 写数据程序 //
// 向LCD1602写数据 //
/********************************************************************************************/
void LCD1602_WriteData(uint8 LCD1602_data){
LCD1602_TestBusy();
LCD1602_RS = 1;
LCD1602_RW = 0;
//写入高4位数据
LCD1602_DB4_DB7 = LCD1602_data;
DELAY_MS(1);
LCD1602_E = 1;
LCD1602_E = 0;
//将低4位数据移到高4位IO口写入
LCD1602_DB4_DB7 = LCD1602_data << 4;
DELAY_MS(1);
LCD1602_E = 1;
LCD1602_E = 0;
}
// LCD1602初始化
void LCD1602_Init(void){
// 设置4线并行口
LCD1602_WriteCMD(LCD_MODE_PIN4); // 显示模式设置:显示2行,每个字符为5*7个像素
LCD1602_WriteCMD(LCD_DIS_ON); // 显示开及光标设置:显示开,光标关
LCD1602_WriteCMD(LCD_CURSOR_RIGHT); //显示光标移动设置:文字不动,光标右移
LCD1602_WriteCMD(LCD_SCREEN_CLR); // 显示清屏
}
/*
输出字符串
x:数据地址
y:输出的行位置,第一行和第二行
str:输入字符串
*/
void printStr(uint8 x, uint8 y, uint8 *str){
if(0 == y){
LCD1602_WriteCMD(0x80 | x);
}
else{
// 第二行起始位置是0x40
LCD1602_WriteCMD(0x80 | (0x40+x));
}
while(*str != '\0'){
LCD1602_WriteData(*str++);
}
}
/*
打印单字符程序 //
第一行位置 0x00~0x17 第二行位置 0x40~0x57
向LCM发送一个字符,以十六进制(0x00)表示
应用举例:print(0xc0,0x30); //在第二行第一位处打印字符“0”
*/
void printChar(uint8 x, uint8 y, uint8 c){
if(0 == y){
LCD1602_WriteCMD(0x80 | x);
}
else{
// 第二行起始位置是0x40
LCD1602_WriteCMD(0x80 | (0x40+x));
}
LCD1602_WriteData(c);
}
/*
函数名:8位A/D转换初始化函数
参 数:
inDate:采集数据端口(0000 0XXX 其中XXX是设置输入端口号,可用十进制0~7表示,0表示P1.0,7表示P1.7)
speed: 设置转换速度
ioMode:设置IO口模式,P1口如果要作为AD使用需要将它设置为高阻或者开漏模式。
返回值:无
功 能:开启ADC功能并设置ADC的输入端口
备 注:适用于STC12C2052AD系列单片机(必须使用STC12C2052AD.h头文件)
*/
void adcInit (unsigned char inIOnum, unsigned char speed, unsigned char p1m0, unsigned char p1m1){
ADC_CONTR |= (inIOnum & 0x07); //选择A/D当前输入信号的通道,当前选择是P1.0(P1.0~P1.7),选择ADC的8个接口中的一个(0000 0111 清0高5位)
ADC_CONTR = speed; //ADC转换的速度(0XX0 0000 其中XX控制速度,请根据数据手册设置)
P1M0 = p1m0;
P1M1 = p1m1; // 设置IO口模式,P1口如果要作为AD使用需要将它设置为高阻或者开漏模式。
_nop_();
ADC_CONTR |= ADC_POWER; //启动A/D电源
DELAY_MS(1); //使输入电压达到稳定(1ms即可)
}
/*
函数名:8位A/D转换函数
调 用:? = Read ();
参 数:无
返回值:8位的ADC数据
结 果:读出指定ADC接口的A/D转换值,并返回数值
备 注:适用于STC12C2052AD系列单片机(必须使用STC12C2052AD.h头文件)
*/
unsigned char getADCResult (){
ADC_CONTR |= ADC_START; //启动A/D转换(0000 1000 令ADCS = 1)
_nop_();
_nop_();
_nop_();
_nop_();
while (!(ADC_CONTR & ADC_FLAG));//等待转换完成,ADC_FLAG为0x10转换完成
ADC_CONTR &= ~ADC_FLAG; //1111 0111 清ADC_FLAG位, 关闭A/D转换,
return ADC_DATA; //返回A/D转换结果(8位)
}
void main(){
unsigned char m;
unsigned char code str[] = "Hello LCD 1602";
unsigned char code str1[] = "ABC";
LCD1602_Init();
printStr(0,0,str);
printStr(0,1,str1);
adcInit(0,ADC_SPEEDH,0x01,0x00);
while(1){
m = getADCResult ();
printChar(5,1,m/100+0x30);//1011 0101
printChar(6,1,m/10%10+0x30);//1011 0101
printChar(7,1,m%10+0x30);//1011 0101
}
}