一、数码管模块
1.数码管介绍
如图所示为一个数码管的结构图:
- 说明:
- 数码管上下各有五个引脚,其中上下中间的两个引脚是联通的,一般为数码管的公共端,分为共阴极或共阳极;
- 其它八个引脚分别对应八个二极管,从 a ~ g 包括右下角的点,每个二极管与就近的引脚一一对应。
2.单片机数码管电路图
2.1数码管电路图
- 说明:
- 如图所示的电路图,可以看到使用的是两个四位一体的共阴极数码管,八个数码管的段选全部并联一起引出,每个数码管一个位选公共端;
- 在通过数码管显示数字的时候,先要确定通过哪个数码管显示数字,将其公共端赋为 0 ,其它数码管公共端赋为 1 ,即为共阴极。然后将指定段 0 ~ 7 赋值 0(熄灭) 1(点亮),以此显示相应的数字;
2.2 74HC138译码器
- 为了节约 MCU 的 IO 口,这里用到了译码器:
- 说明:
- 这是一个三通道输入,八通道输出的译码器,其中 P22 - P24 是对应的单片机上的 I/O 口,一共三个;
- Y0 - Y7 对应的是 LED1 - LED8 八个数码管的公共端,字母上面都有一个横线,低电平有效,因此可以推测这里的数码管共阴极。计算机里三位二进制对应一位八进制,三位二进制范围:000 ~ 111,转换为八进制就是 0 ~ 7,正好 8 个数字,对应八个数码管公共端,这样既可以实现通过三个引脚,控制八个数码管公共端信号了;
- G1、G2A、G2B和 GND 为使能端,字母上面有横线的默认是低电平有效。
2.3 74HC245驱动芯片
- 说明
- 74HC245 是一种三态输出、八路信号收发器,主要应用于大屏显示,以及其它的消费类电子产品中增加驱动;
- 它具有双向输出功能,即 DIR 方向控制,当 DIR=1 时,由 A 到 B,等于 0 时,由 B 到 A,因为这里是给数码管供电,即 A 为输入端,B为输出端,因此直接将 DIR 接 VCC,输出使能 OE 接 GND。
3.数码管实验
3.1指定数码管显示指定数值
- 代码演示
#include <REGX52.H>
// 定义一个数组,存放0~9的数值编码
unsigned char Nums[] = {0x3F,0x06,0x5B,0x4F,0x66,0x6D,0x7D,0x07,0x7F,0x6F};
void Nixie(unsigned char LEDNum, ShowNum)
{
// 选择点亮哪个LED
switch(LEDNum)
{
case 1:P2_4=0;P2_3=0;P2_2=0;break;
case 2:P2_4=0;P2_3=0;P2_2=1;break;
case 3:P2_4=0;P2_3=1;P2_2=0;break;
case 4:P2_4=0;P2_3=1;P2_2=1;break;
case 5:P2_4=1;P2_3=0;P2_2=0;break;
case 6:P2_4=1;P2_3=0;P2_2=1;break;
case 7:P2_4=1;P2_3=1;P2_2=0;break;
case 8:P2_4=1;P2_3=1;P2_2=1;break;
}
P0 = Nums[ShowNum];
}
void main()
{
Nixie(7, 4);
while(1);
}
- 说明:
- 上面演示的结果:使用 LED7 显示数字4,可以指定使用哪个数码管显示指定数值;
- 将 0 ~ 9数字的段码数据存放到一个数组中,函数调用时传入对应的数字的下标索引,取出要显示数字的段码数据。断码数据对照数码管电路图计算,a ~ dp 分别对应八位二进制的低位到高位,想让数码管哪个 LED 亮,就将对应二进制位赋 1 就行;
- 通过 switch 语句确定要使用哪一个数码管显示数字。
3.2数码管动态显示
- 代码演示
#include <REGX52.H>
#include <INTRINS.H>
// 定义一个数组,存放0~9的数值编码
unsigned char Nums[] = {0x3F,0x06,0x5B,0x4F,0x66,0x6D,0x7D,0x07,0x7F,0x6F};
void Delayxms(unsigned int xms) //@12.000MHz
{
unsigned char i, j;
while(xms)
{
i = 2;
j = 239;
do
{
while (--j);
} while (--i);
xms--;
}
}
void Nixie(unsigned char LEDNum, ShowNum)
{
// 选择点亮哪个LED
switch(LEDNum)
{
case 1:P2_4=0;P2_3=0;P2_2=0;break;
case 2:P2_4=0;P2_3=0;P2_2=1;break;
case 3:P2_4=0;P2_3=1;P2_2=0;break;
case 4:P2_4=0;P2_3=1;P2_2=1;break;
case 5:P2_4=1;P2_3=0;P2_2=0;break;
case 6:P2_4=1;P2_3=0;P2_2=1;break;
case 7:P2_4=1;P2_3=1;P2_2=0;break;
case 8:P2_4=1;P2_3=1;P2_2=1;break;
}
P0 = Nums[ShowNum];
// 用于消影
Delayxms(1);
P0=0x00;
}
void main()
{
while(1)
{
Nixie(8, 1);
// Delayxms(20);
Nixie(7, 2);
// Delayxms(20);
Nixie(6, 3);
// Delayxms(20);
}
}
- 说明:
- 代码演示结果,在 LED8 LED7 LED6 分别显示连续数字 123;
- 这里只不过在 while 循环里不断调用函数,在三个数码管快速切换显示数字,利用人眼的余晖效应,类似于多进程的并发。人眼反应不过来,因此看起来是显示了一个连续的数字,实际上是在三个数码管之间快速切换显示;
- 显示过程中会有一个问题,就是调用函数的时候,先位选再段选,接着执行下一个函数,调用时也是先位选再段选,但是由于切换太快,切换到下一个位选的时候,下一个段选还没执行到,结果执行了上一个的段选,就导致显示的数值有重影,需要进行消影;
- 消影就在每次函数执行的最后,将该位段的段码数据全部置 0 ;
- 上面的实现原理是通过单片机的快速扫描实现的,比较消耗单片机的 CPU 资源。
二、LCD1602调试工具
1.LCD1602介绍
这里就是一个微型的显示屏,规格为 2 × 16 ,可以显示两行,每行最多显示 16 个字符。
我们在编写C语言代码的时候,会经常用到 printf 第三方库函数,将运行的结果打印到终端屏幕上,以进行相关的调试工作。在使用单片机的时候,我们也需要查看调试信息,就可以通过这样一块屏幕,然后写好想要的功能函数,通过头文件包含调用,达到输出显示的效果。
2.LCD1602 + 模块化编程
2.1延时函数模块化
模块化编程属于C语言阶段的基础,我在C语言里面有一篇博客专门讲了多文件编程,也就是模块化编程。
- Delay.c(这里的文件名自己随便取,但需要见名知意)
#include <INTRINS.H>
void Delayxms(unsigned int xms) //@12.000MHz
{
unsigned char i, j;
while(xms)
{
i = 2;
j = 239;
do
{
while (--j);
} while (--i);
xms--;
}
}
- Delay.h(头文件名最好与功能文件名相同,只是后缀不相同)
#ifndef __DELAY_H__
#define __DELAY_H__
void Delayxms(unsigned int xms);
#endif
2.2第三方函数介绍
本笔记是基于B站江科大51单片机视频学习,LCD1602具体的用法还未学习,因此使用江科大提供的模块化功能函数来实现,具体文件的获取,在江科大的网址。
获取文件后,直接将一个.c文件和一个头文件拷贝到和当前项目 main 文件同级目录下,在 main 文件里面包含头文件即可使用。
- 函数介绍
函数 | 作用 |
---|---|
LCD_Init(); | 初始化 |
LCD_ShowChar(1,1,‘A’); | 显示一个字符(行数,列数,显示的字符) |
LCD_ShowString(1,3,“Hello”); | 显示字符串(行数,列数,显示的字符串) |
LCD_ShowNum(1,9,123,3); | 显示无符号十进制数字(行数,列数,显示的数值,数值位数) |
LCD_ShowSignedNum(1,13,-66,2); | 显示有符号十进制数字(行数,列数,显示的数值,数值位数) |
LCD_ShowHexNum(2,1,0xA8,2); | 显示十六进制数字(行数,列数,显示的数值,数值位数) |
LCD_ShowBinNum(2,4,0xAA,8); | 显示二进制数字(行数,列数,显示的数值,数值位数) |
2.3LCD1602函数演示
- 代码演示
#include "Delay.h"
#include "LCD1602.h"
void main()
{
unsigned int num = 0; // 局部变量的定义要放在函数第一行
// 记得初始化
LCD_Init();
// LCD_ShowChar(1,1,'A'); // 打印字符 A
// LCD_ShowString(1,1,"hello world!"); // 打印字符串 hello world!
// LCD_ShowNum(1,1,123,3); // 打印数值 123
// LCD_ShowSignedNum(1,1,-66,2); // 打印数值 -66
// LCD_ShowHexNum(1,1,0xA8,2); // 打印十六进制数 A8
// LCD_ShowBinNum(1,1,0xAA,8); // 打印二进制数 1010 1010
// 显示数值以秒递增
while(num < 1000)
{
LCD_ShowNum(1,1,num,3);
Delayxms(1000);
num++;
}
while(1);
}
- 说明:
- 上面分别调试了一下,LCD1602相应的函数;
- 后面做了一个计时器,从0开始以秒为单位递增到最大 999。
三、矩阵键盘
1.矩阵键盘介绍
1.1矩阵键盘实物介绍
- 介绍:
- 如图所示,为一个 4×4 的矩阵键盘,可以通过8个 I/O 口控制16个按键;
- 一个独立按键,就需要分配一个 I/O 口,但如果按键很多时,还这样分配,比较浪费资源,因此将按键按照行列分布,这样只需要使用行数 + 列数个 I/O 口就可以操作多个按键;
- 矩阵键盘获取按键状态的原理和独立按键一样,即将公共端设为低电平,I/O 口读到数据为 0 代表按下,为 1 代表松开。
1.2矩阵键盘电路图
- 说明:
- 矩阵键盘原理图如图所示,可以看到,这里将16个按键,通过8个 I/O 口控制;
- 单独看第一行,就和前面学习的独立按键一样,P17 为公共端,将公共端设置低电平,P10 - P13 就相当于段选端,控制S1 - S4四个按键,谁传回 I/O 的数据为 0 ,就能判断谁按下了。然后,去逐行扫描,就能分别读取每个按键的状态,因为扫描速度非常块,因此我们人在操作的过程中感觉每个按键都是实时判断的一样,本质上是同一时间只能判断一行或者一列;
- 上面将每行作为公共端,也可以将每列作为公共端,因此就分为行刷新和列刷新两种方式,为了防止 I/O 口干扰其它模块,这里选择使用列刷新的方式进行实验。
2.矩阵键盘 + LCD1602案例
2.1按下按键显示数值
这里采用模块化编程,各个文件的代码演示如下:
- MatrixKey.c
#include <REGX52.H>
#include "Delay.h"
unsigned char MatrixKey()
{
// 定义变量,用于保存按下按键对应的数值
unsigned char num=0;
// 将公共端初始化,全部置于高电平
P1=0xFF;
P1_3=0; // 将当前要扫描的列公共端置于低电平
if(P1_7==0){Delayxms(20);while(P1_7==0);Delayxms(20);num=1;}
if(P1_6==0){Delayxms(20);while(P1_6==0);Delayxms(20);num=5;}
if(P1_5==0){Delayxms(20);while(P1_5==0);Delayxms(20);num=9;}
if(P1_4==0){Delayxms(20);while(P1_4==0);Delayxms(20);num=13;}
P1=0xFF;
P1_2=0;
if(P1_7==0){Delayxms(20);while(P1_7==0);Delayxms(20);num=2;}
if(P1_6==0){Delayxms(20);while(P1_6==0);Delayxms(20);num=6;}
if(P1_5==0){Delayxms(20);while(P1_5==0);Delayxms(20);num=10;}
if(P1_4==0){Delayxms(20);while(P1_4==0);Delayxms(20);num=14;}
P1=0xFF;
P1_1=0;
if(P1_7==0){Delayxms(20);while(P1_7==0);Delayxms(20);num=3;}
if(P1_6==0){Delayxms(20);while(P1_6==0);Delayxms(20);num=7;}
if(P1_5==0){Delayxms(20);while(P1_5==0);Delayxms(20);num=11;}
if(P1_4==0){Delayxms(20);while(P1_4==0);Delayxms(20);num=15;}
P1=0xFF;
P1_0=0;
if(P1_7==0){Delayxms(20);while(P1_7==0);Delayxms(20);num=4;}
if(P1_6==0){Delayxms(20);while(P1_6==0);Delayxms(20);num=8;}
if(P1_5==0){Delayxms(20);while(P1_5==0);Delayxms(20);num=12;}
if(P1_4==0){Delayxms(20);while(P1_4==0);Delayxms(20);num=16;}
// 将结果返回
return num;
}
- MatrixKey.h
#ifndef __MATRIXKEY_H__
#define __MATRIXKEY_H__
unsigned char MatrixKey();
#endif
- main.c
#include "Delay.h"
#include "LCD1602.h"
#include "MatrixKey.h"
void main()
{
// 51单片机里局部变量必须放在函数的首行
unsigned char KeyNum=0;
LCD_Init();
LCD_ShowString(1, 1, "MatrixKey");
while(1)
{
KeyNum=MatrixKey();
// 加判断是为了防止被 0 刷新掉。显示不出来
if(KeyNum)
{
LCD_ShowNum(2, 1, KeyNum, 2);
}
}
}
- 说明:
- 上面代码演示效果为:按下矩阵键盘的按键,在LCD1602屏幕上显示对应的数值,如:按下 S12 会在键盘上显示12;
- 这里采用的是列刷新的方式,即将 P10 - P13 作为每一列的公共端,先将公共端整体初始化为高电平,然后将当前要扫描的列的公共端置低电平,去判断这一列的四个按键的回馈信号是 0 还是 1 ,为 0 则将变量 num 赋值为按键对应的数值,然后将结果作为函数返回值返回即可,如果没有按键按下,返回的就是默认值0。再继续去扫描下一列,如此循环;
- 主函数里只需要不停调用矩阵键盘扫描函数即可,如果函数返回值为 0 ,不调用函数显示,如果非 0 ,则调用函数显示。加 if 判断的原因就是防止结果被 0 刷新,因为程序执行很快,这边刚获取返回值,刚打印到屏幕,函数又执行了几遍,返回 0 ,将 0 赋值给了 KeyNum ,打印 KeyNum 的值,这样上一次运行的结果还没看清,就被 0 刷新了。
2.2矩阵键盘密码锁
- 代码演示
#include "Delay.h"
#include "LCD1602.h"
#include "MatrixKey.h"
unsigned char KeyNum; // 获取键盘数值
unsigned int Password, count; // 存储密码,count用于计算输入数值个数
void main()
{
LCD_Init();
LCD_ShowString(1, 1, "Password:");
while(1)
{
KeyNum=MatrixKey();
// 如果键盘扫描的返回值非 0 才执行相应的代码
if(KeyNum)
{
if(KeyNum<=10)
{
if(count<4)
{
Password*=10;
Password+=(KeyNum%10);
}
count++;
LCD_ShowNum(1, 10, Password, 4);
}
if(KeyNum==11)
{
if(Password==1234)
{
// 字符串后面的空格是防止上一次结果的干扰,因此通过空格刷新掉
LCD_ShowString(2, 1, "Hello Boss!!! ");
Password=0;
count=0;
LCD_ShowNum(1, 10, Password, 4);
}
else
{
LCD_ShowString(2, 1, "Password Error! ");
Password=0;
count=0;
LCD_ShowNum(1, 10, Password, 4);
}
}
if(KeyNum==12)
{
Password=0;
count=0;
LCD_ShowNum(1, 10, Password, 4);
// 16个空格,将提示栏全部刷新为空
LCD_ShowString(2, 1, " ");
}
}
}
}
- 说明:
- 运行效果:输入正确的四位数密码,会打印:Hello Boss!!! ,密码输入错误会打印:Password Error!。最多输入四位,如果已经输入了四位,再输入无效,每次输入完密码按下确认键显示密码正确与否的同时,密码全部恢复为 0000,按取消密码全部恢复为 0000 ,可以再重新输入;
- 将键盘 S1 - S10 作为数字键,分别代表 1 - 9 和 0,但是键盘扫描函数的返回值为 0 的话,那么就不会进来执行相关代码了,因此这里的 0 需要通过算法来获得。即用返回值对 10 取余,1 - 9 对 10 取余还是 1 - 9,10 对 10 取余就是0;
- 但是屏幕显示的数值是四位数密码,前面键盘按下输出数值的时候,都是后一个会把前一个数值刷新掉。那这里需要实现四位数显示也需要简单的算法,通过一个变量 Password 专门用于存放密码,(注意:Password变量要用无符号 int ,因为无符号 char 最大只能是 255,用 char 变量会越界),Password 初始值为0,对其先乘 10 再加上按键输入值,如此循环,每次对上一次的 Password 值乘10,加上下一次返回值的数值,就能实现多位密码;
- 为了限制最多输入四个数值,定义 count 变量用于记录按键输入次数,每输入一次,count + 1,通过条件判断最多输入 4 次,超过四次,不会再执行 Password 赋值操作;
- 将 S11 和 S12 作为确认键和取消键,条件判断函数返回值为 11,执行确认键的功能:如果输入的密码正确,则打印 Hello Boss!!! ,同时将 Password 和 count 设置回初始化值0,并且将密码显示为 0000 ,以便下次运行使用。字符串后面的空格是为了将上一次运行的结果刷新掉,保证屏幕只有我们需要显示的结果。如果密码错误,打印 Password Error! ,同样将密码变量和计数变量设为初始值,屏幕显示为 0000;
- 条件判断返回值为 12,则执行取消功能,同样将密码变量和计数变量设为初始值,屏幕密码显示为 0000,提示行全部清空。
上一次的 Password 值乘10,加上下一次返回值的数值,就能实现多位密码; - 为了限制最多输入四个数值,定义 count 变量用于记录按键输入次数,每输入一次,count + 1,通过条件判断最多输入 4 次,超过四次,不会再执行 Password 赋值操作;
- 将 S11 和 S12 作为确认键和取消键,条件判断函数返回值为 11,执行确认键的功能:如果输入的密码正确,则打印 Hello Boss!!! ,同时将 Password 和 count 设置回初始化值0,并且将密码显示为 0000 ,以便下次运行使用。字符串后面的空格是为了将上一次运行的结果刷新掉,保证屏幕只有我们需要显示的结果。如果密码错误,打印 Password Error! ,同样将密码变量和计数变量设为初始值,屏幕显示为 0000;
- 条件判断返回值为 12,则执行取消功能,同样将密码变量和计数变量设为初始值,屏幕密码显示为 0000,提示行全部清空。