目录
LED
介绍
名称:发光二极管/Light Emitting Diode
简称:LED
用途:照明、广告灯、指引灯、屏幕
内部结构
由内部结构图可见,我们所能控制的P2_ l口连接在二极管的负极,单片机给电后另一端VCC一般为5V。图中的RP7和RP9为定值电阻,用于限流分压,保护二极管。
读数方式
上图中RP7/9后面的1K代表着电阻的阻值,而数字的常见表达形式还有类似于科学计数法的形式,比如1K即1000就可以表示成102或1003.
这样的表示方法含义为:若为N位数,则前N-1位数可以直接取出,最后一位相当于零的个数或者说×10 的多少次方,即前N-1位数这个整体的后面还需加上最后一位的值大小多个零。
例:453——45*10的三次方
基本了解
我们的STC89C52RC单片机上有八个LED灯,它们分别由P2的P2_0~P2_7控制,若以从右往左的顺序来看,则P2_0对应最左边的LED,P2_7对应最右边。
则在输入代码的时候,想只让最右边的LED亮,对P2进行赋值就应为:P2=1111 1110
某位是1则代表这个LED的l口也给正电,而我们已经知道LED的另一端在单片机给电时就已经得到正电了,故给1的LED不会亮,给0则亮。
而如果我们在代码中直接写P2=1111 1110的话是无效的,因为编译器需要我们把二进制改为16进制,尽管CPU内部都是二进制。在更换进制时,八位二进制四四分为两部分,每部分对应着十六进制中的一个数字或字母,然后用十六进制的格式赋给P2即可。
此下为对应规则:
在写十六进制数时,字母的大小写并无硬性要求,编译时均可识别对应。
代码格式
先看下要实现“基本了解”中所述情况的代码
void main(void)
{
while(1){
if(P3_1==0)
P2=FE;
else
P2=0;
}
此部分代码并不完整,在Keil uVision 5 中给单片机编辑代码时,一般都需要先加上我们的单片机型号对应的头文件比如#include <REGX52.H>,有了它,单片机才能知道我们写的P2对应着什么,后续操作才得以生效。该头文件可以通过右键后insert的方式插入,不过需要电脑连接并识别到单片机才可以。
代码中if语句的条件P3_1==0中的P3代表着最左边的独立按键,P3_1、P3_0、P3_2、P3_3分别对应自左向右的四个独立按键,当其值为0时,代表对应按键被按下。
基本框架:
#include<REGX52.H>
void main(void)
{
while(1){
}
}
头文件,main函数,一个不使用break或goto等语句就逃不出的while循环构成了我们写给单片机代码的基本框架。至于为什么需要无限循环,这里尝试举例说明。
LED的闪烁
当我们要实现LED的闪烁时,会有两个间隔时间,即亮多久和灭多久,我们结合代码解释一下
#include<REGX52.H>
void Delay(unsigned int xms){
unsigned char data i;
while(xms--){
_nop_();
i = 2;
while (--i);
};
}
void main(void)
{
while(1){
P2=0xFE;
Delay(500);
P2=0xFF;
Delay(500);
}
}
上述的Delay函数,是我们用stc-isp软件中的 得到并改进的,我认为,它的作用就是让单片机在这条代码处“滞留”一段时间,即暂时中断了循环,但也降低了CPU的处理效率。
在此段代码中,我们利用Delay函数来让LED保持其原有状态,为了解释为什么用while又为什么需要Delay函数在此处的作用,我们做以下假设:
1.两者皆无
我们的main函数中只剩下了
P2=0xFE;
P2=0x00;
由于单片机的运行速度非常快,通常都以MHz为单位,故执行这两句的速度之快我们肉眼是不可见的,所以在LED的闪烁中循环的作用就是让我们的语句得以不断的执行,将不可见的化为可见
2.只有while
不断的亮灭,实际上它的亮灭时间是相同的,但由于视觉暂留现象,呈现给我们的就是该LED是常亮的,达不到我们的目的
3.只有Delay
我们在上述代码中给的滞留时间为500ms,没有while的话会有什么现象呢?
没错,就是只亮500ms,没有后话。
其他复杂代码
LED流水灯
#include <REGX52.H>
#include <INTRINS.H>
void Delay500ms(void) //@12.000MHz
{
unsigned char data i, j, k;
_nop_();
//需要添加头文件<INTRINS.H>
i = 4;
j = 205;
k = 187;
do
{
do
{
while (--k);
} while (--j);
} while (--i);
}
void main()
{
while(1)
{
P2 = 0xFE;
Delay500ms();
P2 = 0xFF;
Delay500ms();
}
}
独立按键控制LED状态
#include<REGX52.H>
void Delay(unsigned int xms) //@12.000MHz
{
unsigned char data i, j;
while(xms > 0)
{
i = 2;
j = 239;
do
{
while (--j);
} while (--i);
xms--;
}
}
void main(void)
{
while(1)
{
if(P31 == 0)
{
Delay(20);
// 按键消抖
while(P31 == 0);
Delay(20);
//该while循环的作用是防止按键还没松开就进行下一步操作即保证在松手后发生改变
P2_0 = ~P2_0;
}
}
}
独立按键控制LED显示二进制
#include<REGX52.H>
void main(void)
{
unsigned char Num= 0;
while (1) {
if (P3_1 == 0) {
Dalay(20);
while(P3_1 == 0);
Delay(20);
Num++;
P2=~Num;
}
}
}
独立按键控制LED移位
#include <REGX52.H>
void main()
{
unsigned char num = 0;
P2 = ~0x01;
while(1)
{
if(P31 == 0)
{
Delay(20);
while(P31 == 0);
Delay(20);
if (num == 7)
{
num = 0;
}
else
{
num++;
}
P2 = ~(0x01 << num);
}
if(P30 == 0)
{
Delay(20);
while(P30 == 0);
Delay(20);
if(num == 0)
{
num = 7;
}
else
{
num--;
}
P2 = ~(0x01 <<num);
}
}
}
数码管
是一种简单、廉价的显示器,是由多个发光二极管封装在一起组成的“8”字型器件
引脚定义
一位数码管:
有以上两种连接方式,右上为共阴极连接,右下为共阳极连接
左下角为每个LED灯管的位置代号,给出了ABCDEFG-DP的顺序,更好理解
右下角为引脚代号,结合右图可知LED与引脚的连接大致为就近原则
四位一体数码管:
我们STC89C52单片机上的数码管即为两个四位一体数码管,同样有两种连接方式,右上为共阴极连接,右下为共阳极连接。
该种数码管的连接方式将四个数码管的同位置端点均连到同一个引脚上,使最终四位一体数码管只有十二个引脚,但可控制32个二极管。
弊端:一个四位一体数码管,在同一时刻只能使一个位置有示数/亮,即只有一个数码管可用,若使用多个,则其示数均相同。
在控制亮暗时,需先位选,再给段码。即先选择数码管位置,再给段码控制LED亮暗。
73HC138译码器
通过三个LO口来控制八个端口,其中C为高位,A为低位(简记为CBA)
其中G1接VCC,G2A和G2B接地,此处的连通保证了在单片机给电后,译码器的正常工作。
对应规则:LO口(P22、P23、P24)输入时给1或0,按C->A的顺序结合为三位二进制,而后转换为十进制,所得数字+1即为所选LED
例:P22=1;P23=0;P24=0; 二进制即001,转换为十进制即1,所选LED为1+1,LED2。
由此种对应规则也可得出,每次位选只能选择一个数码管。
74HC245
双向数据缓冲器:如其名,作用为数据缓冲且可双向
OE端:接Gnd高电平时正常工作,接VCC高电平时则不会工作
这是由于单片机高电平驱动能力有限,电流不能过大,故低电平驱动能力更强。
左右端AB对应关系看数字,相同则对应
DIR:控制数据传输方向,接高电平时,数据从左向右传输,低电平则从右向左。(DIR控制的是跳线帽中的接口)
数码管显示
在我们的操作中,一般操作流程为:
先进行位选,给P2(P22、P23、P24)赋值,而后根据所要显示的数据给出段码(P0)
,给段码时一般为1亮0暗
若为一位数码管,则直接给段码就好,不过要注意应给阳码or阴码
子函数
将完成某一种功能的程序代码单独抽取出来形成一个模块,在其他函数中可随时调用此模块,以达到代码的复用和优化程序结构。
比如:
void Num(unsigned int number){
switch (number){
case 0:P0=0x3f;break;
case 1:P0=0x06;break;
case 2:P0=0x5b;break;
case 3:P0=0x4f;break;
case 4:P0=0x66;break;
case 5:P0=0x6d;break;
case 6:P0=0x7d;break;
case 7:P0=0x07;break;
case 8:P0=0x7f;break;
case 9:P0=0x6f;break;
}
}
//显示数字,将数字转换成段码
void Locate(unsigned char location){
switch(location){
case 1:P2_4=1;P2_3=1;P2_2=1; break;
case 2:P2_4=1;P2_3=1;P2_2=0; break;
case 3:P2_4=1;P2_3=0;P2_2=1; break;
case 4:P2_4=1;P2_3=0;P2_2=0; break;
case 5:P2_4=0;P2_3=1;P2_2=1; break;
case 6:P2_4=0;P2_3=1;P2_2=0; break;
case 7:P2_4=0;P2_3=0;P2_2=1; break;
case 8:P2_4=0;P2_3=0;P2_2=0; break;
}
}
//位置的选择,数字转换成位选端数据
//两函数可以合并,此处且分开来写
基本框架:
void Function(unsigned int a,b)
{
}
返回值 函数名(形参)
{
函数体
}
动态数码管显示
#include <REGX52.H>
void delay(unsigned int xms)
{ while(xms--)
{
unsigned char i, j;
i = 2;
j = 239;
do
{
while (--j);
} while (--i);
}
}
void sum(unsigned char Location,Number);
void main()
{
while(1)
{
sum(1,1);
sum(2,2);
sum(3,3);
//消影包含在sum函数里
}
}
unsigned char table []={0x3F,0x06,0x5B,0x4F,0x66,0x6D,0x7D,0x07,0x7F,0x6F};
void sum(unsigned char Location,Number)
{
switch (Location)
{
case 1:P2_4=1;P2_3=1;P2_2=1;break;
case 2:P2_4=1;P2_3=1;P2_2=0;break;
case 3:P2_4=1;P2_3=0;P2_2=1;break;
case 4:P2_4=1;P2_3=0;P2_2=0;break;
case 5:P2_4=0;P2_3=1;P2_2=1;break;
case 6:P2_4=0;P2_3=1;P2_2=0;break;
case 7:P2_4=0;P2_3=0;P2_2=1;break;
case 8:P2_4=0;P2_3=0;P2_2=0;break;
}
P0=table [Number];
delay(1)
P0=0x00;//进行清零
}
按键消抖
独立按键
相当于一种电子开关,按下时开关接通,松开时开关断开,实现原理是通过轻触按键内部的金属弹片受力弹动来实现接通和断开
在同一竖直直线上的两端连在同一块金属片上
消抖方法
对于机械开关,当机械触点断开、闭合时,由于机械触点的弹性作用,一个开关在闭合时不会马上稳定地接通,在断开时也不会一下子断开,所以在开关闭合及断开的瞬间会伴随一连串的抖动。
故我们有硬件和软件两种方式进行消抖
硬件:添加电路等装置
软件:通过代码延时