0
  • 聊天消息
  • 系统消息
  • 评论与回复
登录后你可以
  • 下载海量资料
  • 学习在线课程
  • 观看技术视频
  • 写文章/发帖/加入社区
创作中心

完善资料让更多小伙伴认识你,还能领取20积分哦,立即完善>

3天内不再提示

使用51单片机实现LED呼吸灯

CHANBAEK 来源:嵌入式小书虫 作者:FledgingSu 支离苏 2023-07-04 11:40 次阅读

【说在前面的话】

单片机技术是现代工业自动化电子电气物联网等的一门必不可少的主流技术。随着人们生活智能化的提高,单片机技术也几乎融入了我们生活的各个角落,比如智能电饭煲、智能音箱、等等。

由此,《 重学51单片机 》系列文章意在帮助初学者入门单片机技术。我们会从最简单的点亮一个灯开始,一步一步实现按键、lcd1602、ds18b20、ds1302、双机通信等模块,同时,还会讲一些硬件通信协议,比如uart、IIC、SPI等。并结合C语言编程技巧,以实际的工程项目来给大家讲解编程思路,让大家灵活运用C语言的指针与结构体,实现编程模块化。

言归正传,接下来我们就开始进入今天的主题,用51单片机控制LED实现呼吸灯的效果。

【呼吸灯原理】

我们先看一下呼吸灯的效果下

呼吸灯就是先渐渐变亮再渐渐变暗,如此循环就像呼吸一样。可是单片机的管脚要么输出1(亮)要么输出0(灭),怎么会有渐变的效果呢?

这就和我们的眼睛 观看图像会有滞留时间引起的 。当我们在看东西时,眼睛成像后会滞留0.04s(这个标准是网上找的)。

我们按照0.04s计算,就等于40ms,也就是亮灭都是20ms时,看到的LED就是一直在亮。如下图:

图片

那LED 20ms亮20ms灭和一直常亮的效果一样吗?

哈哈,肯定是不一样的。交替20ms亮20ms灭我们看到的效果要比一直常亮的效果暗。我们假设一直常亮的亮度为100%,那么交替20ms亮20ms灭的亮度就是50%,基于此, 我们就可以调节LED的亮度了 。如下图

图片

到此,我们就可以调节LED灯的亮度了(就是40ms内设置高电平的持续时间),这个就是大名鼎鼎的PWM调节亮度的原理了,而设置高电平的持续时间就是调节 占空比 (即高电平的时间除以周期数:20/40=50%)。

这里,我们 最重要的还是这个占空比, 比如周期是20ms,交替10ms亮10ms灭,我们看到的亮度还是50%(即占空比为10/20=50%)

接下来我们就看看程序怎么实现吧。

【程序实现】

点亮一个LED

首先,我们先从点亮一个LED灯开始,然后再一步一步实现一个呼吸灯的效果。我们使用的硬件如下:

开发板 零壹单片机培训开发板
单片机型号 STC89C52
LED接口 P4^4脚

图片

由原理图我们知道,LED灯接到了单片机的P4^4脚,单片机输出1,LED亮,输出0,LED灭。由此,点亮一个LED的程序就很简单了,如下

sbit LED1 = P4^4;
void LED_ON(){//LED亮
  LED1 = 1;
}
void LED_OFF(){//LED灭
  LED1 = 0;
}

点亮LED灯的程序还是很简单,相信大家都会。

调节LED亮度

接下来我们就实现一个可以调节亮度的函数(即调节占空比),如下

//调节LED亮度
void set_led_luminance(unsigned int pwm_duty_cycle)
{
    static unsigned int s_Counter = 0;//计时
    //调节占空比
    if (pwm_duty_cycle >= s_Counter) {
        LED_ON();
    } else {
        LED_OFF();
    }
    //计数器开始计时
    s_Counter++;
  if(s_Counter > 255){
    //40ms时间到,清零重新计时
    s_Counter = 0;
  }    
}
void main(){
  while(1){
    set_led_luminance(128);
  }
}
  • 我们定义了一个静态变量s_Counter作为 软件定时器 ,s_Counter加到255后清零(这里相当于是一个周期的时间40ms,当然不是严格的40ms,只要周期小于40ms我们就看不到闪烁)
  • 函数的参数就是我们要调节的占空比,比如传入的是128,占空比为128/255=50%

现在有了设置LED亮度的函数,那怎么让它渐渐变亮再渐渐变暗,实现呼吸灯的效果呢?

实现呼吸灯

这个也很简单,我们只要给set_led_luminance函数传的参数从0慢慢加到255然后再从255慢慢减到0就可以了,如下

void breath_led(void){
  static int duty_cycle = 0;  
  static char flag = 1;
  //设置亮度
  set_led_luminance(duty_cycle);
  if(flag == 1){//占空比增加
    duty_cycle++;
    if(duty_cycle > 255){//大于255开始减少
      duty_cycle = 255;
      flag = 0;
    }
  }else{//占空比减少
    duty_cycle--;
    if(duty_cycle < 0){//小于0开始增加
      duty_cycle = 0;
      flag = 1;
    }
  }
}
void main(){
  while(1){
    breath_led();
  }
}
  • 定义一个静态变量duty_cycle来保存占空比,当flag为1时,占空比慢慢增加到255,然后把flag设置成0 ,duty_cycle再从255慢慢减为0,如此循环就可以了。

哈哈,到这里你是不是觉得呼吸灯已经实现了, 然而,并不是 。不相信的同学可以自己试试上面的代码哦。

那到底是哪里有问题呢?

这就是我们直接调用设置亮度的函数set_led_luminance()是有问题的,因为此函数执行一个周期需要40ms,也就是在这40ms期间是不可以改变占空比的,否则可就调不了亮度了。再来看看breath_led函数,每次调用完set_led_luminance()函数设置占空比后就立即改变了duty_cycle的值,并没有延时40ms。

此时,我们还需要再加一个软件定时器,定时超过40ms后修改duty_cycle的值,修改后的程序如下

void breath_led(void){
  static unsigned int s_breathCounter = 0;
  static int duty_cycle = 0;  
  static char flag = 1;  
  //设置亮度
  set_led_luminance(duty_cycle);
  //计时器增加
  s_breathCounter++;
  //超过40ms
  if(s_breathCounter > 511){
    //计时器设置为0,重新计时
    s_Counter = 0;
    if(flag == 1){//占空比增加
      duty_cycle++;
      if(duty_cycle > 255){//大于255开始减少
        duty_cycle = 255;
        flag = 0;
      }
    }else{//占空比减少
      duty_cycle--;
      if(duty_cycle < 0){//小于0开始增加
        duty_cycle = 0;
        flag = 1;
      }
    }
  }
}
  • 注意: 我们定时器的时间要大于40ms(也就是s_breathCounter的值要大于255)就可以, 但最好是周期的倍数 ,比如我们的周期为255(即0 ~ 255共256个数),我们设置成它的两倍即256*2-1=511(即0~511共512个数)。

到这里,我们的呼吸灯就制作好了,是不是很简单(* ̄︶ ̄)

接下来就是今天的彩蛋环节

虽然实现了呼吸灯的效果,但是代码还是不够简洁和优雅,用了一堆if和else,我们想办法再简化一下。

首先化简set_led_luminance()函数中的这一部分,如下图所示

图片

化简之前,先讲一个C语言的小知识,就是 按位与运算

1 & 0 = 01 & 1 = 10 & 0 = 00 & 1 = 0

由此,我们知道:不论是1还是0,和0做与运算都为0,

不论是1还是0,和1做与运算还是它本身

为了方便计算,我们采用16进制数(即前面加0x),比如0xff对应的十进制就是255;那么

一个小于等于0xff的数和0xff做与运算,结果还是它本身 ,如下

0x03 & 0xff = 0x03;
0xff & 0xff = 0xff;

那一个大于0xff的数和0xff做与运算呢?

0x100 & 0xff = 0x00;
0x1ff & 0xff = 0xff;
  • 结果就是这个数除以(0xff+1)的余数(即结果还是在0~0xff之间)

有了这个与运算,上面的代码就可以化简为

s_Counter++;
s_Counter = s_Counter & 0xff;
  • 这样s_Counter的取值范围就一直在0x00~0xff了

同样的,上面breath_led函数中的软件定时器也可以化简一下,如下

//计时器增加
s_breathCounter++;
if (!(s_breathCounter & (0x1ff))) {
    duty_cycle++;
    duty_cycle &= 0x1ff;
}
if(duty_cycle > (0xff)){
  set_led_luminance(duty_cycle - (0xff));
}else{
  set_led_luminance( (0xff) - duty_cycle );
}
  • 第3行,0x1ff就是十进制的511,当s_breathCounter的值为(0x1ff+1)即512时条件成立,因为512&0x1ff=0,前面还有个感叹号就是取非运算(准确的说就是s_breathCounter的值为512的倍数时条件成立,这样都省去了s_breathCounter清零的操作了,不知道给大家解释清楚了没,大家要多想想),条件成立则占空比开始增加或减少

  • 那占空比的范围不是0~ 255吗,第5行怎么也变成0x1ff(511)了,别急,你再往下看第8行,我们又减去了0xff,所以占空比的范围还是0~255

  • 第7~ 10行代码的意思为:当duty_cycle > (0xff)时,即256~511,减去0xff,就相当于从1增加到255,亮度渐渐变亮

    当duty_cycle <= (0xff)时,即duty_cycle 从0增加到255,而设置的亮度为255 - duty_cycle ,相当于从255降到0,亮度 渐渐变暗 ,这样就实现了呼吸灯的效果。

哈哈,你以为我们的简化就结束了吗?

no,no,no

其实第7到10行还可以更简单一些。此时就需要用到取绝对值的函数了。

什么,用绝对值干嘛?

你看第10行0xff- duty_cycle就相当于duty_cycle - 0xff然后再取绝对值,好了,化简后的代码如下

set_led_luminance(ABS(duty_cycle - (0xff)));

取绝对值的宏函数如下

#define ABS(N) ((N) < 0 ? -(N) : (N))

最后我把整个化简后的代码也贴到下面

#define ABS(N)    ((N) < 0 ? -(N) : (N))
static void set_led_luminance(unsigned int pwm_duty_cycle)
{
    static unsigned int s_Counter = 0;
    if (pwm_duty_cycle >= s_Counter) {
        LED1 = 1;
    } else {
        LED1 = 0;
    }
    s_Counter++;
    s_Counter = s_Counter & 0xff;     
}
void breath_led(void){
  static unsigned int s_breathCounter = 0;
  static int duty_cycle = 0;  
  s_breathCounter++;
  if (!(s_breathCounter & (0x1ff))) {
    duty_cycle++;
    duty_cycle &= 0x1ff;
  }
  set_led_luminance(ABS(duty_cycle - (0xff)));
}
void main(){
  while(1){    
    breath_led();    
  }
}

怎么样,是不是很简洁(* ̄︶ ̄)

好了,到这里今天的呼吸灯就讲完了,想和我一起重学51单片机的同学记得 点赞+关注, 这会加速我对文章更新的速度哦

声明:本文内容及配图由入驻作者撰写或者入驻合作网站授权转载。文章观点仅代表作者本人,不代表电子发烧友网立场。文章及其配图仅供工程师学习之用,如有内容侵权或者其他违规问题,请联系本站处理。 举报投诉
  • led
    led
    +关注

    关注

    237

    文章

    22455

    浏览量

    645895
  • 单片机
    +关注

    关注

    6002

    文章

    43981

    浏览量

    620891
  • 51单片机
    +关注

    关注

    269

    文章

    5649

    浏览量

    120719
  • C语言
    +关注

    关注

    180

    文章

    7533

    浏览量

    128814
  • 呼吸灯
    +关注

    关注

    9

    文章

    107

    浏览量

    42482
收藏 人收藏

    评论

    相关推荐

    怎样去设计一种基于51单片机呼吸

    51单片机(呼吸)c语言湖北民族学院信息工程学院课程设计报告书题目:基于51单片机
    发表于 07-14 06:57

    51单片机呼吸程序设计

    51单片机呼吸程序设计是基于单片机的原理与接口设计,采用单片机I/O口,加以C语言编程
    发表于 07-14 07:54

    基于51单片机呼吸怎样设计

    课程设计报告书题目:基于51单片机呼吸设计信息工程学院课程设计任务书学 号1学生姓名专业(班级)设计题目基于51
    发表于 07-15 08:01

    怎么样去设计基于单片机与PWM的呼吸

    51单片机(呼吸)C语言版基于单片机与PWM的呼吸
    发表于 07-15 06:02

    怎么实现51单片机led依次点亮?

    怎么实现51单片机led依次点亮?
    发表于 10-14 07:23

    51单片机如何利用PWM定时器实现呼吸

    51单片机如何利用PWM定时器实现呼吸
    发表于 10-15 09:24

    怎样去设计一种基于51单片机的简易呼吸

    基于51单片机的简易呼吸的设计原理是什么?怎样去设计一种基于51单片机的简易
    发表于 10-18 08:37

    如何利用51单片机实现呼吸的设计?

    如何利用51单片机实现呼吸的设计?
    发表于 10-19 06:24

    如何利用51单片机实现LED闪烁?

    如何利用51单片机实现LED闪烁?
    发表于 10-19 09:18

    如何利用51单片机实现LED闪烁?

    如何利用51单片机实现LED闪烁?
    发表于 10-19 09:23

    如何利用51单片机实现led的点亮?

    如何利用51单片机实现led的点亮?
    发表于 10-26 07:43

    基于51单片机实现呼吸设计

    实现效果如图:注:该51单片机led引脚为P2^ 0 - P2^7,实际代码按照引脚图实现。代
    发表于 11-18 06:48

    基于51单片机的七彩呼吸设计资料分享

    ”。(4)按键3按下时,恢复起始亮法。1 先做一个呼吸由易到难,先做一个呼吸呼吸是让小
    发表于 11-19 07:11

    如何用LED实现呼吸的功能

    一、概述玩过单片机的基本都做过用LED实现呼吸的功能,但是只要认真观察,会发现
    发表于 01-21 08:09

    51单片机呼吸灯的实现源程序

    51单片机呼吸灯的实现,源程序,hex文件都有,用到的led灯是共阴极的
    发表于 12-10 16:35 166次下载