PWM实现“流星雨”效果

作者: 想啥做啥 | 来源:发表于2021-03-03 22:41 被阅读0次

    1、不同频率LED灯闪烁

    接下来我们以下面LED灯的闪烁代码为例子,改变延时长短来看LED灯的效果

    void setup()
    {
      pinMode(2, OUTPUT);
    }
    
    void loop()
    {
      digitalWrite(2, HIGH);
      delay(50); // Wait for xx millisecond(s)
      digitalWrite(2, LOW);
      delay(50); // Wait for xx millisecond(s)
    }
    
    500ms延时闪烁(1Hz频率)
    200ms延时闪烁(2.5Hz频率)
    50延时ms闪烁(10Hz频率)

    通过三个对比实验我们发现随着频率的升高,我们的LED灯慢慢的开始感觉不到闪烁,由于我们人眼的视觉停留效应,一般大于50Hz的刷新率就能满足我们的要求


    2、高频率LED闪烁变形过程

    我们还是以这个代码,将频率固定在50Hz,然后保持周期不变即高低电平加起来的时间等于40ms,然后改变高低电平的占空比(高低电平占总周期的百分比),我们通过调节高低电平的延时的长度来调节亮度的占比
    代码部分:

    void setup()
    {
      pinMode(2, OUTPUT);
    }
    
    void loop()
    {
      digitalWrite(2, HIGH);
      delay(10); // Wait for 1000 millisecond(s)
      digitalWrite(2, LOW);
      delay(10); // Wait for 1000 millisecond(s)
    }
    
    50Hz频率最大亮度(即灯灭的情况下延时为0,亮的情况下延时20ms)
    50Hz频率50%亮度(即灯灭的情况下延时为10ms,灯亮的情况下延时10ms)
    50Hz频率25%亮度(即灯灭的情况下延时为15ms,灯亮的情况下延时5ms)
    50Hz频率10%亮度(即灯灭的情况下延时为18ms,灯亮的情况下延时2ms)

    我们把上面的动作连贯起来,也就是说把亮度延时做成连续变化,为了在实际中效果更好,我们将延时改成延时200个us,这样连续变化效果更好
    代码部分:

    void setup()
    {
      pinMode(2, OUTPUT);
    }
    
    int count = 0;
    int PWM_Time = 50;
      
    void loop()
    {
      digitalWrite(2, HIGH);    // LED灯灭
      delayMicroseconds(200-PWM_Time);              // Wait for xx millisecond(s)
      digitalWrite(2, LOW);     // LED灯亮
      delayMicroseconds(PWM_Time);              // Wait for xx millisecond(s)
      
      count++;
      if(count==50)
      {
          count = 0;
          PWM_Time++;
          if(PWM_Time>=200) PWM_Time = 0;
      }
    }
    
    LED灯渐亮效果

    我们再进一步修改下,让它变成一个呼吸的效果
    代码部分:

    void setup()
    {
      pinMode(2, OUTPUT);
    }
    
    int count = 0;            // 因为延时比较短,直接使用会变化的
                              // 太快看不到效果,说以加个计数的变量
    int PWM_Time = 0;         // LED占空比变量
    int LED_Togle_Flag = 0;   // LED逐渐亮灭翻转标志
      
    void loop()
    {
      if(LED_Togle_Flag)
      {
        digitalWrite(2, HIGH);  // LED灯灭
        delayMicroseconds(200-PWM_Time);                // Wait for xx millisecond(s)
        digitalWrite(2, LOW);       // LED灯亮
        delayMicroseconds(PWM_Time);                // Wait for xx millisecond(s)
      }
      else
      {
        digitalWrite(2, HIGH);  // LED灯灭
        delayMicroseconds(PWM_Time);                // Wait for xx millisecond(s)
        digitalWrite(2, LOW);       // LED灯亮
        delayMicroseconds(200-PWM_Time);                // Wait for xx millisecond(s)
      }
      
      
      count++;
      if(count==50)
      {
        count = 0;
        PWM_Time++;
        // 切换亮暗变化逻辑
        if(PWM_Time>=200) 
        {
            PWM_Time = 0;
            LED_Togle_Flag = ~LED_Togle_Flag;
        }
      }
    }
    
    LED灯呼吸灯

    首先我们先来分析下流星雨的逻辑:
    首先我们要实现一个这样的效果,第一个最亮,然后后一个是前一个的45%的亮度



    代码部分:

    // ----------------------------------------------------------------------------
    // LED_Rains.ino
    //
    // 数字引脚实现的雨滴流动效果
    // 雨滴流动效果与流水灯(跑马灯)的区别在于雨滴流水效果有拖尾效果,即亮过的灯是慢慢熄灭的
    //
    // 使用 UNO 的所有引脚用模拟 PWM 实现雨滴流动的效果,包括模拟输入口也可以用做数字输出
    // 各引脚接 LED 正极,LED 负极接 GND
    // ----------------------------------------------------------------------------
    
    const unsigned char leds[] = { A4, A5, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11 }; // 所有的引脚按 LED 接线顺序排列
    const unsigned int maxPwm       = 100;    // 手工模拟 PWM,可以自己定义最大的 PWM 值是多少,所以定义一个整百整千的数比较方便计算
    
    unsigned int ledPwm[12] = { 1, 3, 4, 6, 9, 13, 18, 25, 35, 50, 70, 100}; // 存放运行时每一个 LED 的亮度 PWM 值
    
    
    void setup()
    {
        for (char i = 0; i < 12; ++i)
        {
            pinMode(leds[i], OUTPUT);
        }
    }
    
    void loop()
    {
        unsigned int i, j;
    
        for (i = 0; i < 12; ++i)      // 先亮灯,等占空比到切换点的时候灭灯
        {
            digitalWrite(leds[i], LOW);
        }
       
        for( i=0; i<maxPwm; ++i)
        {
            for (j = 0; j < 12; ++j)
            {
                if (i == ledPwm[j])
                    digitalWrite(leds[j], HIGH);
            }
            delayMicroseconds(1);
        }
    }
    
    静态流星雨效果

    代码解释:
    我门首先给亮度数组ledPwm[12]储存按比例分配的数值,这里我是按70%的一个比例来计算
    比如说最暗是100,那么次暗的就是100*70% = 70,以此类推,然后我们就按照分配的亮度来把灯分别点亮
    这部分代码是把所有LED灯先点亮

    for (i = 0; i < 12; ++i)      // 先亮灯,等占空比到切换点的时候灭灯
        {
            if (ledPwm[i] == 0)
                continue;
            digitalWrite(leds[i], LOW);
        }
    

    这部分代码根据LED灯的亮暗程度来分别控制灭的时间,我们先根据最大亮度值“maxPWM”来将亮度分为100份,每份的延时是1us,然后在内部的循环里面检查当前的亮度值是否到达分配的份数,如果到达了,那就熄灭,没有到达,就继续保持亮

    for( i=0; i<maxPwm; ++i)
        {
            for (j = 0; j < 12; ++j)
            {
                if (i == ledPwm[j])
                    digitalWrite(leds[j], HIGH);
            }
            delayMicroseconds(1);
        }
    

    让LED”流星雨“运动

    显然这样静态的流星雨还是满足不了我们的要求,接下来我们让流星雨先动起来
    我们需要它这样动


    LED流星雨动态分解示意图

    我们先试着让它动一位,我只需要把ledPwm[12]这个数组里面的值重新进行排列就可以了,这其实就是对数组操作

    unsigned int ledPwm[12] = { 3, 4, 6, 9, 13, 18, 25, 35, 50, 70, 100, 1}; // 存放运行时每一个 LED 的亮度 PWM 值
    
    LED流星雨移动一位的效果

    LED“流星雨”连续运动

    从上面我们知道,我们如果有办法对数组进行连续的操作,那么就能实现“流星雨”流动的效果

    所有代码参考这个哥们的:Arduino 入门程序示例之一排 LED(2015-06-11)

    其中下面这一点代码是arduino自带的时间计数器,可以直接读取里面的数值,用来辅助计数用的,其实你也可以不用这个,可以自己直接在里面计数也可以的

    extern volatile unsigned long timer0_millis;
    

    完整版代码:把所有需要改动的数值变量全部放在最前面,这是编写可复用程序常用的一种做法,可以灵活适应多个灯,同时可以调节速度,调节亮度比例

    // ----------------------------------------------------------------------------
    // LED_Rains.ino
    //
    // 数字引脚实现的雨滴流动效果
    // 雨滴流动效果与流水灯(跑马灯)的区别在于雨滴流水效果有拖尾效果,即亮过的灯是慢慢熄灭的
    //
    // 使用 UNO 的所有引脚用模拟 PWM 实现雨滴流动的效果,包括模拟输入口也可以用做数字输出
    // 各引脚接 LED 正极,LED 负极接 GND
    // ----------------------------------------------------------------------------
    
    const unsigned char leds[] = { A4, A5, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11 }; // 所有的引脚按 LED 接线顺序排列
    const unsigned int maxPwm       = 100;    // 手工模拟 PWM,可以自己定义最大的 PWM 值是多少,所以定义一个整百整千的数比较方便计算
    const unsigned int initPwm      = 100;    // 最亮的灯的 pwm 值,即移动的时候在最头一个灯的亮度
    const unsigned int deltaPwm     = 1;      // 灯慢慢熄灭的 pwm 值,后一个灯比前一个灯暗多少。这相当于是一个等差队列。等差的亮度感觉不大好,所以引入下一个等比的因素
    const unsigned int deltaPercent = 45;     // 后一个灯比前一个灯暗,其亮度是前一个灯的百分之几。相对于前面的递减,这个相当于是等比级数
    const unsigned long delayMs     = 100;     // 移动延迟,单位 ms
    const unsigned char ledNum      = sizeof(leds) / sizeof(leds[0]);  // 引脚数量,即 LED 个数
    
    unsigned int ledPwm[ledNum]; // 存放运行时每一个 LED 的亮度 PWM 值
    
    void setup()
    {
        for (char i = 0; i < ledNum; ++i)
        {
            pinMode(leds[i], OUTPUT);
            ledPwm[i] = 0;
        }
    }
    
    extern volatile unsigned long timer0_millis;    // 声明外部变量 timer0_millis 以便在程序中使用,其实就是 millis() 的返回值——程序运行的毫秒数
    
    void loop()
    {
        static unsigned char head = 0;
        static unsigned long lastTick = timer0_millis;
        unsigned int i, j;
    
        for (i = 0; i < ledNum; ++i)      // 先亮灯,等占空比到切换点的时候灭灯
        {
            if (ledPwm[i] == 0)
                continue;
            digitalWrite(leds[i], LOW);
        }
       
        ledPwm[head] = initPwm;         // 水滴头是最亮的
    
        for (i = 0; i < maxPwm; ++i)    // 这里就是数字口模拟的 PWM 程序了
        {
            for (j = 0; j < ledNum; ++j)
            {
                if (i == ledPwm[j])
                    digitalWrite(leds[j], HIGH);
            }
            delayMicroseconds(1);
        }
    
        // 如果延时时间还没到,先跳出,不进行水滴的移动 
        if (timer0_millis - lastTick < delayMs) // 由于是用数字口模拟的 PWM,程序要不停的跑,不能使用 delay() 来延时,会卡住的
            return;
    
        lastTick = timer0_millis;
    
        for (i = 0; i < ledNum; ++i)      // 处理每一个灯的亮度
        {
            ledPwm[i] = ledPwm[i] * deltaPercent / 100;
            if (ledPwm[i] <= deltaPwm){ ledPwm[i] = 0; }           
            else                      { ledPwm[i] -= deltaPwm; }
                
            if (i == head){ ledPwm[i] = initPwm; }            
        }
        
        head = (head + 1) % ledNum;  // 移动水滴头
    }
    
    完整版LED“流星雨”效果

    总结:

    1、我们了解了PWM的实现方式
    2、LED在不同的频率下,会“欺骗”我们的眼睛,这样是玩单片机中惯用的一种思路
    3、通过剖析LED"流星雨",我们发现其实它就用了一些简单的处理方式实现的,没有我们想象中的那么复杂
    4、LED"流星雨"里面有一点简单的算法,算法是独立于单片机的,在其他平台(51,STM32等)上面同样可以实现,同时算法也是一个程序的灵魂

    相关文章

      网友评论

        本文标题:PWM实现“流星雨”效果

        本文链接:https://www.haomeiwen.com/subject/yhncqltx.html