1602显示原理及应用(课件)由刀豆文库小编整理,希望给你工作、学习、生活带来方便,猜你可能喜欢“lcd1602显示原理图”。
所谓1602是指显示的内容为16*2,即可以显示两行,每行16个字符。目前市面上字符液晶绝大多数是基于HD44780液晶芯片的,控制原理是完全相同的,因此基于HD44780写的控制程序可以很方便地应用于市面上大部分的字符型液晶。
1602液晶的正面(绿色背光,黑色字体)
1602液晶背面
另一种1602液晶模块,显示屏是蓝色背光白色字体
字符型LCD1602通常有14条引脚线或16条引脚线的LCD,多出来的2条线是背光电源线VCC(15脚)和地线GND(16脚),其控制原理与14脚的LCD完全一样,引脚定义如下表所示:
HD44780内置了DDRAM、CGROM和CGRAM。
DDRAM就是显示数据RAM,用来寄存待显示的字符代码。共80个字节,其地址和屏幕的对应关系如下表:
也就是说想要在LCD1602屏幕的第一行第一列显示一个“A”字,就要向DDRAM的00H地址写入“A”字的代码就行了。但具体的写入是要按LCD模块的指令格式来进行的,后面我会说到的。那么一行可有40个地址呀?是的,在1602中我们就用前16个就行了。第二行也一样用前16个地址。对应如下:
DDRAM地址与显示位置的对应关系
(事实上我们往DDRAM里的00H地址处送一个数据,譬如0x31(数字1的代码)并不能显示1出来。这是一个令初学者很容易出错的地方,原因就是如果你要想在DDRAM的00H地址处显示数据,则必须将00H加上80H,即80H,若要在DDRAM的01H处显示数据,则必须将01H加上80H即81H。依次类推。大家看一下控制指令的的8条:DDRAM地址的设定,即可以明白是怎么样的一回事了)
1602液晶模块内部的字符发生存储器(CGROM)已经存储了160个不同的点阵字符图形,如下表所示,这些字符有:阿拉伯数字、英文字母的大小写、常用的符号、和日文假名等,每一个字符都有一个固定的代码,比如大写的英文字母“A”的代码是01000001B(41H),显示时模块把地址41H中的点阵字符图形显示出来,我们就能看到字母“A”
上表中的字符代码与我们PC中的字符代码是基本一致的。因此我们在向DDRAM写C51字符代码程序时甚至可以直接用P1='A'这样的方法。PC在编译时就把“A”先转为41H代码了。
字符代码0x00~0x0F为用户自定义的字符图形RAM(对于5X8点阵的字符,可以存放8组,5X10点阵的字符,存放4组),就是CGRAM了。
0x20~0x7F为标准的ASCII码,0xA0~0xFF为日文字符和希腊文字符,其余字符码(0x10~0x1F及0x80~0x9F)没有定义。
那么如何对DDRAM的内容和地址进行具体操作呢,下面先说说HD44780的指令集及其设置说明,请浏览该指令集,并找出对DDRAM的内容和地址进行操作的指令。共11条指令:
1.清屏指令
功能: 清除液晶显示器,即将DDRAM的内容全部填入“空白”的ASCII码20H;
光标归位,即将光标撤回液晶显示屏的左上方;
将地址计数器(AC)的值设为0。
2.光标归位指令
功能: 把光标撤回到显示器的左上方;
把地址计数器(AC)的值设置为0;
保持DDRAM的内容不变
3.进入模式设置指令
功能:设定每次写入1位数据后光标的移位方向,并且设定每次写入的一个字符是否移动。参数设定的情况如下所示:
位名
设置
I/D
0=写入新数据后光标左移
1=写入新数据后光标右移
S
0=写入新数据后显示屏不移动
1=写入新数据后显示屏整体右移1个字
4.显示开关控制指令
功能:控制显示器开/关、光标显示/关闭以及光标是否闪烁。参数设定的情况如下:
位名
设置
D
0=显示功能关
1=显示功能开
C
0=无光标
1=有光标
B
0=光标闪烁
1=光标不闪烁
5.设定显示屏或光标移动方向指令
功能:使光标移位或使整个显示屏幕移位。参数设定的情况如下:
S/C
R/L
设定情况
0
0
光标左移1格,且AC值减1
0
光标右移1格,且AC值加1
0
显示器上字符全部左移一格,但光标不动
显示器上字符全部右移一格,但光标不动
6.功能设定指令
功能:设定数据总线位数、显示的行数及字型。参数设定的情况如下:
位名
设置
DL
0=数据总线为4位
1=数据总线为8位
N
0=显示1行
1=显示2行
F
0=5×7点阵/每字符
1=5×10点阵/每字符
7.设定CGRAM地址指令
功能:设定下一个要存入数据的CGRAM的地址。
8.设定DDRAM地址指令
功能:设定下一个要存入数据的CGRAM的地址。
(注意这里我们送地址的时候应该是0x80+Addre,这也是前面说到写地址命令的时候要加上0x80的原因)
9.读取忙信号或AC地址指令
功能: 读取忙碌信号BF的内容,BF=1表示液晶显示器忙,暂时无法接收单片机送来的数据或指令;当BF=0时,液晶显示器可以接收单片机送来的数据或指令;
读取地址计数器(AC)的内容。
10.数据写入DDRAM或CGRAM指令一览
功能: 将字符码写入DDRAM,以使液晶显示屏显示出相对应的字符;
将使用者自己设计的图形存入CGRAM。
11.从CGRAM或DDRAM读出数据的指令一览
功能:读取DDRAM或CGRAM中的内容。
基本操作时序:
读状态
输入:RS=L,RW=H,E=H
输出:DB0~DB7=状态字
写指令
输入:RS=L,RW=L,E=下降沿脉冲,DB0~DB7=指令码
输出:无
读数据
输入:RS=H,RW=H,E=H
输出:DB0~DB7=数据
写数据
输入:RS=H,RW=L,E=下降沿脉冲,DB0~DB7=数据
输出:无
这么多的指令刚开始的时候没有必要全部掌握,随着学习的深入可以再尝试去用更复杂的控制指令。
测试用的最小系统,单片机是STC89C516,晶振为12M。液晶为蓝底白字的那种1602。
当我们硬件连接错误,或者程序错误时就会出现下图这种情况,就是上排显示16的白色的块(蓝底黑字的液晶则显示的是16个黑块)。
在1602的上排显示“LCD1602 check ok”下排显示“study up”程序中没有用到忙检测,而是用延时函数来替代忙检测.#include //包含头文件
#define uint unsigned int //预定义一下
#define uchar unsigned char
sbit rs=P3^5;
//1602的数据/指令选择控制线
sbit rw=P3^6;//1602的读写控制线
sbit en=P3^7;//1602的使能控制线
/*P0口接1602的D0~D7,注意不要接错了顺序*/
uchar code table[]=“LCD1602 check ok”;//要显示的内容1放入数组tablel uchar code table1[]=“study up”;
//要显示的内容2放入数组table1 void delay(uint n)
//延时函数
{
uint x,y;
for(x=n;x>0;x--)
for(y=110;y>0;y--);
}
void lcd_wcom(uchar com)//1602写命令函数
{
rs=0;
//选择指令寄存器
rw=0;
//选择写
P0=com;
//把命令字送入P0
delay(5);
//延时一小会儿,让1602准备接收数据
en=1;
//使能线电平变化,命令送入1602的8位数据口
en=0;}
void lcd_wdat(uchar dat)//1602写数据函数
{
rs=1;
//选择数据寄存器
rw=0;
//选择写
P0=dat;
//把要显示的数据送入P0
delay(5);//延时一小会儿,让1602准备接收数据
en=1;
//使能线电平变化,数据送入1602的8位数据口
en=0;}
void lcd_init()
//1602初始化函数
{
lcd_wcom(0x38);
//8位数据,双列,5*7字形
lcd_wcom(0x0c);
//开启显示屏,关光标,光标不闪烁
lcd_wcom(0x06);
//显示地址递增,即写一个数据后,显示位置右移一位
lcd_wcom(0x01);
//清屏
}
void main()
//主函数
{
uchar n,m=0;
lcd_init();
//液晶初始化
lcd_wcom(0x80);
//显示地址设为80H(即00H,)上排第一位
for(m=0;m
{
lcd_wdat(table[m]);
delay(200);
}
lcd_wcom(0x80+0x44);
//重新设定显示地址为0xc4,即下排第5位
for(n=0;n
//将table1[]中的数据依次写入1602显示
{
lcd_wdat(table1[n]);
delay(200);
}
while(1);//动态停机
}
程序写好后烧写进单片机,现在让我们看看效果吧
这就是显示的效果,你做成功了吗?
下面让我们来看看如何显示一个自定义的字符吧
我们从CGROM表上可以看到,在表的最左边是一列可以允许用户自定义的CGRAM,从上往下看着是16个,实际只有8个字节可用。它的字符码是00000000-00000111这8个地址,表的下面还有8个字节,但因为这个CGRAM的字符码规定0-2位为地址,3位无效,4-7全为零。因此CGRAM的字符码只有最后三位能用也就是8个字节了。等效为0000X111,X为无效位,最后三位为000-111共8个。
如果我们要想显示这8个用户自定义的字符,操作方法和显示CGROM的一样,先设置DDRAM位置,再向DDRAM写入字符码,例如“A”就是41H。现在我们要显示CGRAM的第一个自定义字符,就向DDRAM写入00000000B(00H),如果要显示第8个就写入00000111(08H),简单吧!
好!现在我们来看怎么向这八个自定义字符写入字模。有个设置CGRAM地址的指令大家还记得吗?赶快再找出来看看。
从这个指令可以看出指令数据的高2位已固定是01,只有后面的6位是地址数据,而这6位中的高3位就表示这八个自定义字符,最后的3位就是字模数据的八个地址了。例如第一个自定义字符的字模地址为01000000-01000111八个地址。我们向这8个字节写入字模数据,让它能显示出“℃”
地址:01000000
数据:00010000
图示:○○○■○○○○
01000001
00000110
○○○○○■■○
01000010
00001001
○○○○■○○■
01000011
00001000
○○○○■○○○
01000100
00001000
○○○○■○○○
01000101
00001001
○○○○■○○■
01000110
00000110
○○○○○■■○
01000111
00000000
○○○○○○○○
下面我们写一段程序让这8个自定义字符显示出一个心的图案:(由于上面那个显示程序已经有很详细的注释了,因此这个程序只对与上个程序不同的地方写注释)
#include #define uint unsigned int #define uchar unsigned char sbit rs=P3^5;sbit rw=P3^6;sbit en=P3^7;
uchar code table[]={0x03,0x07,0x0f,0x1f,0x1f,0x1f,0x1f,0x1f,0x18,0x1E,0x1f,0x1f,0x1f,0x1f,0x1f,0x1f, 0x07,0x1f,0x1f,0x1f,0x1f,0x1f,0x1f,0x1f, 0x10,0x18,0x1c,0x1E,0x1E,0x1E,0x1E,0x1E, 0x0f,0x07,0x03,0x01,0x00,0x00,0x00,0x00, 0x1f,0x1f,0x1f,0x1f,0x1f,0x0f,0x07,0x01, 0x1f,0x1f,0x1f,0x1f,0x1f,0x1c,0x18,0x00,0x1c,0x18,0x10,0x00,0x00,0x00,0x00,0x00};//心图案
/*uchar code table1[]={0x10,0x06,0x09,0x08,0x08,0x09,0x06,0x00};//字符℃ */
void delay(uint n)
{
uint x,y;
for(x=n;x>0;x--)
for(y=110;y>0;y--);}
void lcd_wcom(uchar com)
{
rs=0;
rw=0;
P2=com;
delay(5);
en=1;
en=0;}
void lcd_wdat(uchar dat)
{
rs=1;
rw=0;
P2=dat;
delay(5);
en=1;
en=0;
}
void lcd_init()
{
lcd_wcom(0x38);
lcd_wcom(0x0c);
lcd_wcom(0x06);
lcd_wcom(0x01);
}
void main(){
char m=0;
lcd_init();
lcd_wcom(0x40);
//设定CGRAM地址
for(m=0;m
{
lcd_wdat(table[m]);
}
lcd_wcom(0x85);//设定上排的显示位置
for(m=0;m
{
lcd_wdat(m);
}
lcd_wcom(0xc5);//将显示坐标转移到下排和上排相对应的地方
for(m=4;m
{
lcd_wdat(m);
}
while(1);}
让我们一起来看看显示的效果吧~~
在绿底黑字液晶模块和蓝底白字液晶模块上分别显示的效果。
下面再为大家展示几种可能出现的问题
1:通电之后,程序也烧写进去了,但是1602就是不显示,只显示一排黑块(一般都是在上排8个小黑块,记得刚开始用1602液晶的时候,被这个整怕了~~),怎么样,你郁闷了吧,其实出现这种问题的原因无非以下几种:硬件连线上的错误,这种错误一般用万用表仔细检查后很容易找出来。第二种情况就是硬件连接上是正确的,那么此时出问题最大的就是程序上了,如果你用的是忙检测,看一下忙检测函数写对了没,如果用的是延时函数,那么看看延时的时间是否够长。再就是看看时序图,这点很重要的哦。如果硬件和软件都没有错,那么就要考虑1602是否坏了,但是出现这种情况的几率很小,如果遇到这种情况,你可以考虑去买彩票了~~
下面这种情况你遇到过吗?我遇到过了的,搞得我很是郁闷~~
我做的实验是要液晶显示ABC这三个字母,并且开光标,光标闪烁。大家可以在第一排的最后几位看到ABC和光标都已经显示出来了。但是为什么其它位会显示这么多8呢?嘿嘿~~郁闷吧。出现这种情况的原因就是在初始化液晶的时候,要把清屏指令放在最后面,否则就会出现上图这种情况。怎么样,第一次听说吧~不过,我不知道其它的液晶是否也有这个问题出现,至少我用的这块就有这种情况,但是我的另一个液晶则没有这种情况出现,不管是在一开始就清屏还是最后清屏。大家注意下就可以了,万一出现了这种情况,就会处理了~~
上面这张图是用1602作为显示的温度电子钟~~上面的年月日三个字就是用自定义字符的方法显示的。呵呵,怎么样~~到此1602的驱动基本上结束了,剩下的就靠大家自己去发挥了。
LCD1602.pdf
(2010-02-05 16:03:05, Size: 579 KB, Downloads: 23)tiankai(2010-2-05 16:31:51)
晨辉教你轻松学51--------按键篇
对于一个由单片机为核心构成的系统而言。输入通道是相当重要的。可以看到几乎每一样基于单片机的产品都有人机交互的部分。如各种仪器设备上的各种按钮和开关,以及我们手机上的键盘,MP3上的按键等等。最常见的输入部分,莫非就是按键了。对于大多数初学者而言,编写一个好的按键程序是一件颇为头疼的事情。于是乎在网上乱搜一气,程序倒是找到了不少,但是看了半天依然是不明白。或者在某某论坛上面发帖“跪求XX按键程序,大虾帮忙……”如果你偶然间进了这个论坛,又偶然看到了这个帖子,而且恰好你对按键程序的写法也不是很清楚,那么我希望你能够静静的看完这个帖子。如果你觉得对你很有帮助,那么我希望你能够在以后的日子中能够坚持到这个论坛来,一起交流学习,分享自己学习过程中的喜悦或者一起探讨棘手的问题,这是我写这个帖子的最大的初衷了。OK,不能再说了,再说就变成水帖了。那么我们开始吧。
按键的种类很多。不过原理基本相似。下面我们以一种轻触开关为例讲解按键程序的写法。
这种轻触开关大家不陌生吧^_^
一般情况下,按键与单片机的连接如下面这幅图所示。
(图中电阻值一般去4.7k~10k之间,对于内部端口有上拉电阻的单片机则可省略此电阻)单片机对于按键的按下与否则是通过检测相应引脚上的电平来实现的。对于上图而言,当P17引脚上面的电平为低时,则表示按键已经按下。反之,则表明按键没有按下。我们在程序中只要检测到了P17引脚上面的电平为低了,就可以判断按键按下。呵呵,简单吧。等会,您先别乐呵,话还没说完呢。下面我们来看看,当按键按下时,P17引脚上面的波形是怎么变化的。
上图是一个理想波形图,当按键按下时,P17口的电平马上被拉低到0V了。当然理想的东西都是不现实的。所以我们还是看看现实的波形图吧。
看出什么区别来了没。呵呵,只要你不是傻子我相信都能看出其中的区别。由于按键的机械特性。当按键闭合时,并不能马上保存良好的接触,而是来回弹跳。这个时间很短,我们的手根本感觉不出来。但是对于一秒钟执行百万条指令的单片机而言,这个时间是相当的长了。那么在这段抖动的时间内,单片机可能读到多次高低电平的变化。如果不加任何处理的话,就会认为已经按下,或者松开很多次了。而事实上,我们的手一直按在按键上,并没有重复按动很多次。要想能够正确的判断按键是否按下就要避开这段抖动的时间。根据一般按键的机械特点,以及按键的新旧程度等而言,这段抖动的时间一般在5MS~20MS之间。
看到这里你明白了该如何做了吧。
看看下面的这个流程图,你应该不陌生吧。
这个流程是好多教科书上的做法。可惜,误导了好多人。为什么呢。因为它根本就没有考虑实际情况。我们根据这幅流程图来写它的代码看看。
unsigned char v_ReadKey_f(void)
{
unsigned char KeyPre;
if(P17 == 0)
{
Delay(20);//延时20MS
If(P17 == 0)
{
KeyPre = 1;
While(!P17);//等待释放
}
else
KeyPre = 0;
}
}
这样一个程序,相信对很多初学者而言都不陌生。因为好多书上基本都是这样的一个流程和写法。可是当有一天,我们想做一个数码管加按键调整的时钟,发现当我们按键按下去的时候,数码管就不亮了。为什么呢。原因就在这个键盘扫描函数。平常没有按键按下还好。一旦有键按下,它先是浪费了CPU的大部分时间(就是那个什么事情都没做的延时20MS函数)然后,又霸占CPU(就是哪个死死等在那里的while(P17);语句)直到按键释放。对于这种情况我们是忍无可忍的,那么就让我们彻底的抛弃它吧。那么到底按键扫描函数改如何写呢……..所谓众里寻她千百度,蓦然回首,那人却在灯火阑珊处。如果我们把CPU延时的那20MS拿出来去做其它事情,那么不就充分利用CPU的时间了吗。而一般情况下我们只要前沿去抖动就可以了。也就是说了,我们只需在按键按下后去抖就可以了,对于按键的释放抖动可以不必要过于关注。当然这主要和应用的场合有关。一个能有效识别按键按下并支持连发功能的按键已经能够应用到大多数的场合了。
下面以四个独立按键的处理程序为例来讲解(支持单击和连发)
#include“regx52.h”
sbit KeyOne = P1^0;
sbit KeyTwo = P1^1;
sbit KeyThree = P1^2;
sbit KeyFour = P1^3;
#define uint16 unsigned int
#define uint8 unsigned char
#define NOKEY 0xff
#define KEY_WOBBLE_TIME 500
//去抖动时间(待定)
#define KEY_OVER_TIME 15000 //等待进入连击时间(待定),该常数要比正常 //按键时间要长,防止非目的性进入连击模式
#define KEY_QUICK_TIME 1000 //等待按键抬起的连击时间(待定)
void v_KeyInit_f(void)
{
KeyOne = 1;//按键初始化(相应端口写1)
KeyTwo = 1;
KeyThree = 1;
KeyFour = 1;
}
uint8 u8_ReadKey_f(void)
{
static uint8 LastKey = NOKEY;//保存上一次的键值
static uint16 KeyCount = 0;//按键延时计数器
static uint16 KeyOverTime = KEY_OVER_TIME;//按键抬起时间
uint8 KeyTemp = NOKEY;
//临时保存读到的键值
KeyTemp = P1 & 0x0f;
//读键值
if(KeyTemp == 0x0f)
{
KeyCount = 0;
KeyOverTime = KEY_OVER_TIME;
return NOKEY;
//无键按下返回NOKEY
}
else
{
if(KeyTemp == LastKey)//是否第一次按下
{
if(++KeyCount == KEY_WOBBLE_TIME)次按下,则判断//抖动是否结束
{
return KeyTemp;
//去抖动结束,返回键值
}
else
{
if(KeyCount > KeyOverTime)
//不是第一
{
KeyCount = 0;
KeyOverTime = KEY_QUICK_TIME;
}
return NOKEY;
}
}
else //是第一次按下则保存键值,以便下次执行此函数时与读到的键值作比较
{
LastKey = KeyTemp;
//保存第一次读到的键值
KeyCount = 0;
//延时计数器清零
KeyOverTime = KEY_OVER_TIME;
return NOKEY;
}
}
}
下面是我测试用的主程序(相关头文件未列出,仅仅作测试演示用)void main(void){
uint8 KeyValue;
int16 Count;
v_LcdInit_f();
v_KeyInit_f();
CLS
LOCATE(3, 1)
PRINT(“Key Test”)
LOCATE(6, 2)
SHOW_ICON
while(1)
{
KeyValue = u8_ReadKey_f();
if(KeyValue!= NOKEY)
{ LOCATE(1, 2)
if(KeyValue == 0x0e)Count++;
if(KeyValue == 0x0d)Count--;
if(KeyValue == 0x0b)Count = 0;
if(KeyValue == 0x07)Count = 0;
HIDE_ICON
PRINTD(Count, 5)
LOCATE(6, 2)
}
else
{
//SHOW_ICON
} }
}
每次执行读键盘函数时,只是对一些标志进行判断,然后退出。因此能够充分的利用CPU的资源。同时可以处理连发按键。此按键扫描按键函数可以直接放在主函数中。如果感觉按键太过灵敏或者迟钝则改一下相关消抖动的宏定义即可。此函数也可以通过中断标志位进行定时的扫描。此时,需要添加一个定时标志位,并将相关消抖动的和连击时间的宏定义改小即可。然后在主程序类似下面这样写即可
if(KeyTime)//定时扫描时间到
{
KeyValue = u8_ReadKey_f();
} 具体的工作就交给您去完成啦。
看看效果: 按键单击
连发时候的截图
至此,关于单个按键的学习就告一段落了,您是否已经明白了。如果您还不明白,那么把这个程序好好的看看,并画下流程图,分析分析。估计您就会恍然大悟。关键是思路要转换过来。下面我们来看看多个按键的情况吧
一般情况下,如果多个按键每个都直接接在单片机的I/O上的话会占用很多的I/O资源。比较合理的一种做法是,按照行列接成矩阵的形式。按键接在每一个的行列的相交处。这样对于m行n列的矩阵,可以接的按键总数是m*n。这里我们以常见的4*4矩阵键盘来讲解矩阵键盘的编程。
上图就是矩阵键盘的一般接法。
这里我们要介绍一种快速的键盘扫描法:线反转法(或者称为行列翻转法)。具体流程如下。首先,让单片机的行全部输出0,列全部输出1,读取列的值(假设行接P3口的高四位,列接低四位)。即P3= 0x0f;此时读列的值,如果有键按下,则相应的列读回来的值应该为低。譬如此时读回来的值为 0x0e;即按键列的位置已经确定。这时反过来,把行作为输入,列作为输出,即P0 = 0xf0;此时再读行的值,如果按键仍然被按下,则相应的行的值应该为低,如果此时读回来的值为0xe0,则确定了行的位置。说到这里,您应该笑了,知道了一个按键被按下的行和列的位置,那么就可以肯定确定它的位置了。我们把读回来的行值和列值进行或运算。即 0xe0 | 0x 0e 即 0xee。那么0xee就是我们按下的按键的键值了。怎么样。只需几步就可以判断所有的键值,简单吧。下面再结合一个例子具体看看。/****************************************** * 此模块所需相关支持库
* ******************************************/ #include“regx52.h” #define uint8 unsigned char #define uint16 unsigned int
/**************************************** * 与硬件连接相关的定义及宏定义和操作宏 * *****************************************/ #define KEYBOARD P3
//键盘连接到单片机上的端口位置
#define READ_ROW_ENLABLE KEYBOARD = 0x0f;//读端口之前先把相应口置位(由基本51单片机特性决定的)#define READ_COL_ENLABLE KEYBOARD = 0xf0;// 根据实际硬件连接情况修改
/***************************************** * 模块内相关的宏定义及常数宏
* ******************************************/ #define NOKEY
0xff //定义无键按下时的返回值 #define DELAY_COUNT 2 //消抖时间常数
/***************************************** * 此模块所需的全局或者外部变量
* *****************************************/ bit bdata StartScan = 0;//此变量需放在定时中断中置位
/***************************************** * 按键扫描函数,按下去后经去抖,确定按下 * * 则返回键值0~15;无键按下则返回0xff;*
* 此函数需要定时器的支持(去抖....)
* *****************************************/ uint8 u8_KeyBoardScan_f(){
static uint8 DelayCount = 0;
uint8 KeyValueRow = 0;
uint8 KeyValueCol = 0;
uint8 KeyValue = 0;
if(StartScan)
//开始扫描,StartScan在定时中断中置位
{
StartScan = 0;//清除开始扫描标志位,避免多次重复执行扫描程序
//读入按键状态前先向相应端口写1(由基本51单片机硬件结构决定)
READ_ROW_ENLABLE
if((KEYBOARD & 0x0f)!= 0x0f)//判断是否有键按下
{
DelayCount++;
if(DelayCount
{
return NOKEY;
}
else
//消除了抖动
{
if((KEYBOARD & 0x0f)!= 0x0f)//再次判断是否按键真的按下
{
DelayCount = 0;
//确定按下后,延时去抖计时器清0
KeyValueRow = KEYBOARD & 0x0f;//取得行码
//准备读列,先向相应端口写1(由基本51单片机硬件结构决定)
READ_COL_ENLABLE
if((KEYBOARD & 0xf0)!= 0xf0)//反转,读列码
{
KeyValueCol = KEYBOARD & 0xf0;//取得列码
//合并取得的行码和列码,即是相应按键的键值
switch(KeyValueCol | KeyValueRow)
{
case 0x77 : KeyValue = 0;break;
case 0xb7 : KeyValue = 1;break;
case 0xd7 : KeyValue = 2;break;
case 0xe7 : KeyValue = 3;break;
case 0x7b : KeyValue = 4;break;
case 0xbb : KeyValue = 5;break;
case 0xdb : KeyValue = 6;break;
case 0xeb : KeyValue = 7;break;
case 0x7d : KeyValue = 8;break;
case 0xbd : KeyValue = 9;break;
case 0xdd : KeyValue = 10;break;
case 0xed : KeyValue = 11;break;
case 0x7e : KeyValue = 12;break;
case 0xbe : KeyValue = 13;break;
case 0xde : KeyValue = 14;break;
case 0xee : KeyValue = 15;break;
default : return NOKEY;
}
return KeyValue;
}
else
{
DelayCount = 0;
return NOKEY;
}
}
else
{
DelayCount = 0;
return NOKEY;
}
}
}
else
{
DelayCount = 0;
return NOKEY;
} }
}
void v_T0_Isr_f(void)interrupt INTERRUPT_TIMER2_OVERFLOW {
StartScan = 1;}
/*************************************************** *模块调试
* ***************************************************/
//主函数仅作演示用,主函数除按键扫描外的函数并没在这里给出 void v_Init_T2_f(void){
T2CON = 0x04;
T2MOD = 0x00;
TH2 = 0xd8;
RCAP2H = 0xd8;
TL2
= 0xf0;
RCAP2L = 0xf0;
ET2 = 1;
TR2 = 1;}
void main(void){
uint8 readkey = 0;
v_Init_T2_f();
v_LcdInit_f();
LOCATE(1, 1)
PRINT(“4*4KeyBoard Test”)
EA = 1;
LOCATE(3, 2)
while(1)
{
SHOW_ICON
readkey = u8_KeyBoardScan_f();
if(readkey!= NOKEY)
{
PRINTN(readkey , 2)
LOCATE(3, 2)
continue;
}
else
{
continue;
}
} }
呵呵,按键扫描程序已经注释的很详细了。我就不多费嘴舌了。如果有不清楚的地方,欢迎跟帖讨论。下面是按键测试的截图
我的自己搭建的实验板
OK,Enioy it!自此按键检测告一段落。下次如果再讲按键。将会讨论另外一种按键的写法:基于状态机的按键程序设计。欢迎讨论。tiankai(2010-2-05 16:37:53)晨辉教你轻松学51--------按键篇
对于一个由单片机为核心构成的系统而言。输入通道是相当重要的。可以看到几乎每一样基于单片机的产品都有人机交互的部分。如各种仪器设备上的各种按钮和开关,以及我们手机上的键盘,MP3上的按键等等。最常见的输入部分,莫非就是按键了。对于大多数初学者而言,编写一个好的按键程序是一件颇为头疼的事情。于是乎在网上乱搜一气,程序倒是找到了不少,但是看了半天依然是不明白。或者在某某论坛上面发帖“跪求XX按键程序,大虾帮忙……”如果你偶然间进了这个论坛,又偶然看到了这个帖子,而且恰好你对按键程序的写法也不是很清楚,那么我希望你能够静静的看完这个帖子。如果你觉得对你很有帮助,那么我希望你能够在以后的日子中能够坚持到这个论坛来,一起交流学习,分享自己学习过程中的喜悦或者一起探讨棘手的问题,这是我写这个帖子的最大的初衷了。OK,不能再说了,再说就变成水帖了。那么我们开始吧。
按键的种类很多。不过原理基本相似。下面我们以一种轻触开关为例讲解按键程序的写法。
这种轻触开关大家不陌生吧^_^
一般情况下,按键与单片机的连接如下面这幅图所示。
(图中电阻值一般去4.7k~10k之间,对于内部端口有上拉电阻的单片机则可省略此电阻)单片机对于按键的按下与否则是通过检测相应引脚上的电平来实现的。对于上图而言,当P17引脚上面的电平为低时,则表示按键已经按下。反之,则表明按键没有按下。我们在程序中只要检测到了P17引脚上
面的电平为低了,就可以判断按键按下。呵呵,简单吧。等会,您先别乐呵,话还没说完呢。下面我们来看看,当按键按下时,P17引脚上面的波形是怎么变化的。
上图是一个理想波形图,当按键按下时,P17口的电平马上被拉低到0V了。当然理想的东西都是不现实的。所以我们还是看看现实的波形图吧。
看出什么区别来了没。呵呵,只要你不是傻子我相信都能看出其中的区别。由于按键的机械特性。当按键闭合时,并不能马上保存良好的接触,而是来回弹跳。这个时间很短,我们的手根本感觉不出来。但是对于一秒钟执行百万条指令的单片机而言,这个时间是相当的长了。那么在这段抖动的时间内,单片机可能读到多次高低电平的变化。如果不加任何处理的话,就会认为已经按下,或者松开很多次了。而事实上,我们的手一直按在按键上,并没有重复按动很多次。要想能够正确的判断按键是否按下就要避开这段抖动的时间。根据一般按键的机械特点,以及按键的新旧程度等而言,这段抖动的时间一般在5MS~20MS之间。
看到这里你明白了该如何做了吧。
看看下面的这个流程图,你应该不陌生吧。
这个流程是好多教科书上的做法。可惜,误导了好多人。为什么呢。因为它根本就没有考虑实际情况。我们根据这幅流程图来写它的代码看看。
unsigned char v_ReadKey_f(void){
unsigned char KeyPre;
if(P17 == 0)
{
Delay(20);//延时20MS
If(P17 == 0)
{
KeyPre = 1;
While(!P17);//等待释放
}
else
KeyPre = 0;
}
}
这样一个程序,相信对很多初学者而言都不陌生。因为好多书上基本都是这样的一个流程和写法。可是当有一天,我们想做一个数码管加按键调整的时钟,发现当我们按键按下去的时候,数码管就不亮了。为什么呢。原因就在这个键盘扫描函数。平常没有按键按下还好。一旦有键按下,它先是浪费了CPU的大部分时间(就是那个什么事情都没做的延时20MS函数)然后,又霸占CPU(就是哪个死死等在那里的while(P17);语句)直到按键释放。对于这种情况我们是忍无可忍的,那么就让我们彻底的抛弃它吧。那么到底按键扫描函数改如何写呢……..所谓众里寻她千百度,蓦然回首,那人却在灯火阑珊处。如果我们把CPU延时的那20MS拿出来去做其它事情,那么不就充分利用CPU的时间了吗。而一般情况下我们只要前沿去抖动就可以了。也就是说了,我们只需在按键按下后去抖就可以了,对于按键的释放抖动可以不必要过于关注。当然这主要和应用的场合有关。一个能有效识别按键按下并支持连发功能的按键已经能够应用到大多数的场合了。
下面以四个独立按键的处理程序为例来讲解(支持单击和连发)
#include“regx52.h”
sbit KeyOne = P1^0;
sbit KeyTwo = P1^1;
sbit KeyThree = P1^2;
sbit KeyFour = P1^3;
#define uint16 unsigned int
#define uint8 unsigned char
#define NOKEY 0xff
#define KEY_WOBBLE_TIME 500
//去抖动时间(待定)
#define KEY_OVER_TIME 15000 //等待进入连击时间(待定),该常数要比正常 //按键时间要长,防止非目的性进入连击模式
#define KEY_QUICK_TIME 1000 //等待按键抬起的连击时间(待定)
void v_KeyInit_f(void)
{
KeyOne = 1;//按键初始化(相应端口写1)
KeyTwo = 1;
KeyThree = 1;
KeyFour = 1;
}
uint8 u8_ReadKey_f(void)
{
static uint8 LastKey = NOKEY;//保存上一次的键值
static uint16 KeyCount = 0;//按键延时计数器
static uint16 KeyOverTime = KEY_OVER_TIME;//按键抬起时间
uint8 KeyTemp = NOKEY;
//临时保存读到的键值
KeyTemp = P1 & 0x0f;
//读键值
if(KeyTemp == 0x0f)
{
KeyCount = 0;
KeyOverTime = KEY_OVER_TIME;
return NOKEY;
//无键按下返回NOKEY
}
else
{
if(KeyTemp == LastKey)//是否第一次按下
{
if(++KeyCount == KEY_WOBBLE_TIME)//不是第一次按下,则判断//抖动是否结束
{
return KeyTemp;
//去抖动结束,返回键值
}
else
{
if(KeyCount > KeyOverTime)
{
KeyCount = 0;
KeyOverTime = KEY_QUICK_TIME;
}
return NOKEY;
}
}
else //是第一次按下则保存键值,以便下次执行此函数时与读到的键值作比较
{
LastKey = KeyTemp;
//保存第一次读到的键值
KeyCount = 0;
//延时计数器清零
KeyOverTime = KEY_OVER_TIME;
return NOKEY;
}
}
}
下面是我测试用的主程序(相关头文件未列出,仅仅作测试演示用)void main(void){
uint8 KeyValue;
int16 Count;
v_LcdInit_f();
v_KeyInit_f();
CLS
LOCATE(3, 1)
PRINT(“Key Test”)
LOCATE(6, 2)
SHOW_ICON
while(1)
{
KeyValue = u8_ReadKey_f();
if(KeyValue!= NOKEY)
{ LOCATE(1, 2)
if(KeyValue == 0x0e)Count++;
if(KeyValue == 0x0d)Count--;
if(KeyValue == 0x0b)Count = 0;
if(KeyValue == 0x07)Count = 0;
HIDE_ICON
PRINTD(Count, 5)
LOCATE(6, 2)
}
else
{
//SHOW_ICON
}
}
}
每次执行读键盘函数时,只是对一些标志进行判断,然后退出。因此能够充分的利用CPU的资源。同时可以处理连发按键。此按键扫描按键函数可以直接放在主函数中。如果感觉按键太过灵敏或者迟钝则改一下相关消抖动的宏定义即可。此函数也可以通过中断标志位进行定时的扫描。此时,需要添加一个定时标志
位,并将相关消抖动的和连击时间的宏定义改小即可。然后在主程序类似下面这样写即可
if(KeyTime)//定时扫描时间到
{
KeyValue = u8_ReadKey_f();
} 具体的工作就交给您去完成啦。
看看效果: 按键单击
连发时候的截图
至此,关于单个按键的学习就告一段落了,您是否已经明白了。如果您还不明白,那么把这个程序好好的看看,并画下流程图,分析分析。估计您就会恍然大悟。关键是思路要转换过来。下面我们来看看多个按键的情况吧
一般情况下,如果多个按键每个都直接接在单片机的I/O上的话会占用很多的I/O资源。比较合理的一种
做法是,按照行列接成矩阵的形式。按键接在每一个的行列的相交处。这样对于m行n列的矩阵,可以接的按键总数是m*n。这里我们以常见的4*4矩阵键盘来讲解矩阵键盘的编程。
上图就是矩阵键盘的一般接法。
这里我们要介绍一种快速的键盘扫描法:线反转法(或者称为行列翻转法)。具体流程如下。首先,让单片机的行全部输出0,列全部输出1,读取列的值(假设行接P3口的高四位,列接低四位)。即P3= 0x0f;此时读列的值,如果有键按下,则相应的列读回来的值应该为低。譬如此时读回来的值为 0x0e;即按键列的位置已经确定。这时反过来,把行作为输入,列作为输出,即P0 = 0xf0;此时再读行的值,如果按键仍然被按下,则相应的行的值应该为低,如果此时读回来的值为0xe0,则确定了行的位置。说到这里,您应该笑了,知道了一个按键被按下的行和列的位置,那么就可以肯定确定它的位置了。我们把读回来的行值和列值进行或运算。即 0xe0 | 0x 0e 即 0xee。那么0xee就是我们按下的按键的键值了。怎么样。只需几步就可以判断所有的键值,简单吧。下面再结合一个例子具体看看。/****************************************** * 此模块所需相关支持库
* ******************************************/ #include“regx52.h” #define uint8 unsigned char #define uint16 unsigned int
/**************************************** * 与硬件连接相关的定义及宏定义和操作宏 *
*****************************************/ #define KEYBOARD P3
//键盘连接到单片机上的端口位置
#define READ_ROW_ENLABLE KEYBOARD = 0x0f;//读端口之前先把相应口置位(由基本51单片机特性决定的)#define READ_COL_ENLABLE KEYBOARD = 0xf0;// 根据实际硬件连接情况修改
/***************************************** * 模块内相关的宏定义及常数宏
* ******************************************/ #define NOKEY
0xff //定义无键按下时的返回值 #define DELAY_COUNT 2 //消抖时间常数
/***************************************** * 此模块所需的全局或者外部变量
* *****************************************/ bit bdata StartScan = 0;//此变量需放在定时中断中置位
/***************************************** * 按键扫描函数,按下去后经去抖,确定按下 * * 则返回键值0~15;无键按下则返回0xff;* * 此函数需要定时器的支持(去抖....)
* *****************************************/ uint8 u8_KeyBoardScan_f(){
static uint8 DelayCount = 0;
uint8 KeyValueRow = 0;
uint8 KeyValueCol = 0;
uint8 KeyValue = 0;
if(StartScan)
//开始扫描,StartScan在定时中断中置位
{
StartScan = 0;//清除开始扫描标志位,避免多次重复执行扫描程序
//读入按键状态前先向相应端口写1(由基本51单片机硬件结构决定)
READ_ROW_ENLABLE
if((KEYBOARD & 0x0f)!= 0x0f)//判断是否有键按下
{
DelayCount++;
if(DelayCount
{
return NOKEY;
}
else
//消除了抖动
{
if((KEYBOARD & 0x0f)!= 0x0f)//再次判断是否按键真的按下
{
DelayCount = 0;
//确定按下后,延时去抖计时器清0
KeyValueRow = KEYBOARD & 0x0f;//取得行码
//准备读列,先向相应端口写1(由基本51单片机硬件结构决定)
READ_COL_ENLABLE
if((KEYBOARD & 0xf0)!= 0xf0)//反转,读列码
{
KeyValueCol = KEYBOARD & 0xf0;//取得列码
//合并取得的行码和列码,即是相应按键的键值
switch(KeyValueCol | KeyValueRow)
{
case 0x77 : KeyValue = 0;break;
case 0xb7 : KeyValue = 1;break;
case 0xd7 : KeyValue = 2;break;
case 0xe7 : KeyValue = 3;break;
case 0x7b : KeyValue = 4;break;
case 0xbb : KeyValue = 5;break;
case 0xdb : KeyValue = 6;break;
case 0xeb : KeyValue = 7;break;
case 0x7d : KeyValue = 8;break;
case 0xbd : KeyValue = 9;break;
case 0xdd : KeyValue = 10;break;
case 0xed : KeyValue = 11;break;
case 0x7e : KeyValue = 12;break;
case 0xbe : KeyValue = 13;break;
case 0xde : KeyValue = 14;break;
case 0xee : KeyValue = 15;break;
default : return NOKEY;
}
return KeyValue;
}
else
{
DelayCount = 0;
return NOKEY;
}
}
else
{
DelayCount = 0;
return NOKEY;
}
}
}
else
{
DelayCount = 0;
return NOKEY;
}
}
}
void v_T0_Isr_f(void)interrupt INTERRUPT_TIMER2_OVERFLOW {
StartScan = 1;}
/*************************************************** *模块调试
* ***************************************************/
//主函数仅作演示用,主函数除按键扫描外的函数并没在这里给出 void v_Init_T2_f(void){
T2CON = 0x04;
T2MOD = 0x00;
TH2 = 0xd8;
RCAP2H = 0xd8;
TL2
= 0xf0;
RCAP2L = 0xf0;
ET2 = 1;
TR2 = 1;}
void main(void){
uint8 readkey = 0;
v_Init_T2_f();
v_LcdInit_f();
LOCATE(1, 1)
PRINT(“4*4KeyBoard Test”)
EA = 1;
LOCATE(3, 2)
while(1)
{
SHOW_ICON
readkey = u8_KeyBoardScan_f()
if(readkey!= NOKEY)
{
PRINTN(readkey , 2)
LOCATE(3, 2)
continue;
}
else
{
continue;
}
;
} }
呵呵,按键扫描程序已经注释的很详细了。我就不多费嘴舌了。如果有不清楚的地方,欢迎跟帖讨论。下面是按键测试的截图
我的自己搭建的实验板
OK,Enioy it!自此按键检测告一段落。下次如果再讲按键。将会讨论另外一种按键的写法:基于状态机的按键程序设计。欢迎讨论。tiankai(2010-2-05 16:39:19)晨辉教你轻松学51--------外围芯片之ds1302和ds18b20
对于市面上的大多数51单片机开发板来说。ds1302和ds18b20应该是比较常见的两种外围芯片。ds1302是具有SPI总线接口的时钟芯片。ds18b20则是具有单总线接口的数字温度传感器。下面让我们分别来认识并学会应用这两种芯片。
首先依旧是看DS1302的datasheet中的相关介绍。
上面是它的一些基本的应用介绍。下面是它的引脚的描述。
下面是DS1302的时钟寄存器。我们要读取的时间数据就是从下面这些数据寄存器中读取出来的。当我们要想调整时间时,可以把时间数据写入到相应的寄存器中就可以了。
这是DS1302内部的31个RAM寄存器。在某些应用场合我们可以应用到。如我们想要做一个带定时功能的闹钟。则可以把闹钟的时间写入到31个RAM寄存器中的任意几个。当单片机掉电时,只要我们的DS1302的备用电池还能工作,那么保存在其中的闹钟数据就不会丢失~~
由于对于这些器件的操作基本上按照数据手册上面提供的时序图和相关命令字来进行操作就可以了。因此在我们应用这些器件的时候一定要对照着手册上面的要求来进行操作。如果觉得还不够放心的话。可以到网上下载一些参考程序。对着手册看别人的程序,看别人的思路是怎么样的。
DS1302和单片机的连接很简单。只需一根复位线,一根时钟线,一根数据线即可。同时它本身还需要接一个32.768KHz的晶振来提供时钟源。对于晶振的两端可以分别接一个6PF左右的电容以提高晶振的精确度。同时可以在第8脚接上一个3.6V的可充电的电池。当系统正常工作时可以对电池进行涓流充电。当系统掉电时,DS1302由这个电池提供的能量继续工作。
下面让我们来驱动它。
sbit io_DS1302_RST = P2^0;sbit io_DS1302_IO = P2^1;sbit io_DS1302_SCLK = P2^2;
//------常数宏--//
#define DS1302_SECOND_WRITE 0x80
//写时钟芯片的寄存器位置
#define DS1302_MINUTE_WRITE 0x82
#define DS1302_HOUR_WRITE 0x84
#define DS1302_WEEK_WRITE 0x8A
#define DS1302_DAY_WRITE 0x86
#define DS1302_MONTH_WRITE 0x88
#define DS1302_YEAR_WRITE 0x8C
#define DS1302_SECOND_READ 0x81
//读时钟芯片的寄存器位置
#define DS1302_MINUTE_READ 0x83
#define DS1302_HOUR_READ 0x85
#define DS1302_WEEK_READ 0x8B
#define DS1302_DAY_READ 0x87
#define DS1302_MONTH_READ 0x89
#define DS1302_YEAR_READ 0x8D
//----操作宏---//
#define DS1302_SCLK_HIGH
io_DS1302_SCLK = 1;
#define DS1302_SCLK_LOW
io_DS1302_SCLK = 0;
#define DS1302_IO_HIGH
io_DS1302_IO = 1;
#define DS1302_IO_LOW
io_DS1302_IO = 0;
#define DS1302_IO_READ
io_DS1302_IO
#define DS1302_RST_HIGH
io_DS1302_RST = 1;
#define DS1302_RST_LOW
io_DS1302_RST = 0;
/****************************************************** * 保存时间数据的结构体
* ******************************************************/ struct {
uint8 Second;
uint8 Minute;
uint8 Hour;
uint8 Day;
uint8 Week;
uint8 Month;
uint8 Year;}CurrentTime;
/****************************************************************************** * Function: static void v_DS1302Write_f(uint8 Content)* * Description:向DS1302写一个字节的内容
* * Parameter:uint8 Content : 要写的字节
* *
* ******************************************************************************/
static void v_DS1302Write_f(uint8 Content)
{
uint8 i;
for(i = 8;i > 0;i--)
{
if(Content & 0x01)
{
DS1302_IO_HIGH
}
else
{
DS1302_IO_LOW
}
Content >>= 1;
DS1302_SCLK_HIGH
DS1302_SCLK_LOW
} }
/****************************************************************************** * Function: static uint8 v_DS1302Read_f(void)
* * Description: 从DS1302当前设定的地址读取一个字节的内容 * * Parameter:
* * Return: 返回读出来的值(uint8)
* ******************************************************************************/ static uint8 v_DS1302Read_f(void){
uint8 i, ReadValue;
DS1302_IO_HIGH
for(i = 8;i > 0;i--)
{
ReadValue >>= 1;
if(DS1302_IO_READ)
{
ReadValue |= 0x80;
}
else
{
ReadValue &= 0x7f;
}
DS1302_SCLK_HIGH
DS1302_SCLK_LOW
}
return ReadValue;}
/****************************************************************************** * Function: void v_DS1302WriteByte_f(uint8 Addre, uint8 Content)* * Description: 从DS1302指定的地址写入一个字节的内容
* * Parameter: Addre: 要写入数据的地址
* * Content: 写入数据的具体值
* * Return:
* ******************************************************************************/ void v_DS1302WriteByte_f(uint8 Addre, uint8 Content){
DS1302_RST_LOW
DS1302_SCLK_LOW
DS1302_RST_HIGH
v_DS1302Write_f(Addre);
v_DS1302Write_f(Content);
DS1302_RST_LOW
DS1302_SCLK_HIGH }
/***********************************************************************
******* * Function: uint8 v_DS1302ReadByte_f(uint8 Addre)
* * Description:从DS1302指定的地址读出一个字节的内容
* * Parameter:Addre: 要读出数据的地址
* *
* * Return:
指定地址读出的值(uint8)
* ******************************************************************************/ uint8 v_DS1302ReadByte_f(uint8 Addre){
uint8 ReadValue;
DS1302_RST_LOW
DS1302_SCLK_LOW
DS1302_RST_HIGH
v_DS1302Write_f(Addre);
ReadValue = v_DS1302Read_f();
DS1302_RST_LOW
DS1302_SCLK_HIGH
return ReadValue;} /****************************************************************************** * Function: void v_ClockInit_f(void)
* * Description:初始化写入DS1302时钟寄存器的值(主程序中只需调用一次即可)* * Parameter:
* *
* * Return:
* ******************************************************************************/ void v_ClockInit_f(void){
if(v_DS1302ReadByte_f(0xc1)!= 0xf0)
{ v_DS1302WriteByte_f(0x8e, 0x00);//允许写操作
v_DS1302WriteByte_f(DS1302_YEAR_WRITE, 0x08);//年
v_DS1302WriteByte_f(DS1302_WEEK_WRITE, 0x04);//星期
v_DS1302WriteByte_f(DS1302_MONTH_WRITE, 0x12);//月
v_DS1302WriteByte_f(DS1302_DAY_WRITE, 0x11);
//日
v_DS1302WriteByte_f(DS1302_HOUR_WRITE, 0x13);//小时
v_DS1302WriteByte_f(DS1302_MINUTE_WRITE, 0x06);//分钟
v_DS1302WriteByte_f(DS1302_SECOND_WRITE, 0x40);//秒
v_DS1302WriteByte_f(0x90, 0xa5);
//充电
v_DS1302WriteByte_f(0xc0, 0xf0);
//判断是否初始化一次标识写入
v_DS1302WriteByte_f(0x8e, 0x80);
//禁止写操作
} } /****************************************************************************** * Function: void v_ClockUpdata_f(void)
* * Description:读取时间数据,并保存在结构体CurrentTime中
* * Parameter:
* *
* * Return:
* ******************************************************************************/ void v_ClockUpdata_f(void){
CurrentTime.Second = v_DS1302ReadByte_f(DS1302_SECOND_READ);
CurrentTime.Minute = v_DS1302ReadByte_f(DS1302_MINUTE_READ);
CurrentTime.Hour = v_DS1302ReadByte_f(DS1302_HOUR_READ);
CurrentTime.Day = v_DS1302ReadByte_f(DS1302_DAY_READ);
CurrentTime.Month = v_DS1302ReadByte_f(DS1302_MONTH_READ);
CurrentTime.Week = v_DS1302ReadByte_f(DS1302_WEEK_READ);
CurrentTime.Year = v_DS1302ReadByte_f(DS1302_YEAR_READ);}
有了上面的这些函数我们就可以对DS1302进行操作了。当我们想要获取当前时间时,只需要调用v_ClockUpdata_f(void)这个函数即可。读取到的时间数据保存在CurrentTime这个结构体中。至于
如何把时间数据在数码管或者是液晶屏上显示出来我相信大家应该都会了吧^_^.看看显示效果如何~~
下面再让我们看看DS18B20吧。
DS18B20是单总线的数字温度传感器。其与单片机的接口只需要一根数据线即可。当然连线简单意味着软件处理上可能要麻烦一点。下面来看看它的优点:
看看它的靓照。外形和我们常用的三极管没有什么两样哦。
DS18B20的内部存储器分为以下几部分
ROM:存放该器件的编码。前8位为单线系列的编码(DS18B20的编码是19H)后面48位为芯片的唯一序列号。在出场的时候就已经设置好,用户无法更改。最后8位是以上56位的CRC码。
RAM:DS18B20的内部暂存器共9个字节。其中第一个和第二个字节存放转换后的温度值。第二个和第三个字节分别存放高温和低温告警值。(可以用RAM指令将其拷贝到EEPROM中)第四个字节为配置寄存器。第5~7个字节保留。第9个字节为前8个字节的CRC码。
DS18B20的温度存放如上图所示。其中S位符号位。当温度值为负值时,S = 1,反之则S = 0。我们把得到的温度数据乘上对应的分辨率即可以得到转换后的温度值。
DS18B20的通讯协议:
在对DS18B20进行读写编程时,必须严格保证读写的时序。否则将无法读取测温结果。根据DS18B20的通讯协议,主机控制DS18B20完成温度转换必须经过3个步骤:每一次读写之前都要对DS18B20进行复位,复位成功后发送一条ROM指令,最后发送RAM指令。这样才能对DS18B20进行预定的操作。复位要求主机将数据线下拉500us,然后释放,DS18B20收到信号后等待16~160us然后发出60~240us的存在低脉冲,主机收到此信号表示复位成功。
上图即DS18B20的复位时序图。下面是读操作的时序图
这是写操作的时序图
防雷器在电源系统中原理以及应用一、雷电防护基本原理雷电及其它强干扰对通信系统的致损及由此引起的后里是严重的,雷电防护将成为必需。雷电由高能的低频成份与极具渗透 性......
压力传感器原理及应用压力传感器是工业实践中最为常用的一种传感器,其广泛应用于各种工业自控环境,涉及水利水电、铁路交通、智能建筑、生产自控、航空航天、军工、石化、油井......
《PLC原理及应用》课程设计任务书 MITSUBISHI(三菱) PLC D组一、全自动洗衣机梯形图控制程序的设计与调试 1.控制要求:按下启动按扭及水位选择开关, 注水直到高(中、低)水位,关水......
微机原理及应用教案电力工程学院苏幸烺编 第一讲第1章 微型计算机基础知识1-1.微处理器、微型机的概念 ,及相关基础知识。 1-2.微型机模型的组成|重点:掌握微机系统概念学习......
《PLC原理及应用》教学大纲Common electric control technique编写人:蒋小辉一、课程性质专业平台课二、适用专业自动化测控电气传动机械等三、课程的目的和任务通过本课程的......