梁山派入门指南4——定时器使用详解,包括定时器中断、PWM产生、输入捕获测量频率
1. 定时器概览
GD32F470ZGT6 一共有 14 个定时器,可以分为五种类型,高级定时器 0/7、通用定时器(L0)1-4、通用定时器(L1)8/11、通用定时器(L2)9/10/12/13 和基本定时器 5/6。不同类型的定时器所拥有的功能数量不同,一般高级定时器的功能最多,通用定时器次之,基本定时器功能最少。
【注】:用户手册350页
关于定时器其他内容,请参照以下内容:
下面直接介绍GD32上的定时器开发流程:
2.基本定时器
2.1 基本定时器介绍
通过查阅GD32的用户手册,可以看到基本定时器的功能比较简单,通常采用定时器+中断的方式来周期性的执行一些操作,下面我将介绍具体的开发流程:
2.2 梁山派上的基本定时器开发
2.2.1. 了解梁山派上的基本定时器资源(实际上我们以及在上面了解过了)
【注】:数据手册低65页
【注】:数据手册低66页
- 可以看到梁山派上的基本定时器分别为定时器5和定时器6,常用来完成一些周期性执行的任务,比如定期触发DAC转化。
接着我们看他们具体挂载到哪个总线上:
【注】:数据手册第10页
- 可以看到定时器5和定时器6都是挂载到APB1总线上。
有了以上这些信息,就足够了,下面我们开始具体的配置:
2.2.2. 配置定时器
通过查阅用户手册,了解基本定时器的配置流程如下:
有了这个之后,我们就可以根据手册进行一步一步的配置,或者自己查阅固件库手册,自行配置,不管哪种方式,下面是配置好的样子:
/* 定时器的时钟 */
#define BSP_TIMER5_RCU RCU_TIMER5
/* 定时器定义 */
#define BSP_TIMER5 TIMER5
/* 定时器中断 */
#define BSP_TIMER5_IQR TIMER5_DAC_IRQn
/* 定时器中断服务程序 */
#define BSP_TIMER5_IRQHander TIMER5_DAC_IRQHandler
/* 配置定时器的预分配系数 */
#define BSP_TIMER5_PRESCALER (24000 - 1)
/* 配置定时器的自动重装载值 */
#define BSP_TIMER5_PERIOD (10000 - 1)
/* 中断触发频率 = 240M /( (pre + 1) * ( per + 1 ) ) */
/************************************************
函数名称 : basic_timer5_config
功 能 : 基本定时器5初始化
参 数 : 无
返 回 值 : 无
作 者 : 不想写代码的我
*************************************************/
void basic_timer_config(void)
{
/* 定义定时器结构体,适用于基本定时器 */
timer_parameter_struct timer_initpara;
/* 开始时钟 */
rcu_periph_clock_enable(BSP_TIMER5_RCU);
/* 设置定时器时钟为240Mhz,由于定时器5挂载在APB1总线上,
时钟在system_gd32f4xx.c中被初始化为SYSCLK/4,所以需要4倍频回240MHz */
rcu_timer_clock_prescaler_config(RCU_TIMER_PSC_MUL4);
/* 复位时钟 */
timer_deinit(BSP_TIMER5);
/* 初始化定时器结构体成员 */
timer_initpara.prescaler = BSP_TIMER5_PRESCALER; //预分配系数
timer_initpara.alignedmode = TIMER_COUNTER_EDGE; //边缘对齐
timer_initpara.counterdirection = TIMER_COUNTER_UP; //向上计数
timer_initpara.period = BSP_TIMER5_PERIOD - 1; //自动重装载值
timer_initpara.clockdivision = TIMER_CKDIV_DIV1; //输入捕获时使用,数字滤波器使用的采样频率之间的分配比例
timer_initpara.repetitioncounter = 0; //重复计数器,重复X+1次才触发一次中断,高级定时器才有此功能
timer_init(BSP_TIMER5,&timer_initpara); //初始化定时器结构体成员
/* 使能定时器中断优先级 */
nvic_irq_enable(BSP_TIMER5_IQR,2,0);
/* 使能定时器中断 */
timer_interrupt_enable(BSP_TIMER5,TIMER_INT_UP);
/* 使能定时器 */
timer_enable(BSP_TIMER5);
}
2.2.3. 编写定时器中断服务程序
完成了定时器的配置之后,下一步就是编写中断服务函数:
/************************************************
函数名称 : BSP_TIMER5_IRQHander
功 能 : 基本定时器5中断服务函数
参 数 : 无
返 回 值 : 无
作 者 : 不想写代码的我
*************************************************/
void BSP_TIMER5_IRQHander(void)
{
/* 查询中断标志位 */
if(timer_interrupt_flag_get(BSP_TIMER5,TIMER_INT_FLAG_UP) == SET)
{
/* 清除中断标志位 */
timer_interrupt_flag_clear(BSP_TIMER5,TIMER_INT_FLAG_UP);
/* 执行相应操作 */
printf("BaseTimerInterrupt\r\n");
gpio_bit_toggle(PORT_LED1,PIN_LED1);
}
}
2.2.4. 举一反三,兼容定时器5和6
完成了以上三步之后,就可以正常使用基本定时器中断实现周期执行了,当然我们也可以举一反三,完成定时器6的配置和中断服务程序的编写,并整合成bsp文件(bsp文件我放在2.3 小节)。
2.3 bsp_base_timer文件(兼容定时器5和定时器6)
- bsp_base_timer.c
#include "bsp_base_timer.h"
/************************************************
函数名称 : basic_timer_config
功 能 : 基本定时器初始化
参 数 : 无
返 回 值 : 无
作 者 : 不想写代码的我
*************************************************/
void basic_timer_config(void)
{
/* 如果使用了定时器5,则初始化定时器5 */
#if USING_TIMER5
/* 初始化定时器5 */
basic_timer5_config();
#endif /* USING_TIMER5 */
/* 如果使用了定时器6,则初始化定时器6 */
#if USING_TIMER6
/* 初始化定时器5 */
basic_timer6_config();
#endif /* USING_TIMER6 */
}
#if USING_TIMER5
/************************************************
函数名称 : basic_timer5_config
功 能 : 基本定时器5初始化
参 数 : 无
返 回 值 : 无
作 者 : 不想写代码的我
*************************************************/
static void basic_timer5_config(void)
{
/* 定义定时器结构体,适用于基本定时器 */
timer_parameter_struct timer_initpara;
/* 开始时钟 */
rcu_periph_clock_enable(BSP_TIMER5_RCU);
/* 设置定时器时钟为240Mhz,由于定时器5挂载在APB1总线上,
时钟在system_gd32f4xx.c中被初始化为SYSCLK/4,所以需要4倍频回240MHz */
rcu_timer_clock_prescaler_config(RCU_TIMER_PSC_MUL4);
/* 复位时钟 */
timer_deinit(BSP_TIMER5);
/* 初始化定时器结构体成员 */
timer_initpara.prescaler = BSP_TIMER5_PRESCALER; //预分配系数
timer_initpara.alignedmode = TIMER_COUNTER_EDGE; //边缘对齐
timer_initpara.counterdirection = TIMER_COUNTER_UP; //向上计数
timer_initpara.period = BSP_TIMER5_PERIOD - 1; //自动重装载值
timer_initpara.clockdivision = TIMER_CKDIV_DIV1; //输入捕获时使用,数字滤波器使用的采样频率之间的分配比例
timer_initpara.repetitioncounter = 0; //重复计数器,重复X+1次才触发一次中断,高级定时器才有此功能
timer_init(BSP_TIMER5,&timer_initpara); //初始化定时器结构体成员
/* 使能定时器中断优先级 */
nvic_irq_enable(BSP_TIMER5_IQR,2,0);
/* 使能定时器中断 */
timer_interrupt_enable(BSP_TIMER5,TIMER_INT_UP);
/* 使能定时器 */
timer_enable(BSP_TIMER5);
}
/************************************************
函数名称 : BSP_TIMER5_IRQHander
功 能 : 基本定时器5中断服务函数
参 数 : 无
返 回 值 : 无
作 者 : 不想写代码的我
*************************************************/
void BSP_TIMER5_IRQHander(void)
{
/* 查询中断标志位 */
if(timer_interrupt_flag_get(BSP_TIMER5,TIMER_INT_FLAG_UP) == SET)
{
/* 清除中断标志位 */
timer_interrupt_flag_clear(BSP_TIMER5,TIMER_INT_FLAG_UP);
/* 执行相应操作 */
printf("BaseTimerInterrupt\r\n");
gpio_bit_toggle(PORT_LED1,PIN_LED1);
}
}
#endif /* USING_TIMER5 */
#if USING_TIMER6
/************************************************
函数名称 : basic_timer5_config
功 能 : 基本定时器5初始化
参 数 : 无
返 回 值 : 无
作 者 : 不想写代码的我
*************************************************/
static void basic_timer6_config(void)
{
/* 定义定时器结构体,适用于基本定时器 */
timer_parameter_struct timer_initpara;
/* 开始时钟 */
rcu_periph_clock_enable(BSP_TIMER6_RCU);
/* 设置定时器时钟为240Mhz, */
rcu_timer_clock_prescaler_config(RCU_TIMER_PSC_MUL4);
/* 复位时钟 */
timer_deinit(BSP_TIMER6);
/* 初始化定时器结构体成员 */
timer_initpara.prescaler = BSP_TIMER6_PRESCALER; //预分配系数
timer_initpara.alignedmode = TIMER_COUNTER_EDGE; //边缘对齐
timer_initpara.counterdirection = TIMER_COUNTER_UP; //向上计数
timer_initpara.period = BSP_TIMER6_PERIOD - 1; //自动重装载值
timer_initpara.clockdivision = TIMER_CKDIV_DIV1; //输入捕获时使用,数字滤波器使用的采样频率之间的分配比例
timer_initpara.repetitioncounter = 0; //重复计数器,重复X+1次才触发一次中断,高级定时器才有此功能
timer_init(BSP_TIMER6,&timer_initpara); //初始化定时器结构体成员
/* 使能定时器中断优先级 */
nvic_irq_enable(BSP_TIMER6_IQR,2,0);
/* 使能定时器中断 */
timer_interrupt_enable(BSP_TIMER6,TIMER_INT_UP);
/* 使能定时器 */
timer_enable(BSP_TIMER6);
}
/************************************************
函数名称 : BSP_TIMER6_IRQHander
功 能 : 基本定时器6中断服务函数
参 数 : 无
返 回 值 : 无
作 者 : 不想写代码的我
*************************************************/
void BSP_TIMER6_IRQHander(void)
{
/* 查询中断标志位 */
if(timer_interrupt_flag_get(BSP_TIMER6,TIMER_INT_FLAG_UP) == SET)
{
/* 清除中断标志位 */
timer_interrupt_flag_clear(BSP_TIMER6,TIMER_INT_FLAG_UP);
/* 执行相应操作 */
printf("BaseTimerInterrupt\r\n");
gpio_bit_toggle(PORT_LED2,PIN_LED2);
}
}
#endif /* USING_TIMER6 */
【注】:这个代码时我在梁山派(GD32F470ZGT6)上写的,还没有对其他芯片做兼容,但是大部分应该都没问题。
- bsp_base_timer.h
#ifndef _BSP_BASE_TIMER_H
#define _BSP_BASE_TIMER_H
#include "gd32f4xx.h"
#include "systick.h"
#include "bsp_uart.h"
#include "bsp_led.h"
/* 配置需要使用那个定时器,可以同时选择多个使用,如果选择多个,
会自己完成初始化,只需要修改对应的中断服务函数和相关的宏即可 */
/* 需要使用的定时器,基本定时器只有5和6,所有这里只有两个 */
#define USING_TIMER5 1
#define USING_TIMER6 1
#if USING_TIMER5
/* 定时器的时钟 */
#define BSP_TIMER5_RCU RCU_TIMER5
/* 定时器定义 */
#define BSP_TIMER5 TIMER5
/* 定时器中断 */
#define BSP_TIMER5_IQR TIMER5_DAC_IRQn
/* 定时器中断服务程序 */
#define BSP_TIMER5_IRQHander TIMER5_DAC_IRQHandler
/* 配置定时器的预分配系数 */
#define BSP_TIMER5_PRESCALER (24000 - 1)
/* 配置定时器的自动重装载值 */
#define BSP_TIMER5_PERIOD (10000 - 1)
/* 中断触发频率 = 240M /( (pre + 1) * ( per + 1 ) ) */
#endif /* USING_TIMER5 */
#if USING_TIMER6
/* 定时器的时钟 */
#define BSP_TIMER6_RCU RCU_TIMER6
/* 定时器定义 */
#define BSP_TIMER6 TIMER6
/* 定时器中断 */
#define BSP_TIMER6_IQR TIMER6_IRQn
/* 定时器中断服务程序 */
#define BSP_TIMER6_IRQHander TIMER6_IRQHandler
/* 配置定时器的预分配系数 */
#define BSP_TIMER6_PRESCALER (24000 - 1)
/* 配置定时器的自动重装载值 */
#define BSP_TIMER6_PERIOD (10000 - 1)
/* 中断触发频率 = 240M /( (pre + 1) * ( per + 1 ) ) */
#endif /* USING_TIMER6 */
/* 基本定时器的初始化 */
void basic_timer_config(void);
/* 基本定时器5初始化 */
static void basic_timer5_config(void);
/* 基本定时器6初始化 */
static void basic_timer6_config(void);
#endif /* _BSP_BASE_TIMER_H */
【注】:这个代码时我在梁山派(GD32F470ZGT6)上写的,还没有对其他芯片做兼容,但是大部分应该都没问题。
2.4 基本定时器中断使用示例
在上面的bsp文件基础上,我们就可以周期的执行任务了,前提是在main函数中调用basic_timer_config(),下面是一个使用的示例:
- main.c
#include "gd32f4xx.h"
#include "systick.h"
#include <stdio.h>
#include "main.h"
#include "string.h"
#include "bsp_led.h"
#include "bsp_key.h"
#include "bsp_uart.h"
#include "bsp_base_timer.h"
/*!
\brief main function
\param[in] none
\param[out] none
\retval none
*/
int main(void)
{
/* 配置中断优先级分组,4位抢占优先级,0位子优先级 */
nvic_priority_group_set(NVIC_PRIGROUP_PRE4_SUB0);
/* 配置sysTick的时钟和中断周期*/
systick_config();
/* 串口初始化 */
uart_gpio_config(115200);
/* led初始化 */
led_gpio_config(Led_Name_Led1);
led_gpio_config(Led_Name_Led2);
/* 基本定时器初始化,具体内容查询bsp_base_timer.h文件 */
basic_timer_config(); // 定时器初始化
while(1)
{
}
}
- 中断服务程序
/************************************************
函数名称 : BSP_TIMER5_IRQHander
功 能 : 基本定时器5中断服务函数
参 数 : 无
返 回 值 : 无
作 者 : 不想写代码的我
*************************************************/
void BSP_TIMER5_IRQHander(void)
{
/* 查询中断标志位 */
if(timer_interrupt_flag_get(BSP_TIMER5,TIMER_INT_FLAG_UP) == SET)
{
/* 清除中断标志位 */
timer_interrupt_flag_clear(BSP_TIMER5,TIMER_INT_FLAG_UP);
/* 执行相应操作 */
printf("BaseTimerInterrupt\r\n");
gpio_bit_toggle(PORT_LED1,PIN_LED1);
}
}
/************************************************
函数名称 : BSP_TIMER6_IRQHander
功 能 : 基本定时器6中断服务函数
参 数 : 无
返 回 值 : 无
作 者 : 不想写代码的我
*************************************************/
void BSP_TIMER6_IRQHander(void)
{
/* 查询中断标志位 */
if(timer_interrupt_flag_get(BSP_TIMER6,TIMER_INT_FLAG_UP) == SET)
{
/* 清除中断标志位 */
timer_interrupt_flag_clear(BSP_TIMER6,TIMER_INT_FLAG_UP);
/* 执行相应操作 */
printf("BaseTimerInterrupt\r\n");
gpio_bit_toggle(PORT_LED2,PIN_LED2);
}
}
最后的示例结果就是,每隔1s中led1和led2改变一次状态,并在串口打印 “ BaseTimerInterrupt ” 。
3. 通用定时器
3.1 通用定时器介绍
- 通用定时器L2
- 通用定时器L1
- 通用定时器L0
通过查阅GD32的用户手册,可以看到通用定时器的数量比较多,分为了L2、L1、L0三种,其中L0的功能更强大,也更复杂,L2的功能最简单,也更简单,最典型的功能有输入捕获检测频率和PWM信号产生,下面将在梁山派上完成这两个功能。
3.2 产生PWM信号(通过定时器1和定时2)
3.2.1. 了解梁山派上的定时器1和定时器2
【注】:数据手册第10页
可以看到定时器1和定时器2都是挂载在APB1总线上的。
3.2.2. 配置定时器
通过查阅用户手册,了解基本定时器的配置流程如下:
在这个过程中,我们需要将GPIO引脚复用成定时器1/2通道0(代码中使用的是通道0)的PWM输出引脚,下面是具体的复用关系:
【注】:数据手册第52页 ~ 60页,这里只是展示了一部分
有了以上信息,我们就可以按照用户指南编写初始化代码,下面是编写好了的样子:
//PWM1信号的相关配置:可选PA0、PA5、PA15选择复用通道1,使用定时器1通道0
/* PWM1输出引脚的时钟*/
#define BSP_PWM1_RCU RCU_GPIOA
/* PWM1输出引脚的端口*/
#define BSP_PWM1_PORT GPIOA
/* PWM1输出引脚 */
#define BSP_PWM1_PIN GPIO_PIN_5
/* GPIO的复用通道 */
#define BSP_PWM1_AF GPIO_AF_1
//需要修改PWM1输出的引脚,只需要修改时钟,端口和引脚即可,这里默认PA5。
/* TIMER1_CH0配置输出PWM1信号 */
#define BSP_PWM1_TIMER_RCU RCU_TIMER1
#define BSP_PWM1_TIMER TIMER1
#define BSP_PWM1_CHANNEL TIMER_CH_0
/* 配置定时器的预分配系数 */
#define BSP_TIMER1_PRESCALER (240 - 1)
/* 配置定时器的自动重装载值 */
#define BSP_TIMER1_PERIOD (10000 - 1)
/* PWM1信号的频率 = 240M /( (pre + 1) * ( per + 1 ) ),这里就是100Hz */
/* 配置定时器的默认脉冲值,占空比 = 脉冲值 / 自动重装载值,
后续修改占空比也是通过修改这个实现的,注意脉冲值不能大于自动重装载值 */
#define BSP_TIMER1_PULSE (5000-1)
/************************************************
函数名称 : pwm1_gpio_config
功 能 : 初始化PWM1信号的产生,使用定时器1的通道0,可选择输出到PA0、PA5、PA15,具体配置见bsp_general_timer.h
参 数 : 无
返 回 值 : 无
作 者 : 不想写代码的我
*************************************************/
void pwm1_gpio_config(void)
{
//GPIO配置
/* 开启时钟 */
rcu_periph_clock_enable(BSP_PWM1_RCU);
/* GPIO模式配置: 复用模式,无上下拉 */
gpio_mode_set(BSP_PWM1_PORT,GPIO_MODE_AF,GPIO_PUPD_NONE,BSP_PWM1_PIN);
/* GPIO输出配置:推挽输出,超级高速 */
gpio_output_options_set(BSP_PWM1_PORT,GPIO_OTYPE_PP,GPIO_OSPEED_50MHZ,BSP_PWM1_PIN);
/* 配置GPIO为定时器的通道 */
gpio_af_set(BSP_PWM1_PORT,BSP_PWM1_AF,BSP_PWM1_PIN);
//定时器配置
/* 定义定时器结构体 */
timer_parameter_struct timer_initpara;
/* 开启时钟 */
rcu_periph_clock_enable(BSP_PWM1_TIMER_RCU);
/* 设置定时器时钟为240Mhz,由于定时器1挂载在APB1总线上,
时钟在system_gd32f4xx.c中被初始化为SYSCLK/4,所以需要4倍频回240MHz */
rcu_timer_clock_prescaler_config(RCU_TIMER_PSC_MUL4);
/* 复位定时器 */
timer_deinit(BSP_PWM1_TIMER);
/* 初始化定时器结构体成员 */
timer_initpara.prescaler = BSP_TIMER1_PRESCALER; //预分配系数
timer_initpara.alignedmode = TIMER_COUNTER_EDGE; //边缘对齐
timer_initpara.counterdirection = TIMER_COUNTER_UP; //向上计数
timer_initpara.period = BSP_TIMER1_PERIOD; //自动重装载值
timer_initpara.clockdivision = TIMER_CKDIV_DIV1; //输入捕获时使用,数字滤波器使用的采样频率之间的分配比例
timer_initpara.repetitioncounter = 0; //重复计数器,重复X+1次才触发一次中断,高级定时器才有此功能
timer_init(BSP_PWM1_TIMER,&timer_initpara); //初始化定时器结构体成员
/* 使能定时器 */
timer_enable(BSP_PWM1_TIMER);
//定时器输出配置
/* 定义定时器比较输出结构体 */
timer_oc_parameter_struct timer_ocintpara;
/* 初始化定时器比较输出结构体成员 */
timer_ocintpara.ocpolarity = TIMER_OC_POLARITY_HIGH; //有效电平为高电平
timer_ocintpara.outputstate = TIMER_CCX_ENABLE; //通道的输出状态:使能PWM1输出到端口
/* 此结构体的其他功能只有高级定时器才会使用,所以不需要配置 */
/* 初始化定时器输出结构体 */
timer_channel_output_config(BSP_PWM1_TIMER,BSP_PWM1_CHANNEL,&timer_ocintpara);
/* 配置占空比,占空比 = 5000 / 10000,其中5000是我们给的,10000是定时器的自动重装载值 */
timer_channel_output_pulse_value_config(BSP_PWM1_TIMER,BSP_PWM1_CHANNEL,BSP_TIMER1_PULSE);
/* 配置定时器的输出比较模式,这里是PWM10模式 */
timer_channel_output_mode_config(BSP_PWM1_TIMER,BSP_PWM1_CHANNEL,TIMER_OC_MODE_PWM0);
/* 配置输出比较影子寄存器的功能,这里是禁止输出比较寄存器 */
timer_channel_output_shadow_config(BSP_PWM1_TIMER,BSP_PWM1_CHANNEL,TIMER_OC_SHADOW_DISABLE);
/* 只有高级定时器使用 */
// timer_primary_output_config(TIMER0,ENABLE);
/* 自动重装载影子寄存器使能 */
timer_auto_reload_shadow_enable(BSP_PWM1_TIMER);
/* 使能定时器 */
timer_enable(BSP_PWM1_TIMER);
}
完成以上步骤之后,默认会在PA5引脚上输出一个频率为100Hz,占空比为50%的PWM信号。
3.2.3 修改占空比
由于PWM信号的产生配置中,我们不需要使能中断,因此也不需要编写中断服务程序,但是我们可能需要改变PWM信号的占空比,下面是一个修改占空比的函数:
/************************************************
函数名称 : pwm_duty_change
功 能 : 改变PWM信号的占空比,可外部调用
参 数 : 无
返 回 值 : 无
作 者 : 不想写代码的我
*************************************************/
void pwm_duty_change(void)
{
static uint8_t direct = 0; // 方向
static uint16_t value = 0; // 脉冲值
if(direct == 0) // 逐渐变亮
{
value += 500; // 占空比增加
if(value > 10000)
direct = 1; // 改变方向
}else // 逐渐变暗
{
value -= 500; // 占空比减小
if(value <= 0)
direct = 0;
}
#if USING_PWM1
timer_channel_output_pulse_value_config(BSP_PWM1_TIMER,BSP_PWM1_CHANNEL,value); // 配置定时器通道输出脉冲值
printf("Duty = %d\r\n",value/100);
delay_1ms(50); // 延时50ms
}
3.2.4 举一反三,兼容定时器1和2
完成了以上几步,完成了PWM信号的产生和占空比的修改,当然我们可以举一反三,兼容定时器1和定时2产生两组PWM信号,并将其封装成bsp文件(对应的bsp文件在3.3小节展示)
3.2.5 bsp_general_timer文件(兼容定时器1和2,两组PWM信号产生)
- bsp_general_timer.c
#include "bsp_general_timer.h"
/************************************************
函数名称 : pwm_gpio_config
功 能 : 初始化PWM信号的产生,可能会用到定时器1和2
参 数 : 无
返 回 值 : 无
作 者 : 不想写代码的我
*************************************************/
void pwm_gpio_config(void)
{
/* 如果开启了PWM1信号功能,就初始化PWM1信号的相关内容,包括gpio和定时器配置 */
#if USING_PWM1
pwm1_gpio_config(); //初始化gpio和定时器1
#endif /* USING_PWM1 */
/* 如果开启了PWM2信号功能,就初始化PWM2信号的相关内容,包括gpio和定时器配置 */
#if USING_PWM2
pwm2_gpio_config(); //初始化gpio和定时器1
#endif /* USING_PWM2 */
}
/* 开启了PWM1信号的产生 */
#if USING_PWM1
/************************************************
函数名称 : pwm1_gpio_config
功 能 : 初始化PWM1信号的产生,使用定时器1的通道0,可选择输出到PA0、PA5、PA15,具体配置见bsp_general_timer.h
参 数 : 无
返 回 值 : 无
作 者 : 不想写代码的我
*************************************************/
static void pwm1_gpio_config(void)
{
//GPIO配置
/* 开启时钟 */
rcu_periph_clock_enable(BSP_PWM1_RCU);
/* GPIO模式配置: 复用模式,无上下拉 */
gpio_mode_set(BSP_PWM1_PORT,GPIO_MODE_AF,GPIO_PUPD_NONE,BSP_PWM1_PIN);
/* GPIO输出配置:推挽输出,超级高速 */
gpio_output_options_set(BSP_PWM1_PORT,GPIO_OTYPE_PP,GPIO_OSPEED_50MHZ,BSP_PWM1_PIN);
/* 配置GPIO为定时器的通道 */
gpio_af_set(BSP_PWM1_PORT,BSP_PWM1_AF,BSP_PWM1_PIN);
//定时器配置
/* 定义定时器结构体 */
timer_parameter_struct timer_initpara;
/* 开启时钟 */
rcu_periph_clock_enable(BSP_PWM1_TIMER_RCU);
/* 设置定时器时钟为240Mhz,由于定时器1挂载在APB1总线上,
时钟在system_gd32f4xx.c中被初始化为SYSCLK/4,所以需要4倍频回240MHz */
rcu_timer_clock_prescaler_config(RCU_TIMER_PSC_MUL4);
/* 复位定时器 */
timer_deinit(BSP_PWM1_TIMER);
/* 初始化定时器结构体成员 */
timer_initpara.prescaler = BSP_TIMER1_PRESCALER; //预分配系数
timer_initpara.alignedmode = TIMER_COUNTER_EDGE; //边缘对齐
timer_initpara.counterdirection = TIMER_COUNTER_UP; //向上计数
timer_initpara.period = BSP_TIMER1_PERIOD; //自动重装载值
timer_initpara.clockdivision = TIMER_CKDIV_DIV1; //输入捕获时使用,数字滤波器使用的采样频率之间的分配比例
timer_initpara.repetitioncounter = 0; //重复计数器,重复X+1次才触发一次中断,高级定时器才有此功能
timer_init(BSP_PWM1_TIMER,&timer_initpara); //初始化定时器结构体成员
/* 使能定时器 */
timer_enable(BSP_PWM1_TIMER);
//定时器输出配置
/* 定义定时器比较输出结构体 */
timer_oc_parameter_struct timer_ocintpara;
/* 初始化定时器比较输出结构体成员 */
timer_ocintpara.ocpolarity = TIMER_OC_POLARITY_HIGH; //有效电平为高电平
timer_ocintpara.outputstate = TIMER_CCX_ENABLE; //通道的输出状态:使能PWM1输出到端口
/* 此结构体的其他功能只有高级定时器才会使用,所以不需要配置 */
/* 初始化定时器输出结构体 */
timer_channel_output_config(BSP_PWM1_TIMER,BSP_PWM1_CHANNEL,&timer_ocintpara);
/* 配置占空比,占空比 = 5000 / 10000,其中5000是我们给的,10000是定时器的自动重装载值 */
timer_channel_output_pulse_value_config(BSP_PWM1_TIMER,BSP_PWM1_CHANNEL,BSP_TIMER1_PULSE);
/* 配置定时器的输出比较模式,这里是PWM10模式 */
timer_channel_output_mode_config(BSP_PWM1_TIMER,BSP_PWM1_CHANNEL,TIMER_OC_MODE_PWM0);
/* 配置输出比较影子寄存器的功能,这里是禁止输出比较寄存器 */
timer_channel_output_shadow_config(BSP_PWM1_TIMER,BSP_PWM1_CHANNEL,TIMER_OC_SHADOW_DISABLE);
/* 只有高级定时器使用 */
// timer_primary_output_config(TIMER0,ENABLE);
/* 自动重装载影子寄存器使能 */
timer_auto_reload_shadow_enable(BSP_PWM1_TIMER);
/* 使能定时器 */
timer_enable(BSP_PWM1_TIMER);
}
#endif /* USING_PWM1 */
/* 开启了PWM2信号的产生 */
#if USING_PWM2
/************************************************
函数名称 : pwm2_gpio_config
功 能 : 初始化PWM1信号的产生,使用定时器2的通道0,可选择输出到PA6、PB4、PC6,具体配置见bsp_general_timer.h
参 数 : 无
返 回 值 : 无
作 者 : 不想写代码的我
*************************************************/
static void pwm2_gpio_config(void)
{
//GPIO配置
/* 开启时钟 */
rcu_periph_clock_enable(BSP_PWM2_RCU);
/* GPIO模式配置: 复用模式,无上下拉 */
gpio_mode_set(BSP_PWM2_PORT,GPIO_MODE_AF,GPIO_PUPD_NONE,BSP_PWM2_PIN);
/* GPIO输出配置:推挽输出,超级高速 */
gpio_output_options_set(BSP_PWM2_PORT,GPIO_OTYPE_PP,GPIO_OSPEED_50MHZ,BSP_PWM2_PIN);
/* 配置GPIO为定时器的通道 */
gpio_af_set(BSP_PWM2_PORT,BSP_PWM2_AF,BSP_PWM2_PIN);
//定时器配置
/* 定义定时器结构体 */
timer_parameter_struct timer_initpara;
/* 开启时钟 */
rcu_periph_clock_enable(BSP_PWM2_TIMER_RCU);
/* 设置定时器时钟为240Mhz,由于定时器2挂载在APB1总线上,
时钟在system_gd32f4xx.c中被初始化为SYSCLK/4,所以需要4倍频回240MHz */
rcu_timer_clock_prescaler_config(RCU_TIMER_PSC_MUL4);
/* 复位定时器 */
timer_deinit(BSP_PWM2_TIMER);
/* 初始化定时器结构体成员 */
timer_initpara.prescaler = BSP_TIMER2_PRESCALER; //预分配系数
timer_initpara.alignedmode = TIMER_COUNTER_EDGE; //边缘对齐
timer_initpara.counterdirection = TIMER_COUNTER_UP; //向上计数
timer_initpara.period = BSP_TIMER2_PERIOD; //自动重装载值
timer_initpara.clockdivision = TIMER_CKDIV_DIV1; //输入捕获时使用,数字滤波器使用的采样频率之间的分配比例
timer_initpara.repetitioncounter = 0; //重复计数器,重复X+2次才触发一次中断,高级定时器才有此功能
timer_init(BSP_PWM2_TIMER,&timer_initpara); //初始化定时器结构体成员
/* 使能定时器 */
timer_enable(BSP_PWM2_TIMER);
//定时器输出配置
/* 定义定时器比较输出结构体 */
timer_oc_parameter_struct timer_ocintpara;
/* 初始化定时器比较输出结构体成员 */
timer_ocintpara.ocpolarity = TIMER_OC_POLARITY_HIGH; //有效电平为高电平
timer_ocintpara.outputstate = TIMER_CCX_ENABLE; //通道的输出状态:使能PWM2输出到端口
/* 此结构体的其他功能只有高级定时器才会使用,所以不需要配置 */
/* 初始化定时器输出结构体 */
timer_channel_output_config(BSP_PWM2_TIMER,BSP_PWM2_CHANNEL,&timer_ocintpara);
/* 配置占空比,占空比 = 500 / 1000,其中500是我们给的,1000是定时器的自动重装载值 */
timer_channel_output_pulse_value_config(BSP_PWM2_TIMER,BSP_PWM2_CHANNEL,BSP_TIMER2_PULSE);
/* 配置定时器的输出比较模式,这里是PWM2模式 */
timer_channel_output_mode_config(BSP_PWM2_TIMER,BSP_PWM2_CHANNEL,TIMER_OC_MODE_PWM0);
/* 配置输出比较影子寄存器的功能,这里是禁止输出比较寄存器 */
timer_channel_output_shadow_config(BSP_PWM2_TIMER,BSP_PWM2_CHANNEL,TIMER_OC_SHADOW_DISABLE);
/* 只有高级定时器使用 */
// timer_primary_output_config(TIMER0,ENABLE);
/* 自动重装载影子寄存器使能 */
timer_auto_reload_shadow_enable(BSP_PWM2_TIMER);
/* 使能定时器 */
timer_enable(BSP_PWM2_TIMER);
}
#endif /* USING_PWM2 */
/************************************************
函数名称 : pwm_duty_change
功 能 : 改变PWM信号的占空比,可外部调用
参 数 : 无
返 回 值 : 无
作 者 : 不想写代码的我
*************************************************/
void pwm_duty_change(void)
{
static uint8_t direct = 0; // 方向
static uint16_t value = 0; // 脉冲值
if(direct == 0) // 逐渐变亮
{
value += 50; // 值越大 越亮
if(value > 1000)
direct = 1; // 改变方向
}else // 逐渐变暗
{
value -= 50; // 值越小 越暗
if(value <= 0)
direct = 0;
}
/* 开启了PWM1信号的产生 */
#if USING_PWM1
timer_channel_output_pulse_value_config(BSP_PWM1_TIMER,BSP_PWM1_CHANNEL,value*5); // 配置定时器通道输出脉冲值
#endif /* USING_PWM1 */
/* 开启了PWM2信号的产生 */
#if USING_PWM2
timer_channel_output_pulse_value_config(BSP_PWM2_TIMER,BSP_PWM2_CHANNEL,value); // 配置定时器通道输出脉冲值
#endif /* USING_PWM1 */
printf("Duty = %d\r\n",value/100);
delay_1ms(50); // 延时50ms
}
【注】:这个代码时我在梁山派(GD32F470ZGT6)上写的,还没有对其他芯片做兼容,但是大部分应该都没问题。
- bsp_general_timer.h
#ifndef _BSP_GENERAL_TIMER_H
#define _BSP_GENERAL_TIMER_H
#include "gd32f4xx.h"
#include "systick.h"
#include "bsp_uart.h"
#include "bsp_led.h"
/* 这里用来控制PWM信号的产生与关闭,以及具体那个PWM信号产生,可以多个选项同时选择 */
#define USING_PWM1 1
#define USING_PWM2 1
#if USING_PWM1
//PWM1信号的相关配置:可选PA0、PA5、PA15选择复用通道1,使用定时器1通道0
/* PWM1输出引脚的时钟*/
#define BSP_PWM1_RCU RCU_GPIOA
/* PWM1输出引脚的端口*/
#define BSP_PWM1_PORT GPIOA
/* PWM1输出引脚 */
#define BSP_PWM1_PIN GPIO_PIN_5
/* GPIO的复用通道 */
#define BSP_PWM1_AF GPIO_AF_1
//需要修改PWM1输出的引脚,只需要修改时钟,端口和引脚即可,这里默认PA5。
/* TIMER1_CH0配置输出PWM1信号 */
#define BSP_PWM1_TIMER_RCU RCU_TIMER1
#define BSP_PWM1_TIMER TIMER1
#define BSP_PWM1_CHANNEL TIMER_CH_0
/* 配置定时器的预分配系数 */
#define BSP_TIMER1_PRESCALER (240 - 1)
/* 配置定时器的自动重装载值 */
#define BSP_TIMER1_PERIOD (10000 - 1)
/* PWM1信号的频率 = 240M /( (pre + 1) * ( per + 1 ) ),这里就是100Hz */
/* 配置定时器的默认脉冲值,占空比 = 脉冲值 / 自动重装载值,
后续修改占空比也是通过修改这个实现的,注意脉冲值不能大于自动重装载值 */
#define BSP_TIMER1_PULSE (5000-1)
#endif /* USING_PWM1 */
#if USING_PWM2
//PWM2信号的相关配置:可选PA6、PB4、PC6选择复用通道2,使用定时器2通道0
/* PWM2输出引脚的时钟*/
#define BSP_PWM2_RCU RCU_GPIOA
/* PWM2输出引脚的端口*/
#define BSP_PWM2_PORT GPIOA
/* PWM2输出引脚*/
#define BSP_PWM2_PIN GPIO_PIN_6
/* GPIO的复用通道 */
#define BSP_PWM2_AF GPIO_AF_2
//需要修改PWM2输出的引脚,只需要修改时钟,端口和引脚即可,这里默认PA6。
/* TIMER2_CH0配置输出PWM2信号 */
#define BSP_PWM2_TIMER_RCU RCU_TIMER2
#define BSP_PWM2_TIMER TIMER2
#define BSP_PWM2_CHANNEL TIMER_CH_0
/* 配置定时器的预分配系数 */
#define BSP_TIMER2_PRESCALER (240 - 1)
/* 配置定时器的自动重装载值 */
#define BSP_TIMER2_PERIOD (1000 - 1)
/* PWM1信号的频率 = 240M /( (pre + 1) * ( per + 1 ) ),这里就是1000Hz */
/* 配置定时器的默认脉冲值,占空比 = 脉冲值 / 自动重装载值,
后续修改占空比也是通过修改这个实现的,注意脉冲值不能大于自动重装载值 */
#define BSP_TIMER2_PULSE (500-1)
#endif /* USING_PWM2 */
/* 初始化PWM信号的产生,可能会用到定时器1和2 */
void pwm_gpio_config(void);
/* 改变PWM信号的占空比,可外部调用 */
void pwm_duty_change(void);
/* 初始化PWM1信号的产生,使用定时器1的通道0,可选择输出到PA0、PA5、PA15 */
static void pwm1_gpio_config(void);
/* 初始化PWM1信号的产生,使用定时器2的通道0,可选择输出到PA6、PB4、PC6 */
static void pwm2_gpio_config(void);
#endif /* _BSP_GENERAL_TIMER_H */
【注】:这个代码时我在梁山派(GD32F470ZGT6)上写的,还没有对其他芯片做兼容,但是大部分应该都没问题。
3.2.6 两路PWM信号产生使用示例
在上面的bsp文件基础上,我们就可以产生PWM信号量了,前提是需要在mian函数中调用pwm_gpio_config() 和pwm_duty_change(),下面是一个简单的使用示例:
#include "gd32f4xx.h"
#include "systick.h"
#include <stdio.h>
#include "main.h"
#include "string.h"
#include "bsp_led.h"
#include "bsp_key.h"
#include "bsp_uart.h"
#include "bsp_base_timer.h"
#include "bsp_general_timer.h"
/*!
\brief main function
\param[in] none
\param[out] none
\retval none
*/
int main(void)
{
/* 配置中断优先级分组,4位抢占优先级,0位子优先级 */
nvic_priority_group_set(NVIC_PRIGROUP_PRE4_SUB0);
/* 配置sysTick的时钟和中断周期*/
systick_config();
/* 串口初始化 */
uart_gpio_config(115200);
/* led初始化 */
led_gpio_config(Led_Name_Led1);
led_gpio_config(Led_Name_Led2);
/* 基本定时器初始化,具体内容查询bsp_base_timer.h文件 */
basic_timer_config(); // 定时器初始化
pwm_gpio_config();
while(1)
{
pwm_duty_change();
}
}
运行结果如下:
以上就是通用定时器产生PWM信号部分的内容,下面我们介绍输入捕获测量频率。
3.3 输入捕获测量频率
【注】:数据手册第10页
可以看到定时器1和定时器2都是挂载在APB1总线上的。
3.2.2. 配置定时器
通过查阅用户手册,发现没有关于输入捕获的配置介绍,我们自己学习一下,总结出下面的配置流程:
- 配置通道引脚的GPIO
- 配置定时器
- 配置输入捕获结构体
- 配置定时器的输入通道,使能定时器
- 使能定时器中断
在这个过程中,我们需要将GPIO引脚复用成定时器3通道0(代码中使用的是通道0)作为输入捕获引脚,下面是具体的复用关系:
【注】:数据手册第52页 ~ 60页,这里只是展示了一部分
有了以上信息,我们就可以参考例程编写初始化代码,下面是编写好了的样子:
//输入捕获的相关配置:可选PB6和PD12选择复用通道2,使用定时器3通道0
/* 输入捕获引脚的时钟 */
#define BSP_INPUT_CAPTURE1_RCU RCU_GPIOB
/* 输入捕获引脚的端口 */
#define BSP_INPUT_CAPTURE1_PORT GPIOB
/* 输入捕获引脚的引脚 */
#define BSP_INPUT_CAPTURE1_PIN GPIO_PIN_6
/* 输入捕获引脚的复用通道 */
#define BSP_INPUT_CAPTURE1_AF GPIO_AF_2
//需要修改输入捕获的引脚引脚,只需要修改时钟,端口和引脚即可,这里默认PB6。
/* TIMER3_CH0配置输入捕获功能 */
#define BSP_INPUT_CAPTURE1_TIMER_RCU RCU_TIMER3
#define BSP_INPUT_CAPTURE1_TIMER TIMER3
#define BSP_INPUT_CAPTURE1_CHANNEL TIMER_CH_0
/* 配置定时器的预分配系数 */
#define BSP_TIMER3_PRESCALER (240 - 1) //这里分频完是1MHz,1us定时器加1
/* 配置定时器的自动重装载值,定时器每溢出1次,表示时间过了65.536ms */
#define BSP_TIMER3_PERIOD (65536 - 1)
//输入捕获的相关的中断配置
/* 中断标志位:通道0中断 */
#define BSP_INPUT_CAPTURE1_TIMER_INT_FLAG TIMER_INT_CH0
/* 中断源:定时器3中断 */
#define BSP_INPUT_CAPTURE1_TIMER_INT_IRQn TIMER3_IRQn
/* 中断服务函数 */
#define BSP_INPUT_CAPTURE1_TIMER_INT_IRQHandler TIMER3_IRQHandler
/************************************************
函数名称 : input_capture_gpio_config
功 能 : 初始化输入捕获1,会使用到定时器3
参 数 : 无
返 回 值 : 无
作 者 : 不想写代码的我
*************************************************/
void input_capture_gpio_config(void)
{
//GPIO配置
/* 开启时钟 */
rcu_periph_clock_enable(BSP_INPUT_CAPTURE1_RCU);
/* GPIO模式配置: 复用模式,无上下拉 */
gpio_mode_set(BSP_INPUT_CAPTURE1_PORT,GPIO_MODE_AF,GPIO_PUPD_NONE,BSP_INPUT_CAPTURE1_PIN);
/* 配置GPIO为定时器的通道 */
gpio_af_set(BSP_INPUT_CAPTURE1_PORT,BSP_INPUT_CAPTURE1_AF,BSP_INPUT_CAPTURE1_PIN);
//定时器配置
/* 定义定时器结构体 */
timer_parameter_struct timer_initpara;
/* 开启时钟 */
rcu_periph_clock_enable(BSP_INPUT_CAPTURE1_TIMER_RCU);
/* 设置定时器时钟为240Mhz,由于定时器3挂载在APB1总线上,
时钟在system_gd32f4xx.c中被初始化为SYSCLK/4,所以需要4倍频回240MHz */
rcu_timer_clock_prescaler_config(RCU_TIMER_PSC_MUL4);
/* 复位定时器 */
timer_deinit(BSP_INPUT_CAPTURE1_TIMER);
/* 初始化定时器结构体成员 */
timer_initpara.prescaler = BSP_TIMER3_PRESCALER; //预分配系数
timer_initpara.alignedmode = TIMER_COUNTER_EDGE; //边缘对齐
timer_initpara.counterdirection = TIMER_COUNTER_UP; //向上计数
timer_initpara.period = BSP_TIMER3_PERIOD; //自动重装载值
timer_initpara.clockdivision = TIMER_CKDIV_DIV1; //输入捕获时使用,数字滤波器使用的采样频率之间的分配比例
timer_initpara.repetitioncounter = 0; //重复计数器,重复X+2次才触发一次中断,高级定时器才有此功能
timer_init(BSP_INPUT_CAPTURE1_TIMER,&timer_initpara); //初始化定时器结构体成员
//定时器输入捕获
/* 定义定时器输入捕获配置结构体 */
timer_ic_parameter_struct timer_icinitpara;
/* 初始化定时器输入捕获配置结构体成员 */
timer_icinitpara.icpolarity = TIMER_IC_POLARITY_RISING; //捕获上升沿
timer_icinitpara.icselection = TIMER_IC_SELECTION_DIRECTTI; //直接输入捕获触发模式
timer_icinitpara.icprescaler = TIMER_IC_PSC_DIV1; //不分频
timer_icinitpara.icfilter = 0x00; //不滤波
/* 初始化定时器输入捕获结构体成员 */
timer_input_capture_config(BSP_INPUT_CAPTURE1_TIMER,BSP_INPUT_CAPTURE1_CHANNEL,&timer_icinitpara);
//使能定时器
/* 开启自动重装载 */
timer_auto_reload_shadow_enable(BSP_INPUT_CAPTURE1_TIMER);
/* 清除定时器使用通道的中断标志位 */
timer_interrupt_flag_clear(BSP_INPUT_CAPTURE1_TIMER,TIMER_INT_FLAG_CH0);
timer_interrupt_flag_clear(BSP_INPUT_CAPTURE1_TIMER,TIMER_INT_FLAG_UP);
/* 定时器通道0的中断使能 */
timer_interrupt_enable(BSP_INPUT_CAPTURE1_TIMER,TIMER_INT_CH0);
/* 定时器更新中断使能 */
timer_interrupt_enable(BSP_INPUT_CAPTURE1_TIMER,TIMER_INT_UP);
/* 使能定时器 */
timer_enable(BSP_INPUT_CAPTURE1_TIMER);
/* 配置中断优先级 */
nvic_irq_enable(BSP_INPUT_CAPTURE1_TIMER_INT_IRQn,0,0);
}
紧接着,编写中断服务函数。
3.2.3 中断服务函数
/************************************************
函数名称 : BSP_INPUT_CAPTURE1_TIMER_INT_IRQHandler
功 能 : 定时器3的中断回调函数
参 数 : 无
返 回 值 : 无
作 者 : 不想写代码的我
*************************************************/
void BSP_INPUT_CAPTURE1_TIMER_INT_IRQHandler(void)
{
static uint8_t edgeFlag = 0; //标志位为0,表示第一个边沿,为1表示第二个边沿
static uint32_t count = 0; //定时器溢出的次数
uint32_t readVal = 0; //定时器的计数值
/* 中断标志为更新中断 */
if(timer_interrupt_flag_get(BSP_INPUT_CAPTURE1_TIMER,TIMER_INT_UP) == SET)
{
/* 清除中断标志位 */
timer_interrupt_flag_clear(BSP_INPUT_CAPTURE1_TIMER,TIMER_INT_UP);
if(edgeFlag == 1)
{
count++;
}
}
/* 捕获到上升沿 */
if(timer_interrupt_flag_get(BSP_INPUT_CAPTURE1_TIMER,BSP_INPUT_CAPTURE1_TIMER_INT_FLAG) == SET)
{
/* 清除中断标志位 */
timer_interrupt_flag_clear(BSP_INPUT_CAPTURE1_TIMER,BSP_INPUT_CAPTURE1_TIMER_INT_FLAG);
if(edgeFlag == 0)
{
/* 第一个上升沿,清零定时器的计数值 */
timer_counter_value_config(BSP_INPUT_CAPTURE1_TIMER,0);
edgeFlag = 1;
}
if(edgeFlag == 1)
{
/* 第二个上升沿,获取定时器的计数值 */
readVal = timer_channel_capture_value_register_read(BSP_INPUT_CAPTURE1_TIMER,BSP_INPUT_CAPTURE1_CHANNEL);
/* 计算频率,这里1000,000是定时器1240分频之后的频率*/
fre1 = 1000000 / ((65535 * count) + readVal);
edgeFlag = 0;
count = 0;
}
}
}
还有获取测量频率的API函数:
static float fre1 = 0;
/************************************************
函数名称 : getFre1
功 能 : 获取输入捕获测量的频率
参 数 : 无
返 回 值 : 无
作 者 : 不想写代码的我
*************************************************/
float getFre1(void)
{
return fre1;
}
#endif /* USING_INPUT_CAPTURE1 */
以上就是输入捕获测量频率的具体实现,下面是对应的bsp文件(我把这里的输入捕获和3.2.5中的PWM输出,放在放在了一起)
3.2.4 bsp_base_timer文件(1,2,3,带有输入捕获和两路PWM输出)
- bsp_base_timer.c
#include "bsp_general_timer.h"
static float fre1 = 0;
/***********************************PWM信号输出**********************************************/
#if USING_PWM1 || USING_PWM2
/************************************************
函数名称 : pwm_gpio_config
功 能 : 初始化PWM信号的产生,可能会用到定时器1和2
参 数 : 无
返 回 值 : 无
作 者 : 不想写代码的我
*************************************************/
void pwm_gpio_config(void)
{
/* 如果开启了PWM1信号功能,就初始化PWM1信号的相关内容,包括gpio和定时器配置 */
#if USING_PWM1
pwm1_gpio_config(); //初始化gpio和定时器1
#endif /* USING_PWM1 */
/* 如果开启了PWM2信号功能,就初始化PWM2信号的相关内容,包括gpio和定时器配置 */
#if USING_PWM2
pwm2_gpio_config(); //初始化gpio和定时器1
#endif /* USING_PWM2 */
}
#endif /* USING_PWM1 || USING_PWM2 */
#if USING_PWM1
/************************************************
函数名称 : pwm1_gpio_config
功 能 : 初始化PWM1信号的产生,使用定时器1的通道0,可选择输出到PA0、PA5、PA15,具体配置见bsp_general_timer.h
参 数 : 无
返 回 值 : 无
作 者 : 不想写代码的我
*************************************************/
static void pwm1_gpio_config(void)
{
//GPIO配置
/* 开启时钟 */
rcu_periph_clock_enable(BSP_PWM1_RCU);
/* GPIO模式配置: 复用模式,无上下拉 */
gpio_mode_set(BSP_PWM1_PORT,GPIO_MODE_AF,GPIO_PUPD_NONE,BSP_PWM1_PIN);
/* GPIO输出配置:推挽输出,超级高速 */
gpio_output_options_set(BSP_PWM1_PORT,GPIO_OTYPE_PP,GPIO_OSPEED_50MHZ,BSP_PWM1_PIN);
/* 配置GPIO为定时器的通道 */
gpio_af_set(BSP_PWM1_PORT,BSP_PWM1_AF,BSP_PWM1_PIN);
//定时器配置
/* 定义定时器结构体 */
timer_parameter_struct timer_initpara;
/* 开启时钟 */
rcu_periph_clock_enable(BSP_PWM1_TIMER_RCU);
/* 设置定时器时钟为240Mhz,由于定时器1挂载在APB1总线上,
时钟在system_gd32f4xx.c中被初始化为SYSCLK/4,所以需要4倍频回240MHz */
rcu_timer_clock_prescaler_config(RCU_TIMER_PSC_MUL4);
/* 复位定时器 */
timer_deinit(BSP_PWM1_TIMER);
/* 初始化定时器结构体成员 */
timer_initpara.prescaler = BSP_TIMER1_PRESCALER; //预分配系数
timer_initpara.alignedmode = TIMER_COUNTER_EDGE; //边缘对齐
timer_initpara.counterdirection = TIMER_COUNTER_UP; //向上计数
timer_initpara.period = BSP_TIMER1_PERIOD; //自动重装载值
timer_initpara.clockdivision = TIMER_CKDIV_DIV1; //输入捕获时使用,数字滤波器使用的采样频率之间的分配比例
timer_initpara.repetitioncounter = 0; //重复计数器,重复X+1次才触发一次中断,高级定时器才有此功能
timer_init(BSP_PWM1_TIMER,&timer_initpara); //初始化定时器结构体成员
/* 使能定时器 */
timer_enable(BSP_PWM1_TIMER);
//定时器输出配置
/* 定义定时器比较输出结构体 */
timer_oc_parameter_struct timer_ocintpara;
/* 初始化定时器比较输出结构体成员 */
timer_ocintpara.ocpolarity = TIMER_OC_POLARITY_HIGH; //有效电平为高电平
timer_ocintpara.outputstate = TIMER_CCX_ENABLE; //通道的输出状态:使能PWM1输出到端口
/* 此结构体的其他功能只有高级定时器才会使用,所以不需要配置 */
/* 初始化定时器输出结构体 */
timer_channel_output_config(BSP_PWM1_TIMER,BSP_PWM1_CHANNEL,&timer_ocintpara);
/* 配置占空比,占空比 = 5000 / 10000,其中5000是我们给的,10000是定时器的自动重装载值 */
timer_channel_output_pulse_value_config(BSP_PWM1_TIMER,BSP_PWM1_CHANNEL,BSP_TIMER1_PULSE);
/* 配置定时器的输出比较模式,这里是PWM10模式 */
timer_channel_output_mode_config(BSP_PWM1_TIMER,BSP_PWM1_CHANNEL,TIMER_OC_MODE_PWM0);
/* 配置输出比较影子寄存器的功能,这里是禁止输出比较寄存器 */
timer_channel_output_shadow_config(BSP_PWM1_TIMER,BSP_PWM1_CHANNEL,TIMER_OC_SHADOW_DISABLE);
/* 只有高级定时器使用 */
// timer_primary_output_config(TIMER0,ENABLE);
/* 自动重装载影子寄存器使能 */
timer_auto_reload_shadow_enable(BSP_PWM1_TIMER);
/* 使能定时器 */
timer_enable(BSP_PWM1_TIMER);
}
#endif /* USING_PWM1 */
#if USING_PWM2
/************************************************
函数名称 : pwm2_gpio_config
功 能 : 初始化PWM1信号的产生,使用定时器2的通道0,可选择输出到PA6、PB4、PC6,具体配置见bsp_general_timer.h
参 数 : 无
返 回 值 : 无
作 者 : 不想写代码的我
*************************************************/
static void pwm2_gpio_config(void)
{
//GPIO配置
/* 开启时钟 */
rcu_periph_clock_enable(BSP_PWM2_RCU);
/* GPIO模式配置: 复用模式,无上下拉 */
gpio_mode_set(BSP_PWM2_PORT,GPIO_MODE_AF,GPIO_PUPD_NONE,BSP_PWM2_PIN);
/* GPIO输出配置:推挽输出,超级高速 */
gpio_output_options_set(BSP_PWM2_PORT,GPIO_OTYPE_PP,GPIO_OSPEED_50MHZ,BSP_PWM2_PIN);
/* 配置GPIO为定时器的通道 */
gpio_af_set(BSP_PWM2_PORT,BSP_PWM2_AF,BSP_PWM2_PIN);
//定时器配置
/* 定义定时器结构体 */
timer_parameter_struct timer_initpara;
/* 开启时钟 */
rcu_periph_clock_enable(BSP_PWM2_TIMER_RCU);
/* 设置定时器时钟为240Mhz,由于定时器2挂载在APB1总线上,
时钟在system_gd32f4xx.c中被初始化为SYSCLK/4,所以需要4倍频回240MHz */
rcu_timer_clock_prescaler_config(RCU_TIMER_PSC_MUL4);
/* 复位定时器 */
timer_deinit(BSP_PWM2_TIMER);
/* 初始化定时器结构体成员 */
timer_initpara.prescaler = BSP_TIMER2_PRESCALER; //预分配系数
timer_initpara.alignedmode = TIMER_COUNTER_EDGE; //边缘对齐
timer_initpara.counterdirection = TIMER_COUNTER_UP; //向上计数
timer_initpara.period = BSP_TIMER2_PERIOD; //自动重装载值
timer_initpara.clockdivision = TIMER_CKDIV_DIV1; //输入捕获时使用,数字滤波器使用的采样频率之间的分配比例
timer_initpara.repetitioncounter = 0; //重复计数器,重复X+2次才触发一次中断,高级定时器才有此功能
timer_init(BSP_PWM2_TIMER,&timer_initpara); //初始化定时器结构体成员
/* 使能定时器 */
timer_enable(BSP_PWM2_TIMER);
//定时器输出配置
/* 定义定时器比较输出结构体 */
timer_oc_parameter_struct timer_ocintpara;
/* 初始化定时器比较输出结构体成员 */
timer_ocintpara.ocpolarity = TIMER_OC_POLARITY_HIGH; //有效电平为高电平
timer_ocintpara.outputstate = TIMER_CCX_ENABLE; //通道的输出状态:使能PWM2输出到端口
/* 此结构体的其他功能只有高级定时器才会使用,所以不需要配置 */
/* 初始化定时器输出结构体 */
timer_channel_output_config(BSP_PWM2_TIMER,BSP_PWM2_CHANNEL,&timer_ocintpara);
/* 配置占空比,占空比 = 500 / 1000,其中500是我们给的,1000是定时器的自动重装载值 */
timer_channel_output_pulse_value_config(BSP_PWM2_TIMER,BSP_PWM2_CHANNEL,BSP_TIMER2_PULSE);
/* 配置定时器的输出比较模式,这里是PWM2模式 */
timer_channel_output_mode_config(BSP_PWM2_TIMER,BSP_PWM2_CHANNEL,TIMER_OC_MODE_PWM0);
/* 配置输出比较影子寄存器的功能,这里是禁止输出比较寄存器 */
timer_channel_output_shadow_config(BSP_PWM2_TIMER,BSP_PWM2_CHANNEL,TIMER_OC_SHADOW_DISABLE);
/* 只有高级定时器使用 */
// timer_primary_output_config(TIMER0,ENABLE);
/* 自动重装载影子寄存器使能 */
timer_auto_reload_shadow_enable(BSP_PWM2_TIMER);
/* 使能定时器 */
timer_enable(BSP_PWM2_TIMER);
}
#endif /* USING_PWM2 */
#if USING_PWM1 || USING_PWM2
/************************************************
函数名称 : pwm_duty_change
功 能 : 改变PWM信号的占空比,可外部调用
参 数 : 无
返 回 值 : 无
作 者 : 不想写代码的我
*************************************************/
void pwm_duty_change(void)
{
static uint8_t direct = 0; // 方向
static uint16_t value = 0; // 脉冲值
if(direct == 0) // 逐渐变亮
{
value += 500; // 值越大 越亮
if(value > 10000)
direct = 1; // 改变方向
}else // 逐渐变暗
{
value -= 500; // 值越小 越暗
if(value <= 0)
direct = 0;
}
/* 开启了PWM1信号的产生 */
#if USING_PWM1
timer_channel_output_pulse_value_config(BSP_PWM1_TIMER,BSP_PWM1_CHANNEL,value); // 配置定时器通道输出脉冲值
printf("Duty = %d\r\n",value/100);
#endif /* USING_PWM1 */
/* 开启了PWM2信号的产生 */
#if USING_PWM2
timer_channel_output_pulse_value_config(BSP_PWM2_TIMER,BSP_PWM2_CHANNEL,value/10); // 配置定时器通道输出脉冲值
printf("Duty = %d\r\n",value/10);
#endif /* USING_PWM1 */
// 延时50ms
}
#endif /* USING_PWM1 || USING_PWM2 */
/***********************************输入捕获1**********************************************/
#if USING_INPUT_CAPTURE1
/************************************************
函数名称 : input_capture_gpio_config
功 能 : 初始化输入捕获1,会使用到定时器3
参 数 : 无
返 回 值 : 无
作 者 : 不想写代码的我
*************************************************/
void input_capture_gpio_config(void)
{
//GPIO配置
/* 开启时钟 */
rcu_periph_clock_enable(BSP_INPUT_CAPTURE1_RCU);
/* GPIO模式配置: 复用模式,无上下拉 */
gpio_mode_set(BSP_INPUT_CAPTURE1_PORT,GPIO_MODE_AF,GPIO_PUPD_NONE,BSP_INPUT_CAPTURE1_PIN);
/* 配置GPIO为定时器的通道 */
gpio_af_set(BSP_INPUT_CAPTURE1_PORT,BSP_INPUT_CAPTURE1_AF,BSP_INPUT_CAPTURE1_PIN);
//定时器配置
/* 定义定时器结构体 */
timer_parameter_struct timer_initpara;
/* 开启时钟 */
rcu_periph_clock_enable(BSP_INPUT_CAPTURE1_TIMER_RCU);
/* 设置定时器时钟为240Mhz,由于定时器3挂载在APB1总线上,
时钟在system_gd32f4xx.c中被初始化为SYSCLK/4,所以需要4倍频回240MHz */
rcu_timer_clock_prescaler_config(RCU_TIMER_PSC_MUL4);
/* 复位定时器 */
timer_deinit(BSP_INPUT_CAPTURE1_TIMER);
/* 初始化定时器结构体成员 */
timer_initpara.prescaler = BSP_TIMER3_PRESCALER; //预分配系数
timer_initpara.alignedmode = TIMER_COUNTER_EDGE; //边缘对齐
timer_initpara.counterdirection = TIMER_COUNTER_UP; //向上计数
timer_initpara.period = BSP_TIMER3_PERIOD; //自动重装载值
timer_initpara.clockdivision = TIMER_CKDIV_DIV1; //输入捕获时使用,数字滤波器使用的采样频率之间的分配比例
timer_initpara.repetitioncounter = 0; //重复计数器,重复X+2次才触发一次中断,高级定时器才有此功能
timer_init(BSP_INPUT_CAPTURE1_TIMER,&timer_initpara); //初始化定时器结构体成员
//定时器输入捕获
/* 定义定时器输入捕获配置结构体 */
timer_ic_parameter_struct timer_icinitpara;
/* 初始化定时器输入捕获配置结构体成员 */
timer_icinitpara.icpolarity = TIMER_IC_POLARITY_RISING; //捕获上升沿
timer_icinitpara.icselection = TIMER_IC_SELECTION_DIRECTTI; //直接输入捕获触发模式
timer_icinitpara.icprescaler = TIMER_IC_PSC_DIV1; //不分频
timer_icinitpara.icfilter = 0x00; //不滤波
/* 初始化定时器输入捕获结构体成员 */
timer_input_capture_config(BSP_INPUT_CAPTURE1_TIMER,BSP_INPUT_CAPTURE1_CHANNEL,&timer_icinitpara);
//使能定时器
/* 开启自动重装载 */
timer_auto_reload_shadow_enable(BSP_INPUT_CAPTURE1_TIMER);
/* 清除定时器使用通道的中断标志位 */
timer_interrupt_flag_clear(BSP_INPUT_CAPTURE1_TIMER,TIMER_INT_FLAG_CH0);
timer_interrupt_flag_clear(BSP_INPUT_CAPTURE1_TIMER,TIMER_INT_FLAG_UP);
/* 定时器通道0的中断使能 */
timer_interrupt_enable(BSP_INPUT_CAPTURE1_TIMER,TIMER_INT_CH0);
/* 定时器更新中断使能 */
timer_interrupt_enable(BSP_INPUT_CAPTURE1_TIMER,TIMER_INT_UP);
/* 使能定时器 */
timer_enable(BSP_INPUT_CAPTURE1_TIMER);
/* 配置中断优先级 */
nvic_irq_enable(BSP_INPUT_CAPTURE1_TIMER_INT_IRQn,0,0);
}
/************************************************
函数名称 : BSP_INPUT_CAPTURE1_TIMER_INT_IRQHandler
功 能 : 定时器3的中断回调函数
参 数 : 无
返 回 值 : 无
作 者 : 不想写代码的我
*************************************************/
void BSP_INPUT_CAPTURE1_TIMER_INT_IRQHandler(void)
{
static uint8_t edgeFlag = 0; //标志位为0,表示第一个边沿,为1表示第二个边沿
static uint32_t count = 0; //定时器溢出的次数
uint32_t readVal = 0; //定时器的计数值
/* 中断标志为更新中断 */
if(timer_interrupt_flag_get(BSP_INPUT_CAPTURE1_TIMER,TIMER_INT_UP) == SET)
{
/* 清除中断标志位 */
timer_interrupt_flag_clear(BSP_INPUT_CAPTURE1_TIMER,TIMER_INT_UP);
if(edgeFlag == 1)
{
count++;
}
}
/* 捕获到上升沿 */
if(timer_interrupt_flag_get(BSP_INPUT_CAPTURE1_TIMER,BSP_INPUT_CAPTURE1_TIMER_INT_FLAG) == SET)
{
/* 清除中断标志位 */
timer_interrupt_flag_clear(BSP_INPUT_CAPTURE1_TIMER,BSP_INPUT_CAPTURE1_TIMER_INT_FLAG);
if(edgeFlag == 0)
{
/* 第一个上升沿,清零定时器的计数值 */
timer_counter_value_config(BSP_INPUT_CAPTURE1_TIMER,0);
edgeFlag = 1;
}
if(edgeFlag == 1)
{
/* 第二个上升沿,获取定时器的计数值 */
readVal = timer_channel_capture_value_register_read(BSP_INPUT_CAPTURE1_TIMER,BSP_INPUT_CAPTURE1_CHANNEL);
/* 计算频率,这里1000,000是定时器1240分频之后的频率*/
fre1 = 1000000 / ((65535 * count) + readVal);
edgeFlag = 0;
count = 0;
}
}
}
/************************************************
函数名称 : getFre1
功 能 : 获取输入捕获测量的频率
参 数 : 无
返 回 值 : 无
作 者 : 不想写代码的我
*************************************************/
float getFre1(void)
{
return fre1;
}
#endif /* USING_INPUT_CAPTURE1 */
【注】:这个代码时我在梁山派(GD32F470ZGT6)上写的,还没有对其他芯片做兼容,但是大部分应该都没问题。
- bsp_base_timer.h
#ifndef _BSP_GENERAL_TIMER_H
#define _BSP_GENERAL_TIMER_H
#include "gd32f4xx.h"
#include "systick.h"
#include "bsp_uart.h"
#include "bsp_led.h"
/* 这里用来控制PWM信号的产生与关闭,以及具体那个PWM信号产生,可以多个选项同时选择 */
#define USING_PWM1 1
#define USING_PWM2 0
/* 这里用来控制是否使用定时器3的通道0测量频率,默认使用的IO口为PB6 */
#define USING_INPUT_CAPTURE1 1
/***********************************PWM1信号输出**********************************************/
#if USING_PWM1
//PWM1信号的相关配置:可选PA0、PA5、PA15选择复用通道1,使用定时器1通道0
/* PWM1输出引脚的时钟*/
#define BSP_PWM1_RCU RCU_GPIOA
/* PWM1输出引脚的端口*/
#define BSP_PWM1_PORT GPIOA
/* PWM1输出引脚 */
#define BSP_PWM1_PIN GPIO_PIN_5
/* GPIO的复用通道 */
#define BSP_PWM1_AF GPIO_AF_1
//需要修改PWM1输出的引脚,只需要修改时钟,端口和引脚即可,这里默认PA5。
/* TIMER1_CH0配置输出PWM1信号 */
#define BSP_PWM1_TIMER_RCU RCU_TIMER1
#define BSP_PWM1_TIMER TIMER1
#define BSP_PWM1_CHANNEL TIMER_CH_0
/* 配置定时器的预分配系数 */
#define BSP_TIMER1_PRESCALER (240 - 1)
/* 配置定时器的自动重装载值 */
#define BSP_TIMER1_PERIOD (10000 - 1)
/* PWM1信号的频率 = 240M /( (pre + 1) * ( per + 1 ) ),这里就是100Hz */
/* 配置定时器的默认脉冲值,占空比 = 脉冲值 / 自动重装载值,
后续修改占空比也是通过修改这个实现的,注意脉冲值不能大于自动重装载值 */
#define BSP_TIMER1_PULSE (5000-1)
#endif /* USING_PWM1 */
/***********************************PWM2信号输出**********************************************/
#if USING_PWM2
//PWM2信号的相关配置:可选PA6、PB4、PC6选择复用通道2,使用定时器2通道0
/* PWM2输出引脚的时钟*/
#define BSP_PWM2_RCU RCU_GPIOA
/* PWM2输出引脚的端口*/
#define BSP_PWM2_PORT GPIOA
/* PWM2输出引脚*/
#define BSP_PWM2_PIN GPIO_PIN_6
/* GPIO的复用通道 */
#define BSP_PWM2_AF GPIO_AF_2
//需要修改PWM2输出的引脚,只需要修改时钟,端口和引脚即可,这里默认PA6。
/* TIMER2_CH0配置输出PWM2信号 */
#define BSP_PWM2_TIMER_RCU RCU_TIMER2
#define BSP_PWM2_TIMER TIMER2
#define BSP_PWM2_CHANNEL TIMER_CH_0
/* 配置定时器的预分配系数 */
#define BSP_TIMER2_PRESCALER (240 - 1)
/* 配置定时器的自动重装载值 */
#define BSP_TIMER2_PERIOD (1000 - 1)
/* PWM1信号的频率 = 240M /( (pre + 1) * ( per + 1 ) ),这里就是1000Hz */
/* 配置定时器的默认脉冲值,占空比 = 脉冲值 / 自动重装载值,
后续修改占空比也是通过修改这个实现的,注意脉冲值不能大于自动重装载值 */
#define BSP_TIMER2_PULSE (500-1)
#endif /* USING_PWM2 */
/***********************************输入捕获1**********************************************/
#if USING_INPUT_CAPTURE1
//输入捕获的相关配置:可选PB6和PD12选择复用通道2,使用定时器3通道0
/* 输入捕获引脚的时钟 */
#define BSP_INPUT_CAPTURE1_RCU RCU_GPIOB
/* 输入捕获引脚的端口 */
#define BSP_INPUT_CAPTURE1_PORT GPIOB
/* 输入捕获引脚的引脚 */
#define BSP_INPUT_CAPTURE1_PIN GPIO_PIN_6
/* 输入捕获引脚的复用通道 */
#define BSP_INPUT_CAPTURE1_AF GPIO_AF_2
//需要修改输入捕获的引脚引脚,只需要修改时钟,端口和引脚即可,这里默认PB6。
/* TIMER3_CH0配置输入捕获功能 */
#define BSP_INPUT_CAPTURE1_TIMER_RCU RCU_TIMER3
#define BSP_INPUT_CAPTURE1_TIMER TIMER3
#define BSP_INPUT_CAPTURE1_CHANNEL TIMER_CH_0
/* 配置定时器的预分配系数 */
#define BSP_TIMER3_PRESCALER (240 - 1) //这里分频完是1MHz,1us定时器加1
/* 配置定时器的自动重装载值,定时器每溢出1次,表示时间过了65.536ms */
#define BSP_TIMER3_PERIOD (65536 - 1)
//输入捕获的相关的中断配置
/* 中断标志位:通道0中断 */
#define BSP_INPUT_CAPTURE1_TIMER_INT_FLAG TIMER_INT_CH0
/* 中断源:定时器3中断 */
#define BSP_INPUT_CAPTURE1_TIMER_INT_IRQn TIMER3_IRQn
/* 中断服务函数 */
#define BSP_INPUT_CAPTURE1_TIMER_INT_IRQHandler TIMER3_IRQHandler
#endif /* USING_INPUT_CAPTURE1 */
/***********************************PWM信号输出**********************************************/
/* 初始化PWM信号的产生,可能会用到定时器1和2 */
void pwm_gpio_config(void);
/* 改变PWM信号的占空比,可外部调用 */
void pwm_duty_change(void);
/* 初始化PWM1信号的产生,使用定时器1的通道0,可选择输出到PA0、PA5、PA15 */
static void pwm1_gpio_config(void);
/* 初始化PWM1信号的产生,使用定时器2的通道0,可选择输出到PA6、PB4、PC6 */
static void pwm2_gpio_config(void);
/***********************************输入捕获**********************************************/
/* 初始化输入捕获测量频率功能,使用定时器3的通道0,可选择配置到PB6和PD12*/
void input_capture_gpio_config(void);
/* 获取频率接口函数 */
float getFre1(void);
#endif /* _BSP_GENERAL_TIMER_H */
【注】:这个代码时我在梁山派(GD32F470ZGT6)上写的,还没有对其他芯片做兼容,但是大部分应该都没问题。
3.2.5 输入捕获的使用示例
下面是一个简单的示例,基于3.2.4中的bsp_base_timer文件:
#include "gd32f4xx.h"
#include "systick.h"
#include <stdio.h>
#include "main.h"
#include "string.h"
#include "bsp_led.h"
#include "bsp_key.h"
#include "bsp_uart.h"
#include "bsp_base_timer.h"
#include "bsp_general_timer.h"
/*!
\brief main function
\param[in] none
\param[out] none
\retval none
*/
int main(void)
{
/* 配置中断优先级分组,4位抢占优先级,0位子优先级 */
nvic_priority_group_set(NVIC_PRIGROUP_PRE4_SUB0);
/* 配置sysTick的时钟和中断周期*/
systick_config();
/* 串口初始化 */
uart_gpio_config(115200);
/* 通用定时器产生PWM信号初始化,具体内容查询bsp_general_timer.h文件 */
pwm_gpio_config();
/* 通用定时器输入捕获功能初始化,具体内容查询bsp_general_timer.h文件 */
input_capture_gpio_config();
while(1)
{
delay_1ms(500);
printf("Fre1 = %.2f Hz\r\n",getFre1());
}
}
【注】:需要用杜邦线把PB6和PA5连接在一起:
运行结果如下:
4. 高级定时器
关于高级定时器,我这里先不做介绍了,不然篇幅太长了,后面有时间再补一下,这里先挖个坑。
标签:定时器,BSP,TIMER,详解,timer,PWM,PWM1,define From: https://blog.csdn.net/weixin_67907028/article/details/143455131以上就是本期的所有内容,创造不易,点个关注再走呗。