AutoLeaders控制组——51单片机学习笔记(模块化编程、LCD_1602、矩阵键盘)

本篇内容是观看B站江科大自化协UP主的教学视频所做的笔记,对其中内容有所引用,并结合自己的单片机板块进行了更改调整。

以下笔记内容以一个视频为一个片段(内容较多,可能不适合速食,望见谅)

一些内容涉及前面的知识点,可能需要提前了解(可以翻看本人之前的文章或者去B站看UP主的视频)

目录

5-1、模块化编程

模块化编程与传统方式编程区别

模块化编程用法

有关c与h文件位置的注意事项:

预编译

 实验操作(以模块化动态数码管代码为例)

5-2、LCD_1602调试工具

调试

LCD1602介绍

 LCD1602原理图

补充:忽略warning方法及一点细节

LCD1602代码函数(源于up主提供)

 Ⅰ、LCD_Init( )函数。

Ⅱ、LCDShowChar ( unsigned char Line, unsigned char Column, char Char)函数。

Ⅲ、LCD_ShowString(unsigned char Line,unsigned char Column,char *String)函数

Ⅳ、LCD_ShowNum(unsigned char Line,unsigned char Column,unsigned int Number,unsigned char Length)函数——显示为十进制

Ⅴ、LCD_ShowSignedNum(unsigned char Line,unsigned char Column,int Number,unsigned char Length)函数——显示为十进制

Ⅵ、LCD_ShowHexNum(unsigned char Line,unsigned char Column,unsigned int Number,unsigned char Length)函数

Ⅶ、LCD_ShowBinNum(unsigned char Line,unsigned char Column,unsigned int Number,unsigned char Length)函数

总结意义:

6-1、矩阵键盘

矩阵键盘介绍

 扫描的概念

矩阵键盘原理图

补充:弱上拉模式与准双向口输出

写入程序

Ⅰ、添加main.c文件

Ⅱ、将需要的之前写过的模块代码添加到工程中。

Ⅲ、编写矩阵按键代码

Ⅳ、编写main.c文件

Ⅴ、烧录程序

补充:使用注释

6-2、矩阵键盘密码锁

写入程序

Ⅰ、新建工程

Ⅱ、更改主函数内容

Ⅲ、烧录程序

自行优化的代码

 写入代码

Ⅰ、编写输入数字、确定、退格、清零功能代码。

Ⅱ、编写设置密码模式

Ⅲ、编写猜密码模式

Ⅳ、编写主函数

补充说明:

Ⅴ、烧录程序


5-1、模块化编程

模块化编程与传统方式编程区别

传统方式编程:

所有的函数均放在main.c里,若使用的模块比较多,则一个文件内会有很多的代码,不利于代码的组织和管理,而且很影响编程者的思路

模块化编程:

把各个模块的代码放在不同的.c文件里,在.h文件里提供外部可调用函数的声明,其它.c文件想使用其中的代码时,只需要#include "XXX.h"文件即可。使用模块化编程可极大的提高代码的可阅读性、可维护性、可移植性等

因此,推荐用模块化编程。

模块化编程用法

①创建一个.c文件,将需要移植的函数放置到这里。(放置函数、变量定义)

②创建一个.h文件(放置可被调用的函数、变量的声明)

 这里只需要加入预编译和声明(即中间的void),即可完成.h文件编辑。

③在需要调用该函数的位置,将.h文件include进去。

(如最上面的语句,添加之后,即可直接使用.h文件内声明过的函数)

有关c与h文件位置的注意事项:

Ⅰ、使用的自定义函数的.C文件,必须放到工程中参与编译(位置跟main文件位置一样)

 Ⅱ、使用到的.h文件必须要放在编译器可寻找到的地方(工程文件夹根目录、安装目录、自定义)——这里一般跟main文件放在同一个位置。

 如果选择另建一个文件夹(自定义)也可,但需要配置.h的路径(不然可能会出现找不到文件的问题)

 配置路径在上方的蓝线处配置即可。(当然直接放在main文件夹处不需要配置)

预编译

C语言的预编译以#开头,作用是在真正的编译开始之前,对代码做一些处理(预编译)。

 ①#include的作用:相当于将里面的内容复制粘贴到当前文件中。

例如:将REGX52.H文件内的东西全部移到main文件中,替代掉#include <REGX52.H>,编译后只会warning(删除掉 #endif 后面的后警告消失),不会报错。 

 下面图片的warning,一个是因为有,一个是因为有函数未使用。

 

②#define的作用:定义一个变量(因此后面可以直接用,无需再定义)。如果后面有内容,那么就将前面的变量更换为后面的内容(即后面使用时,输入的虽然是变量,但是编译后,会将它全部换为后面的内容,有利于给特定数字定名字,便于理解)

 如上面的第二条语句,就是定义。(后面的__Delay_h__其实就是一个名字,只是一般都这么编写名字——__文件名_H__——其中文件名一般英文全部大写)

③#ifdef与#ifndef的作用:前者为如果定义了XXX(后面接的内容),就执行下面语句;后者为如果没有定义XXX(后面接的内容),就执行下面语句。

④#endif的作用:与#ifdef与#ifndef匹配,充当结尾括号(里面的内容就是#ifdef与#ifndef发挥作用的内容)。

解释③与④:

 上面代码,因为没有定义AAA,因此里面的东西不会执行。

 上面代码,因为定义了AAA,因此执行了里面的东西。(然后因为里面的内容是没有意义的,就报错了)

利用这个特性,就可以编辑.h文件,防止重复定义了。

如:

补充:解决Keil中输入中文删减出现乱码的问题

 图中1是我们的默认字体,因为外国对汉字的不兼容问题,因此删减等处理会出现乱码。(且复制网上的其他代码,或者从STC烧录软件拷贝代码时,可能中文会变为乱码)

图中3和4分别是繁体中文和简体中文,但是切换后,之前的英文字体会改变(比较难看),可能会有人考虑通过改字体的方式,但是都不尽人意(而且改动后,初始字体会消失,需要重新进入windows启用)。——但是对中文操作就不会出现乱码;

图中2是全球通用编码,使用该字体,能保留之前好看的英文字体,且处理中文不会出现乱码,但是使用上面选择的字体编辑的中文,切换到该字体后,会变为乱码(但是可以切回去原字体查看原内容,然后重新输入到第二种字体下的文本中)。

Ps:编辑字体的方法(如非必要,建议别切字体)

点击扳手图标,进入下面弹窗,选择[Colors & Fonts],即可编辑。

 

 实验操作(以模块化动态数码管代码为例)

Ⅰ、按照之前操作,完成main.c文件的建立

 补充:打开REGX52.H文件的存储位置

①右击<REGX52.H>,点击Open,进入该文件。

 ②右击上方文件栏的<REGX52.H>文件,点击Open,进入文件存储位置。

 ③在弹出来的弹窗中即可看到位置。(位于安装文件下)

 补充:< >与” 包含文件的区别

 前者表示直接在系统盘中寻找文件,后者表示在当前文件下寻找文件(如果找不到,逐级向下找,直到找到系统盘)。

因此< >可以替换为 ,而 替换为< >可能会出现找不到文件的情况。

Ⅱ、添加需要的.C文件,并使其包含需要的函数

①像之前添加main.c文件一样,新建一个Delay.c文件。

 ②在Delay.c文件中,加入Delay函数(可以复制之前的文件,也可重新在STC烧录文件中生成,重新编写)。

 Ⅲ、添加对应.C文件的.h文件

①像创建main.c文件一样,创建Delay.h文件,但是选择的文件类型改为H类型。

 ②编写Delay.h文件内容,添加预编译以及函数声明。

 ③将Delay.h文件添加进当前工程目录处(即左侧的排列文件)。——这一步建议与步骤②对调

Ps:也可不用添加,因为.h文件存储位置就在存放main.c文件的文件夹内,编译时可以找到。(但是.c文件必须添加)

为了便于管理,我们选择添加.h文件到工程目录中

 

 数码管相关函数添加方法同上。

①Nixie.c文件添加:

这里因为应用了Delay函数,以及使用了P2之类的内容,因此需要在上面加入对应的.h文件。(否则会显示没定义而出错)

Ps:编译时可能会显示warning(如果按照之前,每添加一点内容就测试的话),是因为没有调用函数导致,这个时候可以选择忽视(因为此时程序空间还有,因此不用担心)。

②Nixie.h文件添加:

 Ps:如果编译出错了,可能不一定是当前的文件有问题,有可能是该语句前面一个内容出问题了(所以可能得需要切换文件找原因)。

Ⅳ、编辑主函数

 Ps:一定要记得,如果在使用了对应函数内容,那么就要include对应函数的.h文件(如果模块化编译了的话)

Ⅴ、将程序烧录进单片机

5-2、LCD_1602调试工具

调试

 调试方式:

数码管——缺点:需要不断扫描

串口——缺点:需要连接电脑传输数据,有点麻烦

LCD1602液晶屏

LCD1602介绍

利用附带的LCD1602液晶屏进行调试,安在之前介绍的排座上,此时第一排亮起。

 可以通过转动滑动变阻器,更改液晶屏显示亮度。

 LCD1602原理图

单片机核心:

 动态数码管:

 LED:

 LCD1602:

 由上面接口与单片机接口对应可知,LCD1602占用P0口与P2的5、6、7口,因此数码管无法使用,且LED的三个灯无法使用(根据上图显示的应该是D6~D8灯,但是测试,是前三个灯,可能跟单片机接反有关系)。——LCD1602调试缺点

但是,它只与上面的LED与数码管冲突,其他没有影响,因此依然可以使用这个方法进行调试。

补充:忽略warning方法及一点细节

①#include 后面< >或 里面的英文不区分大小写。

②将函数或变量添加进主函数文件,但没有使用,会出现warning,提示占据内存空间,但是在现阶段(没有写很多内容的情况下)可以选择忽视。

③如果需要不显示特定的warning,可以通过点击[魔术棒]图标,然后点击[BL51 Misc],在warning框输入对应的警告编码即可。

 如下面的,出现了L16的错误提示,只需要输入16,点击[OK],再次编译时就不会再提示了。

④右击对应函数时,即可在快捷栏中前往对应的定义(.C文件)和声明(.h)文件。

LCD1602代码函数(源于up主提供)

 Ⅰ、LCD_Init( )函数。

 作用:初始化定义LCD1602,使得后面的LCD1602代码函数可以使用。(必须定义后,才能使用其他函数)。

用法:直接输入 LCD_Init( ); 完成使用。

Ⅱ、LCDShowChar ( unsigned char Line, unsigned char Column, char Char)函数。

作用:在特定行与列显示一个字符。(根据测试,ASCLL码表里面字符内容可显示,虽然有点出入)

用法:

第一个参数输入行的数字(上下两行),

第二个参数输入列的数字(从左到右一共16列),

第三个参数输入字符(带上单引号表示为字符;不带单引号,选择直接打数字,就会直接转换为ASCLL码表上对应字符)。

——附带的行列极限为本单片机附带LCD1602液晶屏显示内容的范围。

Ⅲ、LCD_ShowString(unsigned char Line,unsigned char Column,char *String)函数

 作用:在LCD1602液晶屏上显示字符串。

用法:

第一个参数为首个字符的行,

第二个参数为首个字符的列,

第三个参数为字符串内容(需要用双引号包围,表示为字符串)。

Ps:如果输入字符串内容超出范围,会无法显示后面内容,且可能会出现乱码、蜂鸣器响动等现象。

Ⅳ、LCD_ShowNum(unsigned char Line,unsigned char Column,unsigned int Number,unsigned char Length)函数——显示为十进制

 作用:显示无符号的数字(范围为0~65535)。

用法:

第一个参数为首个数字的行(每一位数字占据一个行列格),

第二个参数为首个数字的列,

第三个参数为数字(可为八进制,十进制,十六进制,会转换为十进制输出显示),

第四个参数为数字长度(比如数字123,长度为3)。

Ps:

①如果数字大于65535(或小于0),会取该数字对65535的余数减一(或绝对值跟65535取余后,65535+1-余数)。

②如果所给参数的数字长度小于填写的数字长度,会从最高位数到所给参数数字长度,其他位舍弃(即不显示)。

③如果所给参数的数字长度大于填写的数字长度,会从填写的数字前面补零,直到满足参数的数字长度。

Ⅴ、LCD_ShowSignedNum(unsigned char Line,unsigned char Column,int Number,unsigned char Length)函数——显示为十进制

 作用:显示有符号的数字(正数前面显示+,负数前面显示-)。

用法:

第一个参数为首个数字的行,

第二个参数为首个数字的列,

第三个参数为数字(负数前面需要输入负号;正数前面可选择输入正号,或者不输入)(可为八进制,十进制,十六进制,会转换为十进制输出显示),

第四个参数为数字长度(不包含前面的正负号)。

Ps:细节与上面无符号数字函数相同,但是得小心符号也占据一个空间,避免重叠(覆盖之前的内容)或超出范围。

Ⅵ、LCD_ShowHexNum(unsigned char Line,unsigned char Column,unsigned int Number,unsigned char Length)函数

 作用:显示十六进制的数字。

用法:

第一个参数为十六进制数最高位的行,

第二个参数为十六进制数最高位的列,

第三个参数为数字(可为八进制,十进制,十六进制,会转换为十六进制输出显示),

第四个参数为十六进制数长度。

Ps:长度范围为1~4,

①如果对应十六进制数长度低于输入参数的数字长度,会在前面补0到满足长度;

②如果对应十六进制数长度大于输入参数的数字长度,会从前面的数字开始显示到相应长度结束。

③如果输入的数字长度超过指定范围,会如同上面的函数Ⅳ的Ps①一样的处理规则(记得转换进制)。

④如果超出4,会在前面补F,后面四位正常显示(正常显示表示满足上面①②③的规则),

Ⅶ、LCD_ShowBinNum(unsigned char Line,unsigned char Column,unsigned int Number,unsigned char Length)函数

 作用:显示二进制的数字。

用法:

第一个参数为二进制数最高位的行,

第二个参数为二进制最高位的列,

第三个参数为数字(可为八进制,十进制,十六进制,会转换为二进制输出显示),

第四个参数为二进制数长度。

Ps:与函数Ⅵ的Ps类似,但去除④。

总结意义:

①可以通过上面的数字显示函数,进行数字的转换,达到显示转换数字的目的;

②可以在上面的数字显示函数的第三个参数(输入数字参数)中,放入运算式,使得液晶屏显示对应结果。

 ③可以通过显示,测试其他函数是否有作用。

如Delay函数是否有效:

6-1、矩阵键盘

矩阵键盘介绍

 在键盘中按键数量较多时,为了减少I/O口的占用,通常将按键排列成矩阵形式

采用逐行或逐列的“扫描”,就可以读出任何位置按键的状态。

 扫描的概念

数码管扫描(输出扫描)

原理:显示第1位→显示第2位→显示第3位→……,然后快速循环这个过程,最终实现所有数码管同时显示的效果。

——解决不能同一时间可知多位数码管显示不同数字的问题

矩阵键盘扫描(输入扫描)

原理:读取第1行(列)→读取第2行(列) →读取第3行(列) → ……,然后快速循环这个过程,最终实现所有按键同时检测的效果。

以上两种扫描方式的共性:节省I/O口

补充:通过矩阵的方式,能使显示屏上LED等像素点阵节省I/O口,且矩阵越大,节省越多。

矩阵键盘原理图

由两图对比,可见可以将一行的矩阵按键视作独立按键,然后进行操作。

于是就可以将P1_7口(上图P17,后面的以此类推)赋值为0,然后对P1_3~P1_0口进行操作(类似矩阵键盘检测),即可完成一行扫描。

而对应第二三四行,赋值为1即可使得单独选择第一行(即需要判断哪一行,就给哪行赋值为0,其他给1)。——如果不这样做,判断不了是哪行的按键被按下。

以此类推,我们就可以先第一行赋值0,其他为1;然后第二行为0,其他为1,以此类推直到第四行赋值0,这样就可以完成矩阵键盘的扫描了。

上面的方法为逐行扫描,

逐列扫描同理,只需要将P1_3~P1_0口当作列数(如同上面的行数口)进行赋值0,对下面的P1_4~P1_7进行操作(类似矩阵键盘检测),即可完成列扫描。

而我们使用的是逐列扫描,原因如下:

(这里的原因不影响目前使用的单片机,因为这里使用的单片机原理图与江科大up主的有所不同,所以请一定要看过自己的原理图再判断)

(标蓝色字的部分也适用于现今使用的单片机)

由于内部连接问题,按行扫描时,P1_5口会一会高,一会低(1和0来回切换),导致连接在步进电机的电路中,使得P1_5口右侧连接的BZ(相当于驱动器,增大输出电流能力)不断发生改变,而这里的蜂鸣器为无源蜂鸣器,所以BZ以一定频率改变时,蜂鸣器就会响动,而且关不掉。为了使蜂鸣器不响动,因此采用逐列扫描。

江科大视频原理图:

本人使用的单片机原理图:

所以到时使用蜂鸣器时,也要注意这一点区别!

补充:弱上拉模式与准双向口输出

单片机的I/O口为弱上拉模式(弱上拉,强下拉),又叫准双向口(双向口既可以输入,也可以输出)

所以在制作独立按键时,由于是弱上拉,因此输出1的驱动能力有限,即使按下按键时,出现短接现象(即直接1跟0相接,中间没有电阻什么)也是没有问题。

弱上拉模式简要示意图:

图中施密特触发器负责读入。

如果外界什么都没有接,或者接了高电平,那么读入的就是高电平;

如果外界接入低电平,那么即使内部接了VCC(高电平),但是也会被外界直接相连的低电平直接拉低,无法保持高电平。

因此,在弱上拉模式中,内部给1,外部给0,那么读入的数据就是0。

在本单片机中,P1、P2、P3口全部都是弱上拉模式,而P0口为开漏输出(但是原理图中已经接了一个上拉电阻,所以也可以看作是弱上拉模式)。

来自搜索——P0口:开漏输出,引脚悬空时为低电平。 P1/P2/P3:弱上拉,即当引脚悬空时为高电平。

其中更高系列的单片机,或者其他更强大的单片机,可以配置I/O口为其他模式(如推挽输出、高阻输入、开漏输出)。

推挽输出:VCC直接连接开关,中间没有连接电阻,这样通过的电流很大(但是I/O口输入电流其实也是有限的),相当于电源短路。——只能作输出,不能输入。

高阻输入:内部没有选择模块,调整不了电平,直接由外部传入(减少内部对输入的影响)。——只作输入,不选择输出。

开漏输出:开漏输出最主要的特征就是高电平没有驱动能力(即只能输出低电平),需要借助外部上拉电阻才能真正输出高电平。

I/O口检测0而不检测1的原因:

由上图可知,当公共端为GND(低电平)时,将单片机的高电平拉低,就会产生很大电流,因此便于检测。——(可以回到独立按键第一节笔记回顾独立按键的检测)

写入程序

实现效果:能按下矩阵键盘的对应按键,在LCD1602液晶屏显示对应的数字。

步骤:

Ⅰ、添加main.c文件

方法与之前一样。

Ⅱ、将需要的之前写过的模块代码添加到工程中。

①打开之前的LCD1602文件夹,将LCD1602的模块代码与Delay的模块代码复制到main.c所在文件夹中。

(可以通过按住Ctrl键,然后点击需要的文件,即可一次性复制多个不相邻文件;

也可以通过点击起始位置,按住Ctrl与shift键,再点击末位文件,那么包含在两个文件区域内的文件即可快捷被选中。)

②通过之前添加.h文件到左侧工程目录的方式,添加需要的文件。

补充:右击对应函数打不开其所在定义或声明文件的解决方法。

可以通过将文件编译(保存)之后重进,即可解决(如果不行就再编译一次,即可打开)。

补充:有意思的小工具

①查看指导书

可以通过点击上方的[View]中点击[Book Window]打开。(添加书籍窗口)

或者点击上方的[窗口]图标右侧的扩展中点击[book]打开。(打开存在的窗口)

顺带一提,如果点击[窗口]图标(不是点击右边一点的展开),就可以将左侧存在的窗口关闭,再次点击即可打开。

指导书:

点击complete书籍,即可打开用户指导书。(英语内容,官方正版,一手资料)

②显示当前工程所有的函数

按照之前查看指导书的方式,将点击[book]替换为点击[function]即可。

显示窗口:(显示的方式与up主有所出入,以自己的显示为准)

双击对应的函数,即可跳到对应的函数定义位置。

③添加模板

按照之前查看指导书的方式,将点击[book]替换为点击[templates]即可。

显示窗口:(这里是已经配置过的,以自己的为准)

在需要添加模板的文件内容位置点击一下,再双击窗口中对应的模板,即可完成添加。

如:

然后即可进行编辑,减少编写代码时间。

Ps:更改自己的模板方法

①在该窗口空白位置右键,点击[Configure Templates...]进入配置窗口

②点击对应模板,在[Templates]框处点击×即可删除对应模板。

③点击旁边的虚线框图标,即可新建一个自己的模板。输入完模板名字后,即可单击该模板,然后在下方的[Text]框中输入模板代码。(双击模板名字,即可更改模板的名字)

④输入完代码后,可以在特定位置输入竖杠“|”,这样当使用模板时,光标会自动到该位置,即可直接从该位置开始编辑。

⑤完成编辑后,点击[ok]退出,完成配置。

Ⅲ、编写矩阵按键代码

①编写MatrixKey.c文件

up主编写的代码:

这里采用的是逐列扫描。方式简单粗暴,可以选择进行优化。

购买单片机附赠资料中的代码:

这里采用十字交错方式进行扫描,方式巧妙,但不一定容易理解。

②编写MatrixKey.h文件

Ⅳ、编写main.c文件

Ps:如果不加判断语句,那么会一直显示0(其实是出现了数字变化,但很快被刷为0)。

原因是没有判断语句的话,每次调用函数时都会将KeyNum的值刷为0,而且显示出来;加了判断之后,就把0的情况排除出去,使得能变化显示的方式只能通过按键。

——这一个与独立按键控制LED点亮中的if语句原因类似。

Ⅴ、烧录程序

补充:使用注释

添加注释模板:

显示效果:

上面的brief行注释内容为简介,param行内容为函数参数,retval行内容为返回值,如有特别说明,可以新加一行空白行添加内容。

使用模板原因:

①在使用注释编译器(可以把注释提取出来,并生成说明文档,列出表格,最后生成文本,便于别人参考,而不需要查看程序)时,更直观。

②显得更专业。(确信是唯一原因,嗯!)

6-2、矩阵键盘密码锁

写入程序

实现效果:将独立按键的S1~S9设置为设置1~9,S10设置为0,S11设置为确定,S12设置为清零,且在输入密码确认时,错误时显示ERR,正确时显示OK。

Ⅰ、新建工程

这里通过复制的方式,直接将上节内容的文件夹复制粘贴,然后更改文件夹名字,双击里面的project。(这也就是为什么之前工程名字起为project的原因,方便移植时不会显得文不对题,显示其他的内容标题)

 

 

Ⅱ、更改主函数内容

 

 

①输入密码模块

 

②确认密码模块

 这里将密码清零与计次清零,是为了能重新输入,不然无法重新输入(因为前面输入密码模块的计次条件限制)

在OK后面多打一个空格,使得与ERR的占位长度一致,避免显示OK后,后面还多出来一个R的情况。

③密码清零模块

 

Ⅲ、烧录程序

自行优化的代码

实现效果

将独立按键的S1~S9设置为设置1~9,S10设置为0,S11设置为确定(在输入密码确认时,错误时显示ERR,正确时显示OK),S12设置为退格(即清除一位数字),S13为清零,S14为设置密码模式,S15为猜密码模式,S16为显示预设密码模式与更换模式按键。

新建工程

将准备工作做完后,在上面的代码中,获得下面的函数文件,并添加至左侧工程目录列表中。

 写入代码

Ⅰ、编写输入数字、确定、退格、清零功能代码。

根据之前的内容,保留S1~S10的输入数字模式,S11的确定功能利用之前的代码,更改S12为退格功能,S13的清零功能使用之前的代码。

①输入数字模式:

 

②确认功能:

 

③退格功能:

 

④清零功能:

 

Ⅱ、编写设置密码模式

这里利用模块化的方式,将设置密码模式的代码移出,使得主函数更简洁。

Scan.c文件:

 

 这里将Ⅰ中编写好的几个功能代码,复制到该文件内,然后进行几处更改。

①初始化与进入设置密码模式显示:

 这里利用LCD显示字符串的函数,使得进入设置密码模式时,会出现英文变化,提示进入。

②确认密码功能:

 

这里改动确认模块的代码,使得输入完成时,按下S11时,出现的不是判断,而是确认密码。

③完成密码确认显示

 

利用LCD显示字符串函数,将之前位置的字符清零(用空格代替,可使得之前的内容被覆盖,显示空白),并在延时后,将输入好的密码再次显示一次。

④循环设置

 

 

画线部分,利用Sure的值,实现在没有按下确认时,不会退出循环,在点击确认后,跳出循环,完成输入并退出设置密码模式。

Scan.h文件

 

Ⅲ、编写猜密码模式

这里利用模块化的方式,将设置密码模式的代码移出,使得主函数更简洁。

Guess.c文件

 

 

这里将Ⅰ中编写好的几个功能代码,复制到该文件内,然后进行几处更改。

①初始化与进入设置密码模式显示:

 

这里利用LCD显示字符串的函数,使得进入设置密码模式时,会出现英文变化,提示进入。

Guess.h文件

 

Ⅳ、编写主函数

main.c文件

 

 说明几个部分内容作用:

①退出模式后的清除显示:

 

利用LCD的字符串显示函数,清除之前模式留下的痕迹,恢复到无模式页面,代表可以选择模式。

②设置密码模式:

 

这里用Secret变量,存储设置密码函数的返回值(返回设置密码);

并调用again函数进行清除显示。

③猜密码模式:

 

这里将得到的密码作为参数传入猜密码函数中,使得能有值进行猜测;

然后调用again函数进行清除显示。

④显示密码模式:

 

这里没有进行模块化,因此直接将内容表示在这里。

前面的LCD字符串函数进行初始化及显示密码模式显示;

后面利用LCD字符串函数,将设置的密码显示;

再利用while循环,使得在不按下S16时,一直留在这个模式不退出;

最后调用again函数,进行清除显示。

补充说明:

实现S16进行模式的退出,体现在下面的地方:

显示密码模式:

 

这里利用循环,实现S16按键退出模式。

(值得一提的是,必须用if语句,直接在while中判断KeyNum!=16的方式行不通,因为会直接退出来,达不到留在显示密码模式的效果。原因是在进入显示密码模式时,赋值还未结束,KeyNum的值仍是16,因此无法达到进入循环的效果

猜密码模式:

 

这里也利用循环,实现S16按键退出模式。

设置密码模式:

 

这里利用的是判断语句,利用返回,实现在没有完成输入密码时退出模式,能保留之前的密码并返回出去。

Ⅴ、烧录程序

烧录时可能会运行的有点慢,是因为代码内容有点多,如果在一次按下开关没更新效果的话,可以重新关闭再按下单片机开关。——可以继续优化代码,实现速度的加快,减少内存占用。

(在STC烧录程序里面的进度条走完后,程序才算烧录完成)

 

  • 5
    点赞
  • 41
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
以下是基于 C51 单片机矩阵键盘LCD1602 显示器的计算器程序,可以实现基本的加、减、乘、除四则运算: ``` #include <reg51.h> #define uchar unsigned char #define uint unsigned int sbit RS = P2 ^ 0; //定义RS引脚 sbit RW = P2 ^ 1; //定义RW引脚 sbit EN = P2 ^ 2; //定义EN引脚 uchar KeyValue; //定义键值变量 uchar KeyData; //定义键码变量 uchar KeySta; //定义键盘扫描状态 uchar FirstData; //定义第一个操作数 uchar SecondData; //定义第二个操作数 uchar ResultData; //定义结果变量 uchar Operator; //定义运算符变量 uchar Flag; //定义标志位变量 uchar code NumTable[] = { //定义字符集数组 '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '.', '=' }; uchar code KeyTable[] = { //定义键码数组 0xEE, 0xDE, 0xBE, 0x7E, 0xED, 0xDD, 0xBD, 0x7D, 0xEB, 0xDB, 0xBB, 0x7B, 0xE7, 0xD7, 0xB7, 0x77 }; void Delay() { //延时函数 uint i, j; for (i = 0; i < 100; i++) { for (j = 0; j < 1000; j++); } } void WriteCommand(uchar cmd) { //写指令函数 RS = 0; RW = 0; EN = 0; P0 = cmd; Delay(); EN = 1; Delay(); EN = 0; } void WriteData(uchar dat) { //写数据函数 RS = 1; RW = 0; EN = 0; P0 = dat; Delay(); EN = 1; Delay(); EN = 0; } void InitLcd() { //LCD初始化函数 WriteCommand(0x38); WriteCommand(0x0C); WriteCommand(0x06); WriteCommand(0x01); } void ScanKey() { //键盘扫描函数 uchar i, j; KeySta = 0xFF; for (i = 0; i < 4; i++) { P1 = 0x0F; P1 &= ~(0x01 << i); for (j = 0; j < 4; j++) { if (!(P1 & (0x10 << j))) { KeySta = KeyTable[i * 4 + j]; return; } } } } void GetKey() { //获取键值函数 ScanKey(); if (KeySta != 0xFF) { KeyValue = KeySta; KeySta = 0xFF; for (KeyData = 0; KeyData < 16; KeyData++) { if (KeyValue == KeyTable[KeyData]) { break; } } if (KeyData < 10) { //数字键 if (!Flag) { FirstData = FirstData * 10 + KeyData; WriteData('0' + KeyData); //在LCD上显示按键数字 } else { SecondData = SecondData * 10 + KeyData; WriteData('0' + KeyData); } } else if (KeyData == 10) { //小数点键 if (!Flag) { Flag = 1; WriteData('.'); } else { WriteData('.'); } } else if (KeyData == 11) { //等号键 switch (Operator) { case '+': ResultData = FirstData + SecondData; break; case '-': ResultData = FirstData - SecondData; break; case '*': ResultData = FirstData * SecondData; break; case '/': if (SecondData == 0) { WriteData('E'); WriteData('r'); WriteData('r'); WriteData('o'); WriteData('r'); return; } ResultData = FirstData / SecondData; break; } WriteData('='); WriteData('0' + ResultData / 10); WriteData('0' + ResultData % 10); } else { //运算符键 Operator = NumTable[KeyData]; WriteData(Operator); Flag = 1; } } } void main() { InitLcd(); WriteCommand(0x80); Flag = 0; FirstData = 0; SecondData = 0; while (1) { GetKey(); } } ``` 其中,`ScanKey()` 函数用于扫描矩阵键盘,获取按键值;`GetKey()` 函数用于根据按键值进行相应的操作,包括数字输入、运算符输入、等号键计算等;`InitLcd()` 函数用于初始化 LCD 显示器;`WriteCommand()` 函数用于向 LCD 发送指令;`WriteData()` 函数用于向 LCD 发送数据。在 `GetKey()` 函数中,按键数字和运算符会显示在 LCD 显示器上,结果会以“= 数字”形式显示在 LCD 上。注意,除法运算需要进行除数为 0 的判断。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值