本篇内容是观看B站江科大自化协UP主的教学视频所做的笔记,对其中内容有所引用,并结合自己的单片机板块进行了更改调整。
以下笔记内容以一个视频为一个片段(内容较多,可能不适合速食,望见谅)
一些内容涉及前面的知识点,可能需要提前了解(可以翻看本人之前的文章或者去B站看UP主的视频)
目录
9-1、LED点阵屏
LED点阵屏介绍
LED点阵屏由若干个独立的LED组成,LED以矩阵的形式排列,以灯珠亮灭来显示文字、图片、视频等。LED点阵屏广泛应用于各种公共场合,如汽车报站器、广告屏以及公告牌等
LED点阵屏分类
按颜色:
单色
双色
全彩
按像素:8*8、16*16等(大规模的LED点阵通常由很多个小点阵拼接而成)。
—— 一般设置为8的倍数。
LED点阵屏显示原理
解释内容
①LED点阵屏的结构类似于数码管,只不过是数码管把每一列的像素以“8”字型排列而已。
LED点阵屏示意图:
一位数码管:
②LED点阵屏与数码管一样,有共阴和共阳两种接法,不同的接法对应的电路结构不同。
LED点阵屏原理图(共阳接法):
双色LED点阵屏原理图(共阳接法):
当为单色LED点阵屏时,共阴接法与共阳解法区别不大(可以通过扫描转换效果);为多色LED点阵屏时,能明显体会到区别(如上图)。
Ps:
(1)共阴接法只需要将LED倒过来,更改连接的相关线即可。
(2)引脚排序为乱序排序(实物接线就近原则),因此需要对比自己单片机的原理图,避免出错。
③LED点阵屏需要进行逐行或逐列扫描,才能使所有LED同时显示。
Ps:扫描概念类通于矩阵键盘扫描,利用视觉暂留效果。
8×8LED连接原理图
由上图可知,LED点阵屏一侧由P0_0~P0_7的位寄存器(I/O口)控制,另一侧一侧由DPa~DPh控制,而对应的另一侧连接到74HC595中。
由图可见,74HC595可以通过P3_4~P3_6三根线控制右边八根线,由此能极大减少I/O口占用,节省I/O口。
74HC595运行原理
74HC595是串行输入并行输出的移位寄存器,可用3根线输入串行数据,8根线输出并行数据,多片级联后,可输出16位、24位、32位等,常用于IO口扩展。
板子上的74HC595
原理图介绍
右侧QA~QH为八位并行的输出端。
OE(Output Enable)为输出使能,当OE接了低电平,右侧才能输出。
(上面有一横线代表低电平有效,或下降沿有效)
RCLK(register clock)为寄存器时钟。
SRCLR(serial clear)为串行清零端,低电平时会将芯片里面数据进行清空(因此这里接VCC)。
SRCLK(serial clock)为串行时钟。
SER(serial)为串行数据。
QH’用于多片级联。
在这里使用P34~P36这三个引脚控制右边八位的输出。
运行机制解释
①当串行输出时,数据在时钟的激励下一个一个从SER的线输出;
②当并行输出时,数据直接从最右侧八个引脚同时输出外面。
步骤解释:
①写入第一个数据。
首先给SER写入数值。(假设为1)
接着将SERCLK接入高电平(上升沿),此时1会被读取进移位寄存器最上面的位置。
然后将SERCLK清零,回到默认状态(低电平)。
——单片机I/O口默认状态为高电平,因此最开始需要先设置成低电平。
②写入第二个数据。
首先给SER写入设置。(假设为0)
接着将SERCLK接入高电平(上升沿),此时0会被读取进移位寄存器最上面的位置,而最开始的1会向下移动一位。
然后将SERCLK清零,回到默认状态(低电平)。
③写入接下来的数据。
重复步骤②的原理,最终写完8个位置。
④将左边数据移入右边。
将RCLK接入高电平(一开始的I/O口为高电平,因此最开始要初始化为低电平),然后左边数据就会同一时刻被搬入右边(相当于大门被打开),并同时在右侧的输出口输出数据。
补充:多片级联的运行原理
多片级联即在QH’后再相连了与之前一样的一片运行电路。
两片级联:
当第一片的移位写满之后,就会移位到QH’内(即下一片的SER),于是再写入的值,最下面的会被推入第二片区域里面,以此类推,最终将第二片填满。(两片的SERCLK是连接在同一个I/O口上的)
当填满后,即可将RCLK接入高电平,将数据一次性输出。(两片的RCLK也是连接在一个I/O口上的)
其他的多片级联
原理跟两片级联相同,只是在QH’后加入其他的74HC595芯片进行级联,数量增多了。
作用:
通过这种方式,能将I/O口扩展(节省I/O口)——3个I/O口控制多位,但是由于是一位位推入,因此运行时间将会较缓慢。
补充:
①能否LED点阵屏两端都直接连接I/O口
即使想将连接的部分都替换为I/O口直接控制,也不可行。
因为单片机的工作模式为弱上拉模式,输出的高电平信号弱,因此会使得LED亮度低,甚至不工作。
所以需要利用74HC595使得输出高电平能力增强。
或者加入一个三极管开关,使得接入高电平时,开关导通,一头的VCC直接接入LED的正极一侧。
截自视频:
导通:
如上图所示,当给高电平时,VCC直接导通到LED部分,使得LED能正常运行。
②为什么当点阵屏多个灯亮时,灯比较昏暗
因为74HC595芯片为恒压输出,当连接的灯增多时,会使得电流变小,灯亮度变暗。
9-2、LED点阵屏显示图形&动画
代码一
实现效果:在LED点阵屏上显示一个图案(这里以笑脸为例)。
写入代码
Ⅰ、新建工程及main.c文件
方法如之前一样。
补充:C51的sfr、sbit
sfr(special function register):特殊功能寄存器声明
例:sfr P0 = 0x80;
声明P0口寄存器,物理地址为0x80
sbit(special bit):特殊位声明
例:sbit P0_1 = 0x81; 或 sbit P0_1 = P0^1;
声明P0寄存器的第1位
可位寻址/不可位寻址:
在单片机系统中,操作任意寄存器或者某一位的数据时,必须给出其物理地址,又因为一个寄存器里有8位,所以位的数量是寄存器数量的8倍,单片机无法对所有位进行编码,故每8个寄存器中,只有一个是可以位寻址的。
对不可位寻址的寄存器,若要只操作其中一位而不影响其它位时,可用“&=”、“|=”、“^=”的方法进行位操作。
具体可联系定时器的部分。
在REGX52.H文件中,能看到对应的单片机位声明。
寄存器声明:
位声明:
由上面可知,将对应的地址(后面的十六进制数)赋值给P0_0口,使得P0_0代表单片机相应部分地址。——所以可以更改对应的名字,只要地址相同,作用就相同。
(这里是在主函数文件操作的,因此使用的是P3^5之类,其代表的就是对应地址数字)
Ⅱ、编写74HC595运行函数
解释SER=Byte&(0x80>>i):
利用&运算符中有0为0,全1为1的的规则,使得直接取出Byte(十六进制数,转化为二进制进行运算)的二进制中一个位数;
然后通过移位符>>,实现取出对应的位数;
最后利用单位数变量非0即1的规则,给SER赋值,实现提取对应二进制位进行赋值。
Ⅲ、将对应寄存器初始化
Ⅳ、添加已有的模块化文件
将Delay函数的模块化文件添加进工程目录下。
Delay.c文件
Ⅴ、写入LED点阵屏显示一列的代码函数
①前面加入预编译,将MATRIX_LED_PORT的名字在编译后替换为P0(C语言的预编译知识)。——便于理解P0口对应的作用含义。
②写入函数代码。
解释MATRIX_LED_PORT=~(0x80>>Column):
(1)利用0x80的移位,实现选择对应的列数读取数据,其他列数无作用的目标效果。
(2)利用取反(~)符号,将0置换为1,配合移位符号,实现目标效果(因为移位时会在末尾补零,因此利用仅有一个1,其他都是0的数字方便移位)。
(3)将移位后的值赋予P0口,使得P0口直接显示对应的列数灯状态。
Ⅵ、编写主函数代码
①获取图案对应每列的数据。
来自up主视频截图:
在Excel中弄出对应的图案,然后按照列(这里采用的是列扫描)进行读取二进制数(从高到低),并将其转换为十六进制。
Ps:后面有工具进行取数,所以这一步的转换数字了解即可。
②将对应列数写入主函数。
Ⅶ、烧录程序
Main.c文件代码:
将代码烧录进单片机,即可在LED点阵屏看到预期效果(笑脸)。
代码二
实现效果:在LED点阵屏中流动地循环显示Hello!内容。
写入代码:
Ⅰ、将上面的目录复制一份,重命名后打开。
步骤同之前一样。
Ⅱ、模块化LED点阵屏显示的相关函数
MatrixLED.c文件
MatrixLED.h文件
补充:获得对应图案的每列十六进制方法
①利用up主附带的取模软件,打开(取模软件可以上网搜索,功能可能更多)
②点击[新建图像],调整对应的宽度与高度。
Ps:
设置高度时为8;
(因为预期的流动字幕是从左到右流动的,上下高度限制,如果流动方向为上下,可以更改,但是得将前面的按列扫描模式函数调整一下)
设置宽度为32。
(这里流动是从左到右,因此宽度可以调整大于点阵屏的列数限制,但是最好取8的倍数,显示时可能更流畅——也可不调整,自己可以试试效果)
③可以点击[模拟动画],在下方点击[放大格点],使格点显示变大。
④在[参数设置]中调整自己的设置。(调整完成点击[确定])
⑤在生成的空白部分点出自己想要的图案
⑥在[取模方式]中点击[C51格式],即可在下方的[点阵生成区]看到对应的十六进制代码。
Ⅲ、编写数组,存储流动字幕的每一列十六进制数
将获得的代码复制到数组里面。
为了便于读懂代码,将每行调整为8个十六进制数。
Ⅳ、编写主函数
利用偏移量Offset,使得每次在LED点阵屏持续显示的内容不断前进,实现流动效果。
Ⅴ、烧录程序
Ps:这里将数组进行了调整,在最前面与最后面分别加入了一行8个0x00的十六进制数,并调整了Offset的if越界从头条件,使得动画显示不生硬。
烧录进单片机后,即可看到流动的Hello!效果。
代码三
实现效果:在LED点阵屏上显示笑脸与哭脸不断转换的表情。
写入代码
Ⅰ、将代码二的文件复制一份,重命名
(也可直接在代码二中改动,看是否要保存代码二)
步骤与之前一样
Ⅱ、更改存储数据的数组
在取模工具中画好笑脸、无表情脸、哭脸的图案,然后将生成代码粘贴到数组内。
Ⅲ、更改主函数中帧数更改的部分
这里一次性将偏移量加8,使得每次显示切换显示时直接切换到下一张画面。
Ⅳ、烧录程序
烧录后即可看到预期效果。
补充:code关键字
这里加入code之前,将存储内容放入ram(随机存储器)中。
——ram为程序运行时的暂存器(可以回到最开始的单片机介绍)。
加入code后,将存储内容放入flash(闪存存储器)中。
——flash空间较大,可以存储内容更多,但是后面无法对数组内容进行更改(比如说Animation[1]=0xFE之类的事情做不了)
10-1、DS1302实时时钟
DS1302介绍
简介:
DS1302是由美国DALLAS公司推出的具有涓细电流充电能力的低功耗实时时钟芯片。它可以对年、月、日、周、时、分、秒进行计时,且具有闰年补偿等多种功能。
补充:
RTC(Real Time Clock):实时时钟,是一种集成电路,通常称为时钟芯片。
例如DS1302、DS3231(精度很高,内部集成化很高,且比较贵)、DS12C887(里面自带电池)。
(单片机内部没有实时时钟RTC,因此可以外接一个RTC的芯片,如DS1302——这里的单片机指的是最中间的横条部分,具体可以回到最开始的单片机介绍了解)
DS1302:
网上出售的最小功能模块版:
时钟:
单片机上的DS1302芯片:
作用:(与之前定时器制作时钟对比)
①计算时间精度比之前的定时器实现的时钟精度更高。
②无需占用单片机的CPU运行时间。(定时器时钟需要占用CPU给定时器计时)
③掉电可以继续运行。(定时器制作的时钟不能)——自带一个备用点出,当掉电的时候会自动切换到备用电池继续工作。
DS1302引脚定义和应用电路。
两种封装:
其中DIP为直插封装,引脚是立起来的,可以插在PCB的焊盘上。(可以回到最开始的单片机介绍了解)
SO为贴片封装,贴在电路板表面上。——单片机上的DS1302芯片封装。
应用电路
电源部分:
DS1302内部连接图:
由上图可知,VCC1连接部分并没有接备用电池,因此这里无法实现时钟掉电继续运行。
晶振部分:
一般有关实时时钟的晶振值都为32.768KHZ,因为这个频率方便易用,且精度较高。
该晶振为时钟芯片提供稳定的计数脉冲,经过内部的处理,会输出标准的1HZ频率,且频率的精度高。——晶振产生的时钟频率高,且稳定性高。(可回到单片机介绍那里了解)
通讯部分:
利用这三个引脚,可以读出/写入数据进芯片内部时钟。
根据这三个引脚的通讯协议,可以对芯片内部的寄存器写入或读出数据。
这三根线的通讯方式与74HC595芯片的通讯方式相类似。
DS1302芯片内部结构
时钟运行部分
通过外部X1和X2传入的晶振进行内部电路处理,最终以1HZ频率输出。
访问部分
CE线:控制读写的总开关(低电平打开)。——CE未打开时不影响其他两根线的数据处理。
I/O线与SCLK线:作用类似于74HC595的写入过程,不过多了读取功能。
这里的I/O类似于SER,SCLK类似于SERCLK。
寄存器部分
寄存器中的内容已经定义完成,设置好对应的范围,因此只需要进行时钟读写操作即可(无需范围判断等处理)。
寄存器定义:
时钟有关部分的寄存器:
①寄存器每个地址存储数据为一个字节;
②划线部分以上的寄存器存储内容为时分秒,年日月,星期;(最后的RANGE为其范围)
③下面的WP(write protect)为写保护,当置一时,写入的操作无效,但依然可以读出数据。
④TCS寄存器用于存储涓流充电。(不对电池进行充电的话,无需配置)
⑤READ对应的十六进制数为其读取时的寄存器地址,WRITE对应的十六进制数为其写入时的寄存器地址。
命令字:
手册中的命令字:
①该命令字为一个字节,一共八位,
②最高为7,固定值为1;
③如果要操作RAM,第六位给1;如果要操作CK(clock,时钟),第六位给0;
④通过控制第5~1位的数字,设置是哪种状态的寄存器(时分秒、年月日之类);
(根据上面的地址写入)
⑤通过给第0位赋值,控制读取还是写入。其中给1为读取,给0为写入。
时序定义:
①开始写入时需要将CE置高电平1,写完后置为0。
②当SCLK为上升沿时,将数据写入;当SCLK为下降沿时,DS1302将会把数据输出——类似于74HC595。
(仅限于READ的读取模式时下降沿才输出存储的数据;当为WRITE时,写完命令字节还会继续写数据)
③除了画圈部分由DS1302控制外,其余部分均由单片机控制。
操作步骤:
一、首先设置模式(这里设置为写入)
①将CE置1,开始向寄存器写入数据;
②给I/O口赋上最低位的命令字(第0位),选择读取还是写入;(这里设置0,为写入)
③给SCLK上升沿,将I/O口数据写入命令字寄存器相应位置;
④再将SCLK归零;
二、接着选择寄存器类型,按照地址设置
①根据寄存器的地址给I/O口赋值次低位的命令字(第1位);
②给SCLK上升沿,将I/O口数据写入命令字寄存器相应位置;
③再给SCLK归零;
④以此类推,完成第2位到第5位的赋值。
三、选择操作对象(这里为CK,即clock)
仿照步骤二的①~③过程即可。(赋值给I/O口时给0)
四、写入第7位
仿照步骤二的①~③过程即可。(赋值给I/O口时给1)
五、读取或写入设置的寄存器数据
①当设置为写入时,写入第7位的命令字后,即可紧跟着后面写入设置的寄存器里面的数据;
②当设置为读取时,写入第7位的命令字后,一旦SCLK给下降沿,那么DS1302就开始将设置的寄存器里面的数据输出给单片机,每次给一次下降沿读取一位数据,一直读完八个数据。
六、回归初始状态
最后操作完成后把SCLK置零,将CE置零,完成操作。
10-2、DS1302时钟&可调时钟
代码一
实现效果:通过利用DS1302芯片在LCD_1602液晶屏上第一行显示年月日,第二行显示时分秒。
Ⅰ、新建工程并创建main.c文件
Ⅱ、添加存在的已模块化的文件
将LCD1602文件与Delay文件添加进工程目录下。
其中LCD1602为液晶屏显示的相关函数文件,Delay为延时函数文件(最终Delay文件可能用不到)。
Ⅲ、建立DS1302的模块化代码文件
DS1302.c文件
①将引脚名称重新定义名字。(可以不做)
这样能更直观的明白对应I/O口的作用,且利用下划线能明白对应的来源。
②添加DS1302相应I/O口的初始化函数
根据上节所说,需要先将CE置0,以便置1时开始做写入/读取操作;
当SCLK为1时,开始读取IO的数据,因此需要先置0。
③添加DS1302的写入函数
首先写入模板
其中Command为命令字,Data为对设置的寄存器写入的数据。
接着配置命令字的寄存器
根据上一节所说的步骤,得到如下配置命令字的循环。
解释DS1302_IO=Command&(0x01<<i):
首先利用左移运算符,使得每次将八个位的二进制数唯一一个1移动位置;
然后通过&运算符(全1为1,有0置0)来将1所在的数字取出,而其他位因为有0,所以全部变成0,使得影响最终赋值的只有1对应的位置。
最后将得到的值赋予IO口,完成填入工作。(非0即1的原则,即不是0的数,统统变成1填入)
Ps:
最后对SCLK的两条语句操作不能太快,存在间隔时间,否则放入数据的工作可能出错。
可以在手册中查询最小的间隔时间
可以看到最小为50ns,远小于单片机的运行速度(单片机运行一个周期就1us左右了),因此这里不需要加入延时。(如果单片机运行速度很快,则在两条语句中间加入延时即可)
最后配置对设置的寄存器写入数据的函数
这里将上面设置命令字的循环复制过来,更改一些部分即可。(因为操作步骤基本相同)
④添加DS1302的读取函数
首先写入模板
这里只需要Command(命令字数据)参数即可,因为后面是读取的操作。
接着配置命令字的寄存器
根据上节的步骤,可以得到下面的循环。
循环步骤与上面写入的命令字循环类似,但是SCLK是先0再1,原因是READ的SCLK的频率中比WRITE的少一次上升,为15次上升。
为了使得下面读取数据时不至于读取不到第一位数据(因为是下降沿读取,所以最后如果SCLK置0的话,会导致第一位数据已经读取出来,但没有接收而导致遗漏的情况),因此需要先0再1。
最后配置读取设置的寄存器里面数据的循环
解释if(DS1302_IO) Data|=(0x01<<i) :
首先利用左移运算符,使得每次将八个位的二进制数唯一一个1移动位置;
然后通过 | 运算符(全0为0,有1置1)来将1所在的位置,填入Data中,而其他位因为Data初始化为0x00的缘故,所以都是全0,从而实现将1所在的位置对应填入Data中。
(所以需要用判断语句,使得为0时的位,不会进入语句中置1)
最后将得到的值赋予Data,完成每次的更新读取数据。
最终效果
DS1302.h文件
Ⅳ、测试前面写入的代码是否有效
根据上面秒数的寄存器地址,写入对应代码,填写相应地址。
编译后,可在LCD_1602液晶屏中看到对应显示,说明代码有效。
接着将读取与显示秒数的语句移入循环:
可以看到秒数以一定速度加一,但是在到9时会突然变为16,是因为DS1302内部寄存器是以BCD码的方式进行存储的(非二进制的方式进行存储)。
Ps:
当移入循环后,发现数字没有增长跳动的话,记得将写保护模式关闭(即上面的DS1302_WriteByte(0x8E,0x00);)
补充:BCD码
即BCD码通过4位二进制表示1位十进制数,
且高四位的二进制表示十位,低四位的二进制表示个位。
因此4位二进制中对应十进制的0~9的数字是合法的,而出现大于9的数字表示时,是不合法的(如上面的1010,为十进制的10,因此不合法)
解释9突变为16的原因:
因为当数据为0000 1001(即数字09)时,因为个位为9,加一变成10,经过BCD码的转换,便是在高四位的二进制数显示1(即二进制的0001),低四位的二进制数显示0(即二进制的0000)来表示数字正常进位。
但是0001 0000经过十进制的转换,便是16,因此才会出现跳跃进位显示的原因。
当使用进行显示时,可以发现没有跳跃进位的现象,是因为这个函数的显示是以四位二进制数为一位显示的,所以当没有超过10时,也就不会出现A这种字母,自然可以显示成与正常进位一样。
BCD码转十进制:DEC=BCD/16*10+BCD%16; (2位BCD)
这里将BCD码除以16是因为如同上面的一样,BCD码到二进制的9加一后进位到下一位,而十六进制则是到F(十进制的15)加一后进位到下一位,只要把十六进制A~F的数砍掉,到9进位,就可以实现如同BCD码一样的进位方式。
因此将BCD/16再*10,就可以将数转化为十进制数。
十进制转BCD码:BCD=DEC/10*16+DEC%10; (2位BCD)
这一个转化与上面同理,只是将顺序倒转即可。
因此将显示函数写为时,即可解决突跃问题。
补充:DS1302寄存器一些位的作用
①在秒寄存器中,可以见到次前三位的(6、5、4)二进制位负责存储十位的秒数(即BCD码的存储),
而在最高位中为CH,这个位作用是控制时钟暂停。当给1时,时钟暂停;给0时,时钟运行。
②在时寄存器中,最高位为1时,设置为12小时模式;为0时,设置为24小时模式。
当为24小时模式时,第三位(5)就与第四位(4)一同显示小时的十位。
当为12小时模式时,第三位(5)显示AM(0代表AM)与PM(1代表PM),而第四位(4)单独显示小时的十位。
③在其他的寄存器中,则可以很明显的看到寄存器以BCD码的形式显示数据。(前四位为十进制的十位,后四位为十进制的个位)
Ⅴ、设置存储初始时间的数组
在DS1302.c文件写入存储初始时间的数组。
通过这种方法,便于直接读取数据,防止不知道对应数字的含义,显得混乱。
Ⅵ、设置写入时间的函数
在DS1302.c文件中编写写入时间的函数,这样就无需将年、月、日、时、分、秒、星期的写入每次都在主函数调用,直接实现一步到位。
①写入模板
②重新定义DS1302的寄存器
将DS1302相应的时间寄存器重新定义名字,以便后面不用再去翻看手册查地址,可以直接明白对应含义并操作对应寄存器。
③将写保护模式关闭(避免无法写入的情况)
④对之前的读取函数添加一条处理
通过下面的语句,直接将前面定义的地址最低位赋1,将地址变成读取寄存器地址,这样可以不用再重新定义读取的寄存器地址。
⑤将相应的寄存器的写入函数进行调用
Ⅶ、设置读取时间的函数
①写入模板
②将相应的寄存器的读取函数进行调用
在每次调用后,用变量Temp接收返回的读取值,并转化为十进制后存储进时间数组中,便于主函数调用。
Ⅷ、将新加的内容的声明添加进DS1302.h文件中
Ps:这里在数组前面加入extern,是因为当声明变量或数组为外部可调用时,需要加入关键字extern。(当然数组其实可以不加,但是加上比较好)
Ⅸ、在主函数中添加液晶屏的时间显示
Ⅹ、将程序烧录进单片机,即可看到预期效果
main.c文件
这里因为没有调用Delay文件,所以可以选择删除掉。
代码二
实现效果:通过按下独立按键K1,进入设置时间模式(再次点击退出设置);按下独立按键K2,可以在设置模式下,选择设置年、月、日、时、分、秒;按下独立按键K3,对选中的部分加1;按下独立按键K4,对选中的部分减1。
而在设置时间模式时,选中的数字会进行闪动。
Ⅰ、将上面的代码文件复制一份,并重命名
Ⅱ、将需要的已经模块化的代码添加进工程目录下
这里的Key文件为独立按键的文件,Timer0为定时器0的文件
Key.c
Timer0.c
Ⅲ、将显示时间的液晶屏函数代码单独移出成为一个函数
这样能使main函数更加简洁
Ⅳ、添加时间设置函数
①写入框架
②定义需要用到的变量为全局变量
③设置独立按键K2的效果
④设置独立按键K3的效果
这里的时、分、秒只需要利用if语句,当达到上限时归零即可。
而这里的年、月、日加入判断条件比较复杂,目的是为了实现下面的效果:
一、由于存在闰年,单双数月导致的日期上限不同的原因,需要加入比较多的条件语句,以实现当闰年时,二月的最大天数为29,设置年份不是闰年时,二月从原先的29能跳到1。
二、当设置月份时,如果日期的数字为当前设置月份所不存在的天数,将日期部分归1。
三、当设置日期时,能被月份、年份所限制,不同的月份与年份的日期极限不同。
⑤设置独立按键K4的效果
这里的时、分、秒只需要利用if语句,当达到上限时归零即可。
而这里的年、月、日加入判断条件比较复杂,目的与独立按键K3相同。
Ps:当设置的读取数据返回值为unsigned时,从0再减1会直接变到当前变量类型的最大值,所以得小心这一点判断。
⑥设置更新显示的效果
Ⅴ、设置中断函数实现进入设置时,设置数字闪烁效果
Ⅵ、编辑主函数部分
添加独立按键K1的效果
Ⅶ、将程序烧录进单片机
烧录进单片机,即可看到预期效果。
main.c文件
Ps:
该代码仍存在缺陷,比如按住独立按键时,时钟无法进行走动,因此可以继续进行改进。
原因:在key.c文件中,有while循环,使得按住时,CPU直接卡住无法继续下一步工作。
可以通过定时器中断来扫描按键,对按键的上升沿或下降沿进行单独捕获,这样既可以做好消抖工作,又不会因为按住按键时导致时钟无法走动。
尝试:
可以利用定时器来延时,并将key文件中的内容进行更改即可。
下面以矩阵键盘的更改代码为例,独立按键类推即可。
MatrixKey.c文件
MatrixKey.h文件
这里利用again将代码分成两部分,
一部分负责检测是否有按下(按下时电平为0),通过Now变量记录按下的是哪一个按键,并将again赋1,进入第二部分;
另一部分负责检测按键是否有松开(松开时电平恢复为1),通过与Now变量配合,锁定对应的按键,
最终实现按下按键并松开后,返回对应的键码;同时实现了按住按键时不会返回键码,且不会将CPU卡在某一步代码中无法出来。
经过测试,该代码文件也可实现不依靠定时器的情况下,实现消抖。
(利用上升沿与下降沿的原理,可以将again放出来,不放在if语句内;但是如果配合定时器使用的话,放进if语句里面能更好的减少延时时间)