跑马灯实现
直接上代码
#include<regx52.h>
sbit D1=P2^0;
sbit D2=P2^1;
sbit D3=P2^2;
sbit D4=P2^3;
sbit D5=P2^4;
sbit D6=P2^5;
sbit D7=P2^6;
sbit D8=P2^7;
void delay(int num){
while(num--){
}
}
void led_running(){
//从第1盏灯到第8盏灯依次点亮
D1=0;
delay(40000);
D2=0;
delay(40000);
D3=0;
delay(40000);
D4=0;
delay(40000);
D5=0;
delay(40000);
D6=0;
delay(40000);
D7=0;
delay(40000);
D8=0;
delay(40000);
//从第1盏灯到第8盏灯依次熄灭
D1=1;
delay(40000);
D2=1;
delay(40000);
D3=1;
delay(40000);
D4=1;
delay(40000);
D5=1;
delay(40000);
D6=1;
delay(40000);
D7=1;
delay(40000);
D8=1;
delay(40000);
//从第1盏灯到第8盏灯依次点亮
D1=0;
delay(40000);
D2=0;
delay(40000);
D3=0;
delay(40000);
D4=0;
delay(40000);
D5=0;
delay(40000);
D6=0;
delay(40000);
D7=0;
delay(40000);
D8=0;
delay(40000);
//从第8盏灯到第一盏灯依次熄灭
D8=1;
delay(40000);
D7=1;
delay(40000);
D6=1;
delay(40000);
D5=1;
delay(40000);
D4=1;
delay(40000);
D3=1;
delay(40000);
D2=1;
delay(40000);
D1=1;
delay(40000);
}
void main(){
while(1){
int i=3;
while(i--){
P2=0x00;
delay(40000);
P2=0xFF;
delay(40000);
}
led_running();
}
}
STC89C52单片机LED结构以及原理
由图中,我们可以看出每个led灯的正极都接高电平,所有的负极都接在端口上,1-8LED分别对应P20-P27引脚,当我们给这些引脚一个低电平的时候二极管就会导通,发光。
两种方式控制LED点亮
P2=0x00;一次性控制8个引脚从而一次性控制8个LED,转化为二进制为0000000,此时8个led均点亮。P2=0xFF;此时8个led均熄灭。格外注意:这里的P是大写。
用sbit来控制每个LED的亮灭。
sbit关键字
在单片机编程中,sbit 是特定于 Keil C 编译器的关键字,用于定义特定位的名称。通过 sbit 关键字,可以将某个特定的位与一个变量相关联,方便编程时对位进行操作。
例如,在 8051 单片机的 Keil C 编程中,可以使用 sbit 来定义位,示例代码如下:
sbit LED = P1^0; // 将 P1 端口的第 0 位与 LED 变量关联
代码
#include <reg51.h>
sbit LED1 = P2^0; // 将 P2 端口的第 0 位与 LED 变量关联
void main() {
LED1 = 1; // 设置 LED 为高电平,相当于 P2.0 = 1;
//此时第一个LED灯就点亮了
}
数码管实现
数码管结构图(上面是共阴数码管,下面是共阳数码管)
-
共阳数码管: 共阳数码管的基本结构是由多个发光二极管(LED)组成,每个 LED 的阳极连接在一起,而各个 LED 的阴极分别独立控制。在共阳数码管中,当给定阳极一个高电平时,对应的 LED 会被激活,发光显示。因此,通过控制各个阴极的低电平来实现对数码管上不同位置的 LED 的显示和关闭。
-
共阴数码管: 共阴数码管的基本结构与共阳数码管类似,也由多个发光二极管(LED)组成,但是每个 LED 的阴极连接在一起,而各个 LED 的阳极分别独立控制。在共阴数码管中,当给定阴极一个低电平时,对应的 LED 会被激活,发光显示。因此,通过控制各个阳极的高电平来实现对数码管上不同位置的 LED 的显示和关闭。
总结:
- 共阳数码管:阳极共连接,通过控制阴极的低电平来点亮对应的 LED。
- 共阴数码管:阴极共连接,通过控制阳极的高电平来点亮对应的 LED。
数码管的段码值表
我们可以看到上面的图片中,A-G再加上一个小数点dp正好是8个,对应8个二极管。
这个表定义的是无小数点dot的段码值表
共阴带小数点的段码为:{0xbf,0x86,0xdb,0xcf,0xe6,0xed,0xfd,0x87,0xff,0xef,0xf7};
共阳带小数点的断码为:{0x40,0x79,0x24,0x30,0x19,0x12,0x02,0x78,0x00,0x10};
这篇文章里面有详细断码表:
共阳和共阴数码管详细段码(带图)_共阳极数码管显示0~f对应的段码-CSDN博客
共阴带小数点的可以由共阴不带小数点的段码加0x80
共阳带小数点的可以由共阳不带小数点的段码减0x80
以6为例
注意:a对应的是最低位
a b c d e f g dp(共阴数码管)手动定义二极管阳极,让一个二极管点亮给这个端口赋值1
1 0 1 1 1 1 1 0=0x7D,这里要注意a对应的是最低位所以01111101=0x7D
a b c d e f g dp(共阳数码管)手动定义二极管阴极,让一个二极管点亮给这个端口赋值0
0 1 0 0 0 0 0 1=0x82,这里要注意a对应的是最低位所以10000010=0x82
实现让数码管显示数字4
#include<reg52.h>
unsigned char SMGnodot[10]={0x3f,0x06,0x5b,0x4f,0x66,0x6d,0x7d,0x07,0x7f,0x6f};
void main(){
while(1){
P0=SMGnodot[5];
}
}
这里我首先有两个疑惑
1.这里明显是要存储数字类型的数据,但是为什么这里使用的是unsigned char字符类型呢?
在单片机中,char数组中的数据也是以字节(byte)的方式存储的。char类型表示一个字节的数据,即8位(8 bits)。在C语言中,char类型是用来表示字符的,但是它也可以用来存储其他8位的数据。在单片机中内存紧张,为了尽量减少内存消耗,我们选择使用char,并且在这里存储的数字正好是8位,char正好可以满足需求。换句话来说在c语言当中我们char是用来存放字符的,输出char类型变量出来的是字符,但是char在这里完全可以当成一个内存更小的数字存储单元
2.由下面的一张图片,我们可以看到8个引脚要控制8个晶体管,但是现在一个1晶体管就将8个引脚用光掉了,51单片机的共有40个引脚,每一个引脚通常有1位存储空间,用于表示引脚的状态(高电平或低电平)。这让我不得不猜想可能会在短时间内将仅用8个引脚来对8个晶体管状态进行遍历,利用我们的视觉暂留效果来看起来8个晶体管同时亮起来。当然,这只是我目前的猜想,让我们继续学习下去。
单个数码管静态显示(数码管实现遍历0-9)
#include<reg52.h>
unsigned char SMGnodot[10]={0x3f,0x06,0x5b,0x4f,0x66,0x6d,0x7d,0x07,0x7f,0x6f};
void delay(int num){
while(num--){
}
}
void main(){
unsigned char i;
while(1){
for(i=0;i<10;i++){
P0=SMGnodot[i];
delay(40000);
}
}
}
代码注意:这里不能写成for(unsigned char i=0;i<10;i++),在早期版本的C语言标准中,循环变量的定义只能放在函数的开头,而不能在循环内部进行定义。然而,随着C语言标准的演进,C99标准引入了块作用域规则,允许在循环内部定义循环变量。对于8051单片机编程,因为它使用的是较早的C语言标准,所以循环变量的定义通常放在循环外部,这样可以保证与旧版本C语言兼容。因此,你可以将循环变量i
的定义放在main
函数的开头,以符合8051单片机编程的惯例。
数码管中的段选和位选
数码管的段选是用来控制每一位数码管的8段的,一位需要8个端口,数码管的位选是控制数码管的这位是否可以显示。
数码管的位选控制的就是每个数码管的共阳端或者共阴端。就是公共端COM,对于共阳数码管,当位选信号为高电平时,对应的数码管会被选中并显示;而对于共阴数码管,当位选信号为低电平时,对应的数码管会被选中并显示。对于一组四个共阴数码管,给其中一个的共阴端为低电平,其他三个数码管的共阴端为高电平,此时只有一个数码管被选中来能接受传输过来的数据。这就是动态显示选择其中一个数码管作为当前要显示的数码管的原理。
这里的位的意思可以看图片理解
数码管的两种显示方式(静态显示和动态显示)
静态显示
静态显示的特点是每个数码管的段选必须接一个8位数据线来保持显示的字形码。当送入一次字形码后,显示字形可一直保持,直到送入新字形码为止。这种方法的优点是占用CPU时间少,显示便于检测可控制。缺点是硬件电路比较复杂,成本较高。 如果对于一个四位数码管我们采用静态显示方式,仅仅段选加起来就需要32个接口一共需要33个端口,显然不太现实,使用动态显示的话只用12个端口就够了(8+4),所以对于多位数码管,我们常常使用动态显示的方式。
动态显示
看来和我想的一样(狗头)
数码管的动态显示原理是通过快速地切换多个数码管的显示,使人眼产生视觉上的同时显示效果。这种技术通常被称为多路复用显示。
实现数码管动态显示的基本步骤如下:
-
设置位选信号:首先选择其中一个数码管作为当前要显示的数码管,通过位选信号(即控制数码管的某一位)来选中该数码管。
-
发送段选数据:在选择了要显示的数码管之后,发送对应该数码管要显示的段选数据,包括要显示的数字、字母或其他符号的编码信息。
-
等待一小段时间:在当前数码管显示数据的瞬间,需要给予足够的时间让数码管完成显示,这样可以确保人眼能够清晰地看到显示的内容。
-
重复上述步骤:然后,迅速切换到下一个数码管,重复上述步骤1~3,直到所有的数码管都完成显示。
通过不断地重复以上步骤,每个数码管都会以很高的频率被选中并显示对应的内容,由于人眼的视觉暂留效应,就会给人一种所有数码管同时显示的错觉。这样就实现了数码管的动态显示效果。
对于上面的解释:【(即控制数码管的某一位)来选中该数码管。】为什么是只能控制某一位呢?按理来说使用12个端口控制的话,虽然不能让这四个数码管同时显示不同内容,但肯定能让这几个数码管同时更新相同的内容,同时接收,同时控制。其实是因为在我们这个单片机上并没有使用12个接口来控制,而是使用了一种其他芯片(74HC138),下面对其介绍。
74HC138
配合我们的动态数码管的结构来看
我们可以看到74HC138模块与图中动态数码管的8个公共端相连,功能:
它具有多个输入线(A, B, C)和多个输出线(Y0-Y7),可以根据输入线的状态选择其中一个输出线。
具体来说,74HC138 可以将 3 个输入线的组合转换成 1 个输出线,实现将 8 个输入信号(2^3=8)映射到 1 个输出线上的功能。通过控制输入线的状态,可以选择激活其中一个输出线,从而实现解码器或者多路复用器的功能。
其实就是对应的三位二进制的真值表(原理图中C对应高位)
结构图中的左下角功能是上电开关,电路图中已经接好了,表示这个芯片可以直接使用。
74HC245
它和动态数码管是直接相连的,在动态数码管图中的左侧部分,这是一个双向数据缓冲器。直观地理解它的功能就是:原封不动地将左边的数据传输到右边或者将右边的数据传输到左边。就像下面这个图中所示。值得注意的是左上角的接口DIR,对应英语中的direction(方向),作用是决定将右边的数据传输到左边还是将左边的数据传输到右边,DIR如果是高电平,就将左边的数据传输到右边,如果低电平,就将右边的数据传输到左边。
DIR与LE相连(图中黄色的装置,可用跳线棒连接)
采用74HC245这样接的话好处:缓冲器可以提高驱动能力,直接单片机接上数码管的话就相当于这个单片机的电位直接驱动数码管,但是单片机中高电位不会很高,这样驱动能力弱,导致数码管发光暗一点,但是现在有了这个芯片,就相当于是单片机中高电位起到一个控制信号,控制信号可以很微弱,然后接收到信号,这个芯片可以从它本身相连的vcc中汲取能量,这样的话就会使得数码管更亮。
由于单片机的输出端无法提供足够的电流来驱动数码管,可能会导致数码管亮度较低或者亮度不均匀。而使用 74HC245 缓冲器后,单片机的输出信号可以作为控制信号输入到缓冲器中,缓冲器会根据这个控制信号来控制数码管的数据输入输出,同时从自身的 VCC 中获取能量来驱动数码管,从而提高了驱动能力,并且可以改善数码管的发光效果。
多个数码管的动态显示
直接上代码
#include<regx52.h>
sbit A1=P2^2;
sbit B1=P2^3;
sbit C1=P2^4;
unsigned char SMGNOdt[11]={0x3f,0x06,0x5b,0x4f,0x66,0x6d,0x7d,0x07,0x7f,0x6f,0x80};
void delay(int num){
while(num--){
}
}
void show(unsigned char pos,unsigned char num){
switch(pos)
{
case 1:C1=0;B1=0;A1=0;break;
case 2:C1=0;B1=0;A1=1;break;
case 3:C1=0;B1=1;A1=0;break;
case 4:C1=0;B1=1;A1=1;break;
case 5:C1=1;B1=0;A1=0;break;
case 6:C1=1;B1=0;A1=1;break;
case 7:C1=1;B1=1;A1=0;break;
case 8:C1=1;B1=1;A1=1;break;
}
P0=SMGNOdt[num];
}
void main(){
while(1){
show(8,5);
delay(50);
show(7,2);
delay(50);
show(6,0);
delay(50);
show(5,10);
delay(50);
show(4,1);
delay(50);
show(3,3);
delay(50);
show(2,1);
delay(50);
show(1,4);
delay(50);
}
}
这个代码中我主要强调几点:
1.引脚的顺序赋值问题,让我们再看前面的这张图,这里可以看到74HC138芯片中A对应于P22,C对应于P24,在这款单片机中的P0,P1.P2.P3这四组引脚都是P0^0,P1^0,P2^0,P3^0对应8位二进制数字的最低位,所以看我们代码中这里是依次赋值给C1,B1,A1然后对应让哪个数码管可以接收数据,比如110对应Y6就是让第7个数码管接收数据。
2.注意结合我们的板子电路原理图来看对应顺序,其实也是在强调对应顺序。
可以看到从左到右分是数码管的8-1,并不是我们想的1-8,所以,在我们想显示5201314的时候第一位想显示5,其实是要让LED8显示5,让LED8接收数据。如下所示,所以我们一定要通过板子的原理图看清楚对应关系。
对江协科技老师的代码做一个解释
这里的代码相当于是和我的反过来了他主要是想函数内部反一下,外部我们自己写代码比较顺畅,这样的话我们外面想让第一个数码管显示数字5,就调用函数show(1,5);第1个灯显示了5,实际上还是让LED8显示的5,这样的话虽然方便,但是我觉得没有遵循原理图中的对应关系,看大家爱好喜欢,来选择书写方式。
欢迎大家指正!