????????10.STC15W408AS单片机A/D转换器
????????STC15系列单片机内部集成了8路10位高速A/D转换器。STC15系列单片机的A/D转换口在P1口(P1.7-P1.0),有8路10位高速A/D转换器,速度到300KHz(30万次/秒)。8路电压输入型A/D,可做温度检测、电池电压检测、按键扫描、频谱检测等。
?
1.A/D转换器的结构
STC15系列单片机ADC由多路选择开关、比较器、逐次比较寄存器、10位DAC、转换结果寄存器(ADC_RES和ADC_RESL)以及ADC_CONTR构成。
STC15系列单片机的ADC是逐次比较型ADC。逐次比较型ADC由一个比较器和D/A转换器构成,通过逐次比较逻辑,从最高位(MSB)开始,顺序地对每一输入电压与内置D/A转换器输出进行比较,经过多次比较,使转换所得的数字量逐次逼近输入模拟量对应值。逐次比较型A/D转换器具有速度高,功耗低等优点。
从上图可以看出,通过模拟多路开关,将通过ADC0~7的模拟量输入送给比较器。用数/模转换器(DAC)转换的模拟量与输入的模拟量通过比较器进行比较,将比较结果保存到逐次比较寄存器,并通过逐次比较寄存器输出转换结果。A/D转换结束后,最终的转换结果保存到ADC转换结果寄存器ADC_RES和ADC_RESL,同时,置位ADC控制寄存器ADC_CONTR中的A/D转换结束标志位ADC_FLAG,以供程序查询或发出中断申请。模拟通道的选择控制由ADC控制寄存器ADC_CONTR中的CHS2~CHS0确定。ADC的转换速度由ADC控制寄存器中的SPEED1和SPEED0确定。在使用ADC之前,应先给ADC上电,也就是置位ADC控制寄存器中的ADC_POWER位。
当CLK_DIV.5(PCON2.5)/ADRJ = 0时,A/D转换结果寄存器格式如下:
当ADRJ=0时,如果取10位结果,则按下面公式计算:
当ADRJ=0时,如果取8位结果,按下面公式计算:
当CLK_DIV.5(PCON2.5)/ADRJ = 1时,A/D转换结果寄存器格式如下:
当ADRJ=1时,如果取10位结果,则按下面公式计算:
式中,Vin为模拟输入通道输入电压,Vcc为单片机实际工作电压,用单片机工作电压作为
模拟参考电压。
2.与A/D转换相关的寄存器
与STC15系列单片机A/D转换相关的寄存器列于下表所示。
2.1 P1口模拟功能控制寄存器P1ASF
STC15系列单片机的A/D转换口在P1口(P1.7-P1.0),有8路10位高速A/D转换器速度可达到到300KHz(30万次/秒)。8路电压输入型A/D,可做温度检测、电池电压检测、按键扫描、频谱检测等。上电复位后P1口为弱上拉型I/O口,用户可以通过软件设置将8路中的任何一路设置为A/D转换,不需作为A/D使用的P1口可继续作为I/O口使用(建议只作为输入)。需作为A/D使用的口需先将P1ASF特殊功能寄存器中的相应位置为‘1’,将相应的口设置为模拟功能。 P1ASF存器的格式如下:
P1ASF : P1口模拟功能控制寄存器(该寄存器是只写寄存器,读无效)
2.2 ADC控制寄存器ADC_CONTR
ADC_CONTR寄存器的格式如下:
ADC_CONTR : ADC控制寄存器
对ADC_CONTR寄存器进行操作,建议直接用MOV赋值语句,不要用‘与’和‘或’语句。
ADC_POWER: ADC 电源控制位。
0:关闭ADC 电源;
1:打开A/D转换器电源.
建议进入空闲模式和掉电模式前,将ADC电源关闭,即ADC_POWER =0,可降低功耗。
启动A/D转换前一定要确认A/D电源已打开,A/D转换结束后关闭A/D电源可降低功耗,也可
不关闭。初次打开内部A/D转换模拟电源,需适当延时,等内部模拟电源稳定后,再启
动A/D转换。
建议启动A/D转换后,在A/D转换结束之前,不改变任何I/O口的状态,有利于高精度A/D转换,如能将定时器/串行口/中断系统关闭更好。
SPEED1,SPEED0:模数转换器转换速度控制位
ADC_FLAG: 模数转换器转换结束标志位,当A/D转换完成后,ADC_FLAG = 1,要由软件清0。
不管是A/D 转换完成后由该位申请产生中断,还是由软件查询该标志位A/D转换是
否结束,当A/D转换完成后,ADC_FLAG = 1,一定要软件清0。
ADC_START:模数转换器(ADC)转换启动控制位,设置为“1”时,开始转换,转换结束后为0。
CHS2/CHS1/CHS0:模拟输入通道选择,CHS2/CHS1/CHS0
2.3 ADC转换结果调整寄存器位——ADRJ
ADC转换结果调整寄存器位——ADRJ位于寄存器CLK_DIV/PCON中,用于控制ADC转换
结果存放的位置。
ADRJ:ADC转换结果调整
0:ADC_RES[7:0]存放高8位ADC结果,ADC_RESL[1:0]存放低2位ADC结果
1:ADC_RES[1:0]存放高2位ADC结果,ADC_RESL[7:0]存放低8位ADC结果
2.4 A/D转换结果寄存器ADC_RES、ADC_RESL
特殊功能寄存器ADC_RES和ADC_RESL寄存器用于保存A/D转换结果,其格式如下:
CKKO_DIV寄存器的ADRJ位是A/D转换结果寄存器(ADC_RES,ADC_RESL)的数据格式调整控制位。
当ADRJ=0时,10位是A/D转换结果的高8位存放在在ADC_RES中,低2位存放在ADC_RESL的低2位中。
2.5 中断允许寄存器IE
IE : 中断允许寄存器 (可位寻址)
EA : CPU的中断开放标志
EA=1,CPU开放中断,
EA=0,CPU屏蔽所有的中断申请。
EA的作用是使中断允许形成多级控制。即各中断源首先受EA控制;其次还受各中断源自己的
中断允许控制位控制。
EADC : A/D转换中断允许位
EADC=1,允许A/D转换中断,
EADC=0,禁止A/D转换中断。
2.6 A/D转换典型应用线路
3.测试程序
3.1 中断方式
#include "stc15.h"
#include "intrins.h"
#include "delay.h"
#define uchar unsigned char
#define uint unsigned int
#define FOSC 11059200L //系统频率
#define BAUD 9600 //串口波特率
void UatrInit();
void SendData(uchar dat);
void SendString(char *s);
void AdInit();
uchar num[10] = {'0','1','2','3','4','5','6','7','8','9'};
uint adc_result = 0;
void main()
{
P1M0 = 0x02;
P1M1 = 0x00;
UatrInit();
AdInit();
EA = 1; // CPU开放中断
while (1);
}
// 初始化串口
void UatrInit()
{
SCON = 0x50; //8位可变波特率 串口工作模式1
T2L = (65536 - (FOSC/4/BAUD)); //设置波特率重装值
T2H = (65536 - (FOSC/4/BAUD))>>8;
AUXR = 0x14; //T2为1T模式, 并启动定时器2
AUXR |= 0x01; //选择定时器2为串口1的波特率发生器
ES = 1; //使能串口1中断
}
// 初始化ADC
void AdInit()
{
P1ASF = 0x01; // P1.0作为模拟功能A/D使用
ADC_RES = 0;
ADC_RESL = 0; // 结果寄存器清零
ADC_CONTR = 0x88; // 打开ADC的电源 540个周期转换一次 选择P1.0作为A/D输入来用
delayus(20);
EADC = 1; // 允许A/D转换中断
}
// ADC中断服务函数
void adc_isr() interrupt 5
{
ADC_CONTR &= !0x10; // 清除ADC中断标志
adc_result = ADC_RES*4 + ADC_RESL; // 获取ADC结果,高2位在前
SendData(num[adc_result/1000]); // 千
SendData(num[adc_result%1000/100]); // 百
SendData(num[adc_result%100/10]); // 十
SendData(num[adc_result%10]); // 个
// SendData(ADC_RES);
// SendData(ADC_RESL);
SendString("\r\n"); // 换行
ADC_CONTR = 0x88; // 开始ADC 转换
delayms(2000);
}
// 发送串口数据
void SendData(uchar dat)
{
SBUF = dat;
while(TI == 0);
TI = 0;
}
// 发送字符串
void SendString(char *s)
{
while (*s) //检测字符串结束标志
{
SendData(*s++); //发送当前字符
}
}
// 串口中断
void Uart() interrupt 4
{
// 接收中断标志位
if (RI)
{
RI = 0; //清除RI位
// P0 = SBUF; //P0显示串口数据
SendString("HELLO\r\n");
}
// 发送中断标志位
if (TI)
{
TI = 0; //清除TI位
SendString("发送完成!\r\n");
}
}
3.2 查询方式
#include "stc15.h"
#include "intrins.h"
#include "delay.h"
#define uchar unsigned char
#define uint unsigned int
#define FOSC 11059200L //系统频率
#define BAUD 9600 //串口波特率
void UatrInit();
void SendData(uchar dat);
void SendString(char *s);
void AdInit();
uint GetADCResult();
uchar num[10] = {'0','1','2','3','4','5','6','7','8','9'};
uint adc_result = 0;
void main()
{
P1M0 = 0x02;
P1M1 = 0x00;
UatrInit();
AdInit();
EA = 1; // CPU开放中断
while (1)
{
adc_result = GetADCResult();
SendData(num[adc_result/1000]);
SendData(num[adc_result%1000/100]);
SendData(num[adc_result%100/10]);
SendData(num[adc_result%10]);
SendString("\r\n");
delayms(2000);
}
}
// 初始化串口
void UatrInit()
{
SCON = 0x50; //8位可变波特率 串口工作模式1
T2L = (65536 - (FOSC/4/BAUD)); //设置波特率重装值
T2H = (65536 - (FOSC/4/BAUD))>>8;
AUXR = 0x14; //T2为1T模式, 并启动定时器2
AUXR |= 0x01; //选择定时器2为串口1的波特率发生器
ES = 1; //使能串口1中断
}
// 初始化ADC
void AdInit()
{
P1ASF = 0x01; // P1.0作为模拟功能A/D使用
ADC_RES = 0;
ADC_RESL = 0; // 结果寄存器清零
ADC_CONTR = 0x88; // 打开ADC的电源 540个周期转换一次 选择P1.0作为A/D输入来用
delayus(20);
// EADC = 1; // 允许A/D转换中断
}
// 读取ADC结果
uint GetADCResult()
{
ADC_CONTR = 0x88;
_nop_(); //等待4个NOP
_nop_();
_nop_();
_nop_();
while (!(ADC_CONTR & 0x10));//等待ADC转换完成
ADC_CONTR &= ~0x10; //Close ADC
return ADC_RES*4 + ADC_RESL; //返回ADC结果
}
// 发送串口数据
void SendData(uchar dat)
{
SBUF = dat;
while(TI == 0);
TI = 0;
}
// 发送字符串
void SendString(char *s)
{
while (*s) //检测字符串结束标志
{
SendData(*s++); //发送当前字符
}
}
// 串门中断
void Uart() interrupt 4
{
// 接收中断标志位
if (RI)
{
RI = 0; //清除RI位
}
// 发送中断标志位
if (TI)
{
TI = 0; //清除TI位
}
}
四、利用新增的ADC第9通道测量内部参考电压的测试程序
ADC的第9通道是用来测试内部BandGap参考电压的,由于内部BandGap参考电压很稳定,不会随芯片的工作电压的改变而变化,所以可以通过测量内部BandGap参考电压,然后通过ADC的值便可反推出VCC的电压,从而用户可以实现自己的低压检测功能。ADC的第9通道的测量方法:首先将P1ASF初始化为0,即关闭所有P1口的模拟功能然后通过正常的ADC转换的方法读取第0通道的值,即可通过ADC的第9通道读取当前内部BandGap参考电压值。用户实现自己的低压检测功能的实现方法:首先用户需要在VCC很精准的情况下(比如5.0V),测量出内部BandGap参考电压的ADC转换值(比如为BGV5),并将这个值保存到EEPROM中,然后在低压检测的代码中,在实际VCC变化后,测量出的内部BandGap参考电压的ADC转换值(比如为BGVx),最后通过计算公式: 实际VCC = 5.0V * BGV5 / BGVx,即可计算出实际的VCC电压值 ,需要注意的是,第一步的BGV5的基准测量一定要精确。
测试程序:
#include "stc15.h"
#include "intrins.h"
#include "delay.h"
#define uchar unsigned char
#define uint unsigned int
#define FOSC 11059200L //系统频率
#define BAUD 9600 //串口波特率
void UatrInit();
void SendData(uchar dat);
void SendString(char *s);
void AdInit();
uint GetADCResult();
uchar num[] = {'0','1','2','3','4','5','6','7','8','9'};
uint adc_result = 0;
void main()
{
P1M0 = 0x02;
P1M1 = 0x00;
UatrInit();
AdInit();
EA = 1; // CPU开放中断
while (1)
{
adc_result = GetADCResult();
SendData(num[adc_result/1000]);
SendData(num[adc_result%1000/100]);
SendData(num[adc_result%100/10]);
SendData(num[adc_result%10]);
// 串口输出的是0259
// 实际内部电压 259÷1024*5 = 1.264V
// 这个假设电源电压是5V,和下载工具显示的内部电压1245mV很接近了
SendString("\r\n");
delayms(2000);
}
}
// 初始化串口
void UatrInit()
{
SCON = 0x50; //8位可变波特率 串口工作模式1
T2L = (65536 - (FOSC/4/BAUD)); //设置波特率重装值
T2H = (65536 - (FOSC/4/BAUD))>>8;
AUXR = 0x14; //T2为1T模式, 并启动定时器2
AUXR |= 0x01; //选择定时器2为串口1的波特率发生器
ES = 1; //使能串口1中断
}
// 初始化ADC
void AdInit()
{
P1ASF = 0x00; // P1不作为模拟功能A/D使用
ADC_RES = 0;
ADC_RESL = 0; // 结果寄存器清零
ADC_CONTR = 0x88; // 打开ADC的电源 540个周期转换一次 选择P1.0作为A/D输入来用
delayus(20);
// EADC = 1; // 允许A/D转换中断
}
// 读取ADC结果
uint GetADCResult()
{
ADC_CONTR = 0x88;
_nop_(); //等待4个NOP
_nop_();
_nop_();
_nop_();
while (!(ADC_CONTR & 0x10)); //等待ADC转换完成
ADC_CONTR &= ~0x10; //Close ADC
return ADC_RES*4 + ADC_RESL; //返回ADC结果
}
// 发送串口数据
void SendData(uchar dat)
{
SBUF = dat;
while(TI == 0);
TI = 0;
}
// 发送字符串
void SendString(char *s)
{
while (*s) //检测字符串结束标志
{
SendData(*s++); //发送当前字符
}
}
// 串口中断服务函数
void Uart() interrupt 4
{
// 接收中断标志位
if (RI)
{
RI = 0; //清除RI位
// P0 = SBUF; //P0显示串口数据
SendString("HELLO\r\n");
}
// 发送中断标志位
if (TI)
{
TI = 0; //清除TI位
// SendString("发送完成!\r\n");
}
}
五、利用BandGap推算出电源电压
在上面的例子中,我们通过A/D转换的第九通道得到了电源电压的AD值。由于内部BandGap参考电压很稳定,不会随芯片的工作电压的改变而变化,所以可以通过两次测量和一次计算便可得到外部的精确电压。
计算公式:
电源电压 = BandGap(电压mV)÷BandGap(AD转换值)×1024
获取内部BandGap电压的程序
#include "stc15.h"
#include "intrins.h"
#include "delay.h"
#define uchar unsigned char
#define uint unsigned int
#define FOSC 11059200L //系统频率
#define BAUD 9600 //串口波特率
#define ID_ADDR_RAM 0xef //对于只有256字节RAM的MCU存放地址为0EFH
//注意:需要在下载代码时选择"在ID号前添加重要测试参数"选项,才可在程序中获取此参数
#define ID_ADDR_ROM 0x1ff7 //8K程序空间的MCU
void UatrInit();
void SendData(uchar dat);
void SendString(char *s);
void AdInit();
uint GetADCResult();
uchar num[] = {'0','1','2','3','4','5','6','7','8','9'};
uint adc_result = 0;
uint BandGap = 0;
uint Vcc = 0;
void main()
{
uchar idata *iptr;
uchar code *cptr;
UatrInit();
AdInit();
EA = 1; // CPU开放中断
while (1)
{
iptr = ID_ADDR_RAM; //从RAM区读取BandGap电压值(单位:毫伏mV) 实际结果和STC-ISP软件读取的一样 1245mV
BandGap = *iptr++ * 256 + *iptr++; // 1245mV
adc_result = GetADCResult(); // 结果为 259
Vcc = (double)BandGap/adc_result * 1024; // 得到电源电压 4.92V 实际使用万用表测试的也是这个数值
SendData(num[Vcc/1000]);
SendData(num[Vcc%1000/100]);
SendData(num[Vcc%100/10]);
SendData(num[Vcc%10]);
SendString("\r\n"); delayms(2000);
}
}
// 初始化串口
void UatrInit()
{
SCON = 0x50; //8位可变波特率 串口工作模式1
T2L = (65536 - (FOSC/4/BAUD)); //设置波特率重装值
T2H = (65536 - (FOSC/4/BAUD))>>8;
AUXR = 0x14; //T2为1T模式, 并启动定时器2
AUXR |= 0x01; //选择定时器2为串口1的波特率发生器
ES = 1; //使能串口1中断
}
// 初始化ADC
void AdInit()
{
P1ASF = 0x00; // P1不作为模拟功能A/D使用
ADC_RES = 0;
ADC_RESL = 0; // 结果寄存器清零
ADC_CONTR = 0x88; // 打开ADC的电源 540个周期转换一次 选择P1.0作为A/D输入来用
delayus(20);
// EADC = 1; // 允许A/D转换中断
}
// 读取ADC结果 获取内部BandGap电压的AD转换值
uint GetADCResult()
{
ADC_CONTR = 0x88;
_nop_(); //等待4个NOP
_nop_();
_nop_();
_nop_();
while (!(ADC_CONTR & 0x10)); //等待ADC转换完成
ADC_CONTR &= ~0x10; //Close ADC
return ADC_RES*4 + ADC_RESL; //返回ADC结果
}
// 发送串口数据
void SendData(uchar dat)
{
SBUF = dat;
while(TI == 0);
TI = 0;
}
// 发送字符串
void SendString(char *s)
{
while (*s) //检测字符串结束标志
{
SendData(*s++); //发送当前字符
}
}
// 串口服务函数
void Uart() interrupt 4
{
// 接收中断标志位
if (RI)
{
RI = 0; //清除RI位
}
// 发送中断标志位
if (TI)
{
TI = 0; //清除TI位
}
}
|