简易数字电压表+ADC0832+串行SPI方式实现1路数据转换
1 实物与模型
(1)为什么DO和DI引脚连在一个引脚上?
由于ADC0832在通信时并不是会同时使用DO和DI端口,并且DO和DI端口与单片机的接口是双向的,所以在设计电路中可以用一根线将DO端和DI端连接到一起。
(2)ADC0832的外部连接采用SPI总线结构,这样便把它的连接方式与其他设备统一起来了。ADC0832采用同步串行传输,同步是通过时钟线进行数据同步;串行传输通过DO 数据口一位一位传输数据。
2 实验原理
3 系统设计
传统的51单片机没有配置SPI,但是可以利用其并行接口线模拟SPI串行总线时序,以实现与SPI的器件连接。
4 软件设计
1 ADC0832模数转换函数(结合ADC时序图)
#ifndef __ADC0832_H__ #define __ADC0832_H__ #include <reg52.h> #include <intrins.h> sbit ADC0832_CS_N = P1^0; sbit ADC0832_CLK = P1^1; sbit ADC0832_DI = P1^2; sbit ADC0832_DO = P1^2; void ADC0832_Init(void); unsigned char ADC0832_Conv(void); #endif
#include "ADC0832.h" //ADC0832初始化 void ADC0832_Init(void) { ADC0832_CS_N = 1; ADC0832_CLK = 0; ADC0832_DI = 1; } unsigned char ADC0832_Conv(void) { unsigned char adc_result1 = 0; //用来接收第一组数据 unsigned char adc_result2 = 0; //用来接收第二组数据 unsigned char i; //时序1,先将CS使能端置于低电平并且保持低电平直到转换完全结束 ADC0832_CS_N = 0; ADC0832_CLK = 0; //时序2,由MCU向ADC0832时钟输入端CLK输入时钟脉冲,DO/DI端使用DI端输入通道功能选择的数据信号 //在第1个时钟脉冲的下降沿之前DI端必须是高电平,表示启动信号 ADC0832_DI = 1; //START BIT,启动信号 _nop_(); ADC0832_CLK = 1; //第一个脉冲 _nop_(); ADC0832_CLK = 0; //在第2、3个脉冲下降沿之前DI应输入2位数据用于选择通道功能 ADC0832_DI = 1; //单通道输入 _nop_(); ADC0832_CLK = 1; //第二个脉冲 _nop_(); ADC0832_CLK = 0; ADC0832_DI = 0; //选择CH0作为模拟信号输入端 _nop_(); ADC0832_CLK = 1; //第三个脉冲 _nop_(); ADC0832_CLK = 0; /*时序3,从第4个脉冲开始由DO输出转换数据最高位, 随后每个脉冲下降沿DO输出下一位数据, 直到第11个脉冲时发出最低位数据,一个字节的数据输出完成*/ ADC0832_DI = 1; //数据线拉高,主机准备读数据,高位在前 for(i=0;i<8;i++) { ADC0832_CLK = 1; _nop_(); ADC0832_CLK = 0; //CLK下降沿 adc_result1 = adc_result1 << 1; //左移,0000_0001 if(ADC0832_DO==1) adc_result1 = adc_result1 | 0x01; } /*时序4,随后输出8个位数,与前面数据顺序相反, 同时第11个脉冲的下降沿输出DATA0,到第19个脉冲时输出完成DATA7*/ for(i=0;i<8;i++) { adc_result2 = adc_result2 >> 1; //右移,1000_000 if(ADC0832_DO==1) adc_result2 = adc_result2 | 0x80; ADC0832_CLK = 1; _nop_(); ADC0832_CLK = 0; } //时序5 一次AD转换结束 ADC0832_CS_N = 1; ADC0832_CLK = 1; ADC0832_DI = 0; return (adc_result1 == adc_result2)? adc_result1:0; }
2 数码管动态显示函数
#ifndef __DisplaySmg_H__ #define __DisplaySmg_H__ #include <REG52.H> #define GPIO_SEG P0 //段选端 #define GPIO_SEL P2 //位选端 extern unsigned char LedBuf[]; //外部变量声明 extern unsigned char DotDig0,DotDig1,DotDig2,DotDig3; void DisplaySmg(void); #endif
#include "DisplaySmg.h" unsigned char code LedData[]={ //共阴型数码管的段码表,字符,序号 0x3F, //"0",0 0x06, //"1",1 0x5B, //"2",2 0x4F, //"3",3 0x66, //"4",4 0x6D, //"5",5 0x7D, //"6",6 0x07, //"7",7 0x7F, //"8",8 0x6F, //"9",9 0x77, //"A",10 0x7C, //"B",11 0x39, //"C",12 0x5E, //"D",13 0x79, //"E",14 0x71, //"F",15 0x76, //"H",16 0x38, //"L",17 0x37, //"n",18 0x3E, //"u",19 0x73, //"P",20 0x5C, //"o",21 0x40, //"-",22 0x00, //熄灭 23 }; unsigned char DotDig0=0,DotDig1=0,DotDig2=0,DotDig3=0; //小数点控制位 unsigned char code LedAddr[]={0xfe,0xfd,0xfb,0xf7}; //数码管位选 unsigned char LedBuf[]={22,22,22,22}; //显示缓存区 void DisplaySmg() //四位数码管,考虑小数点 { unsigned char i; //等价于 "static unsigned char i = 0;" unsigned char temp; switch(i) { case 0: { GPIO_SEG = 0x00; //消影 if(DotDig0==1) //小数点 { temp = LedData[LedBuf[0]] | 0x80; //点亮小数点 } else { temp = LedData[LedBuf[0]]; } GPIO_SEG = temp; //段码 GPIO_SEL = LedAddr[0]; //位选 i++; break; } case 1: GPIO_SEG = 0x00; if(DotDig1==1) //小数点 { temp = LedData[LedBuf[1]] | 0x80; } else { temp = LedData[LedBuf[1]]; } GPIO_SEG = temp; GPIO_SEL = LedAddr[1]; i++; break; case 2: GPIO_SEG = 0x00; if(DotDig2==1) //小数点 { temp = LedData[LedBuf[2]] | 0x80; } else { temp = LedData[LedBuf[2]]; } GPIO_SEG = temp; GPIO_SEL = LedAddr[2]; i++; break; case 3: GPIO_SEG = 0x00; if(DotDig3==1) //小数点 { temp = LedData[LedBuf[3]] | 0x80; } else { temp = LedData[LedBuf[3]]; } GPIO_SEG = temp; GPIO_SEL = LedAddr[3]; i=0; break; default:break; } }
3 定时器T0
#ifndef __Timer0_H__ #define __Timer0_H__ #include <reg52.h> void Timer0_Init(void); #endif
#include "Timer0.h" void Timer0_Init(void) //1毫秒@11.0592MHz { TMOD &= 0xF0; //设置定时器模式 TMOD |= 0x01; //设置定时器模式 TL0 = 0x66; //设置定时初始值 TH0 = 0xFC; //设置定时初始值 TF0 = 0; //清除TF0标志 TR0 = 1; //定时器0开始计时 ET0 = 1; //定时器0中断开关 // EA = 1; //中断总开关 } //中断服务函数一定是一个没有返回值的函数 //中断服务函数一定是没有参数的函数 //中断服务函数函数名后跟着关键字interrupt //interrupt n 0~4 5个中断源,8*n+0003H // 0003H INT0, 00BH T0, 0013H INT1, 001BH T1, 0023H ES //中断服务函数不能被主程序或者其他程序所调用 //n后面跟着using m(0~3)工作寄存器组 //void Timer0_ISR(void) interrupt 1 //{ // TL0 = 0x66; //设置定时初始值 // TH0 = 0xFC; //设置定时初始值 //}
4 主函数(对采集的数据进行均值滤波)
#include <REG52.H> #include "DisplaySmg.h" #include "ADC0832.h" #include "Timer0.h" unsigned char adc_result = 0; int adc_result_show = 0; void disp_num(void) //显示四位十进制数 { LedBuf[0]= 23; //千位,不显示 LedBuf[1]= adc_result_show/100; //百位 LedBuf[2]= adc_result_show/10%10;//十位 LedBuf[3]= adc_result_show%10; //个位 } void main() { int adc_result_reg; int adc_result_fliter; //采用均值滤波 unsigned char adc_cnt; //采样次数 Timer0_Init(); //定时/计数器T0初始化 ADC0832_Init(); EA=1; //中断总开关 DotDig1=1; //点亮第二个数码管的小数点 while(1) { adc_result = ADC0832_Conv(); //采集数据,ADC转换后结果 adc_result_reg = adc_result*1.0*100*5/255; //数据变换处理(线性标度变换) adc_result_fliter = adc_result_fliter + adc_result_reg; //数据累加 adc_cnt++; if(adc_cnt > 7) { adc_result_show = adc_result_fliter >> 3; //等价于除于8,取平均值 adc_cnt = 0; adc_result_fliter = 0; } disp_num(); //显示数据 } } void Timer0_ISR(void) interrupt 1 { TR0=0; //关闭定时器 DisplaySmg(); //每过1ms,刷新一次数码管显示函数 TL0 = 0x66; //设置定时初始值,定时1ms TH0 = 0xFC; //设置定时初始值,定时1ms TR0=1; //打开定时器 }
5 参考来源
(1)(141条消息) 【mcuclub】模数转换ADC0832_单片机俱乐部--官方的博客-CSDN博客_adc0832模数转换原理;