分享免费的编程资源和教程

网站首页 > 技术教程 正文

全彩呼吸的制作(一)

goqiw 2024-09-09 06:32:31 技术教程 29 ℃ 0 评论

呼吸灯,就是指灯光设备的亮度随着时间由暗到亮逐渐增强,再由亮到暗逐渐衰减, 很有节奏感地一起一伏,就像是在呼吸一样,因而被广泛应用于手机、电脑等电子设备的 指示灯中,冰冷的电子设备应用呼吸灯后,顿时增添了几分温暖。

一、呼吸灯介绍

呼吸的特性是一种类似图 40-1 中的指数曲线过程,吸气是指数上升过程,呼气是指数 下降过程,成年人吸气呼气整个过程持续约 3 秒。

要控制 LED 灯达到呼吸灯的效果,实际上就是要控制 LED 灯的亮度拟合呼吸特性曲线。前面控制全彩 LED灯时,通过控制脉冲的占空比来调整各个通道 LED灯的亮度,从而达到混色的效果。若控制脉冲的占空比在 3 秒的时间周期内按呼吸特性曲线变化,那么就 可以实现呼吸灯的效果了。

二、SPWM 波介绍

上面的这种使用脉冲占空比拟合不同波形的方式称为 PWM(脉冲宽度调制)控制技术——通过对一系列脉冲的宽度进行调制,来等效地获得所需要波形(含形状和幅值)。

PWM 控制的基本原理为:冲量相等而开头不同的窄脉冲加在具有惯性的环节上时,其效果基本相同。其中冲量指窄脉冲的面积;效果相同指环节输出响应波形基本相同。

例如:可以用一系列等幅不用一系列等幅不等宽的脉冲来代替一个正弦半波,见图 40-2。 把正弦半波 N 等分,可看成 N 个彼此相连的脉冲序列,宽度相等,但幅值不等; 用矩形脉冲代替,各个矩形脉冲等幅,不等宽,中点重合,脉冲宽度按正弦规律 变化,脉冲的总面积(冲量)与正弦半波相等。

这种脉冲波形被称为 SPWM 波形——脉冲宽度按正弦规律变化而和正弦波等效的 PWM 波形。SPWM 是一种非常典型的 PWM 波形,它在数字电路控制中应用非常广泛,如果使用低通滤波器,可以由 SPWM 波得到其等效的连续正弦半波。要改变等效输出正弦波幅值,按同一比例改变各脉冲宽度即可。若把拟合的波形改成呼吸特性曲线,即可得到控制呼吸灯使用的 PWM 波形,要生成 拟合的 PWM 波形,通常使用计算法和调制法:(1) 计算法:根据拟合波形的频率、幅值和半周期脉冲数,准确计算 PWM 波各脉冲宽度和间隔,据此控制开关器件的通断,就可得到所需 PWM 波形; (2) 调制法:拟合波形作调制信号,进行调制得到期望的 PWM 波;该方法一般采用等腰三 角波为载波,其任一点水平宽度和高度成线性关系且左右对称。载波(等腰三角波)与 平缓变化的调制信号波(即要拟合的波形)相交,在载波与信号波的交点控制器件通断, 就得宽度正比于信号波幅值的脉冲,符合 PWM 的要求,见图 40-3。相对于计算法,其处理过程计算简单。

下片文章我们一起来演示如何使用计算法得到的呼吸曲线 PWM 波和 SPWM 波,并使用 STM32 定时器 TIM 的 PWM 功能输出波形控制 LED 灯,达到呼吸灯的效果。

三、硬件设计

本开发板中设计的 RGB 灯控制引脚是经过仔细选择的,因为本实验的软件将使用 STM32 的定时器控制输出 PWM 脉冲,然而并不是任意 GPIO 都具有 STM32 定时器的输出 通道功能,所以在设计硬件时,需要根据《STM32 中文数据手册》中的说明,选择具有定时器输出通道功能的引脚来控制 RGB 灯。

三、代码

/*****************************************
利用pwm控制led亮度,
其实我们看到的是灯在呼吸,实际上灯是一直在以很高的频率在闪烁,每次闪烁的亮度都不一样,越来越亮,或者暗
由于人的视觉暂留效应,我们看到灯一直在亮,而且亮度在渐变。
********************************************/
#include "stm32f10x.h"
/* LED亮度等级 PWM表 */
uint8_t indexWave[] = {1,1,2,2,3,4,6,8,10,14,19,25,33,44,59,80,
    107,143,191,255,255,191,143,107,80,59,44,33,25,19,14,10,8,6,4,3,2,2,1,1};
    
    
//函数申明
    
        void Init_LED(void);
        void NVIC_Config_PWM(void);
        void Init_TIMER(void);
        void Init_PWM(void);
        void Delay_Ms(uint16_t time);  
        void Delay_Us(uint16_t time); 
    
    
    
int main(void)
{
            SystemInit();                    //系统时钟配置
            Init_LED();                        //LED初始化
            NVIC_Config_PWM();    
            Init_TIMER();                    //定时器初始化
            Init_PWM();            //PWM初始化设置                            
            GPIO_SetBits(GPIOG,GPIO_Pin_14);    // LED D2 输出为高
            
            while(1);
}
void Init_LED(void)
{
      GPIO_InitTypeDef GPIO_InitStructure;                    //定义一个GPIO结构体变量
      RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOC | RCC_APB2Periph_GPIOD| RCC_APB2Periph_GPIOG |RCC_APB2Periph_AFIO,ENABLE);    
                                                            //使能各个端口时钟,重要!!!
    
      GPIO_InitStructure.GPIO_Pin =  GPIO_Pin_14;             //配置LED D2端口挂接到PG14端口
      GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;           //通用输出推挽
      GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;           //配置端口速度为50M
      GPIO_Init(GPIOG, &GPIO_InitStructure);                       //将端口GPIOD进行初始化配置
      GPIO_InitStructure.GPIO_Pin = GPIO_Pin_13 ;             //配置LED D5端口挂接到13端口
      GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;               //复用功能输出推挽,这是重映射必要地,AF表示复用
      GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;           //配置端口速度为50M
      GPIO_Init(GPIOD, &GPIO_InitStructure);                       //将端口GPIOD进行初始化配置
      GPIO_PinRemapConfig(GPIO_Remap_TIM4,ENABLE);            //将定时器4通道2重映射到PD13引脚,重要!!
}                                                    //我看很多资料都是这样映射的
//还是野火比较专业,人的吸气呼气通常用时为3秒,算出来是这样的
//TIM_BaseInitStructure.TIM_Period = 256-1;
//TIM_BaseInitStructure.TIM_Prescaler = 2000-1; 
void Init_TIMER(void)
{
    
    TIM_TimeBaseInitTypeDef     TIM_BaseInitStructure;            //定义一个定时器结构体变量
    RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM4, ENABLE);       //使能定时器4,重要!!
//    TIM_DeInit(TIM4);                                          //将TIM4定时器初始化位复位值
//    TIM_InternalClockConfig(TIM4);                                      //配置 TIM4 内部时钟
       
    //TIM_BaseInitStructure.TIM_Period = 7200-1;         //设置自动重载寄存器值为最大值    0~65535之间  1000000/1000=1000us=1ms                                                    
                                                                          //TIM_Period(TIM1_ARR)=7200,计数器向上计数到7200后产生更新事件,
                                                                       //计数值归零 也就是 1MS产生更新事件一次
    
    TIM_BaseInitStructure.TIM_Period = 256-1;
    
    TIM_BaseInitStructure.TIM_Prescaler = 2000-1;  //自定义预分频系数为0,即定时器的时钟频率为72M提供给定时器的时钟    0~65535之间
                                                                    
                                         
    TIM_BaseInitStructure.TIM_ClockDivision = TIM_CKD_DIV1; //时钟分割为0
    
    TIM_BaseInitStructure.TIM_CounterMode = TIM_CounterMode_Up;   //TIM向上计数模式 从0开始向上计数,计数到1000后产生更新事件
                                                                
    TIM_TimeBaseInit(TIM4, &TIM_BaseInitStructure);         //根据指定参数初始化TIM时间基数寄存器    
      
     TIM_ARRPreloadConfig(TIM4, ENABLE);       //使能TIMx在 ARR 上的预装载寄存器 
    TIM_Cmd(TIM4, ENABLE);         //TIM4总开关:开启 
    
    TIM_ITConfig(TIM4,TIM_IT_Update,ENABLE);    //使能定时器中断
    
    NVIC_Config_PWM();   //配置中断优先级
    
}
void Init_PWM()
{
    TIM_OCInitTypeDef  TIM_OCInitStructure;                    //定义一个通道输出结构
    TIM_OCStructInit(&TIM_OCInitStructure);                    //设置缺省值
    
    TIM_OCInitStructure.TIM_Pulse = 0;             //设置占空比,占空比=(CCRx/ARR)*100%或(TIM_Pulse/TIM_Period)*100%
                                                                        //PWM的输出频率为Fpwm=72M/7200=1Mhz;  
    /* 下面五句话就把PWM 基本上配置完成,再加上上面定时器的使能,TIM_Cmd(TIM4,ENABLE)   */
    
    TIM_OCInitStructure.TIM_OCMode = TIM_OCMode_PWM1;           //PWM 模式 1 输出     ,
    
    TIM_OCInitStructure.TIM_OutputState = TIM_OutputState_Enable;    //使能输出状态  需要PWM输出才需要这行代码
        
    TIM_OCInitStructure.TIM_OCPolarity = TIM_OCPolarity_High;    //TIM 输出比较极性高 ,就是开始    计数到ccr之前都是high,因为这里的led是正逻辑点亮
                                                                                                                                
  TIM_OC2Init(TIM4, &TIM_OCInitStructure);              //根据参数初始化PWM寄存器 ,使能通道CCR2   
    TIM_OC2PreloadConfig(TIM4,TIM_OCPreload_Enable);           //使能 TIMx在 CCR2 上的预装载寄存器,很关键和容易遗漏的一部
  //TIM_CtrlPWMOutputs(TIM4,ENABLE);                          //设置TIM4 的PWM 输出为使能  
    
}
void NVIC_Config_PWM(void)      //配置嵌套向量中断控制器NVIC
{     
      NVIC_InitTypeDef NVIC_InitStructure;
    
        NVIC_PriorityGroupConfig(NVIC_PriorityGroup_1);    //设置中断优先级分组2
        NVIC_InitStructure.NVIC_IRQChannel = TIM4_IRQn;    //设定中断源为PC13
    
        NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0;    //中断占优先级为0
    
        NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0;    //副优先级为0
    
        NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;    //使能中断
    
        NVIC_Init(&NVIC_InitStructure);    //根据参数初始化中断寄存器
    
}
void TIM4_IRQHandler(void)    //中断入口函数
{    
    static uint8_t pwm_index = 0;            //用于PWM查表
    static uint8_t period_cnt = 0;        //用于计算周期数
    
    if (TIM_GetITStatus(TIM4, TIM_IT_Update) != RESET)    //TIM_IT_Update
     {            
            period_cnt++;
            if(period_cnt >= 10)                                        //若输出的周期数大于10,输出下一种脉冲宽的PWM波,在这里野火说他也不知道为什么大于10
            {
                
                TIM4->CCR2 = indexWave[pwm_index];    //根据PWM表修改定时器的比较寄存器值
                pwm_index++;                                                //标志PWM表的下一个元素
            
                if( pwm_index >=  40)                                //若PWM脉冲表已经输出完成一遍,重置PWM查表标志
                {
                    pwm_index=0;                                
                }
                
                period_cnt=0;                                                //重置周期计数标志
        }
            
        TIM_ClearITPendingBit (TIM4, TIM_IT_Update);    //必须要清除中断标志位
        
    }
}
    /*:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
** 函数名称: Delay_Ms_Ms
** 功能描述: 延时1MS (可通过仿真来判断他的准确度)            
** 参数描述:time (ms) 注意time<65535
:::::::::::::::::::::::::::::::::*/
void Delay_Ms(uint16_t time)  //延时函数
{ 
    uint16_t i,j;
    for(i=0;i<time;i++)
          for(j=0;j<10260;j++);
}
/*:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
** 函数名称: Delay_Ms_Us
** 功能描述: 延时1us (可通过仿真来判断他的准确度)
** 参数描述:time (us) 注意time<65535                 
:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::*/
void Delay_Us(uint16_t time)  //延时函数
{ 
    uint16_t i,j;
    for(i=0;i<time;i++)
          for(j=0;j<9;j++);
}
/*:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
End:-D:-D:-D:-D:-D:-D:-D:-D:-D:-D:-D:-D:-D:-D:-D:-D:-D:-D:-D:-D
:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::*/


四、实验现象

通过实验我们可以看出小灯可以实现像人的呼吸一样的效果,从灭的状态编逐渐亮起,直到最亮的状态,然后从最亮的状态逐渐熄灭,这个时间和人的呼吸时间差不多,所以命名为呼吸灯。


你们点点赞 ,给我充点儿电吧~

Tags:

本文暂时没有评论,来添加一个吧(●'◡'●)

欢迎 发表评论:

最近发表
标签列表