51单片机初学笔记
最近开始接触起了51单片机的知识为巩固所学,写下这篇文章来梳理一下
首先,51单片机是个什么东西?
我的理解她是由一个核心与一堆你所需要的模块所组成的。通过cpu外接的I/O口输不同的电平来使你的单片机的各个模块来达到你所想要的效果,在核心与各个模块之间还可以有不同的芯片来满足你的不同需求。
目前本人学习了五个模块分别是
-
LED模块
-
独立按键模块
-
矩阵按键模块
-
动态数码管模块
-
8x8点阵模块
下面我会对这几个模块进行一一梳理
LED模块
首先请先看原理图
上图我们可以知道 8个led灯分别与单片机的P20到P27相接,因此只要对相应I/O给一个低电平就可以了。
比如我们要点亮第一个灯
#include<reg52.h>
sbit led=P2^0;
int main()
{
led=0;
return 0;
}
在这里有一个问题,为什么不可以直接写: P2^0=0 呢?; 在keil里面,你这么写他会无法编译,原因在于这个i/o口并没有被声明 但是P2=0x00是可以正常使用的,是因为在<reg52.h>这个库里面P2已经被声明过了。
sbit led=P2^0 的意思是led的地址为P2的第0个接口的地址

点亮多个灯
对于这个我们可以采用对P2口直接赋值的方式来的更加简单
比如让位数为偶数的灯亮起
#include<reg52.h>
int main()
{
P2=0x55;
return 0;
}
0x55 换算成二进制也就是01010101 要注意位数要从右往左看
为什么使用16进制会更加方便?
因为机器所认识的语言为二进制所以我们往往一个操作都是以二进制进行模拟的。但是c语言并没有提供二进制的写法,并且二进制位数往往特别多,写代码并不适宜,c语言提供了其他的几个写法,其中2进制转化为16进制最为方便因此在编写代码时往往习惯写为16进制(如不明白转换方法,可自行上网搜索)。
流水灯
前面我们已经可以随意点亮其中的一部分灯了,那么如何使这些LED灯按照一定的规则依次点亮呢?
首先我们需要介绍一个函数 即延时函数
void delay(int xms)
{
int i,j;
for(i=xms;i>0;i--)
for(j=110;j>0;j--);
}
通过空循环的方式使程序暂停一段时间再执行
那么,我们只要在灯每次点亮后延时一段时间,再去点亮下一次要点亮的灯就可以了。我们希望它可以无限循环下去,那么通过while(1)就可以实现了 比如我们想要实现奇数位的灯与偶数位的灯交替点亮:
#include<reg52.h>
void delay(int xms)
{
int i,j;
for(i=xms;i>0;i--)
for(j=110;j>0;j--);
}
int main()
{
while(1)
{
P2=0x55;
delay(500);
P2=0xaa;
delay(500);
}
return 0;
}
ok 这样我们就可以实现流水灯了
代码如下:
#include<reg52.h>
void delay(int xms)
{
int i,j;
for(i=xms;i>0;i--)
for(j=110;j>0;j--);
}
void lled(int i)
{
for(i;i<8;i++)
{
P2=~(0x80>>i);
delay(500);
}
i=0;
}
void rled(int i)
{
for(i;i<8;i++)
{
P2=~(0x01<<i);
delay(500);
}
i=0;
}
int main()
{
while(1)
{
lled(0);
rled(0);
}
return 0;
}
这个代码可以实现灯从左向右再从右向左亮起
数码管模块
还是先看原理图

还有一张

由原理图我们可以知道,动态数码管一端通过79HC245接到P0口上 用于控制其中某个一位数码管的显示,8个接口分别对应这个数码管的8个发光二极管我们可以直接通过给P0输入一个8位2进制数来控制。
ex: 如果我们希望数码管显示C 那么需要a,f,e,d四个发光二级管发亮,那么对应的是P0,P3,P4,P5;也就是说我们要给其赋的值为11000110换成16进制便是c6。
那么,如何来决定由哪个数码管来显示呢?这就要通过另一端所连接的74HC138译码器来控制了。
我们先来看一下74HC138的使用方法
可以看到74HC138左端有 A B C 三个接口与P22,P23,P24 相连,右边则分别与8个数码管相连。该译码器便是通过这三个接口高低电平的不同组合来控制右边8个输出。下面请看该芯片使用手册中的一个表格

x代表任意电平 H代表高电平,L代表低电平;
当E1 E2 不都为低电平时,输出端全部为高电平 因此,E1 E2 都接低电平时该芯片才有用处。
从第四排开始,A1,A2,A3(相当于前面的ABC) 不同的组合可以使输出端的一个输出口输出低电平,
由上述方法可以使这个对应数码管显示你所需要的内容了。
那么如何使8个数码管都一同显示呢?
其实很简单了,人眼是有视觉残留的,而单片机运行速度是很快的,因此我们只需要让他循环输入每个数码管的值就可以了
代码如下:
#include<reg52.h>
#include<stdio.h>
sbit LSA=P2^2;
sbit LSB=P2^3;
sbit LSC=P2^4;
int sum[8]={2,0,1,1,5,0,4,8};
unsigned char code smgduan[]={0x3f,0x06,0x5b,0x4f,0x66,0x6d,0x7d,0x07,
0x7f,0x6f,0x77,0x7c,0x39,0x5e,0x79,0x71};
void delay(int xms)
{
int i,j;
for(i=xms;i>0;i--)
for(j=110;j>0;j--);
return;
}
void smgplay()
{
int i;
for(i=1;i<=8;i++)
{
switch(i)
{
case(1):
LSA=1;LSB=1;LSC=1; break;
case(2):
LSA=0;LSB=1;LSC=1; break;
case(3):
LSA=1;LSB=0;LSC=1; break;
case(4):
LSA=0;LSB=0;LSC=1; break;
case(5):
LSA=1;LSB=1;LSC=0; break;
case(6):
LSA=0;LSB=1;LSC=0; break;
case(7):
LSA=1;LSB=0;LSC=0; break;
case(8):
LSA=0;LSB=0;LSC=0; break;
}
P0=smgduan[sum[i-1]];
P0=0x00;
}
}
int main()
{
while(1)
{
smgplay();
}
return 0;
}
独立按键模块
还是先上原理图
独立按键的原理图很简单,按键本身相当于一个开关,当你按下时电路联通使相应的接口的电平变为低电平。因此我们可以通过判断对应I/O口的电平来判断键盘是否被按下
代码如下
sbit keyb=P3^2;
int Getkey()
{
if(keyb==0)
{
delay(10);
if(keyb==0)
{
while(!keyb);
return 1;
}
}
return 0;
}
我们可以看到 在程序中if(keyb==0) 进行了两次判定,这是为了防止键盘抖动而导致的误判,我们通过一个很短的延时之后再次判定可以防止此类误判。在判定之后还有一个while(!keyb) 是等待按键松开,以防止一次按键被识别多次。
通过独立按键控制流水灯方向
独立按键可以让我们的单片机得到我们的反馈来处理一些事情,在这里提供一个独立按键控制流水灯方向的代码
#include<reg52.h>
#include<stdio.h>
sbit keyb=P3^2;
int l=1,r=0,t=0,flag,i;
void delay(int xms)
{
int i,j;
for(i=xms;i>0;i--)
for(j=110;j>0;j--);
}
int Getkey()
{
if(keyb==0)
{
delay(10);
if(keyb==0)
{
while(!keyb);
return 1;
}
}
return 0;
}
void lled()
{
for(i;i<8;i++)
{
flag=Getkey();
if(flag)
{
i=7-i;
return;
}
P2=~(0x80>>i);
delay(200);
}
i=0;
}
void rled()
{
for(i;i<8;i++)
{
flag=Getkey();
if(flag)
{
i=7-i;
return;
}
P2=~(0x01<<i);
delay(200);
}
i=0;
}
int main()
{
while(1)
{
if(l) lled();
if(r) rled();
if(flag)
{
t=l;
l=r;
r=t;
flag=0;
}
}
return 0;
}
我们会发现独立按键十分占用I/O口这对于我们需要很多按键的时候会十分棘手,于是矩阵按键便应运而生啦。
矩阵按键
先上原理图

矩阵按键每一列分别与P10到P14相连,每一行分别与P14到P17相连。
那么,如果我们给P10一个高电平,P14一个低电平,那么如果此时S16,被按下,电路连通P10会变为高电平。
利用这个原理,我可以给每一行输出低电平,每一列输出高电平,如果有一个按键被按下,那么这个按键所在的列就会变为低电平,这样我们就可以知到哪一列的按键被按下
与之同理 我们反过来,给每一列输出高电平,每一列输出低电平,我们就可以知到哪一行的按键被按下
这样我们就可以得到键值了
代码如下
#define Ky P1
int Getkey()
{
char a=0;
int Keynum=-1;
Ky=0x0f;
if(Ky!=0x0f)
{
delay(10);
if(Ky!=0x0f)
{
Ky=0x0f;
if(Ky==0x07) Keynum=0;
if(Ky==0x0b) Keynum=1;
if(Ky==0x0d) Keynum=2;
if(Ky==0x0e) Keynum=3;
Ky=0xf0;
if(Ky==0x70) Keynum+=0;
if(Ky==0xb0) Keynum+=4;
if(Ky==0xd0) Keynum+=8;
if(Ky==0xe0) Keynum+=12;
}
}
return Keynum;
}
一个简单的应用
我们可以通过矩阵按键来使其显示1-f ,代码如下:
#include<stdio.h>
#include<reg52.h>
#define Ky P1
#define Smg P0
unsigned code Smgduan[]={0x3f,0x06,0x5b,0x4f,0x66,0x6d,0x7d,0x07,
0x7f,0x6f,0x77,0x7c,0x39,0x5e,0x79,0x71};
void delay(int xms)
{
int i,j;
for(i=xms;i>0;i--)
for(j=110;j>0;j--);
return;
}
int Getkey()
{
char a=0;
int Keynum=-1;
Ky=0x0f;
if(Ky!=0x0f)
{
delay(10);
if(Ky!=0x0f)
{
Ky=0x0f;
if(Ky==0x07) Keynum=0;
if(Ky==0x0b) Keynum=1;
if(Ky==0x0d) Keynum=2;
if(Ky==0x0e) Keynum=3;
Ky=0xf0;
if(Ky==0x70) Keynum+=0;
if(Ky==0xb0) Keynum+=4;
if(Ky==0xd0) Keynum+=8;
if(Ky==0xe0) Keynum+=12;
}
}
return Keynum;
}
int main()
{
int flag,duannum=0;
while(1)
{
flag=Getkey();
if(flag!=-1) duannum=flag;
Smg=Smgduan[duannum];
}
return 0;
}
8x8点阵
老规矩,上原理图

还有一个点阵的结构图

点阵的使用其实与数码管的使用十分相似,可以理解为一边为段选,一边为位选,那么我们可以通过类似数码管的动态显示方法来进行逐行显示,利用人眼的特性来达到我们想要的效果
我的开发板在设计这部分电路时利用了79HC595芯片,我们先来了解下他的使用。
芯片原理图

这个芯片可以做到串行输入并行输出从而节省I/O口,通过SER读入数据,以入栈的形式存入SCK(移位寄存器)。串行输入结束后在转到存储寄存器并行输出出去。
需要注意 SCK与RCK都是上升沿有效。也就是拉高电平的那一刻才会工作,因此每次工作之前我们都需要拉低电平(如果已经是低电平则不必)再拉高电平就可以完成这次操作
有以上的知识储备 那么我们可以写代码了
我们来让他显示10
#include<reg52.h>
sbit SER=P3^4;
sbit SCK=P3^6;
sbit RCK=P3^5;
typedef unsigned int u16;
typedef unsigned char u8;
int dzh[]={0x7f,0x00,0x3e,0x41,0x41,0x41,0x3e,0x00};
int dzs[]={0x7f,0xbf,0xdf,0xef,0xf7,0xfb,0xfd,0xfe};
void delay(u16 i)
{
while(i--);
}
void put(u8 dat)
{
int i;
SCK=0;
RCK=0;
for(i=1;i<=8;i++)
{
SER=dat>>7;
dat<<=1;
SCK=1;
SCK=0;
}
RCK=1;
RCK=0;
return;
}
int main()
{
u8 i=1;
while(1)
{
P0=0x00;
for(i=0;i<8;i++)
{
P0=dzs[i];
put(dzh[i]);
delay(100);
put(0x00);
}
}
return 0;
}
ok,那么这篇笔记就结束了