版权说明:本文档由用户提供并上传,收益归属内容提供方,若内容存在侵权,请进行举报或认领
文档简介
第六章
I/O口扩展及应用
目录CONTENTS01I/O口扩展基础51单片机I/O资源限制、并行扩展原理与常用扩展芯片介绍04
LCD1602液晶显示应用LCD1602接口电路、存储器与指令系统、时序分析与驱动编程03
矩阵键盘识别与处理矩阵键盘工作原理、行列扫描方法、按键消抖与键值解析02数码管动态显示技术数码管驱动原理、动态扫描实现、多位数码管显示编程05OLED显示模块拓展I2C/SPI接口OLED的工作原理、初始化配置与字符/图形显示实现06综合工程实践数码管显示、矩阵键盘、LCD/OLED的综合项目设计与代码封装学习目标01基础认知掌握理解51单片机I/O口扩展的必要性与并行扩展的基本原理,掌握常用扩展方式的特点与适用场景。02显示技术精通熟练掌握数码管动态显示的驱动原理与编程方法,理解动态扫描的时序逻辑与消抖技巧。03输入交互掌握掌握矩阵键盘的扫描识别原理与编程实现,学会按键消抖与键值处理的常用方法。04工程实践落地熟悉LCD1602/OLED等显示模块的接口时序、指令系统与驱动编程,能够独立完成显示与输入交互的综合项目开发。6.1并行I/O口扩展方式
并行I/O是一种不需要转发的操作,由各进程独立地发出I/O请求,以得到相应的数据区域。I/O性能是制约计算机系统性能提高的重要瓶颈,扩展并行I/O能力可以在一定程度上提高计算机系统的性能。并行I/O扩展有以下两种类型。(1)并行三总线扩展:采用数据总线DB、地址总线AB和控制总线CB的三总线方式,(2)并行I/O口直接扩展:数据与交互信息均由并行I/O口来完成。51单片机内部采用的是并行三总线扩展方式6.2数码管动态显示数码管是由多只LED封装在一起组成8字形器件,有红、绿、蓝、黄等多种颜色。广泛用于仪表、时钟、车站、家电等场合。按LED的连接方式不同,数码管可分为共阴极(CommonCathode,CC)数码管和共阳极(CommonAnode,CA)数码管。共阳极数码管是指将所有LED的阳极接到一起形成公共阳极的数码管,共阳极数码管在应用时应将公共阳极COM接到+5V电源,当某一字段LED的阴极为低电平时,相应字段点亮。共阴极数码管是指将所有LED的阴极接到一起形成公共阴极的数码管。在应用共阴极数码管时,应将公共阴极COM接到地线GND上,当某一字段LED的阳极为高电平时,相应字段点亮。
通常情况下,点亮数码管某一字段至少需要10mA电流,而单片机的I/O口无法提供如此大的电流,所以需要增加数码管驱动电路,可以采用上拉电阻,也可以使用专门的驱动芯片,如八位锁存器74HC573,其输出电流较大,足以点亮数码管。静态显示驱动是指每个数码管的每一个字段都需要用一个I/O口进行驱动,占用I/O口多。但这样做会大大增加电路复杂性,可以通过动态显示技术解决。
动态显示是指一位一位地轮流点亮数码管各位,对于数码管的每一位来说,每隔一段时间点亮一次,利用人眼的视觉暂留效应,采用循环扫描的方式,分时轮流选通数码管各位的公共端,使数码管各位轮流导通显示。人眼的视觉暂留时间为0.05~0.2s,即200ms以内。当扫描速度达到一定程度,即循环点亮间隔时间小于200ms时,人眼就分辨不出来了。尽管实际上数码管各位并非同时点亮,但只要扫描速度足够快,人眼看到的就是一组稳定的显示数据,即认为数码管各位是同时点亮的。当数码管的位数不大于8时,配合74HC573,只需1个P口(如P0口)来分时发送段码和位码数据即可。六位数码管的动态驱动接口电路如图。其中单片机的P2.6引脚和P2.7引脚接两个74HC573的使能端,分别作为位码和段码操作的控制引脚。六位数码管的动态驱动接口电路多位动态显示时,需要设置位码和段码两个数组。共阴极数码管0~F段码如下。tab[16]={0x3f,0x06,0x5b,0x4f,0x66,0x6d,0x7d,0x07,0x7f,0x6f,0x77,0x7c,0x39,0x5e,0x79,0x71}带小数点0~9段码如下。tab_dot[10]={0xbf,0x86,0xdb,0xcf,0xe6,0xed,0xfd,0x87,0xff,0xef}六位数码管的位码如下。tab_wei[6]={0xfe,0xfd,0xfb,0xf7,0xef,0xdf}以六位数码管的动态驱动接口电路图为例,编程实现六位数码管的动态显示,参考例程
的代码如下。#include<reg51.h>sbitdula=P2^6;//段选信号的控制引脚sbitwela=P2^7;//位选信号的控制引脚unsignedcharcodewei[]={0xfe,0xfd,0xfb,0xf7,0xef,0xdf};//数码管的位码表unsignedcharcodeduan[]={0x3f,0x06,0x5b,0x4f,0x66,0x6d};//0~5的段码表voiddelay(unsignedinti){unsignedintm,n;for(m=i;m>0;m--)for(n=90;n>0;n--);}voidmain(){unsignedcharnum;while(1){for(num=0;num<6;num++){P0=wei[num];wela=1;wela=0;P0=duan[num];dula=1;dula=0;delay(2);}}}上机操作,可以看到数码管各位分别显示数字0~5,改变延时并观察,验证人眼的视觉暂留效应。6.3矩阵键盘识别键盘是由排列成矩阵形式的一系列按键开关组成的,用户通过键盘可以向CPU输入数据、地址和命令。键盘按其结构形式可分为编码式键盘和非编码式键盘两类。编码键盘通过硬件电路直接生成按键信息代码(如ASCII码),并将代码并行或串行传输至CPU,如计算机键盘;非编码键盘则通过软件控制程序处理键盘的行列坐标,具有按键功能可自定义的特性,如矩阵键盘。单片机系统中普遍使用非编码式键盘,使用这类键盘时需要解决以下几个问题。①
按键的识别。②
按键的消抖。③
按键的保护。一般情况下,将按键连接到电路时,内部的上拉电阻或外部的上拉电阻会将按键未被按下时的状态拉高为高电平。当按键被按下时,按键内部会导通,使连接到按键的引脚处于低电平状态,从而表示按键被按下。这种用低电平表示按键被按下的设计方式是较为常见的,因为在数字电路中,低电平通常被认为是逻辑“0”,而高电平被认为是逻辑“1”,使用低电平表示按键被按下,可以更容易地进行逻辑判断和控制。当然,也可以根据特定应用的需求,在设计中采用高电平表示按键被按下,只需根据相应的逻辑处理即可。矩阵键盘通过行和列的交叉连接构成一个矩阵,每个按键都位于一个行和一个列的交点处。矩阵键盘识别按键的方法有两种:一种是行列扫描法;另一种是线反转法。1.行列扫描法
行列扫描法是一种简单且直观的按键扫描方法。通过轮询扫描的方式,逐个检测按键的状态。当有按键被按下时,通过判断对应的行和列,可以确定被按下的是哪一个按键。行列扫描法简单易懂,实现成本较低,适用于规模较小的矩阵键盘。随着矩阵键盘规模的增大,由于需要逐一扫描每个按键,因此扫描效率会降低。行列扫描法的一般过程是先使矩阵键盘上某一行为低电平,而其余行接高电平,然后读取列值,如果读取到的列值中某位为低电平,则表明有按键被按下,进而判断被按下按键所在的位置,找到被按下的按键后,读入相应的键值,再转至相应的按键处理程序。否则扫描下一行,直到扫描完所有行。为了防止双键或多键同时被按下,往往从第一行一直扫描到最后一行,若只发现1个被按下的按键,则为按键有效,否则全部作废。以4×4矩阵键盘电路为例,扫描第一行的参考代码如下。unsignedcharkey_matrix_ranks_scan(void){unsignedcharkey_value=0;P3=0xfe;//给第一行赋值0if(P3!=0xfe)//判断第一行是否有按键被按下{delay_10us(1000);//消抖if(P3!=0xfe){switch(P3)//保存第一行按键被按下后的键值
{case0xee:key_value=1;break;case0xde:key_value=5;break;case0xbe:key_value=9;break;case0x7e:key_value=13;break;}}}
while(P3!=0xf7);//等待按键被松开P3=0xfd;//给第二行赋值0,其余行全为1......returnkey_value;}其余三行的扫描代码请自行推导补齐,想一想,试一试吧!注意:采用查询方式检测按键时,要加入延时(通常采用软件延时10~20ms)以消除抖动。P口为准双向口,P口的每一位都能独立地定义为输出口或输入口。当P口中的某位用作输入引脚时,必须先向P口锁存器相应位写入“l”。51单片机中所有P口锁存器的各位在复位时均置为“l”,如果后来在P口锁存器某位写入过“0”,则在需要时应重新向该位写入一个“1”,以使其再次成为一个输入引脚。2.线反转法线反转法也是识别按键是否被按下的一种常用方法,该法比行列扫描法速度快,但在硬件上要求行线与列线外接上拉电阻,适用于大规模的矩阵键盘。在线反转法中,先将行线作为输出线,列线写“1”作为输入线,行线输出全“0”信号,即向P3口锁存器写入0xf0;然后采集引脚信号,并将采集到的信号与0xf0进行与(&)运算,若此时有按键被按下,则必定会使某一列线值为0,即与运算的结果不等于0xf0,读入该列线的值;最后,将行线和列线的I/O关系互换,读取行线的输入值。这样当有按键被按下时,必定可读到一对唯一的行、列值。同样以4×4矩阵键盘电路为例,采用线反转法识别矩阵键盘按键的参考代码如下。while(1){P3=0xf0;//给所有行赋值0,给所有列赋值1if((P3&0xf0)!=0xf0)//判断是否有按键被按下{delay10ms();//软件消抖if((P3&0xf0)!=0xf0){P3=0xf0;//保存行为0,采集按键被按下后的列值switch(P3){case0xe0:i=0;break;//第一列case0xd0:i=1;break;//第二列case0xb0:i=2;break;//第三列case0x70:i=3;break;//第四列default:break;}}}delay10ms();//软件消抖P3=0x0f;//反转行线和列线的电平if((P3&0x0f)!=0x0f){delay10ms();//软件消抖if((P3&0x0f)!=0x0f){P3=0x0f;//保存列为0,采集按键被按下后的行值switch(P3){case0x0e:j=0;break;//第一行case0x0d:j=1;break;//第二行case0x0b:j=2;break;//第三行case0x07:j=3;break;//第四行default:break;}P3=num[j][i];//输出唯一对应的按键值}}}线反转法的原理可以简单概括为:4个行引脚推挽输出低电平(置0),4个列引脚上拉输入,如果有按键被按下,会连通行与列,导致某个列引脚电压被拉低,故输出寄存器不再是0x0f,而是0x0e、0x0d、0x0b或0x07。6.4LCD1602液晶(LiquidCrystal)是奥地利植物学家莱尼兹于1888年发现的一种特殊的混合物质,其组成物质是有机化合物,即以碳为中心构成的化合物。这种物质在常态下介于固态和液态之间,不仅如此,其还兼具固态物质和液态物质的双重特性。当时并没有对该物质的适当称呼,因此称之为液晶。1968年,在美国RCA公司的沙诺夫研发中心,工程师们发现液晶分子在电压的作用下会改变其分子的排列状态,并且可以让射入的光线产生偏转。利用这一原理,RCA公司发明了世界上第一台使用液晶显示的屏幕,即LCD。有TN结型LCD、STN型LCD、TFT型LCD等类型。LCD模块(LiquidCrystalDisplayModule)简称LCM或字符型LCD。字符型LCD是一种专门用于显示字母、数字、符号等的点阵式液晶显示模块。其显示的每个字符(字母、数字等)都是由5×7或5×11点阵组成的。点阵字符位之间有一空点距的间隔,起到字符间距和行距的作用。LCD1602是一款应用广泛的字符型LCD。LCD1602分为带背光和不带背光两种,其控制器大部分为HD44780,采用并行驱动方式。如图
所示。其中控制芯片U1为HD44780,U2为CGROM(CharacterGeneratorROM,字符生成只读存储器)。带背光的LCD1602(16个引脚)比不带背光的LCD1602(14个引脚)厚,背光电源A为15脚,地线K为16脚。LCD1602可显示两行,每行由16个点阵字符组成,能显示所有ASCII字符,每个字符由5×7点阵组成。LCD1602的尺寸如图6.9所示。LCD1602的主要技术参数如下:显示容量为16×2个字符,工作电压为4.5~5.5V,工作电流为2.0mA,字符尺寸(W×H)为2.95mm×4.35mm。6.4.1LCD1602接口LCD1602与单片机的连接有以下两种方式。一、直接控制方式LCD1602的8根数据线和3根控制线(E、RS、R/W)与单片机相连后即可正常工作如图
右图
所示。数据线用于传送数据和指令,接入P0口。由于一般应用中只需向LCD1602写入命令和数据,因此可将LCD1602的R/W引脚(读/写控制端)直接接地,这样可节省1根控制线。Vo引脚是液晶对比度调整端,通常连接一个10kΩ的电位器实现对比度的调整,也可采用将该引脚通过一个适当阻值的电阻接地的方法进行调整,电阻的阻值应通过调试决定。二、间接控制方式也称为四线制工作方式,其控制线连接与直接控制方式相同,不同之处在于其数据线连接利用了HD44780具有4位数据总线的特性,只采用引脚DB4~DB7与单片机进行通信,先传输数据或命令的高4位,再传输低4位。采用四线并口通信,可减少对微控制器I/O的需求。当产品设计过程中单片机的I/O资源紧张时,可以考虑使用此方法。二者的区别仅在于所用的数据线数量不同,其他都一样。在实际应用中,将LCD1602的16孔插座插入开发板上的排针时,不能插反,否则可能因接线错误而烧毁LCD1602或板上器件,只有接触良好才能保证电路完全畅通,6.4.2LCD1602存储器LCD1602是一种常用的字符型LCD,它内置了CGROM和CGRAM(CharacterGeneratorRAM,字符生成随机存储器)。LCD1602内部的三种核心存储器1.DDRAM2.CGROM3.CGRAM1.DDRAM
HD44780内部有一个地址计数器(AddressCounter,AC),保存的是DDRAM(DisplayDataRAM,显示数据随机存储器)或CGRAM的地址,用来定位DDRAM地址,我们可以通过指令控制具体访问的DDRAM地址。DDRAM通常简称显存,简单地说,你向DDRAM写入什么,屏幕上就会显示什么。DDRAM共80个字节,其地址与屏幕的对应关系如图。
每行16个显示单元,第一行实际显示地址为0x80~0x80+15,第二行实际显示地址为0xC0~0xC0+15。LCD1602屏幕上的内容与DDRAM的实际显示地址是一一对应的。要在LCD1602屏幕上显示字符,只需向相关DDRAM地址中写入该字符的ASCII码即可,写在范围外的字符不能被显示出来。这样,我们在程序中可以利用光标或显示移动指令使字符慢慢移动到可见的显示范围内,呈现字符的移动效果。注意:DDRAM上下两行的地址是不连续的,第一行地址为0x00~0x27、第二行地址为0x40~0x67。显示位置表示DDRAM地址对应的屏幕位置,HD44780包含80个DDRAM地址,也就意味最多可以显示80个字符。但是,我们使用的LCD1602只能显示2行,每行16个字符。默认情况下,它只使用32个DDRAM地址,即第一行0x00~0x0F、第二行0x40~0x4F,一个字符占用一个地址,其他地址也是有存储单元的,只不过不能显示出来。讨论:这里的实际显示地址为什么是0x80而不是0x00呢?可以通过LCD1602指令8找到答案。指令8用于对DDRAM地址,即字符的实际显示地址进行设置,其D7为1,D0~D6为地址。因此,需要在原来两行的首地址0x00和0x40的基础上加0x80,即0x80和0xC0。
2.CGROM
LCD1602中的HD44780芯片集成了CGROM,用于存储标准字符的字模数据。了解CGROM的结构和如何向LCD1602写入数据对于正确使用LCD1602至关重要。CGROM是预先固化好的字模库,包含标准ASCII字符集的显示数据,每个字符由5×8点阵组成,存储在CGROM中。CGROM中的字符编码与ASCII码表一一对应,方便直接使用ASCII码进行显示。我们向DDRAM写入的数据是字模地址(而不是字模本身),具体屏幕上显示什么取决于字模地址中对应的字模是什么。CGROM就是一个字库,内置了192个常用字符的字模,这些字符包括阿拉伯数字、大/小写英文字母、常用符号和日文假名等,按ASCII码排列。0x00~0x0F存储用户自定义的字符图形,0x20~0x7F存储标准的ASCII码,0xA0~0xFF存储日文字符和希腊文字符。
如果要在LCD1602上显示字符,还需要先设置DDRAM地址,再向该地址写入字符的CGROM代码。
例如,在屏幕左上角显示字符A的代码。LCD_Write_Com(0x80);//调用写指令函数,设置DDRAM地址为第一行第一个位置LCD_Write_Data('A');//调用写数据函数,写入字符A的CGROM代码或直接使用ASCII码,即//LCD_Write_Data(0x41);3.CGRAM
CGRAM中的字符是用户自定义的,共64字节,地址为0x40~0x7F,可存储8个5×8点阵图形,其中地址0x00~0x07存储字符代码为0x00的字符图形,0x08~0x0F存储字符代码为0x01的字符图形,以此类推。5×8点阵图形有8行5列,定义这样一个字符需要8个字节,每个字节的前3位没有被使用。
要想向DDRAM写入某个CGROM中不存在的字符,必须在CGRAM中先定义后使用。程序退出后CGRAM中定义的字符也不复存在,下次使用时,必须重新定义。
CGRAM地址从0x40开始,自定义字符实现步骤如下。使用5×8点阵设计字符形状,设计字符点阵;将点阵转换为8个字节的十六进制数据,生成字模数据;发送设置CGRAM地址指令(0x40+地址),将8个字节的字模数据依次写入CGRAM;向DDRAM写入自定义字符的代码(0x00~0x07),显示自定义字符。如定义一个摄氏温度符号(℃)并显示,参考代码如下。//自定义字符℃的字模数据ucharcodecelsius[]={0x10,0x06,0x09,0x08,0x08,0x09,0x06,0x00};//写入CGRAMLCD_write_com(0x40);//设置CGRAM地址for(i=0;i<8;i++){LCD_write_data(celsius[i]);//写入字模数据}//显示自定义字符LCD_write_com(0x80);//设置显示位置LCD_write_data(0x00);//显示第一个自定义字符CGRAM只能同时显示8个自定义字符,需注意合理规划使用。上述自定义字符点阵设计又称字符取模,即将图形转换为单片机可以识别的数据格式。取模方法有手动取模和软件取模两种。软件取模是指打开专门的取模软件,新建5×8点阵,在点阵中绘制所需字符,生成字模数据(8个十六进制数)。通过合理利用CGRAM和快速刷新,还可以在LCD1602上实现简单的动画或视频播放效果。基本思路是:将视频帧分割为多个5×8点阵,为每个点阵生成字模数据;使用双缓冲机制(一个缓冲区用于接收新数据帧,另一个缓冲区用于当前显示),通过串口高速传输数据帧,快速更新CGRAM,从而实现动画效果,即视频播放的高级应用。6.4.3LCD1602指令与时序1.LCD1602的操作时序在LCD1602的时序图中,在将E置高电平前,先设置好RS和R/W信号,在E下降沿到来之前,准备好写入的命令字或数据。只需在适当的地方加上延时,就可以满足要求。LCD1602的基本操作分为以下四种。①
读状态。输入:RS=0,R/W=1,E=1;输出:D0~D7=状态字。②
读数据。输入:RS=1,R/W=1,E=1;输出:D0~D7=数据。③
写指令。输入:RS=0,R/W=0,E=1,D0~D7=指令;输出:无。④
写数据。输入:RS=1,R/W=0,E=1,D0~D7=数据;输出:无。LCD1602的读操作控制时序如图时序参数时序参数符号极限值单位测试条件E信号周期400--ns引脚EE脉冲宽度150--nsE上升沿/下降沿时间、--25ns地址建立时间30--ns引脚E、RS、R/W地址保持时间10--ns数据建立时间(读操作)--100ns引脚DB0-DB7数据保持时间(读操作)20--ns数据建立时间(写操作)40--ns数据保持时间(写操作)10--ns2.LCD1602指令与编程1清显示00000000012光标返回000000001*3置输入模式00000001I/DS4显示开/关控制0000001DCB5光标或字符移位000001S/CR/L**6置功能00001DLNF**7置字符发生存贮器地址0001字符发生存贮器地址8置数据存贮器地址001显示数据存贮器地址9读忙标志或地址01BF计数器地址10写数到CGRAM或DDRAM)10要写的数据内容11从CGRAM或DDRAM读数11读出的数据内容(1)LCD1602指令。LCD1602内部的控制器共有11条控制指令,LCD1602指令集如表所示。各指令的功能或含义如下。指令1:清显示,指令码01H,将光标复位到地址00H。指令2:光标复位,使光标返回到地址00H。指令3:设置光标移动方向和显示模式。I/D表示光标移动方向,I/D为高电平时,光标右移;I/D为低电平时,光标左移。S表示屏幕上所有字符是否移动,高电平时移动,低电平时不移动。指令4:显示开关控制及光标设置。D用于控制整体显示的开与关,高电平表示开显示,低电平表示关显示。C用于控制光标的开与关,高电平表示有光标,低电平表示无光标。B用于控制光标是否闪烁,高电平表示闪烁,低电平表示不闪烁。指令5:光标或显示移位。S/C为高电平时移动显示的文字,S/C为低电平时移动光标;R/L为高电平时右移,R/L为低电平时左移。指令6:设置功能。DL为高电平时表示8位总线,DL为低电平时表示4位总线。N为低电平时表示单行显示,N为高电平时表示双行显示。F为低电平时显示5×7点阵字符,F为高电平时显示5×10点阵字符。指令7:设置CGRAM地址。指令8:设置DDRAM地址,即字符的实际显示地址。指令9:读忙标志和光标地址。BF为忙标志,BF为高电平表示忙,此时LCD1602不能接收指令或者数据,BF为低电平表示不忙。指令10:写数据到CGRAM或DDRAM。指令11:从CGRAM或DDRAM中读数据。函数lcd_busy的返回值为1时表示LCD1602忙,需等待;函数lcd_busy的返回值为0时,表示LCD1602不忙,可写指令与数据。LCD1602的应用编程一般需要编写三个基本操作函数,包括初始化、写指令、写数据。(2)LCD1602数据写入机制。LCD1602是一个慢显示器件,所以在执行每条指令之前一定要确保LCD1602的忙标志为低电平(表示不忙),否则此指令失效。在实践中,有些单片机对LCD1602的操作慢,无须等待上一次操作完成再继续,因此不需要检测忙标志。而有些工作主频高的单片机(如STM32系列单片机)操作速率太快,导致LCD1602不能及时接收指令,因此需要检测忙标志,当然实际应用中也可以用延时函数替代检测忙标志函数。检测忙标志函数的参考代码如下。bitlcd_busy(){LCD_RS=0;LCD_RW=1;//读状态LCD_EN=1;delayNOP();result=(bit)(P0&0x80);//读忙标志LCD_EN=0;return(result);}voidLCD_Init(void)//初始化函数{LCD_WriteCommand(0x38);//LCD1602显示方式设置LCD_WriteCommand(0x0C);//显示开关控制及光标设置LCD_WriteCommand(0x06);//光标移动方向和显示模式设置LCD_WriteCommand(0x01);//清屏}一般初始化过程(复位过程)如下。1.写指令38H(00111000),对应指令6,DL=1表示输出数据长度为8位,双行显示,显示5×7点阵字符。2.写指令0cH(00001100)对应指令4,D=1表示开显示,C=0表示无光标,B=0表示光标不闪烁。3.写指令06H(00000110),对应指令3,I/D=1表示写入数据后光标右移,S=0表示写入数据后字符不移动。4.写指令01H(00000001),对应指令1,功能为清屏,光标复位。初始化函数的参考代码如下。LCD1602数据口接单片机的P0口,通过控制RS、R/W和E引脚,发送8位数据。写指令函数的参考代码如下。voidwrite_com(ucharcom){//lcd_busy();//检测忙标志,若实际应用中不需要检测忙标志,可删除该语句P0=com;rs=0;lcden=0;delay(10);lcden=1;delay(10);lcden=0;}设置RS为高电平(选择数据寄存器),设置R/W为低电平(写操作),将要显示的字符ASCII码送到数据线D0~D7,产生E使能信号的高电平脉冲(先高电平后低电平),则可以将上述代码改写为写数据函数,参考代码如下。voidwrite_data(uchardata)//写数据函数{//lcd_busy();//检测忙标志P0=data;rs=1;lcden=0;delay(10);lcden=1;delay(10);lcden=0;}在实际应用中,由于CGROM中字符与地址的映射关系为:数字0~9对应地址0x30~0x39,大写字母A~Z对应地址0x41~0x5A,小写字母a~z对应地址0x61~0x7A,这些地址与字符的ASCII码相同,因此可以通过直接发送ASCII码来显示对应字符。例如,显示单个字符的参考代码如下。voidLCD_ShowChar(unsignedcharaddr,charByte){write_com(0x80|addr);//addr的取值范围为0x00~0x0F或0x40~0x4Fwrite_data(Byte);//写入字符数据}显示字符串的参考代码如下。voidLCD_ShowString(unsignedcharaddr,char*Str){
write_com(0x80|addr);while(*Str!='\0'){write_data(*Str++);}}显示数字的参考代码如下。voidLCD_ShowNum(unsignedcharaddr,unsignedlongNum,unsignedintLength){unsignedinti;unsignedlongNum1;write_com(0x80|addr);for(i=1;i<=Length;i++){Num1=(Num/Pow_LCD(10,Length-i))%10;write_data('0'+Num1);}}从以上代码中可以注意到,如果要显示字符,需要先输入显示字符地址,也就是告诉模块在哪里显示字符,即设置显示位置。LCD1602的DDRAM地址与屏幕位置对应关系:屏幕第一行对应的DDRAM地址为0x00~0x0F;屏幕第二行对应的DDRAM地址为0x40~0x4F。设置光标位置时需要将DDRAM地址与0x80进行或运算:write_com(0x80|addr);//addr为要设置的DDRAM地址我们可以在主函数中写数据之前使用调用指令函数进行显示地址设置,也可以自编一个专门用于显示地址设置的函数,参考代码如下。voidSet_xy_LCM(unsignedcharx,unsignedchary){unsignedcharaddress;if(x==0)address=0x80+y;elseaddress=0xc0+y;write_com(address);}LCD1602的编程还需注意时序要求:每次写操作前后都需要进行适当的延时,确保LCD1602有足够时间处理指令,典型的延时约为1ms。6.5工程实践任务1数码管动态显示任务2矩阵键盘识别任务3LCD1602液晶显示任务4*OLED显示任务1数码管动态显示编程实现显示任意6位十进制数,基于模块化的程序设计思路,考虑将动态显示设计为一个可调用的有参函数。按图6.4连接动态驱动接口电路,单片机的P2.6引脚和P2.7引脚分别作为段码和位码操作的控制引脚。参考例程的代码如下。#include<reg52.h>#defineucharunsignedcharunsignedlongnum;ucharj,k;uchara0,b0,c0,d0,e0,f0;sbitdula=P2^6;sbitwela=P2^7;unsignedcharcodetable[]={0x3f,0x06,0x5b,0x4f,0x66,0x6d,0x7d,0x07,0x7f,0x6f,0x77,0x7c,0x39,0x5e,0x79,0x71,0x00};voiddelay(uchari){for(j=i;j>0;j--)for(k=125;k>0;k--);}voiddisplay(uchara,ucharb,ucharc,uchard,uchare,ucharf){dula=0;P0=table[a];图6.4dula=1;dula=0;wela=0;P0=0xfe;wela=1;wela=0;delay(5);P0=table[b];dula=1;dula=0;P0=0xfd;wela=1;wela=0;delay(5);P0=table[c];dula=1;dula=0;P0=0xfb;
wela=1;wela=0;delay(5);P0=table[d];dula=1;dula=0;P0=0xf7;wela=1;wela=0;delay(5);P0=table[e];dula=1;dula=0;P0=0xef;wela=1;wela=0;delay(5);P0=table[f];dula=1;dula=0;P0=0xdf;wela=1;wela=0;delay(5);voidmain(){num=783520;//num是被显示的整数,只要不超过六位,均可显示出来//同学们可以随意更改num的值试验下while(1){
if(num<10){a0=num;b0=16;c0=16;d0=16;e0=16;f0=16;}else{if(num<100){a0=num/10;b0=num%10;c0=16;d0=16;e0=16;f0=16;}else{if(num<1000)
{a0=num/100;b0=num%100/10;c0=num%10;d0=16;e0=16;f0=16;}else{if(num<10000){a0=num/1000;b0=num%1000/100;c0=num%100/10;d0=num%10;e0=16;f0=16;}else{if(num<100000){a0=num/10000;b0=num%10000/1000;c0=num%1000/100;d0=num%100/10;e0=num%10;f0=16;}{if(num<1000000){a0=num/100000;b0=num%100000/10000;c0=num%10000/1000;d0=num%1000/100;e0=num%100/10;f0=num%10;}}}}}}display(a0,b0,c0,d0,e0,f0);}}上机操作一下吧!想一想段码表最后一个段码0x00的实现效果是什么?例程中display函数可实现动态显示功能,现在要求实现一个两位十进制数的动态显示,组队讨论试一试吧!下面采用更精确的片内定时/计数器,编程实现一个可动态实时显示的60s计时器,按图6.4连接动态驱动接口电路,参考例程
的代码如下。#include<reg51.h>#defineucharunsignedcharsbitdula=P2^6;sbitwela=P2^7;unsignedcharj,k,d1,d0,m,n=255;unsignedcharpp;unsignedcharcodetable[]={0x3f,0x06,0x5b,0x4f,0x66,0x6d,0x7d,0x07,0x7f,0x6f,0x77,0x7c,0x39,0x5e,0x79,0x71};voiddelay(unsignedchari){for(j=i;j>0;j--)for(k=125;k>0;k--);}voiddisplay(uchara,ucharb){P0=table[a];dula=1;dula=0;P0=0xef;wela=1;wela=0;delay(5);P0=table[b];dula=1;dula=0;P0=0xdf;wela=1;wela=0;
delay(5);}voidmain(){TMOD=0x01;//模式设置,采用T0,设置其工作于方式1(M1=0,M0=1)TR0=1;//启动T0TH0=(65536-46080)/256;//由于晶振频率为11.0592MHz,因此所计次数应为46080,T0每//隔50000μs,即50ms发起一次中断TL0=(65536-46080)%256;//46080=50000*11.0592/12//ET0=1;//开T0中断//EA=1;//开总中断while(1){if(TF0==1)//查询T0的中断请求标志位TF0的状态{TF0=0;pp++;TH0=(65536-46080)/256;TL0=(65536-46080)%256;}if(pp==20){pp=0;m++;n--;P1=n;//8位LEDif(m==60){m=0;//若计时到60s,则计时值归零}}d0=m%10;//取出当前计时值的个位与十位d1=m/10;display(d1,d0);//显示}}上机操作验证一下吧!如果加入按键控制功能,如暂停功能(按下按键1可暂停计时,按下按键2可继续时),应如何实现计时器呢?组队讨论试一试吧!任务2矩阵键盘识别编程实现0~F十六进制数的键盘输入识别并显示,4×4矩阵键盘的4行分别与单片机的P3.0~P3.3引脚连接,4列分别与单片机的P3.4~P3.7引脚连接,按图6.6连接电路,将任一按键被按下后对应的十六进制数显示在数码管上,参考例程
的代码如下。#include<reg51.h>sbitbeep=P2^3;sbitdula=P2^6;sbitwela=P2^7;unsignedchari=100;unsignedcharj,k,temp,key;voiddelay(unsignedchari){for(j=i;j>0;j--)for(k=125;k>0;k--);}unsignedcharcodetable[]={0x3f,0x06,0x5b,0x4f,0x66,0x6d,0x7d,0x07,0x7f,0x6f,0x77,0x7c,0x39,0x5e,0x79,0x71};display(unsignedcharnum){P0=table[num];dula=1;dula=0;P0=0xc0;wela=1;wela=0;}voidmain(){dula=0;wela=0;while(1){P3=0xfe;//11111110,令第一行为0temp=P3;//采集行列信号值temp=temp&0xf0;//与运算,若不为0,则被按下的按键在第一行某列if(temp!=0xf0){delay(10);//消抖if(temp!=0xf0){temp=P3;//再次采集行列信号值switch(temp){case0xee://11101110,被按下的按键在第一行第一列key=0;break;case0xde://11011110,被按下的按键在第一行第二列key=1;break;case0xbe://10111110,被按下的按键在第一行第三列key=2;break;case0x7e://01111110,被按下的按键在第一行第四列key=3;break;}while(temp!=0xf0){temp=P3;beep=0;}beep=1;display(key);//数码显示P1=0xfe;}}P3=0xfd;//11111101,令第二行为0temp=P3;temp=temp&0xf0;if(temp!=0xf0){delay(10);if(temp!=0xf0){temp=P3;switch(temp){case0xed:key=4;break;case0xdd:key=5;break;case0xbd:key=6;break;case0x7d:key=7;break;}while(temp!=0xf0){temp=P3;temp=temp&0xf0;beep=0;}beep=1;display(key);}}P3=0xfb;temp=P3;temp=temp&0xf0;if(temp!=0xf0){delay(10);if(temp!=0xf0){temp=P3;switch(temp){case0xeb:key=8;break;case0xdb:key=9;break;case0xbb:key=10;break;case0x7b:key=11;break;}while(temp!=0xf0){temp=P3;temp=temp&0xf0;beep=0;}beep=1;display(key);}}P3=0xf7;temp=P3;temp=temp&0xf0;if(temp!=0xf0){delay(10);if(temp!=0xf0){temp=P3;switch(temp){case0xe7:key=12;break;case0xd7:key=13;break;case0xb7:key=14;break;case0x77:key=15;break;}while(temp!=0xf0){temp=P3;temp=temp&0xf0;beep=0;}beep=1;display(key);}}}}上机操作验证一下吧!仔细阅读例程中矩阵键盘识别部分的代码,思考本例程采用的是行列扫描法还是线反转法。尝试给按键赋予运算或控制的功能,如用S6~S11表示数字0~9的输入,用S13表示相加,用S17表示等于,用S16表示清0,设计一个简易计算器。组队讨论试一试吧!下面通过编程实现一个6位显示的计时器,最大计时时间为99小时,支持暂停功能。同样基于模块化的程序设计思路,将矩阵键盘识别部分设计为一个可调用的函数。按图6.6连接电路,参考例程的代码如下。#include<reg52.h>#defineucharunsignedcharsbitdula=P2^6;sbitwela=P2^7;unsignedcharhalt,j,k,a1,a0,b1,b0,c1,c0,s,f,m,n=255;unsignedcharpp;unsignedcharcodetable[]={0x3f,0x06,0x5b,0x4f,0x66,0x6d,0x7d,0x07,0x7f,0x6f,0x77,0x7c,0x39,0x5e,0x79,0x71};voiddelay(unsignedchari){for(j=i;j>0;j--)for(k=125;k>0;k--);}voiddisplay(ucharshi2,ucharshi1,ucharfen2,ucharfen1,ucharmiao2,ucharmiao1){dula=0;P0=table[shi2];dula=1;dula=0;wela=0;P0=0xfe;wela=1;wela=0;delay(5);P0=table[shi1]|0x80;dula=1;dula=0;P0=0xfd;wela=1;wela=0;delay(5);P0=table[fen2];dula=1;dula=0;P0=0xfb;wela=1;wela=0;delay(5);P0=table[fen1]|0x80;dula=1;dula=0;P0=0xf7;wela=1;wela=0;delay(5);P0=table[miao2];dula=1;dula=0;P0=0xef;wela=1;wela=0;delay(5);P0=table[miao1];dula=1;dula=0;P0=0xdf;wela=1;wela=0;delay(5);}voidkeyscan(){P3=0xfe;temp=P3;temp=temp&0xf0;if(temp!=0xf0){delay(10);if(temp!=0xf0){temp=P3;switch(temp){case0xee:halt=1;//按下按键S6,暂停计时break;case0xde:halt=0;//按下按键S7,继续计时break;}}}}voidmain(){TMOD=0x01;//采用T0,设置其工作于方式1TR0=1;TH0=(65536-46080)/256;//由于晶振频率为11.0592MHz,因此所计次数应为46080,计时器每隔50000μs发起一次中断TL0=(65536-46080)%256;//46080=50000*11.0592/12ET0=1;EA=1;while(1){keyscan();//键盘扫描if(halt==0)//计时继续{TR0=1;if(pp==20){pp=0;//定时器重新置数m++;n--;if(m==60){m=0;f++;if(f==60){f=0;s++;if(s==24){s=0;}}}}a0=s%10;a1=s/10;b0=f%10;b1=f/10;c0=m%10;c1=m/10;display(a1,a0,b1,b0,c1,c0);}elseTR0=0;//计时暂停display(a1,a0,b1,b0,c1,c0);}}voidtime0()interrupt1{TH0=(65536-46080)/256;TL0=(65536-46080)%256;pp++;}上机操作验证一下吧!思考如何存储每次暂停的时间数据,并通过按键控制在暂停状态下进行二次循环显示,组队讨论试一试吧!任务3LCD1602液晶显示编程实现在LCD1602指定的位置显示数字或字符,实现字符滚动显示。LCD1602显示电路如图6.16所示。#include<reg52.h>#defineucharunsignedchar//宏定义#defineuintunsignedint//宏定义sbitrs=P3^5;//操作选择端sbitlcden=P3^4;//LCD1602使能端uchartable1[]="NO.1";//待显示的字符表uchartable2[]="helloworld!";//待显示的字符表voiddelay(uintx)//延时函数{uinta,b;for(a=x;a>0;a--)for(b=10;b>0;b--);}voiddelay1(uintx)//延时函数{uinta,b;for(a=x;a>0;a--)for(b=100;b>0;b--);}voidwrite_com(ucharcom)//写指令函数{P0=com;rs=0;lcden=0;delay(10);lcden=1;delay(10);lcden=0;}voidwrite_date(uchardate)//写数据函数{P0=date;rs=1;lcden=0;delay(10);lcden=1;delay(10);lcden=0;}voidinit()//初始化函数{dula=0;wela=0;write_com(0x38);//LCD1602显示方式设置delay(20);write_com(0x0f);//显示开关控制及光标设置delay(20);write_com(0x06);//光标移动方向和显示模式设置delay(20);write_com(0x01);//清屏delay(20);}voidmain()//主函数{uchara;init();write_com(0x80+17);//先写在第一行不能显示的地方delay(20);for(a=0;a<9;a++)//依次写入table1中的字符{write_date(table1[a]);delay(20);}write_com(0xc0+17);//先写在第二行不能显示的地方delay(50);for(a=0;a<13;a++)//依次写入table2中的字符{write_date(table2[a]);delay(40);}for(a=0;a<16;a++)//写16个0x18把字符从右边移入显示屏幕{//0x18是移动字符指令的代码write_com(0x18);delay1(200);}while(1);}上机操作验证一下吧,注意在开发板上插好LCD1602后,如果显示字符不清楚,可通过10kΩ
电位器调节VO引脚电压,以获得最佳显示效果。另外,时序控制要严格按照时序图操作,特别关注E使能信号的高低电平延续时间。在上面历程
中,显示位置通过写指令函数来设置,如write_com(0x80+0)。基于模块化的程序设计思路,将显示定位也设计为一个可调用的有参函数,编程实现字符数据的液晶显示,按图6.16连接电路,参考例程mcu607.c的代码如下。#include<reg52.h>#defineucharunsignedchar#defineuintunsignedintsbitrs=P3^5;sbitlcden=P3^4;sbitdula=P2^6;sbitwela=P2^7;voiddelay(uintx){uinta,b;for(a=x;a>0;a--)for(b=10;b>0;b--);}voiddelay1(uintx){uinta,b;for(a=x;a>0;a--)for(b=100;b>0;b--);}/***********************************************************检查LCD1602忙标志。**当lcd_busy的返回值为1时,LCD1602忙,等待。**当lcd_busy的返回值为0时,LCD1602不忙,可写指令或数据。***********************************************************/bitlcd_busy(){bitresult;LCD_RS=0;//LCD_RW=1;LCD_EN=1;delayNOP();result=(bit)(P0&0x80);LCD_EN=0;return(result);}/***********************************************************写指令到LCD1602**RS=0,RW=0,E=1,D0~D7=指令***********************************************************/voidwrite_com(ucharcom){//lcd_busy();//检测忙标志P0=com;rs=0;lcden=0;delay(10);lcden=1;delay(10);lcden=0;}/***********************************************************写数据到LCD1602**RS=1,RW=0,E=1,D0~D7=数据***********************************************************/voidwrite_date(uchardate){//lcd_busy();//检测忙标志P0=date;rs=1;lcden=0;delay(10);lcden=1;delay(10);lcden=0;}/*-------------------------------------------函数名:Set_xy_LCM()功能:设定显示位置--------------------------------------------*/voidSet_xy_LCM(unsignedcharx,unsignedchary){unsignedcharaddress;if(x==0)address=0x80+y;elseaddress=0xc0+y;write_com(address);}/*-------------------------------------------函数名:Display_List_Char()功能:在指定位置显示一串字符--------------------------------------------*/voidDisplay_List_Char(unsignedcharx,unsignedchary,unsignedchar*s){Set_xy_LCM(x,y);while(*s){P0=*s;write_date(*s);s++;}}voidLCD_init(){dula=0;wela=0;write_com(0x38);//LCD1602显示方式设置delay(20);write_com(0x0f);//显示开关控制及光标设置delay(20);write_com(0x06);//光标移动方向和显示模式设置delay(20);write_com(0x01);//清屏delay(20);}voidmain(){
LCD_init();while(1){Display_List_Char(0,0,"robotno.1");Display_List_Char(1,0,"servethepeople
温馨提示
- 1. 本站所有资源如无特殊说明,都需要本地电脑安装OFFICE2007和PDF阅读器。图纸软件为CAD,CAXA,PROE,UG,SolidWorks等.压缩文件请下载最新的WinRAR软件解压。
- 2. 本站的文档不包含任何第三方提供的附件图纸等,如果需要附件,请联系上传者。文件的所有权益归上传用户所有。
- 3. 本站RAR压缩包中若带图纸,网页内容里面会有图纸预览,若没有图纸预览就没有图纸。
- 4. 未经权益所有人同意不得将文件中的内容挪作商业或盈利用途。
- 5. 人人文库网仅提供信息存储空间,仅对用户上传内容的表现方式做保护处理,对用户上传分享的文档内容本身不做任何修改或编辑,并不能对任何下载内容负责。
- 6. 下载文件中如有侵权或不适当内容,请与我们联系,我们立即纠正。
- 7. 本站不保证下载资源的准确性、安全性和完整性, 同时也不承担用户因使用这些下载资源对自己和他人造成任何形式的伤害或损失。
最新文档
- 2015年西藏中考英语真题及解析
- 深度解析(2026)《GBT 30246.11-2013家庭网络 第11部分:控制网络接口一致性测试规范》
- 深度解析(2026)《GBT 30167.1-2013纺织机械 织机边撑 第1部分:边撑刺轴》
- 2026年甘肃中考语文试题及答案
- 发热待查诊治专家共识总结2026
- 《GBT 5704-2008人体测量仪器》(2026年)合规红线与避坑实操手册
- 《GBT 2029-2008铸钢吸入通海阀》(2026年)合规红线与避坑实操手册
- 《DLT 5037-2022轴流式水轮机埋件安装工艺导则》(2026年)合规红线与避坑实操手册
- 2026年食品加工厂设备改造合同
- 年产10万吨萤石块数字化浮选深加工项目可行性研究报告模板拿地申报
- 江苏省连云港市海州区新海实验中学2025届中考生物全真模拟试卷含解析
- 2024-2025成都各区初二年级下册期末数学试卷
- 知行合一 - 社会实践•创新创业学习通超星期末考试答案章节答案2024年
- 公安机关保密协议
- 老年人能力评估师理论知识考核要素细目表一级
- BB∕T 0047-2018 气雾漆行业标准
- 人工智能训练师理论知识考试题库(浓缩500题)
- 护理翻转课堂
- 相关知识培训课件
- 汉代典客、大行、鸿寐考述
- 船舶焊接工艺船舶材料与焊接第三章演示文稿
评论
0/150
提交评论