学习目标
- 理解基本定时器的作用
- 掌握定时器开发流程
- 掌握基本定时器中断处理的操作流程
- 掌握AHB和APB时钟查询方式
- 理解周期,分频系数,周期计数,分频计数。
-
学习内容
基本定时器
只能用于定时计时操作,没有输出引脚通道的定时器,在GD32中,
TIMER5
和TIMER6
为基本定时器。开发流程
添加Timer依赖
- 初始化Timer
-
Timer依赖添加
双击项目栏中的组
Firmware
,来到标准库源码目录下,添加gd32f4xx_timer.c
文件。
Timer初始化
static void TIMER_config() {
// 时钟配置
rcu_periph_clock_enable(RCU_TIMER5);
// 复位定时器
timer_deinit(TIMER5);
rcu_timer_clock_prescaler_config(RCU_TIMER_PSC_MUL4);
timer_parameter_struct tps;
timer_struct_para_init(&tps);
tps.prescaler = 10 - 1; // 分频系数 240 000 000
tps.period = SystemCoreClock / 10000 - 1; // 周期
timer_init(TIMER5, &tps);
nvic_irq_enable(TIMER5_DAC_IRQn, 2, 2);
timer_interrupt_enable(TIMER5, TIMER_INT_UP);
timer_enable(TIMER5);
}
Timer中断函数
void TIMER5_DAC_IRQHandler(void) {
if(SET == timer_interrupt_flag_get(TIMER5, TIMER_INT_UP)) {
// TODO:
}
//清除中断标志位
timer_interrupt_flag_clear(TIMER5,TIMER_INT_FLAG_UP);
}
倍频
要了解倍频,就得从总线和时钟树给大家说起。
AHB是高级高性能总线,时钟频率极高,在GD32F4中,可高达240MHZ。
我们的Timer作为外设,他并没有直接采用AHB总线,采用的是APB。根据结构框图,我们可以知道,14个Timer中,有的采用APB1,有的采用APB2。对于这两个外设总线,频率也各不相同。
AHB的总线频率,其实就是我们系统时钟频率。
APB外设总线,其实和系统时钟总线是会存在倍差的,我们的代码执行的依据是系统时钟。因此我们代码执行的时钟和APB外设的时钟也存在倍差。
简单理解,天上一年,地下一天。AHB的1秒钟,就是APB1的4秒,是APB2的2秒。
我们在初始化代码中:
rcu_timer_clock_prescaler_config(RCU_TIMER_PSC_MUL4);
进行了以上逻辑的倍频,倍频了4倍。
我们在此需要理解几个问题:
- 为什么要倍频?
- 为什么要倍频4倍?
我们要认识到,其实可以不需要倍频的。倍频的目的其实就是消除程序编码时钟频率切换带给程序员的理解和转换问题。
程序员都知道主频,但是不一定都知道某些外设有独立的频率,而且还和主频不同,想当然的理解为就是主频。
程序员这些频率都知道,但是每次都要去查一下,再算一下,麻烦。
因此,通常我们就直接倍频到和系统频率相同,再也不用去考虑一些繁琐的问题。
关心的内容
static void TIMER_config() {
uint32_t timerx = TIMER5; // 哪个定时器
uint32_t timerx_rcu = RCU_TIMER5;
uint32_t timerx_psc = RCU_TIMER_PSC_MUL4;// 倍频
uint32_t timerx_irq = TIMER5_DAC_IRQn;
uint32_t timerx_prescaler = 10000 - 1; // 分频计数
uint32_t timerx_period = SystemCoreClock / 10000 - 1; // 周期计数
/*************** Timer config **************/
// 时钟配置
rcu_periph_clock_enable(timerx_rcu);
// 复位定时器
timer_deinit(timerx);
// 倍频配置
rcu_timer_clock_prescaler_config(timerx_psc);
// 初始化定时器
timer_parameter_struct tps;
timer_struct_para_init(&tps);
tps.prescaler = timerx_prescaler;
tps.period = timerx_period;
timer_init(timerx, &tps);
// 中断优先级
nvic_irq_enable(timerx_irq, 2, 2);
timer_interrupt_enable(timerx, TIMER_INT_UP);
timer_enable(timerx);
}
- 哪个定时器
- 定时器倍频配置
- 定时器分频计数
- 定时器周期计数
重要的关键词
周期和频率
周期是时间单位,我们理解为在单位时间内,干一件事情的触发时间。
例如,1秒钟我们触发五次中断函数。
那么触发一次中断函数,就需要0.2秒。
这里的0.2秒钟就是一个周期。
周期和频率是反的,以上面的说法为例,0.2秒是一个周期,1秒钟就有5个这样的周期。周期 = 1 / 频率
频率 = 1 / 周期
周期计数
周期计数,是一个数值,是指我在一个周期时间内,数的累加数值是多少。
这里必须结合系统主频来说。
主频240M,指的是将1秒钟分为240M份。用数数来说,就是数240M次就是1秒钟。
周期是具体的时间值,例如一个周期是0.2秒
,按照主频切的份数,这个时间可以分到几份,或者用数数来说,能数几下。
周期计数就是表示这个份数的。
特别注意的是,其实我们写代码过程中,写入都是周期计数:
上面代码中,其实就是配置的周期计数,将主频切为10000份。理解起来就是如下公式:tps.period = SystemCoreClock / 10000 - 1;
值得注意的是,后面有进行减一操作,原因是,这个周期计数值最终会配置到芯片的寄存器中,芯片的寄存器计数累加的起点是0,不是1,所以我们需要减1。周期计数 = 系统主频 / 频率 - 1;
在这里特别需要注意的是,这个周期计数值是有范围的,这个范围取决于寄存器存储大小。GD32中timer的这个周期寄存器大小为16位,所以这个值不可以超过2的16次方,即65536 - 1 = 65535
当前芯片为240M,根据这个特点,我们大致上可以知道最小的频率是多少:频率 = 系统主频 / (周期计数 + 1);
240000000 / 65536 = 3662
分频系数和分频计数
什么是分频?
例如,我们说我们采用2分频。实际含义,原来1秒钟能干完的活,现在得2秒钟干完,这个就是分频。
分频系数,就是上面说所的2分频中的2。
分频计数,是(分频系数 - 1),这个是写入寄存器的,因为是从0开始计数,所以要减1
完整代码
#include "gd32f4xx.h"
#include "systick.h"
#include <stdio.h>
#include "main.h"
#include "Usart0.h"
void Usart0_recv(uint8_t *data, uint32_t len) {
}
static void TIMER_config() {
uint32_t timerx = TIMER5; // 哪个定时器
uint32_t timerx_rcu = RCU_TIMER5;
uint32_t timerx_psc = RCU_TIMER_PSC_MUL4;// 倍频
uint32_t timerx_irq = TIMER5_DAC_IRQn;
uint32_t timerx_prescaler = 10000 - 1; // 分频计数
uint32_t timerx_period = SystemCoreClock / 10000 - 1; // 周期计数
/*************** Timer config **************/
// 时钟配置
rcu_periph_clock_enable(timerx_rcu);
// 复位定时器
timer_deinit(timerx);
// 倍频配置
rcu_timer_clock_prescaler_config(timerx_psc);
// 初始化定时器
timer_parameter_struct tps;
timer_struct_para_init(&tps);
tps.prescaler = timerx_prescaler; // 分频系数 240 000 000
tps.period = timerx_period; // 周期
timer_init(timerx, &tps);
// 中断优先级
nvic_irq_enable(timerx_irq, 2, 2);
timer_interrupt_enable(timerx, TIMER_INT_UP);
timer_enable(timerx);
}
void TIMER5_DAC_IRQHandler(void) {
if(SET == timer_interrupt_flag_get(TIMER5, TIMER_INT_UP)) {
gpio_bit_toggle(GPIOD, GPIO_PIN_5);
// gpio_bit_set(GPIOD, GPIO_PIN_5);
}
//清除中断标志位
timer_interrupt_flag_clear(TIMER5,TIMER_INT_FLAG_UP);
}
static void GPIO_config() {
// 1. 时钟初始化
rcu_periph_clock_enable(RCU_GPIOD);
// 2. 配置GPIO 输入输出模式
gpio_mode_set(GPIOD, GPIO_MODE_OUTPUT, GPIO_PUPD_NONE, GPIO_PIN_5);
// 3. 配置GPIO 模式的操作方式
gpio_output_options_set(GPIOD, GPIO_OTYPE_PP, GPIO_OSPEED_MAX, GPIO_PIN_5);
}
int main(void)
{
systick_config();
nvic_priority_group_set(NVIC_PRIGROUP_PRE2_SUB2);
Usart0_init();
GPIO_config();
TIMER_config();
while(1) {
}
}
练习
- 调试Timer5 和 Timer6的定时器回调。