GD32开发实战指南(基础篇) 第9章 呼吸灯

发布者:EtherealEssence最新更新时间:2024-11-08 来源: elecfans关键字:GD32  开发实战  呼吸灯 手机看文章 扫描二维码
随时随地手机看文章

开发环境:


MDK:Keil 5.30


开发板:GD32F207I-EVAL


MCU:GD32F207IK


1 呼吸灯的工作原理

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


要使用数字器件控制灯光的强弱,我们很自然就想到 PWM(脉冲宽度调制)技术。假如以LED 作为灯光设备,且由控制器输出的 PWM 信号可以直接驱动 LED,PWM 信号中的低电平可点亮 LED 灯。当 LED 以较高的频率进行开关(亮灭)切换时,由于视觉暂留效应,人眼是看不到 LED 灯的闪烁现象的,反映到人眼中能感觉到的是亮度的差别。即以一定的时间长度为周期,LED 灯亮的平均时间越长,亮度就越高,反之越暗。因此,我们可以使用高频率的 PWM 信号,通过调制信号的占空比,控制 LED 灯的亮度。


那么具体我们应该控制 LED 灯以怎样的亮度曲线变化能够达到最好的效果呢?亮度随着时间逐渐变强再衰减,可以用两种常见的数学函数表示,分别是半个周期的正弦函数与指数上升曲线及其对称得到的下降曲线。

1683894862773yoqqladc47

相对来说,使用下凹函数曲线灯光处于暗的状态更长,所以指数函数的曲线更符合我们呼吸灯的亮度变化要求。


2 呼吸灯实现

2.1 简单方式

笔者先用最简单的方式来实现,也就是定时改变比较寄存器的值。


1.初始化 GPIO


下面分析具体的定时器配置代码。本实验使用 PB0 作为定时器 PWM 输出通道,先对它进行初始化。作 PWM 输出通道的引脚需要被配置为复用推挽输出模式。


/*

    brief      configure PWM GPIO

    param[in]  none

    param[out] none

    retval     none

*/

static void timer_gpio_init(void)

{

    rcu_periph_clock_enable(RCU_GPIOB);

    rcu_periph_clock_enable(RCU_AF);


    /* Configure PB0 (TIMER2 CH2) as alternate function */

    gpio_init(GPIOB, GPIO_MODE_AF_PP, GPIO_OSPEED_50MHZ, GPIO_PIN_0);

}

2.配置定时器模式


在timer2_init()函数中,完成了呼吸灯所需要的定时器 PWM 输出模式配置。


/*

    brief      configure the Breath LED peripheral

    param[in]  none

    param[out] none

    retval     none

  */

void breath_led_init(void)

{

    /* TIMER2 configuration: generate PWM signals with different duty cycles:

       TIMER2CLK = SystemCoreClock / 120 = 1MHz */

    timer_oc_parameter_struct timer_ocintpara;

    timer_parameter_struct timer_initpara;


    /* configure the GPIO ports */

    timer_gpio_init();


    rcu_periph_clock_enable(RCU_TIMER2);


    timer_deinit(TIMER2);


    /* TIMER1 configuration */

    timer_initpara.prescaler         = 119;

    timer_initpara.alignedmode       = TIMER_COUNTER_EDGE;

    timer_initpara.counterdirection  = TIMER_COUNTER_UP;

    timer_initpara.period            = 250;

    timer_initpara.clockdivision     = TIMER_CKDIV_DIV1;

    timer_initpara.repetitioncounter = 0;

    timer_init(TIMER2, &timer_initpara);


    /* CH0 configuration in PWM mode 0 */

    timer_ocintpara.outputstate  = TIMER_CCX_ENABLE;

    timer_ocintpara.outputnstate = TIMER_CCXN_DISABLE;

    timer_ocintpara.ocpolarity   = TIMER_OC_POLARITY_HIGH;

    timer_ocintpara.ocnpolarity  = TIMER_OCN_POLARITY_HIGH;

    timer_ocintpara.ocidlestate  = TIMER_OC_IDLE_STATE_LOW;

    timer_ocintpara.ocnidlestate = TIMER_OCN_IDLE_STATE_LOW;


    timer_channel_output_config(TIMER2, TIMER_CH_2, &timer_ocintpara);


    /* CH0 configuration in PWM mode 0,duty cycle 25% */

    timer_channel_output_pulse_value_config(TIMER2, TIMER_CH_2, 0);

    timer_channel_output_mode_config(TIMER2, TIMER_CH_2, TIMER_OC_MODE_PWM0);

    timer_channel_output_shadow_config(TIMER2, TIMER_CH_2, TIMER_OC_SHADOW_DISABLE);


    /* auto-reload preload enable */

    timer_auto_reload_shadow_enable(TIMER2);

    /* TIMER2 enable */

    timer_enable(TIMER2);

}

这个定时器的模式配置主要分为三个部分,分别为时基初始化,输出模式初始化。


时基初始化

代码中前面的部分是定时器的时基初始化,这部分主要负责配置定时器的定时周期、时钟频率、计数方式等。它使用到库函数timer_init()函数,利用结构体timer_parameter_struct进行配置,该结构体有以下成员:


period

定时周期,实质是存储到重载寄存器CAR的数值,脉冲计数器从 0 累加到这个值上溢或从这个值自减至 0 下溢。这个数值加 1 然后乘以时钟源周期就是实际定时周期。


本实验中向该成员赋值为 255,即定时周期为(255+1)* T ,T 为定时器的时钟周期。


prescaler

对定时器时钟CLK 的预分频值,分频后作为脉冲计数器TIMERx_CNT的驱动时钟,得到脉冲计数器的时钟频率为:CNT=CLK/(N+1),其中 N 为即为赋给本成员的时钟分频值。


本实验给 prescaler 成员赋值为 119,即对时钟 120 分频,所以定时器的时钟周期 T 为 120/120000000。


clockdivision

时钟分频因子。怎么又出现一个配置时钟分频的呢?要注意这个clockdivision和上面的 prescaler 是不一样的。prescaler 预分频配置是对CLK进行分频,分频后的时钟被输出到脉冲计数器CNT。


本实验中是使用内部时钟CLK 作为定时器时钟源的,没有进行滤波所以配置clockdivision为任何数值都没有影响。


alignedmode

本成员配置的为脉冲计数器 CNT 的计数模式,分别为向上计数,向下计数,及中央对齐模式。向上计数即 CNT 从 0 向上累加到 period 中的值,(重载寄存器 CAR 的值),产生上溢事件;向下计数则 CNT 从period 的值累减至0,产生下溢事件。而中央对齐模式则为向上、向下计数的合体,CNT 从 0 累加到period 的值减 1 时,产生一个上溢事件,然后向下计数到 1 时,产生一个计数器下溢事件,再从 0 开始重新计数。


输出模式配置

在本函数代码的后面是关于定时器的输出模式配置的。通用定时器的输出模式由 timer_oc_parameter_struct类型结构体的主要有以下几个成员:


outputstate

配置输出模式的状态使能或关闭输出。


outputnstate

本成员的参数值即为比较寄存器 CH2CV的数值,当脉冲计数器CNT与CH2CV的比较结果发生变化时,输出脉冲将发生跳变。


ocpolarity

有效电平的极性,把 PWM 模式中的有效电平设置为高电平或低电平。


本实验中向该成员赋值为 TIMER_OC_POLARITY_LOW (有效电平为低电平),因为在上面把输出模式配置为 PWM0 模式,向上计数,所以在 CNT< CH0CV 时,通道 n 输出为低电平,否则为高电平。


ocnpolarity

用于比较有效电平的极性。


本实验中就是通过不断改变比较寄存器CH2CV的值,达到控制 PWM 信号的占空比呈指数曲线变化的目的。在本函数代码中,我们对该成员赋予初始为 0,而改变比较寄存器 CH0CV 值的操作是在中断服务函数中修改的。填充完输出模式初始化结构体后,调用输出模式初始化函数 timer_channel_output_config()对通道进行初始化。


以上是最基本的PWM输出调制实现呼吸灯。


笔者接下来还要讲解一下重映射的输出配置。在这里讲解的是通过重映射 TIMER2_CH2到 PB0 上,由 TIMER2_CH2 输出 PWM 来控制LED的亮度。下面我们介绍通过库函数来配置该功能的步骤。


1)开启 TIMER2时钟以及复用功能时钟,配置 PB0为复用输出。


要使用 TIMER2,我们必须先开启 TIMER2的时钟,这点相信大家看了这么多代码,应该明白了。这里我们还要配置 PB0为复用输出,此时,PB0属于复用功能输出。在此只列出库函数设置 AFIO 时钟的方法。


rcu_periph_clock_enable(RCU_AF);

其余的和前面的配置一样,就不再列出了。


2)初始化 TIMER2,设置 TIMER2的 CAR 和 PSC。


3)设置 TIMER2_CH2 的 PWM 模式,使能 TIMER2的 CH2 输出。


4)使能 TIMER2。


在完成以上设置了之后,我们需要使能 TIMER2。 使能 TIMER2的方法前面已经讲解过:


timer_enable(TIMER2);

5)修改 TIMER2_ CH2CV来控制占空比。


最后,在经过以上设置之后, PWM 其实已经开始输出了,只是其占空比和频率都是固定的,而我们通过修改 TIMER2_CH2CV则可以控制 CH2 的输出占空比。继而控制LED的亮度。在库函数中,修改 TIMER2_CH2CV占空比的函数是:


void timer_channel_output_pulse_value_config(uint32_t timer_periph, uint16_t channel, uint32_t pulse)

通过以上5个步骤,我们就可以控制 TIMER2的 CH2 输出 PWM 波了。


接下来看看主函数的代码:


/*

    brief      main function

    param[in]  none

    param[out] none

    retval     none

*/

int main(void)

{

    uint16_t i = 0;

    FlagStatus breathe_flag = SET;


    //systick init

    sysTick_init();


    /* configure the Breath LED peripheral */

    breath_led_init();


    while(1)

    {

        /* delay a time in milliseconds */

        delay_ms(5);

        if(SET == breathe_flag) 

        {

            i++;

        }

        else

        {

            i--;

        }

        if(250 < i)

        {

            breathe_flag = RESET;

        }

        if(0 >= i)

        {

            breathe_flag = SET;

        }

        /* configure TIMER channel output pulse value */

        //timer_channel_output_pulse_value_config(TIMER2, TIMER_CH_2, i);

        TIMER_CH2CV(TIMER2) = (uint32_t)i;

    }

}

代码很简单,就是不断改变CH2CV的值从而控制 CH2 的输出占空比。


2.2 中断方式

1.生成指数曲线 PWM 数据


要实现 LED 亮度随着指数曲线变化,我们需要使用占空比呈指数曲线变化的 PWM 信号,而这样的信号由定时器经过查表产生。这个表的数据存储在程序中的数组 indexWave中。


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};

这个表有 40 个数字,从图中可以看到这些数字呈指数上升再衰减,正好是呼吸灯的一个控制周期。数字的大小范围是 0255,即把 LED 的亮度分为了 0255 个等级。


假如我们把定时器的脉冲计数器 CNT 上限设置为 255,把这个表的数据一个一个地赋值到定时器的比较寄存器CH2CV中,那么在每个 PWM 周期中,当 CNT的计数值小于比较寄存器 CH2CV的值时, 就会在通道中输出低电平,点亮 LED,而随着 CCR 的值由 LED 亮度表得来,所以 LED 点亮的时间就会呈图中的曲线变化,实现呼吸灯的功能。


这个表的数据是使用 matlab 软件生成的。该代码运行后会生成一个“index_wave.c”的文件,用户把该文件中的数据复制到工程中的数组中即可。


%本代码用于产生呼吸灯使用的指数函数数据

clear;


x = [0 : 8/19 : 8];       %设置序列 ,指数上升

up = 2.^x ;               %求上升指数序列  

up = uint8(up);           %化为8位数据


y = [8: -8/19 :0];       %设置序列 ,指数下降

down = 2.^y ;            %求下降指数序列

down = uint8(down);      %化为8位数据


line = [[0:8/19:8],[8:8/19:16]]         %拼接序列

val = [up , down]                       %拼接输出序列


dlmwrite('index_wave.c',val);       %输出到文件index_wave.c

plot(line,val,'.');                 %显示波形图

2.初始化 GPIO


这部分和前面的一样,没啥好说的。


3.配置定时器模式


这里也差不多,只是将分频系数设置的稍微大些,另外开启了中断。


/*

    brief      configure the Breath LED peripheral

    param[in]  none

    param[out] none

    retval     none

  */

void breath_led_init(void)

{

    /* TIMER2 configuration: generate PWM signals with different duty cycles*/

    timer_oc_parameter_struct timer_ocintpara;

    timer_parameter_struct timer_initpara;


    /* configure the GPIO ports */

    timer_gpio_init();


    rcu_periph_clock_enable(RCU_TIMER2);


    timer_deinit(TIMER2);


    /* TIMER2 configuration */

    timer_initpara.prescaler         = 3999;

    timer_initpara.alignedmode       = TIMER_COUNTER_EDGE;

    timer_initpara.counterdirection  = TIMER_COUNTER_UP;

    timer_initpara.period            = 255;

    timer_initpara.clockdivision     = TIMER_CKDIV_DIV1;

    timer_initpara.repetitioncounter = 0;

    timer_init(TIMER2, &timer_initpara);


    /* CH2 configuration in PWM mode 0 */

    timer_ocintpara.outputstate  = TIMER_CCX_ENABLE;

    timer_ocintpara.outputnstate = TIMER_CCXN_DISABLE;

    timer_ocintpara.ocpolarity   = TIMER_OC_POLARITY_HIGH;

    timer_ocintpara.ocnpolarity  = TIMER_OCN_POLARITY_HIGH;

    timer_ocintpara.ocidlestate  = TIMER_OC_IDLE_STATE_LOW;

    timer_ocintpara.ocnidlestate = TIMER_OCN_IDLE_STATE_LOW;

[1] [2]
关键字:GD32  开发实战  呼吸灯 引用地址:GD32开发实战指南(基础篇) 第9章 呼吸灯

上一篇:GD32 MCU如何使用双ADC内核提高ADC采样率?
下一篇:GD32开发实战指南(基础篇) 第10章 串口通信

推荐阅读最新更新时间:2026-03-22 12:09

GD32开发实战指南(基础篇) 第9章 呼吸灯
开发环境: MDK:Keil 5.30 开发板:GD32F207I-EVAL MCU:GD32F207IK 1 呼吸灯的工作原理 呼吸灯,就是指灯光设备的亮度随着时间由暗到亮逐渐增强,再由亮到暗逐渐衰减,很有节奏感地一起一伏,就像是在呼吸一样,因而被广泛应用于手机、电脑等电子设备的指示灯中。 要使用数字器件控制灯光的强弱,我们很自然就想到 PWM(脉冲宽度调制)技术。假如以LED 作为灯光设备,且由控制器输出的 PWM 信号可以直接驱动 LED,PWM 信号中的低电平可点亮 LED 灯。当 LED 以较高的频率进行开关(亮灭)切换时,由于视觉暂留效应,人眼是看不到 LED 灯的闪烁现象的,反映到人眼中能感觉到的
[单片机]
GD32开发实战指南(基础篇) 第2章 初始GPIO流水灯
开发环境: MDK:Keil 5.30 开发板:GD32F207I-EVAL MCU:GD32F207IK 1 GPIO工作原理 熟悉单片机的朋友都知道,学习的第一个例程就是流水灯,要想实现流水灯,首先必须了解GPIO的工作原理。GPIO的基本结构如下图所示。 GD32 的 IO 口可以由软件配置成如下 8 种模式: 输入模式 浮空输入:浮空(floating)就是逻辑器件的输入引脚即不接高电平,也不接低电平。由于逻辑器件的内部结构,当它输入引脚悬空时,相当于该引脚接了高电平。一般实际运用时,引脚不建议悬空,易受干扰。通俗讲就是让管脚什么都不接,浮空着。信号进入芯片内部后,既没有接上拉电阻也没有接下拉电阻,经由触发器输
[单片机]
GD32开发实战指南(基础篇) 第14章 内部温度传感器
开发环境: MDK:Keil 5.30 开发板:GD32F207I-EVAL MCU:GD32F207IK 1 内部温度传感器工作原理 GD32 有一个内部的温度传感器,可以用来测量 CPU 及周围的温度(TA)。该温度传感器在内部和 ADCx_IN16 输入通道相连接,此通道把传感器输出的电压转换成数字值。温度传感器模拟输入推荐采样时间是 17.1μs。GD32 的内部温度传感器支持的温度范围为: -40~125度。精度比较差,为±1.5℃左右。 GD32 内部温度传感器的使用很简单,只要设置一下内部 ADC,并激活其内部通道就差不多了。关于 ADC 的设置,我们在前面的章节已经进行了详细的介绍,这里就不再多说。接下来我们介
[单片机]
GD32开发实战指南(基础篇) 第20章 GD32的存储结构
开发环境: MDK:Keil 5.30 开发板:GD32F207I-EVAL MCU:GD32F207IK 1 GD32存储结构的工作原理 1.1 Cortex-M内核的存储器映射 存储器映射是指把芯片中或芯片外的FLASH,RAM,外设,BOOTBLOCK等进行统一编址。即用地址来表示对象。这个地址绝大多数是由厂家规定好的,用户只能用而不能改。用户只能在挂外部RAM或FLASH的情况下可进行自定义。 如下图,是Cortex-M3存储器映射结构图。 Cortex-M3是32位的内核,因此其PC指针可以指向2^32=4G的地址空间,也就是0x0000_0000 - 0xFFFF_FFFF这一大块空间。根据图中描述,Corte
[单片机]
嵌入式C开发环境构建指南:工具链配置、Makefile编写与调试实战
许多学习者在入门时,首要步骤便因环境配置而受阻。 “安装不上、CubeIDE卡死、make命令找不到、下载不进芯片”——这些问题往往比代码更容易劝退人。 实际上,搭建环境这件事看似“配置”,但它是每个嵌入式工程师的入门仪式。你能否顺利跑通第一个程序,决定了你之后能不能真正理解底层逻辑。 今天我们就从最基础出发,完整走一遍环境的构建流程,从编译、链接、烧录到调试,讲清楚编译器、Makefile和调试工具之间到底在做什么。 一、为什么环境总是“装不对” 很多人第一次装环境时的感受是:教程很多,但都不一样。 有的说要装Keil,有的让你用STM32CubeIDE,有的又推荐VS Code+Makefile。 问题出在大
[嵌入式]
助力V2G,SECC GreenPHY实战开发
随着电动汽车与电网双向交互(V2G)技术的快速发展,充电桩与车辆间的高效通信成为实现智能能源管理的关键。 SECC作为充电桩的通信控制核心,其与电力线载波通信芯片的适配尤为重要。 本文将分享基于米尔核心板,调试联芯通MSE102x GreenPHY芯片的实战经验,为V2G通信开发提供参考。 MSE102x芯片介绍 联芯通MSE102x系列芯片是一款专注于电动汽车充电通信和智能能源管理的GreenPHY电力线载波通信芯片,MSE102x支持RMII和SPI两种主机接口,可根据具体应用场景灵活选择。本文主要介绍如何基于RMII和SPI两种不同的接口方式来驱动MSE102x。 MSE102x系统框图 方案一:
[汽车电子]
助力V2G,SECC GreenPHY<font color='red'>实战</font><font color='red'>开发</font>
MYD-LD25X Cortex-M33实时核开发实战解析
在嵌入式系统设计中,如何平衡高性能计算与实时控制一直是工程师面临的挑战。STM32MP257的异构架构为这一难题提供了优雅的解决方案,而其中的Cortex-M33实时核更是实现硬实时性能的关键所在。 一、异构架构:分工明确,效能卓越 STM32MP257采用创新的双核子系统设计: Cortex-A35应用核(双核1.5GHz):运行Linux系统,负责复杂UI、网络通信、文件管理等非实时任务。 Cortex-M33实时核(400MHz):专攻实时控制,具备纳秒级中断响应,集成FPU和DSP指令集。 二、架构优势凸显: 硬件级资源隔离:通过RIF单元确保M33核独占关键外设,避免核间冲突 能效精细控制:支持独
[嵌入式]
MYD-LD25X Cortex-M33实时核<font color='red'>开发</font><font color='red'>实战</font>解析
基于STM32F103精灵开发板点亮LED灯实战教程:以PA0为例
一、引言 在嵌入式开发领域,STM32系列单片机凭借其强大的性能和丰富的外设深受开发者喜爱。普中STM32 – F103 – 精灵开发板是初学者入门STM32开发的优质选择。点亮LED是STM32开发中最基础的实验之一,通过这个实验,我们可以熟悉开发板的GPIO(通用输入输出)功能,为后续更复杂的项目开发奠定基础。本文将详细介绍如何在上电后点亮连接在PA0引脚上的LED。 二、硬件连接原理 在普中STM32 – F103 – 精灵开发板上,LED的点亮原理基于GPIO端口的电平控制。一般来说,LED的阳极连接到开发板的电源(如3.3V),阴极通过限流电阻连接到STM32的GPIO引脚(这里是PA0) 。当PA0引脚输出低电平时
[单片机]
基于STM32F103精灵<font color='red'>开发</font>板点亮LED灯<font color='red'>实战</font>教程:以PA0为例
小广播
最新单片机文章
何立民专栏 单片机及嵌入式宝典

北京航空航天大学教授,20余年来致力于单片机与嵌入式系统推广工作。

厂商技术中心

 
EEWorld订阅号

 
EEWorld服务号

 
汽车开发圈

 
机器人开发圈

电子工程世界版权所有 京ICP证060456号 京ICP备10001474号-1 电信业务审批[2006]字第258号函 京公网安备 11010802033920号 Copyright © 2005-2026 EEWORLD.com.cn, Inc. All rights reserved