xld0932 发表于 2022-3-25 18:28

【MM32+模块】系列:02、LED灯控制

本帖最后由 xld0932 于 2022-3-25 19:00 编辑

#申请原创#   @21小跑堂

在我的印象当中LED灯控制是接触MCU时的第一个外设实验了,因为其本身只需要通过GPIO端口输出高低电平就可以实现了,常规的原理图画法有很多,有VCC接LED灯再串一个电阻连接到MCU的引脚上的,也有MCU引脚接LED灯再串一个电阻连接到GND上的,当然还有通过三极管等等电路来驱动的……

在如上原理图中,串联的1k电阻是用于限制电流大小的;对于方式1中MCU_PIN输出高电平时,LED处于熄灭的状态,输出低电平时,LED则处于点亮的状态;对于方式2中的MCU_PIN输出高电平时,LED处于点亮的状态,输出低电平时,LED则处于熄灭的状态;对于其它的方式,则需要根据实际电路设计来进行高低电平的输出控制了。
在淘宝上买了一个LED模块,带有红黄绿3个LED灯;结合MM32F0140的核心板,我们今天实现LED的几个实验:1、LED闪烁实验2、LED流水灯实验3、PWM方式调节LED灯显示亮度4、对数方式实现呼吸灯
LED闪烁实验LED灯闪烁就是通过时间间隔来控制LED灯点亮或者熄灭的操作,达到闪烁的效果,具体的初始化及功能实现代码如下所示:void LED_Init(void)
{
    GPIO_InitTypeDef GPIO_InitStructure;

    RCC_AHBPeriphClockCmd(RCC_AHBENR_GPIOA, ENABLE);

    GPIO_StructInit(&GPIO_InitStructure);
    GPIO_InitStructure.GPIO_Pin   = GPIO_Pin_3 | GPIO_Pin_4 | GPIO_Pin_5;
    GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
    GPIO_InitStructure.GPIO_Mode= GPIO_Mode_Out_PP;
    GPIO_Init(GPIOA, &GPIO_InitStructure);

    GPIO_WriteBit(GPIOA, GPIO_Pin_3, Bit_RESET);
    GPIO_WriteBit(GPIOA, GPIO_Pin_4, Bit_RESET);
    GPIO_WriteBit(GPIOA, GPIO_Pin_5, Bit_RESET);

    TASK_Append(TASK_ID_RYG, LED_Handler,250);
}


void LED_Handler(void)
{
    bool PA3 = GPIO_ReadOutputDataBit(GPIOA, GPIO_Pin_3);
    bool PA4 = GPIO_ReadOutputDataBit(GPIOA, GPIO_Pin_4);
    bool PA5 = GPIO_ReadOutputDataBit(GPIOA, GPIO_Pin_5);

    if(!PA3) GPIO_WriteBit(GPIOA, GPIO_Pin_3, Bit_SET);
    else   GPIO_WriteBit(GPIOA, GPIO_Pin_3, Bit_RESET);

    if(!PA4) GPIO_WriteBit(GPIOA, GPIO_Pin_4, Bit_SET);
    else   GPIO_WriteBit(GPIOA, GPIO_Pin_4, Bit_RESET);

    if(!PA5) GPIO_WriteBit(GPIOA, GPIO_Pin_5, Bit_SET);
    else   GPIO_WriteBit(GPIOA, GPIO_Pin_5, Bit_RESET);
}
LED闪烁实验效果
LED流水灯实验LED灯模块上有3个灯,我们按照固定的方向依次点亮LED灯,不停的循环反复达到流水灯的效果,具体的初始化及功能实现代码如下所示:void LED_Init(void)
{
    GPIO_InitTypeDef GPIO_InitStructure;

    RCC_AHBPeriphClockCmd(RCC_AHBENR_GPIOA, ENABLE);

    GPIO_StructInit(&GPIO_InitStructure);
    GPIO_InitStructure.GPIO_Pin   = GPIO_Pin_3 | GPIO_Pin_4 | GPIO_Pin_5;
    GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
    GPIO_InitStructure.GPIO_Mode= GPIO_Mode_Out_PP;
    GPIO_Init(GPIOA, &GPIO_InitStructure);

    GPIO_WriteBit(GPIOA, GPIO_Pin_3, Bit_RESET);
    GPIO_WriteBit(GPIOA, GPIO_Pin_4, Bit_RESET);
    GPIO_WriteBit(GPIOA, GPIO_Pin_5, Bit_RESET);

    TASK_Append(TASK_ID_RYG, LED_Handler,250);
}

void LED_Handler(void)
{
    static uint8_t Index = 0;

    switch(Index)
    {
      case 0:
            GPIO_WriteBit(GPIOA, GPIO_Pin_3, Bit_RESET);
            GPIO_WriteBit(GPIOA, GPIO_Pin_4, Bit_RESET);
            GPIO_WriteBit(GPIOA, GPIO_Pin_5, Bit_RESET);
            break;

      case 1:
            GPIO_WriteBit(GPIOA, GPIO_Pin_3, Bit_SET);
            GPIO_WriteBit(GPIOA, GPIO_Pin_4, Bit_RESET);
            GPIO_WriteBit(GPIOA, GPIO_Pin_5, Bit_RESET);
            break;

      case 2:
            GPIO_WriteBit(GPIOA, GPIO_Pin_3, Bit_RESET);
            GPIO_WriteBit(GPIOA, GPIO_Pin_4, Bit_SET);
            GPIO_WriteBit(GPIOA, GPIO_Pin_5, Bit_RESET);
            break;

      case 3:
            GPIO_WriteBit(GPIOA, GPIO_Pin_3, Bit_RESET);
            GPIO_WriteBit(GPIOA, GPIO_Pin_4, Bit_RESET);
            GPIO_WriteBit(GPIOA, GPIO_Pin_5, Bit_SET);
            break;
    }

    Index = (Index + 1) % 4;
}
LED流水灯实验效果
PWM方式调节LED灯显示亮度上面我们是通过GPIO端口引脚直接输出高或者低电平来驱动LED灯点亮或熄灭的,在GPIO输出电平后一直维持着输出电平的状态,我们可以理解为这个时候输出占空比是100%或者0%;LED灯一直处于最亮或者熄灭的状态;而PWM方式则是通过固定的一个频率,通过修改其占空比(高电平与低电平在一个周期内占用时间的比例)可以来控制LED灯的显示亮度,可以直观的理解之前是100%供电的,LED灯则是最亮的,PWM方式是断断续续的供电,这个时候LED灯显示就弱了,亮弱则是由PWM占空比来决定的。具体的初始化及功能实现代码如下所示:void LED_Init(void)
{
    GPIO_InitTypeDef      GPIO_InitStructure;
    TIM_OCInitTypeDef       TIM_OCInitStructure;
    TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure;

    RCC_APB1PeriphClockCmd(RCC_APB1ENR_TIM2, ENABLE);

    TIM_TimeBaseStructInit(&TIM_TimeBaseStructure);
    TIM_TimeBaseStructure.TIM_Prescaler         = 0;
    TIM_TimeBaseStructure.TIM_CounterMode       = TIM_CounterMode_Up;
    TIM_TimeBaseStructure.TIM_Period            = 1000 - 1;
    TIM_TimeBaseStructure.TIM_ClockDivision   = TIM_CKD_DIV1;
    TIM_TimeBaseStructure.TIM_RepetitionCounter = 0;
    TIM_TimeBaseInit(TIM2, &TIM_TimeBaseStructure);

    TIM_OCStructInit(&TIM_OCInitStructure);
    TIM_OCInitStructure.TIM_OCMode          = TIM_OCMode_PWM1;
    TIM_OCInitStructure.TIM_OutputState   = TIM_OutputState_Enable;
    TIM_OCInitStructure.TIM_Pulse         = 500 - 1;
    TIM_OCInitStructure.TIM_OCPolarity      = TIM_OCPolarity_High;
    TIM_OCInitStructure.TIM_OCIdleState   = TIM_OCIdleState_Reset;

    TIM_OC4Init(TIM2, &TIM_OCInitStructure);
    TIM_OC4PreloadConfig(TIM2, TIM_OCPreload_Enable);

    TIM_ARRPreloadConfig(TIM2, ENABLE);
    TIM_Cmd(TIM2, ENABLE);

    TIM_CtrlPWMOutputs(TIM2, ENABLE);

    RCC_AHBPeriphClockCmd(RCC_AHBENR_GPIOA, ENABLE);

    GPIO_PinAFConfig(GPIOA, GPIO_PinSource3, GPIO_AF_2);    /* TIM2_CH4*/

    GPIO_StructInit(&GPIO_InitStructure);
    GPIO_InitStructure.GPIO_Pin   = GPIO_Pin_3;
    GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
    GPIO_InitStructure.GPIO_Mode= GPIO_Mode_AF_PP;
    GPIO_Init(GPIOA, &GPIO_InitStructure);

    TASK_Append(TASK_ID_RYG, LED_Handler, 10);
}


void LED_Handler(void)
{
    static uint16_t Value = 0, State = 0;

    TIM_SetCompare4(TIM2, Value);

    if(State == 0)
    {
      Value += 10;

      if(Value >= 1000)
      {
            Value = 990;
            State = 1;
      }
    }
    else
    {
      if(Value > 10)
      {
            Value -= 10;
      }
      else
      {
            Value = 0;
            State = 0;
      }
    }
}
PWM方式调节LED灯显示亮度效果
对数方式实现呼吸灯在上面通过直接修改PWM固定步长的占空比来实现的呼吸灯实验中,我们发现在占空比由小往大变化时,占空比在1%到45%左右时,看到的变化最明显,但再往上变化时已经基本上看不出什么变化了,效果不是很好,甚至可以说没有达到呼吸灯应有的效果;
那么PWM占空比应该如何来控制才能够达到视觉感观上呼吸亮度的效果呢?通过尝试,或者有经验的网友已经知道了,通过对数的关系来调节PWM的占空比,呼吸灯的效果最佳;具体怎么操作呢?
在下面的示例程序中,我们将PWM等分成1000份(0~999),在这1000份当中,我们设定有100个亮度级别,那最亮的那个级别100和PWM的关系式为100 = x*log10(999),x为一个固定的常数,是需要我们求出来的:x = 100 / log10(99);在计算出这个x之后,我们再将100这个等级替换成0~99,结合刚刚的常数x,求出log10(n)这个数值,再通过反LOG的计算方式求出n的数值:如果 m = log10(n) ,那么则有 n = pow(10, m);这个n就是我们应该设定的PWM占空比值了;具体的函数计算过程如下所示:void LED_Init(void)
{
    GPIO_InitTypeDef      GPIO_InitStructure;
    TIM_OCInitTypeDef       TIM_OCInitStructure;
    TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure;

    RCC_APB1PeriphClockCmd(RCC_APB1ENR_TIM2, ENABLE);

    TIM_TimeBaseStructInit(&TIM_TimeBaseStructure);
    TIM_TimeBaseStructure.TIM_Prescaler         = 0;
    TIM_TimeBaseStructure.TIM_CounterMode       = TIM_CounterMode_Up;
    TIM_TimeBaseStructure.TIM_Period            = 1000 - 1;
    TIM_TimeBaseStructure.TIM_ClockDivision   = TIM_CKD_DIV1;
    TIM_TimeBaseStructure.TIM_RepetitionCounter = 0;
    TIM_TimeBaseInit(TIM2, &TIM_TimeBaseStructure);

    TIM_OCStructInit(&TIM_OCInitStructure);
    TIM_OCInitStructure.TIM_OCMode          = TIM_OCMode_PWM1;
    TIM_OCInitStructure.TIM_OutputState   = TIM_OutputState_Enable;
    TIM_OCInitStructure.TIM_Pulse         = 500 - 1;
    TIM_OCInitStructure.TIM_OCPolarity      = TIM_OCPolarity_High;
    TIM_OCInitStructure.TIM_OCIdleState   = TIM_OCIdleState_Reset;

    TIM_OC4Init(TIM2, &TIM_OCInitStructure);
    TIM_OC4PreloadConfig(TIM2, TIM_OCPreload_Enable);

    TIM_ARRPreloadConfig(TIM2, ENABLE);
    TIM_Cmd(TIM2, ENABLE);

    TIM_CtrlPWMOutputs(TIM2, ENABLE);

    RCC_AHBPeriphClockCmd(RCC_AHBENR_GPIOA, ENABLE);

    GPIO_PinAFConfig(GPIOA, GPIO_PinSource3, GPIO_AF_2);    /* TIM2_CH4*/

    GPIO_StructInit(&GPIO_InitStructure);
    GPIO_InitStructure.GPIO_Pin   = GPIO_Pin_3;
    GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
    GPIO_InitStructure.GPIO_Mode= GPIO_Mode_AF_PP;
    GPIO_Init(GPIOA, &GPIO_InitStructure);

    TASK_Append(TASK_ID_RYG, LED_Handler, 50);
}

double LED_GetLOG(double level, double max)
{
    double a = log10(999);

    printf("\r\n%d(a) = %f", (uint32_t)level, a);

    double b = level / (max / a);

    printf("\r\n%d(b) = %f", (uint32_t)level, b);

    double c = pow(10, b);

    printf("\r\n%d(c) = %f", (uint32_t)level, c);

    return c;
}

void LED_Handler(void)
{
    static doubleLevel = 1.0, MAX = 50.0;
    static uint8_t State = 0;

    if(State == 0)
    {
      if(Level >= MAX)
      {
            Level = MAX;
            State = 1;
      }
      else
      {
            Level++;
      }
    }
    else
    {
      if(Level <= 1)
      {
            Level = 1;
            State = 0;
      }
      else
      {
            Level--;
      }
    }

    TIM_SetCompare4(TIM2, (uint32_t)LED_GetLOG(Level, MAX));
}
对数方式实现呼吸灯效果效果

xld0932 发表于 2022-4-4 10:02

对于实现LED呼吸灯效果,对数的方式效果相当不错,有兴趣的小伙伴可以尝试一下哈

foxsbig 发表于 2022-4-8 15:44

现在是不是有新活动了?

xld0932 发表于 2022-4-8 16:38

foxsbig 发表于 2022-4-8 15:44
现在是不是有新活动了?

什么活动?

1908278102 发表于 2022-6-7 15:20

真好,这篇讲得很通透,好用

xld0932 发表于 2022-6-7 16:22

1908278102 发表于 2022-6-7 15:20
真好,这篇讲得很通透,好用

sainuo598 发表于 2022-6-9 14:18

感谢分享
页: [1]
查看完整版本: 【MM32+模块】系列:02、LED灯控制