版权说明:本文档由用户提供并上传,收益归属内容提供方,若内容存在侵权,请进行举报或认领
文档简介
《单片机应用技术(C语言版)》任务4-5
密码锁设计明确本次密码锁设计项目的核心指标,划定项目的功能边界与验收标准,保障后续开发过程方向统一。本次设计的密码锁,硬件层面需要集成两部分核心交互组件与一个状态执行组件。第一部分是16个按键组成的输入模块,第二部分是1位数码管组成的提示显示模块,另外搭配一个发光二极管,完成开锁与关锁状态的物理呈现。功能层面设定清晰的判定逻辑,用户输入的密码限定在十六进制字符范围,即0到9的阿拉伯数字,以及A到F的六个大写英文字母。系统获取输入值后,自动与预设的正确密码完成比对,输出对应的结果。比对结果匹配时,数码管显示字符“P”,同时触发开锁动作,点亮发光二极管指示开锁状态。比对结果不匹配时,数码管显示字符“E”,保持锁闭状态,发光二极管维持熄灭,允许用户重新输入尝试。这个设计看似规则简单,实则覆盖了单片机开发中两个核心知识点,矩阵键盘的扫描识别与数码管的显示驱动,同时引入基础的逻辑判定流程,能帮助学习者完整掌握单片机项目从硬件搭建到软件逻辑开发的全流程。完成密码锁功能设计,首先要明确硬件电路的功能分工,三个核心模块分别承担不同作用,共同支撑完整的密码锁逻辑运行。第一个核心模块是共阳数码管,它承担状态提示功能,负责向用户反馈密码输入的验证结果。不同于任务导入中提到的多位密码显示设计,本次任务仅需展示验证结果,用单个数码管就可以满足需求,既降低了硬件接线的复杂度,也能清晰传递核心信息。第二个模块是输入装置,16个按键采用4×4矩阵键盘的连接方式。这种连接方式最大的优势,就是可以大幅减少单片机IO口的占用,原本16个独立按键需要16个IO口,矩阵结构仅需要4根行线加4根列线,总共8个IO口就能实现16个按键的输入识别,充分利用了单片机有限的IO资源。第三个模块是执行装置,连接P3.0引脚的发光二极管承担锁状态的物理展示。二极管点亮对应开锁状态,熄灭对应锁定状态,用最简单的硬件实现了直观的状态输出,让密码锁的功能结果一目了然。三个模块分工清晰,从输入、反馈到执行,构建了完整的密码锁硬件基础。我们已经明确了密码锁设计的任务要求,也梳理了对应的硬件电路方案。要完成这个密码锁设计,首先得解决密码输入的问题,而我们这次用到的输入部件就是4×4矩阵键盘。接下来,我们就一起来搞清楚矩阵键盘的结构原理,看看它是怎么实现按键输入识别的,为后续的程序编写打好基础。我们设计密码锁需要16个按键来输入0到F的十六进制密码,直接给每个按键接一根控制线需要16个IO口,这会浪费单片机有限的IO资源。4×4矩阵键盘就是解决这个问题的经典方案,它用行列交叉的结构来布局按键,把16个按键安排在四行四列的网格里。图中横向的四根线是行线,我们给它们依次编号0到3,纵向的四根线是列线,同样编号0到3。每个按键都接在一根行线和一根列线的交叉点上,也就是说,只需要四根行线加四根列线,一共八根控制线就能控制16个按键,直接省下了一半的IO资源,这对于IO口紧张的单片机项目来说,无疑是性价比极高的选择。这种行列扫描的结构,不仅简化了硬件布线,降低了电路复杂度,还为后续的按键识别打下了清晰的逻辑基础,我们只需要通过检测行线和列线的电平变化,就能准确判断出哪一个按键被按下。常规外接键盘的设计思路,是给每个按键分配一根独立控制线,16个按键就需要占用单片机16个IO口。单片机的IO资源本身十分有限,大量IO被按键占用后,会挤压其他外设的接线空间,甚至导致整个项目资源不够用。4×4矩阵键盘的设计,完美解决了这一资源浪费问题。矩阵键盘采用行列扫描的架构,把16个按键分布在4行4列的交叉点上,每个按键只需要连接对应行线和列线即可。最终整个键盘只需要引出4根行线和4根列线,一共8根控制线接入单片机,直接把IO口占用量减少了一半,省出的资源可以供给数码管、指示二极管等其他外设使用。架构优化的同时,矩阵键盘的按键识别逻辑也足够简洁可靠。单片机只需要逐行拉低行线电平,逐一读取列线电平变化,就能精准定位按下的按键位置,识别准确率完全满足密码锁的输入需求,是小尺寸键盘最经济高效的设计方案。我们实现密码锁的状态显示功能,选择了1602LCD作为显示载体,要驱动这款液晶模块正常工作,必须先掌握它的命令字编码规则。1602LCD的每一条控制命令都对应一个8位二进制的命令字,不同位的组合对应不同的功能设定,比如设置显示模式、清屏、调整光标位置等操作,都需要通过写入对应的命令字完成。命令字是主机和液晶模块通信的核心语言,模块会根据接收到的命令字,解析并执行对应的操作,建立起符合我们需求的工作模式,后续才能正确写入要显示的字符数据。我们梳理编号对应的命令字,是为后续初始化程序、写命令操作提供清晰的参数对照,帮助我们精准调用对应功能,避免因为命令字参数错误导致显示异常,保障整个密码锁显示逻辑的稳定运行。完成LCD驱动功能的基础搭建后,进入最关键的初始化环节。初始化是模块上电后正常工作的前提,所有参数的配置都要严格遵循时序逻辑,任何一个命令的顺序或者参数错误,都会导致整个LCD无法正常工作。当前给出的是标准1602LCD的C语言初始化函数,整个函数通过四次调用写命令函数完成核心配置。第一条命令传入0x3c,核心作用是设置LCD的工作方式,明确8位数据接口、两行显示的基本配置,确定模块的基础工作模式。第二条命令传入0x0e,完成光标显示配置,开启显示功能同时设置可见光标,方便调试阶段定位显示位置。第三条命令传入0x01,执行清屏操作,清空DDRAM中的随机数据,为后续写入显示内容准备干净的画布。最后一条命令传入0x06,设置字符输入方式,设定写入新数据后光标自动右移,符合我们从左到右的显示习惯。这套初始化流程是经过无数项目验证的标准配置,每一条命令的参数都对应确定的功能,是驱动LCD稳定运行的核心基础。第10页我们和1602LCD通信的第一步,往往先读取它的工作状态,判断它是否准备好接收新指令。这一步决定了整个通信流程的稳定性,很多初学者遇到LCD不显示的问题,根源就出在状态判断环节出错。实现状态读取需要遵守LCD固定的通信时序,先配置引脚电平:RS拉低代表读取的是状态而非显示数据,RW拉高代表当前是读取操作,配置完成后给使能端E一个正脉冲,让LCD把内部状态输出到数据总线,我们就能从8位数据口中读取状态字。读取到的状态字中,最高位D7承载了核心信息,我们把这个标志位叫做BF忙标志。BF取值为1时,代表LCD正忙于内部处理,无法接收新的指令或数据;BF取值为0时,代表LCD已经完成当前操作,可以接收新指令。这个读状态函数会把读取到的状态字返回,供后续操作判断LCD状态,后续写命令、写数据的操作,都会调用这个函数完成忙检测,保证每次操作都在LCD就绪时执行,从根源避免指令丢失导致的显示异常。第11页我们要实现对1602LCD的命令写入操作,首先需要解决LCD响应速度慢于单片机处理速度的核心矛盾,因此必须加入忙检测机制保障通信稳定。函数接收一个unsignedchar类型的参数com,这个参数就是我们要发送给LCD的命令字。整个操作先通过do-while循环完成忙检测:循环内先调用我们之前写好的lcd_r_start函数读取LCD的状态字,再将读取结果和0x80做按位与操作,这个操作会直接屏蔽低7位,只保留最高位的忙标志BF。只要BF不为0,证明LCD仍处于忙状态,循环就会持续检测,直到LCD准备就绪才退出循环。忙检测完成后,我们按照1602LCD的通信规范配置引脚:写入命令时需要保持RW为0代表写操作,RS为0代表写入的是命令字,随后给E引脚一个正脉冲触发LCD锁存数据,把命令com写入LCD的数据端口,最后拉低E引脚完成一次完整的命令写入操作,复位RW引脚并加入延时稳定时序,保障下一次操作不会出错。这套流程完全遵循LCD的硬件通信逻辑,能稳定实现各类显示控制命令的下发。第12页完成LCD命令字写入操作后,就需要掌握给LCD写入显示数据的操作,这是让LCD输出我们需要的字符内容的核心步骤。本函数和上一页的写命令函数核心逻辑框架高度相似,二者的核心区别,在于RS引脚的电平设置不同。写命令要求RS置0,写数据要求RS置1,这是由1602LCD本身的通信规则决定的:RS引脚用来区分当前传输的是命令还是显示数据,高电平代表数据,低电平代表命令。函数启动后,首先执行忙检测流程。循环调用读状态函数读取LCD当前状态,通过和0x80做与操作,提取最高位的忙标志位BF。只要BF位不为0,说明LCD仍在处理之前的操作,就持续循环检测,直到LCD空闲后退出循环,执行后续写入操作。确认LCD空闲后,先将RW置0,表示当前执行写入操作,等待时序稳定后将RS置1,标记本次写入的是待显示数据。随后拉高E引脚触发使能脉冲,把准备好的显示数据放到8位数据线上,再拉低E引脚完成写入,最后拉高RW恢复读状态,等待50毫秒稳定时序,完成本次写数据操作。这套操作保证LCD每次只处理一件任务,避免总线冲突,也确保显示数据能准确被LCD识别渲染,我们就能在屏幕上得到想要展示的字符。第13页我们控制1602LCD显示内容的位置,本质是直接操作LCD内部的DDRAM地址。1602LCD的显示规则为,DDRAM中存储的每个字节数据,都会直接对应屏幕上一个光标位置的显示内容,我们只要把对应位置的DDRAM地址作为命令发送给LCD,就能把光标移动到目标位置,准备写入要显示的字符。这里的命令字采用十六进制格式,它本身就是目标位置对应的DDRAM地址,并非我们额外编码得到的数值,这种设计简化了LCD的控制逻辑,降低了MCU和LCD的通信成本。需要特别注意一个容易出错的细节:1602LCD第一行和第二行的DDRAM地址并不连续,第一行的起始地址是0x80,第二行的起始地址固定为0xC0,二者地址偏移量相差整整0x40,并不是顺延排列。我们可以套用公式快速计算目标位置的命令字:第一行第n列的命令字就是0x80加上n-1,比如示例中要定位第一行第5列,计算可得命令字就是0x80+4=0x84,调用写命令函数发送这个值,就能把光标准确放到目标位置。第14页我们已经完成了1602LCD核心驱动函数的拆解,掌握了读状态、写命令、写数据以及初始化这些基础功能的实现逻辑,接下来正式进入完整项目的程序设计环节。这一环节是把零散的功能模块整合为可运行的完整工程,验证我们前面拆解的每一个驱动逻辑是否能正常协同工作,最终实现1602LCD的指定内容显示,完成从功能碎片到可用系统的落地。程序设计环节会按照嵌入式开发的标准流程展开,先梳理工程的前置配置,包括头文件引入、单片机端口宏定义这些基础准备,再整合我们前面分析的所有驱动函数,最后编写主函数实现业务逻辑,一步步推导出完整的可运行代码,帮助大家建立从模块设计到系统开发的完整认知。第15页完成LCD1602显示实验的底层驱动开发,第一步要完成程序的预处理工作,这部分是整个项目运行的基础,直接决定后续驱动代码能否正确编译和执行。首先需要引入两个核心头文件。第一个是reg51.h头文件,它封装了51系列单片机所有专用寄存器的地址定义,我们后续操作端口、寄存器时,不需要手动声明地址,直接调用对应名称即可完成访问,大幅简化了代码编写流程。第二个是intrins.h头文件,我们在实现延时、E端脉冲时序控制时,需要调用_nop_()函数实现空操作延时,这个函数的定义就存储在该头文件中,必须提前引入才能正常使用。完成头文件引入后,需要对LCD1602的三个控制引脚进行位定义。我们用51单片机的P3.0引脚连接LCD的寄存器选择引脚RS,用P3.1连接读写控制引脚RW,用P3.2连接使能信号引脚E,因此通过sbit关键字将三个引脚映射到对应地址,后续操作时直接修改引脚变量就能输出对应控制信号。这部分内容看似简单,却是整个工程运行的前提,任何头文件遗漏或者引脚地址定义错误,都会导致后续整个驱动代码完全无法运行,开发时必须仔细核对核对硬件连线和地址定义。第16页定义待显示字符串"SHENZHEN",声明循环计数变量i,完成程序运行前的变量准备工作。将P1端口置为全1电平,完成端口输入模式的初始化配置,避免引脚电平异常干扰后续通信。调用已经封装好的lcd_int函数完成1602LCD的底层初始化,配置显示模式、光标状态等核心参数。插入长延时等待初始化彻底完成,避免LCD模块未就绪就执行后续操作。调用我们前几页讲过的写命令函数lcd_w_cmd,传入0x83命令字,根据DDRAM地址规则,这个命令字会把显示光标定位到第一行第四列。再次插入延时,等待LCD完成地址设置。启动for循环遍历字符串,循环终止条件为识别到C语言字符串默认的结束符'\0',确保不会越界读取内存。每取出一个字符,就调用封装好的写数据函数lcd_w_dat发送字符编码,插入短延时保证字符稳定写入,依次完成全字符串输出。最后进入死循环,让程序保持待机状态,维持当前显示内容不变,避免程序跑飞导致显示异常。这段代码完整展示了模块化编程的优势,我们把底层操作封装成函数,主函数只需要聚焦业务逻辑,可读性和可维护性都得到大幅提升。第17页完成1602LCD基础显示的程序设计后,我们进入进阶练习环节,通过拓展任务深化对LCD驱动原理的理解,跳出基础框架掌握更灵活的开发方法。基础开发阶段,我们只需要调用自带标准字库,就能直接输出英文、数字和常用符号,开发流程简单清晰,只需要掌握基础命令和时序就能完成。但实际项目开发中,用户经常需要显示标准字库没有的自定义内容,最常见的需求就是显示中文汉字,这就要求开发者掌握LCD模块的自定义字符开发能力,这也是本次举一反三环节的核心锻炼目标。举一反三环节的核心价值,不在于完成一个新的显示任务,而在于倒逼我们吃透LCD模块的内部结构,掌握官方手册的参数规则,从只会调用示例代码的初学者,成长为能根据需求灵活定制功能的开发者,为后续更复杂的点阵屏开发打下逻辑基础。第18页我们完成了1602LCD显示英文字符的基础实验,现在通过一个新任务拓展能力,要求在1602LCD的第一行第二列显示中文“工人”二字。这个任务看似简单,实则藏着一个容易踩的入门坑,刚好帮我们厘清1602LCD的核心特性。很多初次接触1602LCD的开发者,会直接照搬显示英文字符的流程调用接口,结果始终无法得到预期输出,核心原因就在于忽略了模块的固有限制:1602LCD的内置标准字库,仅包含英文字母、阿拉伯数字、常用符号等ASCII字符,根本没有内置中文字符。汉字属于象形文字,结构比字母复杂得多,存储单个汉字字模需要的存储空间远大于1602内置字库容量,原厂自然不会给通用模块预装中文字库。这一特性决定,想要在1602LCD上显示自定义字符包括中文,必须掌握另外一种显示方式,也就是利用模块自带的自定义字符存储区自行生成字模,这正是我们接下来要拆解的进阶内容。一、任务导入任务要求设计具有16个按键和1个数码管显示的密码锁,具体要求:输入一位密码(为0~9,A~F之间的数字),密码输入正确显示“P”并将锁打开;否则显示“E”,继续保持锁定状态。硬件电路共阳数码管显示抢答器的状态信息;16个按键连接成4×4矩阵键盘方式;连接P3.0的发光二极管的亮灭表示开锁或关锁状态。二、矩阵键盘4×4矩阵键盘四行四列01230123行线列线4×4矩阵键盘01230123行线列线单片机+VCC4x4矩阵键盘将16根独立连接的控制线减少到只需要8根控制线,即4根行线、4根列线如何识别矩阵式键盘中的按键?识别按键一般应包括以下内容:(1)判别有无键按下。(2)键盘扫描取得闭合键的行、列号。(3)用计算法或查表法得到键值。(4)判断闭合键是否释放,如没释放则继续等待。(5)将闭合键的键值保存,同时转去执行该闭合键的功能。逐列扫描法行列反转法01230123行线列线+VCC1K×4逐列扫描法输出-列线输入-行线P0.0P0.1P0.2P0.3P2.0P2.1P2.2P2.3是否有键按下;
判断具体按键;第一步,先来判断是否有键按下,向P2.0到P2.3的四条列线全部输出低电平,读入P0.0-P0.3四条行线的信号01230123行线列线+VCC是否有键按下;
判断具体按键;逐列扫描法:1K×4输出输入00000000S0S1S2S3S4S5S6S7S8S9S10S11S12S13S14S15信号读入P2.0P2.1P2.2P2.3P0.0P0.1P0.2P0.3P0.3P0.0P0.3P0.2P0.0P0.3P0.2P0.0P0.3P2.1P0.2P0.0P0.3P2.3P2.1P0.2P0.0P0.3P2.0P2.3P2.1P0.2P0.0P0.3当所有的按键都没有被按下时,p0.0到P0.3读入的行电平都为高。01230123行线列线+VCC1K×4输出输入0000无键按下1111S0S1S2S3S4S5S6S7S8S9S10S11S12S13S14S15是否有键按下;
判断具体按键;逐列扫描法:P0.0P0.1P0.2P0.3P0.3P0.0P0.3P0.2P0.0P0.3P0.2P0.0P0.3P0.2P0.0P0.3P0.2P0.0P0.3P0.2P0.0P0.3P2.0P2.1P2.2P2.3P2.1P2.3P2.1P2.0P2.3P2.1有键按下,例如这个9#键按下,这个键所在的2#行线与1#列线导通,读入的P0.2行电平为低。01230123行线列线+VCC是否有键按下;
判断具体按键;逐列扫描法:1K×4输出输入000011110S0S1S2S3S4S5S6S7S8S9S10S11S12S13S14S15P2.0P2.1P2.2P2.3P2.1P2.3P2.1P2.0P2.3P2.1P0.0P0.1P0.2P0.3P0.3P0.0P0.3P0.2P0.0P0.3P0.2P0.0P0.3P0.2P0.0P0.3P0.2P0.0P0.3P0.2P0.0P0.301230123行线列线+VCC是否有键按下;
判断具体按键;逐列扫描法:1K×4输出输入000011110行电平不全为高→有键按下S0S1S2S3S4S5S6S7S8S9S10S11S12S13S14S15P0.0P0.1P0.2P0.3P0.3P0.0P0.3P0.2P0.0P0.3P0.2P0.0P0.3P0.2P0.0P0.3P0.2P0.0P0.3P0.2P0.0P0.3P2.0P2.1P2.2P2.3P2.1P2.3P2.1P2.0P2.3P2.1第二步,判断到底是哪一个键按下。01230123行线列线+VCC
是否有键按下;
判断具体按键;逐列扫描法:1K×4输出输入00001101行电平不全为高→有键按下S0S1S2S3S4S5S6S7S8S9S10S11S12S13S14S15P0.0P0.1P0.2P0.3P0.3P0.0P0.3P0.2P0.0P0.3P0.2P0.0P0.3P0.2P0.0P0.3P0.2P0.0P0.3P0.2P0.0P0.3P2.0P2.1P2.2P2.3P2.1P2.3P2.1P2.0P2.3P2.1逐列扫描:具体方法是,先给第0列输出低电平“0”,其他三列都为高电平“1”。读入行电平,判断第一列四个按键的状态。01230123行线列线+VCC
是否有键按下;
判断具体按键;逐列扫描法:1K×4输出输入11101101行电平不全为高→有键按下S0S1S2S3S4S5S6S7S8S9S10S11S12S13S14S15P0.0P0.1P0.2P0.3P0.3P0.0P0.3P0.2P0.0P0.3P0.2P0.0P0.3P0.2P0.0P0.3P0.2P0.0P0.3P0.2P0.0P0.3P2.0P2.1P2.2P2.3P2.1P2.3P2.1P2.0P2.3P2.1送第1列为低电平“0”,其他三列为高电平,进行第一列4个按键是否按下的判断。01230123行线列线+VCC
是否有键按下;
判断具体按键;逐列扫描法:1K×4输出输入1101行电平不全为高→有键按下S0S1S2S3S4S5S6S7S8S9S10S11S12S13S14S15P0.0P0.1P0.2P0.3P0.3P0.0P0.3P0.2P0.0P0.3P0.2P0.0P0.3P0.2P0.0P0.3P0.2P0.0P0.3P0.2P0.0P0.3P2.0P2.1P2.2P2.3P2.1P2.3P2.1P2.0P2.3P2.1送第2列为低电平“0”,
其他三列为高电平,进行第2列4个按键是否按下的判断。01230123行线列线+VCC
是否有键按下;
判断具体按键;逐列扫描法:1K×4输出输入1011行电平不全为高→有键按下S0S1S2S3S4S5S6S7S8S9S10S11S12S13S14S15P0.0P0.1P0.2P0.3P0.3P0.0P0.3P0.2P0.0P0.3P0.2P0.0P0.3P0.2P0.0P0.3P0.2P0.0P0.3P0.2P0.0P0.3P2.0P2.1P2.2P2.3P2.1P2.3P2.1P2.0P2.3P2.101230123行线列线+VCC
是否有键按下;
判断具体按键;逐列扫描法:1K×4输出输入0111行电平不全为高→有键按下S0S1S2S3S4S5S6S7S8S9S10S11S12S13S14S15P0.0P0.1P0.2P0.3P0.3P0.0P0.3P0.2P0.0P0.3P0.2P0.0P0.3P0.2P0.0P0.3P0.2P0.0P0.3P0.2P0.0P0.3P2.0P2.1P2.2P2.3P2.1P2.3P2.1P2.0P2.3P2.101230123行线列线+VCC
是否有键按下;
判断具体按键;逐列扫描法:1K×4输出输入行电平不全为高→有键按下S0S1S2S3S4S5S6S7S8S9S10S11S12S13S14S15逐列送低电平→判断每列按键状态P0.0P0.1P0.2P0.3P0.3P0.0P0.3P0.2P0.0P0.3P0.2P0.0P0.3P0.2P0.0P0.3P0.2P0.0P0.3P0.2P0.0P0.3P2.0P2.1P2.2P2.3P2.1P2.3P2.1P2.0P2.3P2.101230123行线列线+VCC
是否有键按下;
判断具体按键;逐列扫描法:1K×4输出输入行电平不全为高→有键按下S0S1S2S3S4S5S6S7S8S9S10S11S12S13S14S15逐列送低电平→判断每列按键状态键值=列号+行号*4P0.0P0.1P0.2P0.3P0.3P0.0P0.3P0.2P0.0P0.3P0.2P0.0P0.3P0.2P0.0P0.3P0.2P0.0P0.3P0.2P0.0P0.3P2.0P2.1P2.2P2.3P2.1P2.3P2.1P2.0P2.3P2.1三、程序设计charscan_key(){chari,temp,m,n;bitfind=0; //有键按下标志位
P2=0xf0;//向所有的列线上输出低电平
i=P0;//读入行值
i&=0x0f;//屏蔽掉高四位
if(i!=0x0f)//行值不为全1,有键按下
{delay(1200);//延时消抖
i=P0;//再次读入行值
i&=0x0f;//屏蔽掉高四位
if(i!=0x0f)//第二次判断有键按下{
for(i=0;i<4;i++){P2=0xfe<<i;//逐列送出低电平
temp=~P0; //读行值,并取反,全1→全0temp=temp&0x0f;//屏蔽掉行值高4位
if(temp!=0x00)//判断有无键按下,为0则无键按下,否则有键按下
{m=i;//保存列号至m变量
find=1;//置找到按键标志
switch(temp) //判断哪一行有键按下,记录行号到n变量
{case0x01:n=0;break;//第0行有键按下
case0x02:n=1;break;//第1行有键按下
case0x04:n=2;break;//第2行有键按下
case0x08:n=3;break;//第3行有键按下
default:break;}break;}//有键按下,退出for循环
}}}if(find==0)return-1;//无键按下则返回-1elsereturn(n*4+m);//否则返回键值,键值=列号*4+行号}#include<reg51.h>//包含头文件reg51.h,定义51单片机的专用寄存器sbitP30=P3^0;//位定义,控制发光二极管,其亮灭表示锁的打开和锁定状态voidmain()//主函数{unsignedcharled[]={0xc0,0xf9,0xa4,0xb0,0x99,0x92,0x82,0xf8,0x80,0x90,0x88,0x83,0xc6,0xa1,0x86,0x8e};//0~9、A~F的共阳极显示码
unsignedcharled1[]={0xbf,0x8c,0x86};//“
”、“P”和“E”的共阳极显示码
unsignedchari;P1=led1[0];//数码管显示“”
P30=1;//开锁指示灯关闭
P0=0xff; //P0口低四位做输入口,先输出全1while(1){i=scan_key();//调用键盘函数
if(i==-1)continue;//没有键按下,继续循环
elseif(i!=8){//按键不是密码8 P1=led[i];//显示按下键的数字号
delay(10000);//延时
P1=led1[2];//显示E delay(50000);//延时
P1=led1[0];//显示“”
}else{//按键是密码8 P1=led[i];//显示按下键的数字号
delay(10000); //延时
P1=led1[1];//显示P P30=0;//开锁
delay(50000);//延时
P1=led1[0];//数码管显示“”
P30=1;//开锁指示灯关闭
}}}四、举一反三
系统上电,液晶显示“password:”,此时可以输入6位数字密码,然后以10号键作为输入结束键,如果输入密码位数错误,或位数正确但密码错误,在液晶第二行显示“error”,并自动重新进入密码输入状态;当输入密码正确,在液晶第二行显示“pass”,密码锁解锁。在密码锁已经解锁的状态下,如果按下11号键(密码设置按键),则进入密码重置状态,按其它任意键则返回密码输入状态。进入密码重置状态时,液晶第一行显示“passwordsetup:”,等待输入6位0-9的新数字密码,6位数字密码输入完毕,则自动进入密码输入状态。假设初始密码为000000。设计一个具有16个按键输入,1602液晶显示的实用6位密码锁。具体要求:举一反三硬件设计P17P16P15P14P13P12P11P10P32P31P30P10P11P12P13P14P15P16P17P30P31P32P20P21P22P23P24P25P26P27P24P25P26P27P20P21P22P23P33P33行列反转法识别按键KeilC51多文件项目工程管理行列反转法多文件项目工程软件设计思路使用一个I/O口的低4位、高4位分别连接矩阵键盘的行线和列线,可考虑用行列反转方法来识别按键。行列反转法按键识别码01230123行线列线+VCC行列反转法输出两次行列相反S0S1S2S3S4S5S6S7S8S9S10S11S12S13S14S1501230123行线列线+VCCS0S1S2S3S4S5S6S7S8S9S10S11S12S13S14S151111000000001111P2=0x0f;输出行列反转法输出:列全0行全101230123行线列线+VCCS0S1S2S3S4S5S6S7S8S9S10S11S12S13S14S1500001111读入行列反转法输出:列全0行全1;读入:行列值例S0按下,P2值为0x0E01230123行线列线+VCCS0S1S2S3S4S5S6S7S8S9S10S11S12S13S14S151111000011110000P2=0xf0;输出行列反转法输出:列全1行全0;输出:列全0行全1;读入:行列值例S0按下,P2值为0x0E01230123行线列线+VCCS0S1S2S3S4S5S6S7S8S9S10S11S12S13S14S1511110000行列反转法再次读入输出:列全1行全0;再读入:行列值输出:列全0行全1;读入:行列值例S0按下,P2值为0x0E例S0按下,P2值为0xE0行列反转法输出:列全0行全1;再读入:行列值输出:列全0行全1;读入:行列值例S0按下,P2值为0x0E例S0按下,P2值为0xE0两次行列值合并,形成扫描码
相加按位或例:0xEE,S0的扫描码16个按键的扫描码确定
按键keyscan变量定义键值key赋-1列全1、行全0读行列值有键按下?再次读行列值有键按下?延时去抖列全0、行全1读行列值两次行列值相或查取键号返回键号NNYY//行列反转法得到键值charkeyscan(){charscan1,scan2,keycode,j,key;key=-1;//键值初值为-1,如果没有扫描到按键,函数返回-1P2=0xf0;//写:行为全1,列为全0scan1=P2;//读:行列值
if(scan1!=0xf0)//如果读入的值不为0xf0,则表示有键按下
{delay(1200);//延时去抖
scan1=P2;//再次读入行列值
if(scan1!=0xf0)//再次判断是否有键按下
{P2=0x0f;//行列反转,写:行为全0,列为全1scan2=P2;//读入行列值
keycode=scan1|scan2;//两次读入值按位或操作,合并在一起,得到扫描码
for(j=0;j<16;j++)//由扫描码表得到键值
{ if(keycode==key_code[j]) { key=j;break; }} }}return(key);//返回键值}#include<reg51.h>//包含头文件reg51.h,定义51单片机的专用寄存器#include<lcd.h> //包含lcd子函数头文件voiddelay(unsignedinti); //延时函数声明,函数定义参见任务2中的ex1_1.ccharkeyscan(); //采用行列反转法实现的矩阵键盘扫描函数voidputs(unsignedcharch[]); //显示字符串函数voidset_pw();//密码设置函数,设置6位密码,存放到pw数组中unsignedcharcodekey_code[]={0xee,0xde,0xbe,0x7e,0xed,0xdd,0xbd,0x7d,0xeb,0xdb,0xbb,0x7b,0xe7,0xd7,0xb7,0x77};//键盘按键对应的扫描码表unsignedcharpw[6]={0,0,0,0,0,0}; //存放原始密码unsignedcharpwnew[10]={0,0,0,0,0,0,0,0,0,0}; //pwnew存放输入的密码sbitP33=P3^3;//控制发光二极管的亮灭,表示锁的打开和锁定状态voidmain(){unsignedchari,count; bitpw_flag;//密码标志,1表示输入密码正确,0表示输入密码错误P0=0xff;lcd_int(); //lcd初始化
delay(255);lcd_w_cmd(0x80); //光标定位,第1行第1列puts("password:"); //显示字符串"password:"count=0; //输入字符个数清0while(1){ //等待按键输入i=keyscan(); //调用键盘扫描函数if(i!=-1)//判断按键为有效键{if(i>=0&&i<=9)//i是0-9之间的数字
{ pwnew[count]=i; //输入数字存入pwnew数组
lcd_w_dat(i+0x30);//显示输入密码
count++;//不是结束键,则输入字符个数增1 P2=0xf0;while(P2!=0xf0);//判断按键释放
} elseif(i==10)//判断是否10号键,如果是,则结束密码输入
{
else { for(i=0;i<6;i++) //比较输入字符与原密码是否相同
{ if(pw[i]!=pwnew[i])//有不相同字符,则修改密码标志为0{pw_flag=0;break;} }} if(pw_flag==1) //密码标志为1,密码输入正确
{ lcd_w_cmd(0xc0); //光标定位,第2行第1列
puts("pass"); //显示字符串"pass" P33=0; //开锁指示灯点亮
while(1){ //等待按键按下
i=keyscan(); if(i!=-1)break;//有键按下则退出等待按键状态
} P2=0xf0;while(P2!=0xf0);//判断按键释放
if(i==11)set_pw();//判断是否11号键,如果是,则进入密码设置
//调用密码设置函数
}
else//密码输入错误
{ lcd_w_cmd(0xc0);//光标定位,第2行第1列
P33=1; //开锁指示灯熄灭
puts("error"); //显示字符串"error" } count=0; delay(50000);//延时
lcd_w_cmd(0x01);//清屏
puts("password:"); //显示字符串"password:" }}}}//函数名:puts//函数功能:在lcd上显示字符串函数//形式参数:待显示字符串的首地址//返回值:无voidputs(unsignedcharch[]){unsignedchari;i=0;while(ch[i]!='\0') { lcd_w_dat(ch[i]); i++;}}//函数名:set_pw//函数功能:密码设置函数,设置6位密码,存放到pw数组中//形式参数:无//返回值:无voidset_pw(){unsignedcharkey,count;lcd_w_cmd(0x01);//清屏,并光标定位,第1行第1列
puts("passwordsetup:");//显示字符串"passwordsetup:"lcd_w_cmd(0xc0);//光标定位,第2行第2列
while(1){key=keyscan(); if(key>=0&&key<=9) //key是0-9之间的数字
{ lcd_w_dat('*');//显示“*”
pw[count]=key;//输入数字存入pwnew数组
count++;//不是结束键,则输入字符个数增1 if(count==6)break;//输入6个字符,自动结束
P1=0xf0;while(P1!=0xf0);//判断按键释放
}}}在项目工程下组织若干个包含项目需要子函数的模块文件,避免大量函数代码都堆积在主程序文件中,使得程序的结构清晰、模块性强,提高可读性和可移植性。lcd.c源文件中包含除主函数main()以外的所有读状态、写命令、写数据、初始化等LCD的子函数。lcd.h头文件,对lcd.c中的子函数进行外部函数声明感谢您的观看Thanks
for
your
attention.
明确本次密码锁设计项目的核心指标,划定项目的功能边界与验收标准,保障后续开发过程方向统一。本次设计的密码锁,硬件层面需要集成两部分核心交互组件与一个状态执行组件。第一部分是16个按键组成的输入模块,第二部分是1位数码管组成的提示显示模块,另外搭配一个发光二极管,完成开锁与关锁状态的物理呈现。功能层面设定清晰的判定逻辑,用户输入的密码限定在十六进制字符范围,即0到9的阿拉伯数字,以及A到F的六个大写英文字母。系统获取输入值后,自动与预设的正确密码完成比对,输出对应的结果。比对结果匹配时,数码管显示字符“P”,同时触发开锁动作,点亮发光二极管指示开锁状态。比对结果不匹配时,数码管显示字符“E”,保持锁闭状态,发光二极管维持熄灭,允许用户重新输入尝试。这个设计看似规则简单,实则覆盖了单片机开发中两个核心知识点,矩阵键盘的扫描识别与数码管的显示驱动,同时引入基础的逻辑判定流程,能帮助学习者完整掌握单片机项目从硬件搭建到软件逻辑开发的全流程。完成密码锁功能设计,首先要明确硬件电路的功能分工,三个核心模块分别承担不同作用,共同支撑完整的密码锁逻辑运行。第一个核心模块是共阳数码管,它承担状态提示功能,负责向用户反馈密码输入的验证结果。不同于任务导入中提到的多位密码显示设计,本次任务仅需展示验证结果,用单个数码管就可以满足需求,既降低了硬件接线的复杂度,也能清晰传递核心信息。第二个模块是输入装置,16个按键采用4×4矩阵键盘的连接方式。这种连接方式最大的优势,就是可以大幅减少单片机IO口的占用,原本16个独立按键需要16个IO口,矩阵结构仅需要4根行线加4根列线,总共8个IO口就能实现16个按键的输入识别,充分利用了单片机有限的IO资源。第三个模块是执行装置,连接P3.0引脚的发光二极管承担锁状态的物理展示。二极管点亮对应开锁状态,熄灭对应锁定状态,用最简单的硬件实现了直观的状态输出,让密码锁的功能结果一目了然。三个模块分工清晰,从输入、反馈到执行,构建了完整的密码锁硬件基础。我们已经明确了密码锁设计的任务要求,也梳理了对应的硬件电路方案。要完成这个密码锁设计,首先得解决密码输入的问题,而我们这次用到的输入部件就是4×4矩阵键盘。接下来,我们就一起来搞清楚矩阵键盘的结构原理,看看它是怎么实现按键输入识别的,为后续的程序编写打好基础。我们设计密码锁需要16个按键来输入0到F的十六进制密码,直接给每个按键接一根控制线需要16个IO口,这会浪费单片机有限的IO资源。4×4矩阵键盘就是解决这个问题的经典方案,它用行列交叉的结构来布局按键,把16个按键安排在四行四列的网格里。图中横向的四根线是行线,我们给它们依次编号0到3,纵向的四根线是列线,同样编号0到3。每个按键都接在一根行线和一根列线的交叉点上,也就是说,只需要四根行线加四根列线,一共八根控制线就能控制16个按键,直接省下了一半的IO资源,这对于IO口紧张的单片机项目来说,无疑是性价比极高的选择。这种行列扫描的结构,不仅简化了硬件布线,降低了电路复杂度,还为后续的按键识别打下了清晰的逻辑基础,我们只需要通过检测行线和列线的电平变化,就能准确判断出哪一个按键被按下。常规外接键盘的设计思路,是给每个按键分配一根独立控制线,16个按键就需要占用单片机16个IO口。单片机的IO资源本身十分有限,大量IO被按键占用后,会挤压其他外设的接线空间,甚至导致整个项目资源不够用。4×4矩阵键盘的设计,完美解决了这一资源浪费问题。矩阵键盘采用行列扫描的架构,把16个按键分布在4行4列的交叉点上,每个按键只需要连接对应行线和列线即可。最终整个键盘只需要引出4根行线和4根列线,一共8根控制线接入单片机,直接把IO口占用量减少了一半,省出的资源可以供给数码管、指示二极管等其他外设使用。架构优化的同时,矩阵键盘的按键识别逻辑也足够简洁可靠。单片机只需要逐行拉低行线电平,逐一读取列线电平变化,就能精准定位按下的按键位置,识别准确率完全满足密码锁的输入需求,是小尺寸键盘最经济高效的设计方案。我们实现密码锁的状态显示功能,选择了1602LCD作为显示载体,要驱动这款液晶模块正常工作,必须先掌握它的命令字编码规则。1602LCD的每一条控制命令都对应一个8位二进制的命令字,不同位的组合对应不同的功能设定,比如设置显示模式、清屏、调整光标位置等操作,都需要通过写入对应的命令字完成。命令字是主机和液晶模块通信的核心语言,模块会根据接收到的命令字,解析并执行对应的操作,建立起符合我们需求的工作模式,后续才能正确写入要显示的字符数据。我们梳理编号对应的命令字,是为后续初始化程序、写命令操作提供清晰的参数对照,帮助我们精准调用对应功能,避免因为命令字参数错误导致显示异常,保障整个密码锁显示逻辑的稳定运行。完成LCD驱动功能的基础搭建后,进入最关键的初始化环节。初始化是模块上电后正常工作的前提,所有参数的配置都要严格遵循时序逻辑,任何一个命令的顺序或者参数错误,都会导致整个LCD无法正常工作。当前给出的是标准1602LCD的C语言初始化函数,整个函数通过四次调用写命令函数完成核心配置。第一条命令传入0x3c,核心作用是设置LCD的工作方式,明确8位数据接口、两行显示的基本配置,确定模块的基础工作模式。第二条命令传入0x0e,完成光标显示配置,开启显示功能同时设置可见光标,方便调试阶段定位显示位置。第三条命令传入0x01,执行清屏操作,清空DDRAM中的随机数据,为后续写入显示内容准备干净的画布。最后一条命令传入0x06,设置字符输入方式,设定写入新数据后光标自动右移,符合我们从左到右的显示习惯。这套初始化流程是经过无数项目验证的标准配置,每一条命令的参数都对应确定的功能,是驱动LCD稳定运行的核心基础。第10页我们和1602LCD通信的第一步,往往先读取它的工作状态,判断它是否准备好接收新指令。这一步决定了整个通信流程的稳定性,很多初学者遇到LCD不显示的问题,根源就出在状态判断环节出错。实现状态读取需要遵守LCD固定的通信时序,先配置引脚电平:RS拉低代表读取的是状态而非显示数据,RW拉高代表当前是读取操作,配置完成后给使能端E一个正脉冲,让LCD把内部状态输出到数据总线,我们就能从8位数据口中读取状态字。读取到的状态字中,最高位D7承载了核心信息,我们把这个标志位叫做BF忙标志。BF取值为1时,代表LCD正忙于内部处理,无法接收新的指令或数据;BF取值为0时,代表LCD已经完成当前操作,可以接收新指令。这个读状态函数会把读取到的状态字返回,供后续操作判断LCD状态,后续写命令、写数据的操作,都会调用这个函数完成忙检测,保证每次操作都在LCD就绪时执行,从根源避免指令丢失导致的显示异常。第11页我们要实现对1602LCD的命令写入操作,首先需要解决LCD响应速度慢于单片机处理速度的核心矛盾,因此必须加入忙检测机制保障通信稳定。函数接收一个unsignedchar类型的参数com,这个参数就是我们要发送给LCD的命令字。整个操作先通过do-while循环完成忙检测:循环内先调用我们之前写好的lcd_r_start函数读取LCD的状态字,再将读取结果和0x80做按位与操作,这个操作会直接屏蔽低7位,只保留最高位的忙标志BF。只要BF不为0,证明LCD仍处于忙状态,循环就会持续检测,直到LCD准备就绪才退出循环。忙检测完成后,我们按照1602LCD的通信规范配置引脚:写入命令时需要保持RW为0代表写操作,RS为0代表写入的是命令字,随后给E引脚一个正脉冲触发LCD锁存数据,把命令com写入LCD的数据端口,最后拉低E引脚完成一次完整的命令写入操作,复位RW引脚并加入延时稳定时序,保障下一次操作不会出错。这套流程完全遵循LCD的硬件通信逻辑,能稳定实现各类显示控制命令的下发。第12页完成LCD命令字写入操作后,就需要掌握给LCD写入显示数据的操作,这是让LCD输出我们需要的字符内容的核心步骤。本函数和上一页的写命令函数核心逻辑框架高度相似,二者的核心区别,在于RS引脚的电平设置不同。写命令要求RS置0,写数据要求RS置1,这是由1602LCD本身的通信规则决定的:RS引脚用来区分当前传输的是命令还是显示数据,高电平代表数据,低电平代表命令。函数启动后,首先执行忙检测流程。循环调用读状态函数读取LCD当前状态,通过和0x80做与操作,提取最高位的忙标志位BF。只要BF位不为0,说明LCD仍在处理
温馨提示
- 1. 本站所有资源如无特殊说明,都需要本地电脑安装OFFICE2007和PDF阅读器。图纸软件为CAD,CAXA,PROE,UG,SolidWorks等.压缩文件请下载最新的WinRAR软件解压。
- 2. 本站的文档不包含任何第三方提供的附件图纸等,如果需要附件,请联系上传者。文件的所有权益归上传用户所有。
- 3. 本站RAR压缩包中若带图纸,网页内容里面会有图纸预览,若没有图纸预览就没有图纸。
- 4. 未经权益所有人同意不得将文件中的内容挪作商业或盈利用途。
- 5. 人人文库网仅提供信息存储空间,仅对用户上传内容的表现方式做保护处理,对用户上传分享的文档内容本身不做任何修改或编辑,并不能对任何下载内容负责。
- 6. 下载文件中如有侵权或不适当内容,请与我们联系,我们立即纠正。
- 7. 本站不保证下载资源的准确性、安全性和完整性, 同时也不承担用户因使用这些下载资源对自己和他人造成任何形式的伤害或损失。
最新文档
- 2026年坐过山车心理测试题及答案
- 280亩柴胡标准化种植可行性研究报告
- 常见礼貌礼仪试题及详细答案集
- 动物内科进阶试题及答案
- 并殖吸虫题库及答案详解
- 2026年山东中招模拟试题及答案详解
- 2026年神经外科三基三严考试全真模拟试题及答案详解
- 2026年山西美术一模模拟试题及答案详解
- 2026年中国全域旅游行业发展前景预测及投资分析报告(版)
- 2026年山西陕西英语模拟试题及答案详解
- PCS-9785-H2时间同步装置说明书
- 《堤防抢险技术导则SLT 832-2024》知识培训
- 公务员面试宝典:2025年升级版详解
- 七年级数学上册有理数加减乘除混合运算测试卷
- T-GXAS 421-2022 成人急性中毒洗胃操作技术规范
- 《品篆刻之美》课件 2024-2025学年人美版(2024)初中美术七年级上册
- 双氧水SIS联锁联调确认表
- 《水电工程水利计算规范》(NB-T 10083-2018)
- 欢乐的那达慕混声合唱简谱
- 二级动火安全施工方案
- 中国居民膳食营养素参考摄入量(DRIs)(2013-修订版)资料
评论
0/150
提交评论