在BLE芯片QN9021上实现呼吸灯效果

        本文记述在低功耗蓝牙芯片QN9021上,利用PWM+定时器实现呼吸灯效果的过程,以及过程的一点心得体会。QN9021是NXP的一款低功耗蓝牙SoC芯片,集成了一个Cortex-M0内核,这里没有用到它的蓝牙功能,因此把它当作一个M0核的单片机即可。

        呼吸灯是指灯的亮度由暗逐渐变亮,然后由亮逐渐变暗的一种视觉效果,因为变化的过程与人的呼吸节奏相似,因此称为“呼吸灯”。其基本原理是不断调整PWM的占空比,从而达到灯的亮度渐变的效果。这里有3个比较重要的值:

1)PWM的周期

        PWM的周期越大,频率越小,闪烁感越强烈。一般人眼能觉察的闪烁频率最大为50Hz,即只要频率大于这个值,则人眼看不出明显的闪烁。但是PWM的频率越大,对单片机定时器的要求也就越高,因此需要根据单片机的性能,合理选择PWM的频率。经过试验,我发现PWM频率取1KHz是比较合适的,即PWM周期为1000us。

2)占空比的调整周期

        上文说到,亮度渐变的效果是通过不断调整PWM占空比实现,但要以什么样的时间间隔来调整占空比,需要仔细推敲。如果间隔太小,接近甚至等于PWM的周期,则容易造成PWM时序紊乱。间隔太大,亮度过度不平滑,会有阶梯感。经过试验,调整周期取10ms是比较合适的,即1s内调整100次。

3)调整的步长

        在“吸气”阶段,高电平的占空比是不断增加的。在“呼气”阶段,高电平的占空比不断减小。但是每次增加或减少多少合适呢?由于PWM的周期我取的是1000us,因此高电平的宽度调整范围是0—1000。需要注意的是,在某些单片机上,占空比不能设置为0(使用单片机自带的PWM外设的情况,如果使用定时器实现PWM则另当别论)。

        从0开始,如果每次增加1,则需要调整1000次,才能达到最亮。而调整一次的时间间隔是10ms,也就是说要花费10s的时间,才能从最暗到最亮。这显然与人的呼吸节奏不匹配。若每次增加5,则需要5s,仍然有些长。结合人的呼吸节奏,“吸气”时间为1s比较合适,即高电平取值每次增加10。“呼气”时间比“吸气”时间略长,我这里取1:1.5,即“呼气”时长为1.5s,换算下来呼气阶段高电平宽度每次减少7,大致符合这个比例关系。每次“呼吸”之间,有短暂的停顿,取1—2s均可。

        下面说说一次完整的“呼吸”过程分为哪几个步骤。我这里大致分为三个步骤:

        第1步:吸气过程,高电平宽度从0开始,每10ms增加10,调整100次达到最大,耗时1s。

        第2步:呼气过程,高电平从1000开始,每10ms减少7,调整约140次达到最小,耗时约1.4s。

        第3步:停顿过程,熄灭LED(因为占空比最低时,LED不一定是灭的),等待1s,然后开始下一个呼吸周期。

        接下来看下呼吸灯的软件算法。要实现呼吸灯的效果,要求单片机有2个定时器,或者1个定时器+1个PWM。其中,一个定时器用于产生PWM,另一个定时器提供占空比调整和停顿延时需要的时基。其实,只要单片机的定时器精度够高,两个功能复用同一个定时器,也是可以的,这里不详细说,读者可以自行尝试。我这里使用的是QN9021的Timer1提供10ms定时,Timer2的PWM模式来控制LED。部分代码如下:

/* 全局变量定义 */
uint8_t      g_10ms_flag = 0;                  // 10ms定时时间到的标志       
uint8_t      g_step = 1;                       // 用于标识呼吸过程进行到第几步                 
uint8_t      g_wait_cnt = 0;                   // 用于停顿过程的计时               
uint16_t     g_pwm_h = 0;                      // 用于记录PWM高电平的宽度 
/* 主函数 */

int main(void)
{
        timer_init(QN_TIMER1, timer1_irq_callback);         // Timer1初始化
        timer_init(QN_TIMER2, NULL);                        // Timer2初始化

        // Timer1定时产生10ms中断,通过置位g_10ms_flag变量体现 
        timer_config(QN_TIMER1, TIMER_PSCAL_DIV, TIMER_COUNT_MS(10, TIMER_PSCAL_DIV));

        // Timer2产生PWM输出,已提前将输出引脚配置为P26
        timer_pwm_config(QN_TIMER2, TIMER_PSCAL_DIV,                            // 设置定时器预分频
                                       TIMER_COUNT_US(1000, TIMER_PSCAL_DIV),   // 设置PWM周期为1000us
                                       TIMER_COUNT_US(1, TIMER_PSCAL_DIV));     // 设置初始高电平宽度为1us

        timer_enable(QN_TIMER1, MASK_ENABLE);              // 启动Timer1
        timer_enable(QN_TIMER2, MASK_ENABLE);              // 启动Timer2

         while(1)
        {
                if(g_10ms_flag == 1)    // 判断10ms定时是否到
                {
                        g_10ms_flag = 0;
                        if(g_step == 1)    // 当前过程为第一阶段
                        {
                                if(g_pwm_h <= 1000)
                                {
                                        g_pwm_h += 10;                              // 高电平按10us步长增加
                                        // 占空比更新为新的值
                                        timer_pwm_config(QN_TIMER2, TIMER_PSCAL_DIV, 
                                                                       TIMER_COUNT_US(1000, TIMER_PSCAL_DIV), 
                                                                       TIMER_COUNT_US(g_pwm_h, TIMER_PSCAL_DIV));    
                                        timer_enable(QN_TIMER2, MASK_ENABLE);       // 重新启动Timer2
                                }  
                                else                       // 高电平宽度达到最大
                                {
                                        g_step = 2;        // 转入第二阶段
                                }
                }
                else if(g_step == 2)    // 当前过程为第二阶段
                {
                        if(g_pwm_h >= 7)
                        {
                                g_pwm_h -= 7;                                      // 高电平按7us步长减小
                                // 占空比更新为新的值
                                timer_pwm_config(QN_TIMER2, TIMER_PSCAL_DIV, 
                                                               TIMER_COUNT_US(1000, TIMER_PSCAL_DIV), 
                                                               TIMER_COUNT_US(g_pwm_h, TIMER_PSCAL_DIV));    
                                timer_enable(QN_TIMER2, MASK_ENABLE);       // 重新启动Timer2
                        }
                        else                             // 高电平宽度达到最小
                        {
                                g_step = 3;              // 转入第三阶段,重置相关变量值
                                g_pwm_h = 0;
                                g_wait_cnt = 100;        // 设置停顿时长为10ms*100=1s
                        }
                }
                else if(g_step == 3)    // 当前过程为第三阶段,禁能Timer2和LED
                {
                        timer_enable(QN_TIMER2, MASK_DISABLE);
                        gpio_write_pin(LED5_PIN, (enum gpio_level)GPIO_HIGH);
                }
           }
        }
}
/* Timer1中断回调函数 */
void timer1_irq_callback(void)
{
        g_10ms_flag = 1;                       // 10ms定时周期到,置位标志变量
        if(g_step == 3)                        // 当前处于第三阶段,计时变量递减
        {
                g_wait_cnt--;
                if(g_wait_cnt == 0)            // 计时变量减为0,等待结束
                 {
                        g_pwm_h = 0;           // 重置相关变量,重新开始下一个呼吸周期
                        g_step = 1; 
                }
        }
}




你可能感兴趣的:(BLE)