发新帖本帖赏金 50.00元(功能说明)我要提问
返回列表
[MM32生态]

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

[复制链接]
7228|6
手机看帖
扫描二维码
随时随地手机跟帖
xld0932|  楼主 | 2022-3-25 18:28 | 显示全部楼层 |阅读模式
本帖最后由 xld0932 于 2022-3-25 19:00 编辑

#申请原创#   @21小跑堂

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

LED控制.png

在如上原理图中,串联的1k电阻是用于限制电流大小的;对于方式1MCU_PIN输出高电平时,LED处于熄灭的状态,输出低电平时,LED则处于点亮的状态;对于方式2中的MCU_PIN输出高电平时,LED处于点亮的状态,输出低电平时,LED则处于熄灭的状态;对于其它的方式,则需要根据实际电路设计来进行高低电平的输出控制了。

在淘宝上买了一个LED模块,带有红黄绿3LED灯;结合MM32F0140的核心板,我们今天实现LED的几个实验:
1LED闪烁实验
2LED流水灯实验
3PWM方式调节LED灯显示亮度
4、对数方式实现呼吸灯
LED模块.png

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闪烁实验效果
0325_1.gif

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流水灯实验效果
0325_2.gif

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灯显示亮度效果
0325_3.gif

对数方式实现呼吸灯
在上面通过直接修改PWM固定步长的占空比来实现的呼吸灯实验中,我们发现在占空比由小往大变化时,占空比在1%45%左右时,看到的变化最明显,但再往上变化时已经基本上看不出什么变化了,效果不是很好,甚至可以说没有达到呼吸灯应有的效果;

那么PWM占空比应该如何来控制才能够达到视觉感观上呼吸亮度的效果呢?通过尝试,或者有经验的网友已经知道了,通过对数的关系来调节PWM的占空比,呼吸灯的效果最佳;具体怎么操作呢?

在下面的示例程序中,我们将PWM等分成1000份(0~999),在这1000份当中,我们设定有100个亮度级别,那最亮的那个级别100PWM的关系式为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 double  Level = 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));
}

对数方式实现呼吸灯效果效果
0325_4.gif


使用特权

评论回复

打赏榜单

21小跑堂 打赏了 50.00 元 2022-03-29
理由:恭喜通过原创文章审核!请多多加油哦!

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 | 显示全部楼层
感谢分享

使用特权

评论回复
发新帖 本帖赏金 50.00元(功能说明)我要提问
您需要登录后才可以回帖 登录 | 注册

本版积分规则

认证:上海灵动微电子股份有限公司资深现场应用工程师
简介:诚信·承诺·创新·合作

67

主题

2992

帖子

29

粉丝