本篇内容是观看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、矩阵键盘密码锁
写入程序
Ⅰ、新建工程
Ⅱ、更改主函数内容
Ⅲ、烧录程序
自行优化的代码
写入代码
Ⅰ、编写输入数字、确定、退格、清零功能代码。
Ⅱ、编写设置密码模式
Ⅲ、编写猜密码模式
Ⅳ、编写主函数
补充说明:
Ⅴ、烧录程序
传统方式编程:
所有的函数均放在main.c里,若使用的模块比较多,则一个文件内会有很多的代码,不利于代码的组织和管理,而且很影响编程者的思路
模块化编程:
把各个模块的代码放在不同的.c文件里,在.h文件里提供外部可调用函数的声明,其它.c文件想使用其中的代码时,只需要#include "XXX.h"文件即可。使用模块化编程可极大的提高代码的可阅读性、可维护性、可移植性等
因此,推荐用模块化编程。
①创建一个.c文件,将需要移植的函数放置到这里。(放置函数、变量定义)
②创建一个.h文件(放置可被调用的函数、变量的声明)
这里只需要加入预编译和声明(即中间的void),即可完成.h文件编辑。
③在需要调用该函数的位置,将.h文件include进去。
(如最上面的语句,添加之后,即可直接使用.h文件内声明过的函数)
Ⅰ、使用的自定义函数的.C文件,必须放到工程中参与编译(位置跟main文件位置一样)
Ⅱ、使用到的.h文件必须要放在编译器可寻找到的地方(工程文件夹根目录、安装目录、自定义)——这里一般跟main文件放在同一个位置。
如果选择另建一个文件夹(自定义)也可,但需要配置.h的路径(不然可能会出现找不到文件的问题)
配置路径在上方的蓝线处配置即可。(当然直接放在main文件夹处不需要配置)
C语言的预编译以#开头,作用是在真正的编译开始之前,对代码做一些处理(预编译)。
①#include的作用:相当于将里面的内容复制粘贴到当前文件中。
例如:将REGX52.H文件内的东西全部移到main文件中,替代掉#include 后警告消失),不会报错。
下面图片的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文件的存储位置
①右击
②右击上方文件栏的
③在弹出来的弹窗中即可看到位置。(位于安装文件下)
补充:< >与” ”包含文件的区别
前者表示直接在系统盘中寻找文件,后者表示在当前文件下寻找文件(如果找不到,逐级向下找,直到找到系统盘)。
因此< >可以替换为” ”,而” “替换为< >可能会出现找不到文件的情况。
Ⅱ、添加需要的.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文件(如果模块化编译了的话)
Ⅴ、将程序烧录进单片机
调试方式:
数码管——缺点:需要不断扫描
串口——缺点:需要连接电脑传输数据,有点麻烦
LCD1602液晶屏
利用附带的LCD1602液晶屏进行调试,安在之前介绍的排座上,此时第一排亮起。
可以通过转动滑动变阻器,更改液晶屏显示亮度。
单片机核心:
动态数码管:
LED:
LCD1602:
由上面接口与单片机接口对应可知,LCD1602占用P0口与P2的5、6、7口,因此数码管无法使用,且LED的三个灯无法使用(根据上图显示的应该是D6~D8灯,但是测试,是前三个灯,可能跟单片机接反有关系)。——LCD1602调试缺点
但是,它只与上面的LED与数码管冲突,其他没有影响,因此依然可以使用这个方法进行调试。
①#include 后面< >或” “里面的英文不区分大小写。
②将函数或变量添加进主函数文件,但没有使用,会出现warning,提示占据内存空间,但是在现阶段(没有写很多内容的情况下)可以选择忽视。
③如果需要不显示特定的warning,可以通过点击[魔术棒]图标,然后点击[BL51 Misc],在warning框输入对应的警告编码即可。
如下面的,出现了L16的错误提示,只需要输入16,点击[OK],再次编译时就不会再提示了。
④右击对应函数时,即可在快捷栏中前往对应的定义(.C文件)和声明(.h)文件。
作用:初始化定义LCD1602,使得后面的LCD1602代码函数可以使用。(必须定义后,才能使用其他函数)。
用法:直接输入 LCD_Init( ); 完成使用。
作用:在特定行与列显示一个字符。(根据测试,ASCLL码表里面字符内容可显示,虽然有点出入)
用法:
第一个参数输入行的数字(上下两行),
第二个参数输入列的数字(从左到右一共16列),
第三个参数输入字符(带上单引号表示为字符;不带单引号,选择直接打数字,就会直接转换为ASCLL码表上对应字符)。
——附带的行列极限为本单片机附带LCD1602液晶屏显示内容的范围。
作用:在LCD1602液晶屏上显示字符串。
用法:
第一个参数为首个字符的行,
第二个参数为首个字符的列,
第三个参数为字符串内容(需要用双引号包围,表示为字符串)。
Ps:如果输入字符串内容超出范围,会无法显示后面内容,且可能会出现乱码、蜂鸣器响动等现象。
作用:显示无符号的数字(范围为0~65535)。
用法:
第一个参数为首个数字的行(每一位数字占据一个行列格),
第二个参数为首个数字的列,
第三个参数为数字(可为八进制,十进制,十六进制,会转换为十进制输出显示),
第四个参数为数字长度(比如数字123,长度为3)。
Ps:
①如果数字大于65535(或小于0),会取该数字对65535的余数减一(或绝对值跟65535取余后,65535+1-余数)。
②如果所给参数的数字长度小于填写的数字长度,会从最高位数到所给参数数字长度,其他位舍弃(即不显示)。
③如果所给参数的数字长度大于填写的数字长度,会从填写的数字前面补零,直到满足参数的数字长度。
作用:显示有符号的数字(正数前面显示+,负数前面显示-)。
用法:
第一个参数为首个数字的行,
第二个参数为首个数字的列,
第三个参数为数字(负数前面需要输入负号;正数前面可选择输入正号,或者不输入)(可为八进制,十进制,十六进制,会转换为十进制输出显示),
第四个参数为数字长度(不包含前面的正负号)。
Ps:细节与上面无符号数字函数相同,但是得小心符号也占据一个空间,避免重叠(覆盖之前的内容)或超出范围。
作用:显示十六进制的数字。
用法:
第一个参数为十六进制数最高位的行,
第二个参数为十六进制数最高位的列,
第三个参数为数字(可为八进制,十进制,十六进制,会转换为十六进制输出显示),
第四个参数为十六进制数长度。
Ps:长度范围为1~4,
①如果对应十六进制数长度低于输入参数的数字长度,会在前面补0到满足长度;
②如果对应十六进制数长度大于输入参数的数字长度,会从前面的数字开始显示到相应长度结束。
③如果输入的数字长度超过指定范围,会如同上面的函数Ⅳ的Ps①一样的处理规则(记得转换进制)。
④如果超出4,会在前面补F,后面四位正常显示(正常显示表示满足上面①②③的规则),
作用:显示二进制的数字。
用法:
第一个参数为二进制数最高位的行,
第二个参数为二进制最高位的列,
第三个参数为数字(可为八进制,十进制,十六进制,会转换为二进制输出显示),
第四个参数为二进制数长度。
Ps:与函数Ⅵ的Ps类似,但去除④。
①可以通过上面的数字显示函数,进行数字的转换,达到显示转换数字的目的;
②可以在上面的数字显示函数的第三个参数(输入数字参数)中,放入运算式,使得液晶屏显示对应结果。
③可以通过显示,测试其他函数是否有作用。
如Delay函数是否有效:
在键盘中按键数量较多时,为了减少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液晶屏显示对应的数字。
步骤:
方法与之前一样。
①打开之前的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文件
Ps:如果不加判断语句,那么会一直显示0(其实是出现了数字变化,但很快被刷为0)。
原因是没有判断语句的话,每次调用函数时都会将KeyNum的值刷为0,而且显示出来;加了判断之后,就把0的情况排除出去,使得能变化显示的方式只能通过按键。
——这一个与独立按键控制LED点亮中的if语句原因类似。
添加注释模板:
显示效果:
上面的brief行注释内容为简介,param行内容为函数参数,retval行内容为返回值,如有特别说明,可以新加一行空白行添加内容。
使用模板原因:
①在使用注释编译器(可以把注释提取出来,并生成说明文档,列出表格,最后生成文本,便于别人参考,而不需要查看程序)时,更直观。
②显得更专业。(确信是唯一原因,嗯!)
实现效果:将独立按键的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烧录程序里面的进度条走完后,程序才算烧录完成)