1、在proteus软件上绘制原理图
- 本实验采集电压芯片是ADC0808(代码中标注的是ADC0809),ADC0808 是含8 位A/D 转换器、8 路多路开关,以及与微型计算机兼容的控制逻辑的CMOS组件,其转换方法为逐次逼近型。
- 采用3*3的矩阵按键对显示通道进行选择。
- 通过电阻分压的到随机的电压输入源,同时在输入源处接了电压表来检验我们测得的电压是否正确。
- 采用lcd1206对采集到的电压进行显示。
2、在keil5中编写程序
#include <REGX51.H>
//命名ADC0809信号引脚
#define ADC0809_START_ALE P3_1
#define ADC0809_CLOCK P3_2
#define ADC0809_EOC P3_3
#define ADC0809_OE P3_4
#define ADC0809_ADDA P3_5
#define ADC0809_ADDB P3_6
#define ADC0809_ADDC P3_7
#define ADC0809_DATA P2
//命名LCD引脚
#define RS P1_6 //1602液晶的RS脚接在P1.6口上
#define RW P1_7 //1602液晶的RW脚接在P1.7口上
#define E P3_0 //1602液晶的E脚接在P3.0口上
//矩阵按键变量定义
enum KEY{water,ch1,ch2,ch3,ch4,ch5,ch6,ch7,ch8};
unsigned char keybuff;
bit key_flag = 0;
bit water_flag = 0;//轮询模式标志位
unsigned char key_value;//
unsigned int count_1s = 0;//1s计数
//延时函数定义
void delay1ms(unsigned int n) //误差 0us ,延时n毫秒
{
unsigned int a,b,c;
for(c=n;c>0;c--)
for(b=199;b>0;b--)
for(a=1;a>0;a--);
}
/LCD相关函数///
//---------------------------------------
//名称:等待1602之前的写操作已经完成
//---------------------------------------
void lcd_wait(void)
{
RS=0; //选择数据状态寄存器
RW=1; //选择读入状态
P0=0xff; //P0口输出高电平,准备输入
while (1) {
E=1; //E高电平使能1602读
if(!P0_7) break; //写操作已经完成
E=0; //读完以后,恢复E的电平
}
E=0; //读完以后,恢复E的电平
}
//---------------------------------------
//名称:1602写命令函数
//---------------------------------------
void lcd_cmd(unsigned char cmd)
{
lcd_wait(); //等待1602空闲
RS=0; //选择指令寄存器
RW=0; //选择写状态
P0=cmd; //将命令字通过P0口送至DB
E=1; //E高电平将命令字写入1602
E=0; //写完以后,恢复E的电平
}
//---------------------------------------
//名称:1602写数据函数
//---------------------------------------
void lcd_data(unsigned char buf)
{
lcd_wait(); //等待1602空闲
RS=1; //选择数据寄存器
RW=0; //选择写状态
P0=buf; //将数据通过P0口送至DB
E=1; //E高电平将命令字写入1602液晶
E=0; //写完以后,恢复E的电平
}
//---------------------------------------
//名称:1602液晶初始化函数
//---------------------------------------
void lcd_init(void)
{
lcd_cmd(0x38); //8位数据总线,两行显示模式,5*7点阵显示
lcd_cmd(0x0C); //显示功能开,无光标
lcd_cmd(0x01); //清屏
lcd_cmd(0x06); //写入新的数据后,光标右移,显示屏不移动
}
void lcd_string(unsigned char x, unsigned char y, char *str)
{
int i = 0;
lcd_cmd(0x80 + x + y*0x40); // 设置起始位置(行和列)
for (i = 0; str[i] != '\0'; i++) {
lcd_data(str[i]); // 逐个字符写入数据
}
}
const unsigned char code HEX[]="0123456789ABCDEF"; //16进制字符表,用于数字到显示字符转换
矩阵按键检测函数///
void key_input(void)
{
unsigned char temp1,temp2; //临时保存读取的行或者列
key_flag = 0;
P1 &= 0xC0;//将P1口的低6位置0
P1 |= 0x07;//将P1口的低3位置1,读取按键的行数
temp1 = P1<<2; //忽略高2位,先读取行
if(0x1C != temp1) //按键按下
{
delay1ms(5); //延时,消除抖动
temp1 = P1<<2; //再次读取行
if(0x1C != temp1) //说明按键确实按下
{
key_flag = 1;
temp1 = temp1 & 0x1C; //保留P1低3位
P1 &= 0xC0;//将P1口的低6位置0
P1 |= 0x38;//将P1口的3,4,5位置1,读取按键的列数
delay1ms(1); //延时一下,防止下一句读不到
temp2 = P1<<2; //直接读取列
temp2 = temp2 & 0xE0; //保留temp2的高3位
temp1 = temp1 | temp2; //将temp1和temp2的值合并
switch(temp1)
{
case 0x78:keybuff=water;break;
case 0xB8:keybuff=ch1;break;
case 0xD8:keybuff=ch2;break;
case 0x74:keybuff=ch3;break;
case 0xB4:keybuff=ch4;break;
case 0xD4:keybuff=ch5;break;
case 0x6C:keybuff=ch6;break;
case 0xAC:keybuff=ch7;break;
case 0xCC:keybuff=ch8;break;
default:break;
}
}
}
}
主函数/
void main(void)
{
unsigned char i;//轮询变量
unsigned int ADCValue ;
ADC0809_DATA = 0xFF;
/lcd初始化及固定显示
lcd_init();
lcd_string(5,0,"tongdao");
/定时器0配置
TMOD = 0x01;
TH0 = (65536 - 1000)/256;
TL0 = (65536 - 1000)/256;
TR0 = 1;//开启定时器0
ET0 = 1;//允许定时器0产生中断
EA = 1;//打开总中断
//ADC0809_START_ALE产生一个正脉冲
ADC0809_EOC = 1;
ADC0809_START_ALE = 0;
ADC0809_START_ALE = 1;//锁存地址,同时复位0808,启动ad转换
ADC0809_START_ALE = 0;
ADC0809_OE = 0;//不允许输出数据
while(1)
{
//实时读取指定通道的电压值
if(1 == ADC0809_EOC)//AD转换结束
{
P3 &= 0x1F;//将ADC0809寻址位置0
switch(key_value)
{
case water :P3 |= i<<5;if(water_flag) {i=(i<7)?i+1:0;water_flag = 0;} break;
case ch1 :break;
case ch2 :P3 |= 0x20;break;
case ch3 :P3 |= 0x40;break;
case ch4 :P3 |= 0x60;break;
case ch5 :P3 |= 0x80;break;
case ch6 :P3 |= 0xA0;break;
case ch7 :P3 |= 0xC0;break;
case ch8 :P3 |= 0xE0;break;
default :break;
}
ADC0809_START_ALE = 1;//正脉冲,锁存地址,同时复位0808,启动ad转换
ADC0809_START_ALE = 0;
ADC0809_OE = 1;//允许数据输出
ADCValue = ADC0809_DATA*1.95;//读取数据端口的值
ADC0809_OE = 0;//关闭数据输出允许
//LCD显示/
lcd_cmd(0x84); //设置光标位置为第1行,5列
if(key_value == water)
{
lcd_data(HEX[i+1]); //显示通道名
}
else
{
lcd_data(HEX[key_value]); //显示通道名
}
lcd_cmd(0xC5); //设置光标位置为第2行,6列
lcd_data(HEX[ADCValue/100]); //显示个位
lcd_data('.'); //显示小数点
lcd_data(HEX[(ADCValue/10)%10]); //显示小数点后1位
lcd_data(HEX[ADCValue%10]); //显示小数点后1位
lcd_data('V'); //显示单位
}
//实时扫描矩阵按键值
key_input();
if(key_flag)
{
key_value = keybuff;
i = 0;
key_flag = 0;
if(key_value == water)//轮询模式
{
count_1s = 0;//刚进入轮询模式,将1s计数器清零
}
}
else
{
key_value = key_value;
}
}
}
void T0_ISR(void) interrupt 1
{
TH0 = (65536 - 1000)/256;//1ms
TL0 = (65536 - 1000)/256;
ADC0809_CLOCK = ~ADC0809_CLOCK;
//定时1s
if(key_value == water)
if(count_1s==1000)
{
count_1s = 0;
water_flag = 1;
}
else
{
count_1s = count_1s + 1 ;
}
}
-
首先对各引脚进行重定义,方便赋值,且在更改硬件连接时可直接在重命名处进行更改,无需对其他代码内容进行更改。//命名ADC0809信号引脚 #define ADC0809_START_ALE P3_1 #define ADC0809_CLOCK P3_2 #define ADC0809_EOC P3_3 #define ADC0809_OE P3_4 #define ADC0809_ADDA P3_5 #define ADC0809_ADDB P3_6 #define ADC0809_ADDC P3_7 #define ADC0809_DATA P2 //命名LCD引脚 #define RS P1_6 //1602液晶的RS脚接在P1.6口上 #define RW P1_7 //1602液晶的RW脚接在P1.7口上 #define E P3_0 //1602液晶的E脚接在P3.0口上
-
/LCD相关函数/// //--------------------------------------- //名称:等待1602之前的写操作已经完成 //--------------------------------------- void lcd_wait(void) { RS=0; //选择数据状态寄存器 RW=1; //选择读入状态 P0=0xff; //P0口输出高电平,准备输入 while (1) { E=1; //E高电平使能1602读 if(!P0_7) break; //写操作已经完成 E=0; //读完以后,恢复E的电平 } E=0; //读完以后,恢复E的电平 } //--------------------------------------- //名称:1602写命令函数 //--------------------------------------- void lcd_cmd(unsigned char cmd) { lcd_wait(); //等待1602空闲 RS=0; //选择指令寄存器 RW=0; //选择写状态 P0=cmd; //将命令字通过P0口送至DB E=1; //E高电平将命令字写入1602 E=0; //写完以后,恢复E的电平 } //--------------------------------------- //名称:1602写数据函数 //--------------------------------------- void lcd_data(unsigned char buf) { lcd_wait(); //等待1602空闲 RS=1; //选择数据寄存器 RW=0; //选择写状态 P0=buf; //将数据通过P0口送至DB E=1; //E高电平将命令字写入1602液晶 E=0; //写完以后,恢复E的电平 } //--------------------------------------- //名称:1602液晶初始化函数 //--------------------------------------- void lcd_init(void) { lcd_cmd(0x38); //8位数据总线,两行显示模式,5*7点阵显示 lcd_cmd(0x0C); //显示功能开,无光标 lcd_cmd(0x01); //清屏 lcd_cmd(0x06); //写入新的数据后,光标右移,显示屏不移动 } void lcd_string(unsigned char x, unsigned char y, char *str) { int i = 0; lcd_cmd(0x80 + x + y*0x40); // 设置起始位置(行和列) for (i = 0; str[i] != '\0'; i++) { lcd_data(str[i]); // 逐个字符写入数据 } } const unsigned char code HEX[]="0123456789ABCDEF"; //16进制字符表,用于数字到显示字符转换
以上是lcd1206的操作函数,用到lcd1206时可直接移植,编写显示代码是可不考虑lcd1206与单片机的通信协议,直接通过lcd_cmd、lcd_data、lcd_init和lcd_string这些函数对1206进行操作。
-
矩阵按键检测函数/// void key_input(void) { unsigned char temp1,temp2; //临时保存读取的行或者列 key_flag = 0; P1 &= 0xC0;//将P1口的低6位置0 P1 |= 0x07;//将P1口的低3位置1,读取按键的行数 temp1 = P1<<2; //忽略高2位,先读取行 if(0x1C != temp1) //按键按下 { delay1ms(5); //延时,消除抖动 temp1 = P1<<2; //再次读取行 if(0x1C != temp1) //说明按键确实按下 { key_flag = 1; temp1 = temp1 & 0x1C; //保留P1低3位 P1 &= 0xC0;//将P1口的低6位置0 P1 |= 0x38;//将P1口的3,4,5位置1,读取按键的列数 delay1ms(1); //延时一下,防止下一句读不到 temp2 = P1<<2; //直接读取列 temp2 = temp2 & 0xE0; //保留temp2的高3位 temp1 = temp1 | temp2; //将temp1和temp2的值合并 switch(temp1) { case 0x78:keybuff=water;break; case 0xB8:keybuff=ch1;break; case 0xD8:keybuff=ch2;break; case 0x74:keybuff=ch3;break; case 0xB4:keybuff=ch4;break; case 0xD4:keybuff=ch5;break; case 0x6C:keybuff=ch6;break; case 0xAC:keybuff=ch7;break; case 0xCC:keybuff=ch8;break; default:break; } } } }
以上是3*3矩阵按键键测函数,通过全局变量keybuff来传递按键值,全局变量key_flag按键状态,key_flag为1时表示有按键按下且无做出反应。通过具体定位的方式对按键进行定位可在对按键消抖的情况下快速的检测到时哪个按键按下。
-
switch(key_value) { case water :P3 |= i<<5;if(water_flag) {i=(i<7)?i+1:0;water_flag = 0;} break; case ch1 :break; case ch2 :P3 |= 0x20;break; case ch3 :P3 |= 0x40;break; case ch4 :P3 |= 0x60;break; case ch5 :P3 |= 0x80;break; case ch6 :P3 |= 0xA0;break; case ch7 :P3 |= 0xC0;break; case ch8 :P3 |= 0xE0;break; default :break; }
通道选择代码,变量key_value记录着当前的显示模式,通过其值来选择adc0808对哪个通道进行ad转换,water_flag是1s计数标志位,及轮询查询模式时1s切换一个通道进行测量显示;由于adc0808的通道选择信号接在P3口的高三位,所以轮询模式是i需要向左移5位后再与P3口进行或运算。
-
ADC0809_START_ALE = 1;//正脉冲,锁存地址,同时复位0808,启动ad转换 ADC0809_START_ALE = 0; ADC0809_OE = 1;//允许数据输出 ADCValue = ADC0809_DATA*1.95;//读取数据端口的值 ADC0809_OE = 0;//关闭数据输出允许
上面是对adc0808进行通信,根据adc0808芯片手册可知,在更改完通道选择位后,需要给ADC0809_START_AL信号一个正脉冲,芯片才能切换采集通道,随后再允许输出再读取ad转换的输出值。其输出值乘上adc0808的最小分度值(最小分度值=adc芯片供电电压/256)转换成模拟电压值。
-
//实时扫描矩阵按键值 key_input(); if(key_flag) { key_value = keybuff; i = 0; key_flag = 0; if(key_value == water)//轮询模式 { count_1s = 0;//刚进入轮询模式,将1s计数器清零 } } else { key_value = key_value; }
上面是对检测到的按键进行处理,在处理完后需将按键标志位key_flag清零,表示已对按下的按键进行了反应。
3、编译生成hex文件,打开proteus绘制的原理图 ,双击51芯片,在程序文件一栏添加我们生成的hex文件。