LED流水灯
前言:
前几篇学会了LED驱动原理,并且成功点亮了一颗LED和实现了LED的闪烁,那么这篇紧接着就来解锁LED的新功能,LED流水灯。当然这里前提是基于肉眼余晖可见光达成的效果。
1、硬件电路设计和原理分析
开发板上 LED 模块电路原理图如下图所示:
看上图中 LED 采用共阳接法,D1-D8 连接到单片机的 P20-P27 口,即所有LED 阳极管脚接电源 VCC,阴极管脚通过一个 470 欧的限流电阻接到 P2 口上。
原理分析:
根据前面篇章 点亮一颗LED 的介绍我们知道,要让 LED 发光即对应的阴极管脚应该为低电平,若为高电平则熄灭。
根据前面篇章 LED闪烁 的介绍我们知道,要让 LED 闪烁,主要引用延时函数。
所以如果要想 51 单片机控制 LED实现流水灯的效果,就必须通过单片机管脚在 P2 口上反复循环的调用寄存器控制对应D1~D8引脚的高低电平,当第一颗LED点亮,则其它LED处于熄灭状态,当第二颗LED点亮时,其它LED处于熄灭状态,同理,其余的LED都是这个逻辑,其次MCU执行程序语句都会有一定的时间,为了达到人肉眼的余晖效应,因此只需编写一个循环函数,让CPU 不干其它事,专门在那循环运行即可实现延时功能。
2、软件设计
程序这边主要介绍两种思路的写法:
(1)、利用循环和移位操作符,实现反复改写对应LED端口高低电平的改写。实验效果:单向流水灯
(2)、利用封装好的库函数:左移_crol_、右移_cror_函数实现流水灯效果。实验效果:双向流水灯
2.1、利用循环和移位操作符功能实现:LED流水灯
//1.利用循环和移位操作符<< >>
/**/
#include <reg52.h>
#define LED_PORT P2//使用宏定义--- P2端口
typedef unsigned int u16;
void delay_10us(u16 ten_us)
{
while(ten_us--);
}
void main()
{
int i = 0;
//LED_PORT = 0xfe;
//LED_PORT = 0x01;
while(1)
{
for(i=0;i<8;i++)
{
LED_PORT = ~(0x01<<i);//i=0时,D1灯亮,i++依次点亮
//移位后自动补0,当7次移位后,高位的1被移走,低位就自动补0
//导致同时会亮两个灯,那么就采取取反操作符。以移动1然后取反来解决移位补0的这个问题
delay_10us(50000);
}
}
}
解释说明:
前面章节已经介绍了的内容,这里的 delay_10us延时函数 和 typedef 类型重命名,就一笔带过了。具体参考:前面篇章 点亮一颗LED 的介绍
然后这里简单说一下,#define关键字,是C语言中的一个宏定义预处理指令(“#”开头的均为预处理命令)命令,它用来将一个标识符定义为一个字符串,语法格式如:#define LED_PORT P2,该LED_PORT标识符被称为宏名,被定义的字符串P2称为替换文本。
大概描述一下,主要的作用就是,在遇见大量重复又需要经常改变的参数,常常用宏定义一个新的字符串替换标识符,从而实现便捷的更换程序参数。
最后,主函数main:进入 main 函数后首先定义一个变量 i,然后进入 while 循环,由于要实现 8个 LED 从 D1->D8 循环点亮,因此可以使用 for 循环语句循环 8 次,每循环一次,点亮的小灯向右移动一个,而 D1-D8 是连接到 P2.0-P2.7 的,因此输出的低电平要左移一位,因此可以使用 LED_PORT=~(0x01<<i);语句实现。
主要解释一下,~(0X01<<i):
表示 i 循环 1 次,则0x01 中的 1 就移动多少位。另外,因为 1(高电平)不会让 LED 点亮,需要取反后变为低电平 0 才能点亮,所以最后的结果需要取反后给 LED_PORT 口,并且每次循环都要延时一段时间,这样才能分辨出来 LED 在流水形式显示。
如果不取反这里以二进制表示就是:
0000 0001 << 1 得到 0000 0010 左移1位后最低位自动补0,当7次移位后1000 0000,第八次最高位的1被移走,最低位就自动补0,则为0000 0000,又根据开发板的LED硬件原理图,LED 采用共阳接法,给0为亮,所以最后的移位后的值,需要取反使得对应的灯亮,其它灯灭。
2.2、利用利用封装好的库函数功能实现:LED流水灯
//2.利用_crol_库函数
/**/
#include <reg52.h>
#include <intrins.h>
//当调用_cror_右移库函数和_crol_左移库函数时,需调用头文件<intrins.h>
//注意:库函数的移位不会自动补0,是最高位向最低位,进行移位的
//了解函数的参数:extern unsigned char _crol_ (unsigned char, unsigned char);
//第一个参数是移动的值,第二个参数是移动的位数
#define LED_PORT P2
typedef unsigned int u16;
typedef unsigned char u8;
void delay_10us(u16 ten_us)
{
while(ten_us--);
}
void main()
{
u8 i = 0;
LED_PORT = 0xfe;//1111 1110
delay_10us(50000);
//LED_PORT = ~0x01;//等价
while(1)
{
for(i=0;i<7;i++)//当我们给LED_PORT赋初值,0xfe时,只需要循环7次即可
{
LED_PORT = _crol_(LED_PORT,1);
delay_10us(50000);
}
//右移同理
for(i=0;i<7;i++)//当我们给LED_PORT赋初值,0xfe时,只需要循环7次即可
{
LED_PORT = _cror_(LED_PORT,1);
delay_10us(50000);
}
}
解释说明:
以库函数的写法就相对更简单了,因为只需要会用就行,直接调用已经封装好的左移_crol_、右移_cror_函数,然后查阅对应的用法和参数,以及对应的头文件即可。
以左移_crol_函数为例,其主要作用就是:
实现的移位功能就相当于一个队列内循环移动,如果是左移,那么最高位就被移到最低位了,次高位变为最高位,依次类推的循环排列。
最后,主函数main:进入 main 函数后首先定义一个变量 i,LED_PORT=~0x01,因为 LED 是低电平
点亮,所以 0X01 取反后的结果是 0XFE,对应二进制数为 1111 1110,即最低位为 0,因此最开始的 D1 指示灯会点亮,然后进入 while 循环,使用 for 循环_crol_和_cror_移位函数实现 LED 左右流水显示。
注意到的是:此处每个 for 循环只有 7 次,为什么不是 8 次呢,这是因为在进入 main 开始,就已经将 LED_PORT 端口设置了一次状态,即让 D1 点亮,并且我们是想让 LED 从左至右依次点亮,然后继续又从右至左依次点亮,这样形成左右流水效果。
假如将循环次数改为 8 次,我们列举下第一个 for 循环的LED_PORT 端口状态值,如下所示:
初始状态:LED_PORT=1111 1110
i=0:LED_PORT=1111 1101
i=1:LED_PORT=1111 1011
i=2:LED_PORT=1111 0111
i=3:LED_PORT=1110 1111
i=4:LED_PORT=1101 1111
i=5:LED_PORT=1011 1111
i=6:LED_PORT=0111 1111
i=7:LED_PORT=1111 1110
可以很直观的查看到,当i = 7时,再次回到了初始状态的显示,可这里我们是想实现的是:LED从左流动过来,又从右流动回去的流水灯效果。所以,i = 6只需要for循环7次即可。
3、编译结果
至此,两个程序就编写完成,我们编译一下,如下图所示:
从上图编译信息可以看出,我们的代码占用FLASH 大小为:code = 65 字节,所用的 SRAM 大小为:data = 9 个字节(9.0),xdata指是扩展的外部存储XSRAM所占的大小,并没有使用XSRAM所以为0。
从上图编译信息可以看出,我们的代码占用FLASH 大小为:code = 98 字节,所用的 SRAM 大小为:data = 9 个字节(9.0),xdata指是扩展的外部存储XSRAM所占的大小,并没有使用XSRAM所以为0。
硬件实验效果如图所示:
C51基础实验 LED流水灯实验效果展示1
C51基础实验 LED流水灯实验效果展示2
4、结束语
写这篇文章记录作为自己学习的笔记,笔者水平有限,希望有错误的地方还请多多指教,各抒己见交流学习,同时希望笔者的内容有帮助到你。如果有所帮助还请点点赞,表示支持哦。最后感谢各位的阅读(不喜勿喷)。