一、基本流程
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。
系统总线分为三条。分别是高速总线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]位来得到。
我查看了板子的寄存器值是11010。查询手册可知这是PLL源时钟 x 27得到。所以
CK_PLL = 8MHz / 2 x 27 = 108MHz
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管脚的外部时钟。
2.2 系统时钟CK_SYS
系统时钟CK_SYS可选择的有上面2.1的三个时钟源。系统复位后,IRC8M时钟默认做为CK_SYS的时钟源,改变配置寄存器0(RCU_CFG0)中的系统时钟变换
位SCS可以切换系统时钟源为HXTAL或CK_PLL。当SCS的值被改变,系统时钟将使用原来的时钟源继续运行直到转换的目标时钟源稳定。当一个时钟源被直接或通
过PLL间接作为系统时钟时,它将不能被停止。
查看寄存器得到SCS的值10。所以系统时钟选择的是CK_PLL。也就是108M。
此时已经确定系统时钟是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。
查看对应寄存器结果是0x0,此时代表不分频。所以我自己板子的
CK_AHB=CK_SYS=108MHz
2.3.2 CK_APB1
CK_APB1由CK_AHB分频得到。预分频因子查看时钟配置寄存器0(RCU_CFG0)的APB1PSC[2:0]位可获取。最大分频为16。
查看自己寄存器的结果是0x4,对应100,代表选择CK_AHB时钟2分频。所以
CK_APB1 = CK_AHB / 2 = 108MHz / 2 = 54MHz
2.3.3 CK_APB2
CK_APB1由CK_AHB分频得到。同CK_APB1。预分频因子查看时钟配置寄存器0(RCU_CFG0)的APB2PSC[2:0]位可获取。最大分频为16。
查看自己板子的寄存器的值为0x0,所以是选择CK_AHB时钟不分频。
CK_APB2 = CK_AHB = 108MHz
2.4 定时器时钟CK_TIMERx
定时器外设都是挂靠在低速总线上的,多个定时器挂靠的低速总线不一样。观察中密度GD32F10X系列结构。可以看出TIMER0定时器挂靠在APB2上,TIMER1~3挂在APB1上。
针对TIMER2来分析。
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);
}
系统时钟初始化其实就是将我们选定的时钟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
}
注意点
-
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复用功能的推挽模式
-
频率的计算
计算频率是这个例子的关键。上面我们已经计算得到了当前定时器的时钟是108MHz。我们最终需要的结果是1KHz。所以需要108M / 1K = 108000。
这儿需要认识一个新的时钟。计数器时钟(PSC_CLK)。预分频器可以将定时器的时钟(TIMER_CK)频率按1到65536之间的任意值分频,分频后的时钟
PSC_CLK驱动计数器计数。分频系数受预分频寄存器TIMERx_PSC控制,这个控制寄存器带有缓冲器,它能够在运行时被改变。新的预分频器的参数在下一次
更新事件到来时被采用。
在代码中我们配置的prescaler = 108-1 = 107。我们检查一下寄存器的值是0x006B。换算得到的十进制就是107。
所以在代码中我们得到的计数器时钟:
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