GD32F103系列学习

一、基本流程

systick_config();                                                   // 系统时钟配置

 rcu_periph_clock_enable(RCU_GPIOC);                                 // 使能LED对应的GPIO时钟
 gpio_init(GPIOC, GPIO_MODE_OUT_PP, GPIO_OSPEED_50MHZ, GPIO_PIN_13); // 配置LED灯对应的端口和引脚
 gpio_bit_reset(GPIOC, GPIO_PIN_13);                                 // 重置引脚

 while(1){
 gpio_bit_set(GPIOC, GPIO_PIN_13);                                 // PC13引脚拉到高电平,打开灯
 delay_1ms(500);                                                   // 延时500ms
 gpio_bit_reset(GPIOC, GPIO_PIN_13);                               // PC13引脚拉到低电平,关闭灯
 delay_1ms(500);                                                   // 延时500ms
 }

拿LED闪烁这个例子来说,基本流程是先进行系统时钟初始化systick_config()。然后根据我们业务需要,去使能和配置对应的外设即可。

像这个例子中就是先打开GPIOC口。因为我们要使用PC13这个GPIO口。然后初始化PC13的引脚,配置为输出推挽模式,频率设置为50MHz。最后复位PC13引脚。最后在死循环中让PC13每隔500ms电平翻转一次即可。

二、时钟

GD32F10X系列的系统时钟(CK_SYS)有三个可选的时钟源。一个内部8MRC振荡器时钟(IRC8M)、一个外部高速晶体振荡器(HXTAL)、一个锁相环(PLL)。

系统时钟的最大运行频率可以达到108MHz。

image-20230412142545797.png

系统总线分为三条。分别是高速总线AHB,两条低速总线APB1和APB2。APB2最高可以达到108MHz,APB1最高可以达到54MHz。外设上的时钟是根据所在总线的频率倍频或者分频得到。

目前有这么几个时钟。三个时钟源CK_PLL,CK_IRC8M,CK_HXTAL,系统时钟CK_SYS,高速总线时钟CK_AHB,低速总线时钟1(CK_APB1),低速总线时钟2(CK_APB2)。剩下的就是外设时钟,包括ADC时钟(CK_ADCx),定时器时钟(CK_TIMERx)等。

这几个时钟的转换关系可以通过观察对应寄存器的值来算出。

2.1 时钟源

首先三个时钟源对应的频率是。

2.1.1 CK_IRC8M

这个就是8MHz。

2.1.2 CK_PLL

这个通过观察时钟树,由CK_IRC8M先进行二分频,然后经过PLLMF倍频得到。只要知道倍频是多少,即可算出。这儿的倍频是需要看时钟配置寄存器 0(RCU_CFG0)的PLLMF[3:0]位来得到。

image-20230412143927252.png

我查看了板子的寄存器值是11010。查询手册可知这是PLL源时钟 x 27得到。所以

CK_PLL = 8MHz / 2 x 27 = 108MHz

image-20230412144136664.png
2.1.3 CK_HXTAL

4到16M的外部高速晶体振荡器时钟,这个由外界输入决定。外部的输入时钟是多少就是多少。

HXTAL晶体振荡器可以通过设置控制寄存器RCU_CTL的HXTALEN位来启动或关闭,在控制寄存器RCU_CTL中的HXTALSTB位用来指示外部高速振荡器是否已

稳定。在启动时,直到这一位被硬件置‘1’,时钟才被释放出来。这个特定的延迟时间被称为振荡器的启动时间。当HXTAL时钟稳定后,如果在中断寄存器RCU_INT

中的相应中断使能位HXTALSTBIE位被置‘1’,将会产生相应中断。此时,HXTAL时钟可以被直接用作系统时钟源或者PLL输入时钟。

将控制寄存器RCU_CTL的HXTALBPS和HXTAKEN位置‘1’来使能外部旁路模式。旁路输入时,信号接至OSCIN,OSCOUT保持悬空状态,如下图所示。此时,

CK_HXTAL等于驱动OSCIN管脚的外部时钟。

image-20230412145428630.png

2.2 系统时钟CK_SYS

系统时钟CK_SYS可选择的有上面2.1的三个时钟源。系统复位后,IRC8M时钟默认做为CK_SYS的时钟源,改变配置寄存器0(RCU_CFG0)中的系统时钟变换

位SCS可以切换系统时钟源为HXTAL或CK_PLL。当SCS的值被改变,系统时钟将使用原来的时钟源继续运行直到转换的目标时钟源稳定。当一个时钟源被直接或通

过PLL间接作为系统时钟时,它将不能被停止。

image-20230412150005094.png

查看寄存器得到SCS的值10。所以系统时钟选择的是CK_PLL。也就是108M。

image-20230412150124736.png

此时已经确定系统时钟是108MHz。

2.3 总线时钟

通过观察时钟树我们知道。高速总线AHB上的时钟由系统时钟CK_SYS分频得到,低速总线APB1和APB2上的时钟由CK_AHB分频得到。

2.3.1 CK_AHB

CK_AHB由CK_SYS分频得到,预分频因子查看时钟配置寄存器0(RCU_CFG0)的AHBPSC[3:0]位可获取。最大分频为512。

image-20230412151454903.png

查看对应寄存器结果是0x0,此时代表不分频。所以我自己板子的

CK_AHB=CK_SYS=108MHz

image-20230412151540960.png
2.3.2 CK_APB1

CK_APB1由CK_AHB分频得到。预分频因子查看时钟配置寄存器0(RCU_CFG0)的APB1PSC[2:0]位可获取。最大分频为16。

image-20230412152442607.png

查看自己寄存器的结果是0x4,对应100,代表选择CK_AHB时钟2分频。所以

CK_APB1 = CK_AHB / 2 = 108MHz / 2 = 54MHz

image-20230412152545699.png
2.3.3 CK_APB2

CK_APB1由CK_AHB分频得到。同CK_APB1。预分频因子查看时钟配置寄存器0(RCU_CFG0)的APB2PSC[2:0]位可获取。最大分频为16。

image-20230412152820793.png

查看自己板子的寄存器的值为0x0,所以是选择CK_AHB时钟不分频。

CK_APB2 = CK_AHB = 108MHz

image-20230412152915961.png

2.4 定时器时钟CK_TIMERx

定时器外设都是挂靠在低速总线上的,多个定时器挂靠的低速总线不一样。观察中密度GD32F10X系列结构。可以看出TIMER0定时器挂靠在APB2上,TIMER1~3挂在APB1上。

image-20230412153318827.png

针对TIMER2来分析。

image-20230412153509281.png

CK_TIMER2时钟是根据CK_APB1来得到的。判断APB1的预分频因子,如果是1,那么CK_TIMERx = CK_APB1 x 1;如果不是1,那么CK_TIMERx = CK_APB1 x 2。

我们从上面知道APB1的预分频因子是2。所以CK_TIMERx的时钟就是2倍的CK_APB1。

CK_TIMERx = CK_APB1 x 2 = 54MHz x 2 = 108MHz

所以TIMER2定时器的时钟就是108MHz。

三、例程

3.1、灯运行

main.c

#include "gd32f10x.h"
#include "gd32f103b_start.h"
#include "systick.h"

/**
* LED2每隔1s闪烁一次
* PC13引脚是LED2的关联引脚
*/
int main(void)
{
 systick_config();                                                   // 系统时钟配置

 rcu_periph_clock_enable(RCU_GPIOC);                                 // 使能LED对应的GPIO时钟
 gpio_init(GPIOC, GPIO_MODE_OUT_PP, GPIO_OSPEED_50MHZ, GPIO_PIN_13); // 配置LED灯对应的端口和引脚
 gpio_bit_reset(GPIOC, GPIO_PIN_13);                                 // 重置引脚

 while(1){
 gpio_bit_set(GPIOC, GPIO_PIN_13);                                 // PC13引脚拉到高电平,打开灯
 delay_1ms(500);                                                   // 延时500ms
 gpio_bit_reset(GPIOC, GPIO_PIN_13);                               // PC13引脚拉到低电平,关闭灯
 delay_1ms(500);                                                   // 延时500ms
 }
}

上面main函数中使用的函数都是调用的系统函数或者外设函数。

重点:系统时钟初始化的本质

void systick_config(void)
{
 /* setup systick timer for 1000Hz interrupts */
 if(SysTick_Config(SystemCoreClock / 1000U)){
 /* capture error */
 while(1){
 }
 }
 /* configure the systick handler priority */
 NVIC_SetPriority(SysTick_IRQn, 0x00U);
}
image-20230412160749582.png

系统时钟初始化其实就是将我们选定的时钟108M分到我们自己需要的系统定时器,这儿我们需要1KHz。所以需要在这个函数中除以1000即可。系统时钟SystemCoreClock = 108M。

然后我们查看中断源文件gd32f10x_it.c

/*!
 \brief      this function handles SysTick exception
 \param[in]  none
 \param[out] none
 \retval     none
*/
void SysTick_Handler(void)
{
 delay_decrement();
}

delay_decrement()

void delay_decrement(void)
{
 if(0U != delay){
 delay--;
 }
}

系统定时中断,这儿的中断程序执行按频率衰减。一个周期减一,这样就能实现延时函数delay_1ms()

3.2、PMW模式输出方波

main.c

#include "gd32f10x.h"
#include <stdio.h>
#include "gd32f10x_eval.h"

void gpio_config(void);
void timer_config(void);

/**
* 利用定时器2的通道2(TIMER2_CH2)输出方波。波形频率为1KHz。 
*/
int main(void)
{
 gpio_config();     // GPIO配置
 timer_config();    // 定时器TIMER配置
 while (1);
}

/**
* GPIO配置,并且初始化GPIO的复用功能。
*/
void gpio_config(void)
{
 rcu_periph_clock_enable(RCU_GPIOB);                              // 配置GPIOB的时钟
 rcu_periph_clock_enable(RCU_AF);                                 // 配置GPIO的AF复用功能,因为这个引脚要作为定时器的通道输出。
 gpio_init(GPIOB, GPIO_MODE_AF_PP, GPIO_OSPEED_50MHZ, GPIO_PIN_0);// 初始化PB0的引脚,AF复用功能的推挽模式
}

/**
* 定时器的配置
*/
void timer_config(void)
{
 /* -----------------------------------------------------------------------
 TIMER2 配置: 以50的占空比生成一个pwm信号:
 CK_TIMER2时钟 = CK_APB1 / 预分频因子  = 108MHz / 108 = 1MHz
 TIMER2 通道2的占空比 = (500/ 1000)* 100  = 50%
 ----------------------------------------------------------------------- */
 timer_oc_parameter_struct timer_ocintpara;
 timer_parameter_struct timer_initpara;

 rcu_periph_clock_enable(RCU_TIMER2);    // 使能TIMER2定时器的时钟

 timer_deinit(TIMER2);                   // 复位TIMER2

 /* TIMER2 配置 */
 timer_initpara.prescaler         = 108-1;                 // 预分频因子。0~65535
 timer_initpara.alignedmode       = TIMER_COUNTER_EDGE;    // 边沿计数对齐
 timer_initpara.counterdirection  = TIMER_COUNTER_UP;      // 上升沿触发
 timer_initpara.period            = 1000-1;                // 周期是100
 timer_initpara.clockdivision     = TIMER_CKDIV_DIV1;      // 时钟分频值
 timer_initpara.repetitioncounter = 0;                     // 重复计数为0
 timer_init(TIMER2,&timer_initpara);                       // 初始化TIMER2

 /* PWM模式下的通道2配置 */
 timer_ocintpara.ocpolarity   = TIMER_OC_POLARITY_HIGH;     // 通道输出极性高
 timer_ocintpara.outputstate  = TIMER_CCX_ENABLE;           // 通道输出状态使能
 timer_ocintpara.ocnpolarity  = TIMER_OCN_POLARITY_HIGH;    // 通道互补输出极性高
 timer_ocintpara.outputnstate = TIMER_CCXN_DISABLE;         // 通道互补输出状态不使能
 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);              // 定时器通道输出配置

 timer_channel_output_pulse_value_config(TIMER2,TIMER_CH_2,499);               // 输出脉冲值为500
 timer_channel_output_mode_config(TIMER2,TIMER_CH_2,TIMER_OC_MODE_PWM0);       // 通道输出模式为PWM0
 timer_channel_output_shadow_config(TIMER2,TIMER_CH_2,TIMER_OC_SHADOW_DISABLE);// 通道输出影子不使能

 timer_auto_reload_shadow_enable(TIMER2);                       // 启用自动重新加载阴影功能
 timer_primary_output_config(TIMER2,ENABLE);                    // 启用TIMER主输出功能
 timer_enable(TIMER2);                                          // 使能TIMER2
}

注意点

  1. GPIO使能时的模式

    使能PB0作为TIMER2的CH2通道时,要注意同时使能GPIO的AF复用功能。并且选择引脚为AF(复用)的PP(推挽模式)

    rcu_periph_clock_enable(RCU_AF);                                 // 配置GPIO的AF复用功能,因为这个引脚要作为定时器的通道输出。
    gpio_init(GPIOB, GPIO_MODE_AF_PP, GPIO_OSPEED_50MHZ, GPIO_PIN_0);// 初始化PB0的引脚,AF复用功能的推挽模式
  1. 频率的计算

    计算频率是这个例子的关键。上面我们已经计算得到了当前定时器的时钟是108MHz。我们最终需要的结果是1KHz。所以需要108M / 1K = 108000。

    这儿需要认识一个新的时钟。计数器时钟(PSC_CLK)。预分频器可以将定时器的时钟(TIMER_CK)频率按1到65536之间的任意值分频,分频后的时钟

    PSC_CLK驱动计数器计数。分频系数受预分频寄存器TIMERx_PSC控制,这个控制寄存器带有缓冲器,它能够在运行时被改变。新的预分频器的参数在下一次

    更新事件到来时被采用。

image-20230412170103350.png

在代码中我们配置的prescaler = 108-1 = 107。我们检查一下寄存器的值是0x006B。换算得到的十进制就是107。

image-20230412170333738.png

所以在代码中我们得到的计数器时钟:

PSC_CLK = CLK_TIMER2 / (prescaler + 1) = 108MHz / 108 = 1MHz

然后使用计数器时钟除以(周期+1)即可得到我们最后的方波频率。我们设置的周期period是1000-1 = 999。

最终计算结果为

PSC_CLK / (period + 1) = 1MHz / 1000 = 1KHz

3.3、pwm输出呼吸灯

main.c

#include "gd32f10x.h"
#include "core_cm3.h"

FlagStatus flag = SET;

void gpio_config(void);
void timer_config(void);
void systick_config(void);
void delay_1ms(uint32_t count);

/**
 * 呼吸灯的实现(使用pwm脉冲)
 */
 int main(void)
{
 uint16_t i = 0;
 systick_config();
 gpio_config();
 timer_config();

 while(1)
 {
 delay_1ms(10);
 if (SET == flag)
 {
 i++;
 } else {
 i--;
 }
 if (i > 250)
 {
 flag = RESET;
 }
 if (i <= 0 )
 {
 flag = SET;
 }
 /** 修改TIMER2的CH2通道的CH2CVL的寄存器值,也就是 **/
 /** timer_channel_output_pulse_value_config(TIMER2,TIMER_CH_2,i);的i值 **/
 TIMER_CH2CV(TIMER2) = (uint32_t)i;
 }
}

/**
 * GPIO配置,配置PC13引脚(LED2的引脚)配置PB0作为PWM输出的引脚
 */
void gpio_config(void)
{
 /** PB0作为timer2通道2的pwm输出 **/
 rcu_periph_clock_enable(RCU_GPIOB);                              // 配置GPIOB的时钟
 rcu_periph_clock_enable(RCU_AF);                                 // 配置GPIO的AF复用功能,因为这个引脚要作为定时器的通道输出。
 gpio_init(GPIOB, GPIO_MODE_AF_PP, GPIO_OSPEED_50MHZ, GPIO_PIN_0);// 初始化PB0的引脚,AF复用功能的推挽模式

 rcu_periph_clock_enable(RCU_GPIOC);                              // PC13作为LED2的输出引脚
 gpio_init(GPIOC, GPIO_MODE_OUT_PP, GPIO_OSPEED_50MHZ, GPIO_PIN_13);// 初始化PB0的引脚,AF复用功能的推挽模式
 gpio_bit_reset(GPIOC,GPIO_PIN_13);
}

/**
 * 定时器的配置。配置TIMER2的CH2通道。
 */
void timer_config()
{
 timer_oc_parameter_struct timer_ocintpara;
 timer_parameter_struct timer_initpara;

 rcu_periph_clock_enable(RCU_TIMER2);    // 使能TIMER2定时器的时钟

 timer_deinit(TIMER2);                   // 复位TIMER2

 /* TIMER2 配置 */
 timer_initpara.prescaler         = 108-1;                 // 预分频因子。0~65535
 timer_initpara.alignedmode       = TIMER_COUNTER_EDGE;    // 边沿计数对齐
 timer_initpara.counterdirection  = TIMER_COUNTER_UP;      // 上升沿触发
 timer_initpara.period            = 1000-1;                // 周期是100
 timer_initpara.clockdivision     = TIMER_CKDIV_DIV1;      // 时钟分频值
 timer_initpara.repetitioncounter = 0;                     // 重复计数为0
 timer_init(TIMER2,&timer_initpara);                       // 初始化TIMER2

 /* PWM模式下的通道2配置 */
 timer_ocintpara.ocpolarity   = TIMER_OC_POLARITY_HIGH;     // 通道输出极性高
 timer_ocintpara.outputstate  = TIMER_CCX_ENABLE;           // 通道输出状态使能
 timer_ocintpara.ocnpolarity  = TIMER_OCN_POLARITY_HIGH;    // 通道互补输出极性高
 timer_ocintpara.outputnstate = TIMER_CCXN_DISABLE;         // 通道互补输出状态不使能
 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);              // 定时器通道输出配置

 timer_channel_output_pulse_value_config(TIMER2,TIMER_CH_2,0);               // 输出脉冲值为500
 timer_channel_output_mode_config(TIMER2,TIMER_CH_2,TIMER_OC_MODE_PWM0);       // 通道输出模式为PWM0
 timer_channel_output_shadow_config(TIMER2,TIMER_CH_2,TIMER_OC_SHADOW_DISABLE);// 通道输出影子不使能

 timer_auto_reload_shadow_enable(TIMER2);                       // 启用自动重新加载阴影功能
 timer_primary_output_config(TIMER2,ENABLE);                    // 启用TIMER主输出功能
 timer_enable(TIMER2);                                          // 使能TIMER2
}

首先是系统时钟的初始化,然后依靠系统时钟形成延时函数

systick_config();

然后是gpio的初始化

/**
 * GPIO配置,配置PC13引脚(LED2的引脚)配置PB0作为PWM输出的引脚
 */
void gpio_config(void)
{
 /** PB0作为timer2通道2的pwm输出 **/
 rcu_periph_clock_enable(RCU_GPIOB);                              // 配置GPIOB的时钟
 rcu_periph_clock_enable(RCU_AF);                                 // 配置GPIO的AF复用功能,因为这个引脚要作为定时器的通道输出。
 gpio_init(GPIOB, GPIO_MODE_AF_PP, GPIO_OSPEED_50MHZ, GPIO_PIN_0);// 初始化PB0的引脚,AF复用功能的推挽模式

 rcu_periph_clock_enable(RCU_GPIOC);                              // PC13作为LED2的输出引脚
 gpio_init(GPIOC, GPIO_MODE_OUT_PP, GPIO_OSPEED_50MHZ, GPIO_PIN_13);// 初始化PB0的引脚,AF复用功能的推挽模式
 gpio_bit_reset(GPIOC,GPIO_PIN_13);
}

PB0是TIMER2的CH2复用引脚。配置是AF(复用)的PP(推挽)模式。

PC13是LED2的引脚,配置为推挽输出模式(OUT_PP)

然后是定时器配置

/**
 * 定时器的配置。配置TIMER2的CH2通道。
 */
void timer_config()
{
 timer_oc_parameter_struct timer_ocintpara;
 timer_parameter_struct timer_initpara;

 rcu_periph_clock_enable(RCU_TIMER2);    // 使能TIMER2定时器的时钟

 timer_deinit(TIMER2);                   // 复位TIMER2

 /* TIMER2 配置 */
 timer_initpara.prescaler         = 108-1;                 // 预分频因子。0~65535
 timer_initpara.alignedmode       = TIMER_COUNTER_EDGE;    // 边沿计数对齐
 timer_initpara.counterdirection  = TIMER_COUNTER_UP;      // 上升沿触发
 timer_initpara.period            = 1000-1;                // 周期是100
 timer_initpara.clockdivision     = TIMER_CKDIV_DIV1;      // 时钟分频值
 timer_initpara.repetitioncounter = 0;                     // 重复计数为0
 timer_init(TIMER2,&timer_initpara);                       // 初始化TIMER2

 /* PWM模式下的通道2配置 */
 timer_ocintpara.ocpolarity   = TIMER_OC_POLARITY_HIGH;     // 通道输出极性高
 timer_ocintpara.outputstate  = TIMER_CCX_ENABLE;           // 通道输出状态使能
 timer_ocintpara.ocnpolarity  = TIMER_OCN_POLARITY_HIGH;    // 通道互补输出极性高
 timer_ocintpara.outputnstate = TIMER_CCXN_DISABLE;         // 通道互补输出状态不使能
 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);              // 定时器通道输出配置

 timer_channel_output_pulse_value_config(TIMER2,TIMER_CH_2,0);               // 输出脉冲值为500
 timer_channel_output_mode_config(TIMER2,TIMER_CH_2,TIMER_OC_MODE_PWM0);       // 通道输出模式为PWM0
 timer_channel_output_shadow_config(TIMER2,TIMER_CH_2,TIMER_OC_SHADOW_DISABLE);// 通道输出影子不使能

 timer_auto_reload_shadow_enable(TIMER2);                       // 启用自动重新加载阴影功能
 timer_primary_output_config(TIMER2,ENABLE);                    // 启用TIMER主输出功能
 timer_enable(TIMER2);                                          // 使能TIMER2
}

这儿主要考虑的是timer_channel_output_config(TIMER2,TIMER_CH_2,&timer_ocintpara);这句。因为我们要形成呼吸的效果,那这儿的输出的脉冲值就得发生变化。其他配置和输出方波一样。

然后在主函数中使用延时,随着系统时钟中断不断进入,i的值不停增加。当系统时钟中断进入到50次时再逐渐将i变小。在这个过程中将i的值不停赋值给CH2CV。这样占空比就一直在变化。

验证时,需要将PB0的输出接到PC13上。LED2灯会有呼吸灯效果。

while(1)
 {
 delay_1ms(10);
 if (SET == flag)
 {
 i++;
 } else {
 i--;
 }
 if (i > 250)
 {
 flag = RESET;
 }
 if (i <= 0 )
 {
 flag = SET;
 }
 /** 修改TIMER2的CH2通道的CH2CVL的寄存器值,也就是 **/
 /** timer_channel_output_pulse_value_config(TIMER2,TIMER_CH_2,i);的i值 **/
 TIMER_CH2CV(TIMER2) = (uint32_t)i;
 }

3.4、定时器中断输出呼吸灯

利用TIMER2定时器的中断来生成呼吸灯。

main.c

#include "gd32f10x.h"
#include "core_cm3.h"

void gpio_config(void);
void timer_config(void);

/**
 * 呼吸灯的实现(使用TIMER2定时实现)
 */
 int main(void)
{
 gpio_config();
 timer_config();

 while(1){}
}

void gpio_config(void)
{
 /** PB0作为timer2通道2的pwm输出 **/
 rcu_periph_clock_enable(RCU_GPIOB);                              // 配置GPIOB的时钟
 rcu_periph_clock_enable(RCU_AF);                                 // 配置GPIO的AF复用功能,因为这个引脚要作为定时器的通道输出。
 gpio_init(GPIOB, GPIO_MODE_AF_PP, GPIO_OSPEED_50MHZ, GPIO_PIN_0);// 初始化PB0的引脚,AF复用功能的推挽模式

 rcu_periph_clock_enable(RCU_GPIOC);                              // PC13作为LED2的输出引脚
 gpio_init(GPIOC, GPIO_MODE_OUT_PP, GPIO_OSPEED_50MHZ, GPIO_PIN_13);// 初始化PB0的引脚,AF复用功能的推挽模式
 gpio_bit_reset(GPIOC,GPIO_PIN_13);
}

void timer_config()
{
 timer_oc_parameter_struct timer_ocintpara;
 timer_parameter_struct timer_initpara;

 rcu_periph_clock_enable(RCU_TIMER2);    // 使能TIMER2定时器的时钟

 timer_deinit(TIMER2);                   // 复位TIMER2

 /* TIMER2 配置 */
 timer_initpara.prescaler         = 3600-1;                // 预分频因子。0~65535
 timer_initpara.alignedmode       = TIMER_COUNTER_EDGE;    // 边沿计数对齐
 timer_initpara.counterdirection  = TIMER_COUNTER_UP;      // 上升沿触发
 timer_initpara.period            = 250-1;                 // 周期是250
 timer_initpara.clockdivision     = TIMER_CKDIV_DIV1;      // 时钟分频值
 timer_initpara.repetitioncounter = 0;                     // 重复计数为0
 timer_init(TIMER2,&timer_initpara);                       // 初始化TIMER2

 /* PWM模式下的通道2配置 */
 timer_ocintpara.ocpolarity   = TIMER_OC_POLARITY_HIGH;     // 通道输出极性高
 timer_ocintpara.outputstate  = TIMER_CCX_ENABLE;           // 通道输出状态使能
 timer_ocintpara.ocnpolarity  = TIMER_OCN_POLARITY_HIGH;    // 通道互补输出极性高
 timer_ocintpara.outputnstate = TIMER_CCXN_DISABLE;         // 通道互补输出状态不使能
 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);              // 定时器通道输出配置

 timer_channel_output_pulse_value_config(TIMER2,TIMER_CH_2,0);               // 输出脉冲值为动态值
 timer_channel_output_mode_config(TIMER2,TIMER_CH_2,TIMER_OC_MODE_PWM0);       // 通道输出模式为PWM0
 timer_channel_output_shadow_config(TIMER2,TIMER_CH_2,TIMER_OC_SHADOW_DISABLE);// 通道输出影子不使能

 timer_auto_reload_shadow_enable(TIMER2);                       // 启用自动重新加载阴影功能
 timer_primary_output_config(TIMER2,ENABLE);                    // 启用TIMER主输出功能

 nvic_irq_enable(TIMER2_IRQn, 0, 2);                            // TIMER2中断设置, 抢占优先级为0, 次优先级为2
 timer_interrupt_enable(TIMER2, TIMER_INT_UP);                  // 使能TIMER2更新中断 
 timer_enable(TIMER2);                                          // 使能TIMER2
}

主函数中,刚开始不用声明系统时钟,因为我们不需要。GPIO设置一样。还是PB0和PC13。

在定时器设置中需要开启中断

nvic_irq_enable(TIMER2_IRQn, 0, 2);                            // TIMER2中断设置, 抢占优先级为0, 次优先级为2
timer_interrupt_enable(TIMER2, TIMER_INT_UP);                  // 使能TIMER2更新中断 

然后在中断函数文件中gd32f10x_it.c编写中断程序

#include "gd32f10x_it.h"
#include "gd32f10x_eval.h"

uint8_t indexWave[] = {1,1,2,2,3,4,6,8,10,14,19,25,33,44,59,80,
107,143,191,250,250,191,143,107,80,59,44,33,25,19,14,10,8,6,4,3,2,2,1,1};

/**
 * TIMER2中断程序
 */
void TIMER2_IRQHandler(void)
{
 static uint8_t pwm_index = 0;      //用于PWM查表
 static uint8_t period_cnt = 0;  //用于计算周期数

 if(timer_interrupt_flag_get(TIMER2, TIMER_INT_FLAG_UP))
 {
 /* 清除TIMER2 中断标志位 */
 timer_interrupt_flag_clear(TIMER2, TIMER_INT_FLAG_UP);

 period_cnt++;
 if(period_cnt >= 10)  //10次定时器中断更改一次比较寄存器的值,输出下一种脉冲宽的PWM波
 {
 //根据PWM表修改定时器的比较寄存器值
 TIMER_CH2CV(TIMER2) = indexWave[pwm_index];
 pwm_index++;  //标志PWM表的下一个元素
 //若PWM脉冲表已经输出完成一遍,重置PWM查表标志
 if( pwm_index >=  40) 
 {
 pwm_index=0; 
 }
 period_cnt=0;  //重置周期计数标志
 }
 } 
}

indexWave数组是占空比呈指数曲线变化的 PWM 信号。共40个数字。最大是250。把LED的亮度分成了0~250个等级。

本中断服务函数在每次定时器更新事件发生时执行一次。函数中使用了静态变量 pwm_index 和 period_cnt,它们分别用来查找 PWM 表元素和记录同样占空

比的脉冲输出了多少次。

本代码的目的是每 10 次定时器中断更新一次 PWM 表中的数据到比较寄存器中,当遍历完 PWM 表的 40 个元素时,再重头开始遍历 PWM 表,周而复始,

重复 LED 的呼吸过程。

整个呼吸过程的时间计算方法如下:

因为定时器的 prescaler 设置为 3600 -1;

所以定时器的时钟频率:CK_TIMER2 = 108M / (prescaler +1) = 108000000 / 3600 = 30000HZ

即定时器的时钟周期为:T = 1 / CK_TIMER2 = 1/30000 s

定时器的周期period为 250-1。

定时器的中断周期就是 t = T * ( period + 1 ) = 1/30000 * 250 = 1/120s

pwm表有pwm_index=40个占空比数据,同一个占空比输出是10次中断。

所以一个呼吸周期就是 Tf = t * 40 * 10 = 3.33s

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 194,319评论 5 459
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 81,801评论 2 371
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 141,567评论 0 319
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,156评论 1 263
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,019评论 4 355
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,090评论 1 272
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,500评论 3 381
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,192评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,474评论 1 290
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,566评论 2 309
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,338评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,212评论 3 312
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,572评论 3 298
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,890评论 0 17
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,169评论 1 250
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,478评论 2 341
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,661评论 2 335

推荐阅读更多精彩内容