先把代码献上,然后我们再来分析
/******************************************************************************** 文件名: ADC转换器使用 * 描 述: 电池电压 * 功 能:中断方式 * 作 者:大核桃 * 版本号:1.0.1(2017.05.23) *******************************************************************************/ #include "stc15w.h"//头文件 #include "intrins.h" /******************************************************************************* * 文件名: 重定义 * 描 述: * 功 能: * 作 者:大核桃 * 版本号:1.0.1(2017.05.23) *******************************************************************************/ typedef unsigned char uint8; typedef unsigned int uint16; typedef unsigned long uint32; #define ADC_POWER 0x80 //ADC电源控制位 #define ADC_FLAG 0x10 //ADC完成标志 #define ADC_START 0x08 //ADC起始控制位 #define ADC_SPEEDLL 0x00 //540个时钟 //#define ADC_SPEEDL 0x20 //360个时钟 //#define ADC_SPEEDH 0x40 //180个时钟 //#define ADC_SPEEDHH 0x60 //90个时钟 /******************************************************************************* * 文件名:全局变量定义区域 * 描 述: * 功 能: * 作 者:大核桃 * 版本号:1.0.1(2015.03.03) *******************************************************************************/ uint8 val,ch; uint16 temp; bit flag_ad2 = 0;//电压采集完成标志 bit flag_coll1 = 0;//数据采集间隔 uint16 Adresult_val = 0;//采集的AD数值xx uint8 ad_count = 0; //采集AD的次数计数器 /******************************************************************************* * 文件名:共阳数码管真值表 * 描 述: * 功 能: * 作 者:大核桃 * 版本号:1.0.1(2015.03.03) *******************************************************************************/ code uint8 LedChar[] = { 0xc0,0xf9,0xa4,0xb0,0x99,0x92,0x82,0xf8, 0x80,0x90,0x88,0x83,0xc6,0xa1,0x86,0x8e,0xff,0xc1 }; /******************************************************************************* * 文件名:单独位定义 * 描 述: * 功 能: * 作 者:大核桃 * 版本号:1.0.1(2017.05.23) *******************************************************************************/ sbit LED0 = P1^0;//第1组LED sbit LED1 = P1^1;//第2组LED sbit LED2 = P1^2;//第3组LED sbit LED3 = P1^3;//第4组LED sbit LED4 = P1^4;//第5组LED sbit LED5 = P3^2;//第6组LED sbit LED6 = P0^0;//第7组LED sbit LED7 = P0^1;//第8组LED sbit LEDS1 = P3^3;//数码管1 sbit LEDS2 = P3^4;//数码管2 sbit LEDS3 = P3^6;//数码管3 sbit LEDS4 = P3^7;//数码管4 /******************************************************************************* * 文件名:函数前置声明 * 描 述: * 功 能: * 作 者:大核桃 * 版本号:1.0.1(2017.05.23) *******************************************************************************/ void Mcu_Port_Init(); void LedScan(); void Time0_Init();//定时器0 void InitADC(void); uint16 VolTage_Monitor(uint8 times); //AD转换与查表处理程序 /******************************************************************************* * 文件名 * 描 述: 主函数 * 功 能:入口 * 作 者:大核桃 * 版本号:1.0.1(2017.05.23) *******************************************************************************/ void main(void) { Mcu_Port_Init();//IO上电初始化 Time0_Init(); InitADC(); while(1) { VolTage_Monitor(16);//采集16次数据 } } /******************************************************************************* * 文件名:void LedScan() * 描 述: LED刷新 * 功 能: * 作 者:大核桃 * 版本号:1.0.1(2017.05.23) *******************************************************************************/ void LedScan() { static uint8 i = 0; P2 = 0Xff; switch(i) { case 0: LEDS4 = 0;LEDS1 = 1;P2 = 0x7f & LedChar[16];i++;break; case 1: LEDS1 = 0;LEDS2 = 1;P2 = LedChar[val / 10 % 10];i++;break; case 2: LEDS2 = 0;LEDS3 = 1;P2 = LedChar[val % 10];i++;break; case 3: LEDS3 = 0;LEDS4 = 1;P2 = LedChar[17];i = 0;break; default:break; } } /******************************************************************************* * 文件名:void InitADC(void) * 描 述: //初始化 AD 转换 * 功 能: * 作 者:大核桃 * 版本号:1.0.1(2015.03.03) *******************************************************************************/ void InitADC(void) { P1ASF = 0xE0; //设置 P1 口为模拟口 ADC_RES = 0; //清除结果寄存器 CLK_DIV |= 0x20; //ADRJ 为 1,ADC_RES 存放高两位结果,ADC_RESL 存放低 8 位结果 //ADC_CONTR = ADC_POWER | ADC_SPEEDLL | ADC_START; ADC_CONTR = ADC_POWER | ADC_SPEEDLL | ADC_START; //ADC上电并延时 } /******************************************************************************* * 文件名:void adc_isr() interrupt 5 using 1 * 描 述: 中断服务程序 * 功 能: * 作 者:大核桃 * 版本号:1.0.1(2015.03.03) *******************************************************************************/ void adc_isr() interrupt 5 using 1 { EADC = 1;//开ADC中断 ADC_CONTR &= !ADC_FLAG; //清除ADC中断标志 temp = ADC_RES; temp <<= 8; temp |= ADC_RESL; flag_ad2 = 1; //电压采集完成标志 ADC_CONTR = ADC_POWER | ADC_SPEEDLL | ADC_START | 5; EADC = 0;//关闭中断 } /******************************************************************************* * 文件名:VolTage_Monitor(void); * 描 述: 电压结果计算 * 功 能:模编程块化 * 作 者:大核桃 * 版本号:1.0.1(2015.03.03) *******************************************************************************/ uint16 VolTage_Monitor(uint8 times) //AD转换与查表处理程序 { if(flag_coll1) //每次采集AD的时间间隔标志位 { if(ad_count < times)//连续采集16次后再把求总数据的平均值 { if(flag_ad2 == 1) //完成一次AD采样 { flag_ad2 = 0; //清除完成一次采样的标志位 Adresult_val = Adresult_val + temp; ad_count++; ADC_CONTR = ADC_POWER | ADC_SPEEDLL | ADC_START | 5; } } //右移动一位数据就相当于整除以2 else //已经采集完16次数据,这个时候把总累加数据除以16就可以求得平均值了 { Adresult_val >>= 4; val = ((Adresult_val) * 2 * (3.3 / 1023) * 10);//放大10 Adresult_val = 0; //AD暂存清零 temp = 0; //把采集AD的结果清零 ad_count = 0; //把采集次数重新清零 } } return val;//返回采集的电压数值 } /******************************************************************************* * 文件名:void Time0_Init() * 描 述: 定时器0初始化 * 功 能:1毫秒@11.0592MHz * 作 者:大核桃 * 版本号:1.0.1(2017.05.23) *******************************************************************************/ void Time0_Init(void) { AUXR |= 0x80; //定时器时钟1T模式 TMOD &= 0xF0; //设置定时器模式 TL0 = 0xCD; //设置定时初值 TH0 = 0xD4; //设置定时初值 ET0 = 1; TR0 = 1; //定时器0开始计时 EA = 1; } /******************************************************************************* * 文件名: * 描 述: 中断函数 * 功 能:1毫秒@11.0592MHz * 作 者:大核桃 * 版本号:1.0.1(2017.05.23) *******************************************************************************/ void ET0_IRQHandler() interrupt 1 { static uint8 tmrcoll1 = 0;//数据采集间隔 EADC = 0; //在定时中断中禁止AD中断 TL0 = 0xCD; //设置定时初值 TH0 = 0xD4; //设置定时初值 tmrcoll1++;//数据采集时间累加 if(tmrcoll1 >= 2) // 2 = 2ms //1 { tmrcoll1 = 0; flag_coll1 = 1;//数据采集间隔标志位 2ms读取一次数据 } LedScan(); EADC = 1; //在定时中断中打开AD中断 } /******************************************************************************* * 文件名:void Mcu_Port_Init() * 描 述: io初始化 * 功 能: * 作 者:大核桃 * 版本号:1.0.1(2017.05.23) *******************************************************************************/ void Mcu_Port_Init() { IE = 0xa8;//允许AD转换 //将P0口低二位配置为推挽输出 //234567位配置位高阻输入 P0M1 = 0xFC;//1111 1100 P0M0 = 0X03;//0000 0011 //P0 = 0X01;//第6个 //P0 = 0X02;//第7个 //高3位配置高阻输入,用作模拟口 //其他配置推挽输出,驱动LED P1M1 = 0xE0;//1110 0000 P1M0 = 0X1F;//0001 1111 //P2口配置准双向口 P2M1 = 0X00; P2M0 = 0X00; P2 = 0Xff; //上电为1111 1111 // //P54,P55口为推挽输出 P5M1 = 0X00; P5M0 = 0X00; P5 = 0xFF; //P37,P36,3.2,P3.3 P3.4口为推挽输出 P3M1 = 0X00; P3M0 = 0XFC; P3 = 0X23; //0010 0111//第5个LED端口 LED0 = 0;//第1组LED,如果使能请置为1 LED1 = 0; LED2 = 0; LED3 = 0; LED4 = 0; LED5 = 0; LED6 = 0; LED7 = 0; }
复制代码先来介绍一些基本的理论知识,不然的话,可能有些东西无法搞懂。
关于ADC的参考电压
因为我们的电子时钟是锂电池供电的,电压是3.7V的,我们这里用了一个3.3V的稳压芯片662K,输出3.3V直接作为单片机的电源,也作为ADC的参考电压,这里我们简化了设计,没有用外部的参考电压源,对于一个简单的来说,这样也是可以的。
关于ADC的位数和分辨率
在这里,我们选择ADC工作在10位方式,10位的ADC,是从0-1023,那么分辨率也就是3.3/1023 = 0.0032258064516129V,大概一个分辨率3mv左右。
关于转换时间和转换速率
转换时间和转换速率是倒数的关系,所谓的转换时间,指的是ADC从开始启动,到ADC转换完成出结果,这个时间该怎么去计算呢?我们在程序中选择了时钟频率是11.0592MHZ,那么我们ADC的时钟频率也就是11.0592MHZ了,在程序中,我们选择了540个时钟周期完成一个ADC转换,转换速率也就是20KHZ左右,转换时间大约是48US左右
关于采样频率和采样周期
采样频率和采样周期也是互为倒数的关系,这个和上面的转换时间,转换速率非常容易让人搞迷糊,关于采样频率,有一个采样定理,叫奈奎斯特采样定律,这个定律说的是,采样频率不能低于输入ADC的信号的最高频率的2倍,举个例子,比如上面这样的情况,我们选择540个时钟周期完成一次ADC转换,那么转换速率是20KHZ,那么也就是说,如果我们要保证信号采集的是完整的波形,那么这个输入的信号不能超过10KHZ,你想想看,如果输入的信号大于10KHZ,而你转换速率是20KHZ,如果采样频率小于20KHZ,那么可能你还没有完成一个完整的ADC转换过程,或者采集的波形不是完整的,那么这样的ADC的结果跟实际值比较会存在严重的失真,这样是不被允许的。
好了,经过以上知识的铺垫,再来看程序代码就应该比较容易懂了,在程序中,我们选择了在ADC进中断前打开EADC,处理完数据后,要关闭EADC这个ADC转换中断使能标志位,防止其他中断或者任务打断ADC的采集。我们在任务中选择的采样频率是500HZ,也就是2MS启动ADC采集依次数据,连续采集16次,因为我们所采集的电压信号的变化频率没有那么快速,所以,我们这里是可以这样用的,当启动了一次AD转换之后,进行计数,如果小于16次,那么一直在IF里面执行,当系统检测到完成一个AD转换,将AD转换完成标志位清零,将读取的相关通道的ADC数值累加,继续采集,如果采集完成了,那么进行取平均运算,然后将结果计算出来,赋值给相关的变量就可以了,然后对相关的变量或者缓冲区清零,这样,显示在数码管上的电压结果是比较稳定的。和前面那个例子不一样,我们这个例子用的是STC15W单片机定时器0的1T模式,也就是说比原来快了12倍,这个移植的时候一定要注意。