【GD32系列--基本定时器Timer + 定时1ms 灯光间隔1s闪烁例程】

这里写目录标题

一、定时器的作用

  1. 定时功能,设置不同时间长度的定时。(系统滴答定时器一般用来提供"心跳"作用)如延时操作、周期性任务调度、测试某段代码的执行时间等。
  2. 中断触发:定时器可以在达到设定的计数值时产生中断,从而触发中断服务程序。这种机制使得CPU可以在处理其他任务的同时,响应定时器的中断请求,执行相应的中断服务程序。这对于需要实时响应的应用场景非常有用,如实时数据采集、实时控制等。
  3. 与GPIO一起实现外设功能 。如PWM输入捕获、输出比较、DMA占空比等。其中PWM(脉冲宽度调制)输出,应用于控制电机、调节亮度、产生音频等场景的信号。通过配置定时器的PWM模式和相关参数,可以精确地控制PWM信号的频率占空比,从而实现对外部设备的精确控制。
  4. 外部事件计数:GD32F350的定时器还可以用于测量外部事件的频率或计数外部事件的发生次数。通过将定时器的输入连接到外部信号源,可以实现对外部信号的精确测量和计数。

二、定时器简介

1、定时器类型

以GD32为例,此款芯片共有8个定时器。分三大类:高级定时器、通用定时器和基本定时器。

2、时钟树

各定时器挂载总线不同。

3、定时器功能配置框图

(1)高级定时器

高级定时器(TIMER0)是四通道定时器,支持输入捕获和输出比较。可以产生PWM信号控制电机和电源管理。

高级定时器含有一个16位无符号计数器。

高级定时器是可编程的,可被用来计数,其外部事件可以驱动其他定时器

高级定时器包含了一个死区时间插入模块,非常适合电机控制。

定时器和定时器之间是相互独立,但是它们可以被同步在一起形成一个更大的定时器,这些定时器的计数器一致地增加。

(2)通用定时器

(3)基本定时器

基本定时器可以由内部时钟源CK_TIMER驱动。

基本定时器时钟内部连接到TIMER_CK。

基本定时器仅有一个时钟源TIMER_CK,用来驱动计数器预分频器。当CEN置位,TIMER_CK 经过预分频器(预分频值由TIMERx_PSC寄存器确定)产生PSC_CLK。

三、定时器寄存器分析

以常用的通用定时器为例。

1、控制寄存器(TIMERx_CTL0))

最低位 CEN 为计数器使能

0:计数器禁能

1:计数器使能

在软件将CEN位置1后,外部时钟、暂停模式和编码器模式才能工作。触发模式可

以自动地通过硬件设置CEN位。

第7位 ARSE 自动重载影子使能

0:禁能TIMERx_CAR寄存器的影子寄存器

1:使能TIMERx_CAR寄存器的影子寄存器

第8、9位 CKDIV[1:0] 时钟分频。

其中,使用定时器的前提是要使能时钟 enable a TIMER ,即TIMER_CTL0_CEN置1

此设置一般在timer_enable()函数里。

c 复制代码
TIMER_CTL0(timer_periph) |=(uint32_t)TIMER_CTL0_CEN;

2、DMA 和中断使能寄存器 (TIMERx_DMAINTEN)

最低位:UPIE 更新中断使能

0:禁止更新中断

1:使能更新中断

第1位:CH0IE 通道0比较/捕获中断使能

0:禁止通道0中断

1:使能通道0中断

第8位:UPDEN 更新DMA请求使能

0:禁止更新DMA请求

1:使能更新DMA请求

第9位:CH0DEN 通道0比较/捕获 DMA请求使能

0:禁止通道0比较/捕获DMA请求

1:使能通道0比较/捕获DMA请求

第14位 TRGDEN 触发DMA请求使能

0:禁止触发DMA请求

1:使能触发DMA请求

常用在enable the TIMER DMA 和 enable the TIMER interrupt 里。

c 复制代码
TIMER_DMAINTEN(timer_periph) |= (uint32_t)interrupt;//timer_interrupt_enable()
TIMER_DMAINTEN(timer_periph) |= (uint32_t)dma;//timer_dma_enable()

3、预分频寄存器 (TIMERx_PSC)

c 复制代码
TIMER_PSC(timer_periph) =(uint16_t)initpara->prescaler;//设置预分频值

常用在定时器的初始化中,如:timer_init()

4、计数器自动重载寄存器 (TIMERx_CAR)

计数器自动重载寄存器 (TIMERx_CAR)在物理上对应着两个寄存器。一个是可以直接操作的,一个是看不到的。看不到的那个寄存器叫影子寄存器,其实真正起作用的是影子寄存器。由控制寄存器(TIMERx_CTL0))中的ARSE位的设置

ARSE = 0:禁能TIMERx_CAR寄存器的影子寄存器。在这种情况下,对TIMERx_CAR寄存器的直接写操作将直接修改该寄存器的值,而没有备份或影子值。预装载寄存器的内容可随时传送到改寄存器,此时两者是相通的。

ARSE=1:使能TIMERx_CAR寄存器的影子寄存器,当启用影子寄存器时,对TIMERx_CAR寄存器的写操作实际上首先会更新影子寄存器的值。在某些特定的条件或事件(如硬件复位或特定的同步事件)发生时,影子寄存器的值会被复制到主寄存器中,从而确保主寄存器中的值始终是一个可靠和一致的备份。在每一次更新事件时,才把预装载寄存器的内容传送到影子寄存器,自动重装载寄存器的位描述如上所示。

5、中断标志寄存器 (TIMERx_INTF)

该寄存器用来标记当前与定时器相关的各种事件/中断是否发生。其位描述如下:

第7位 BRKIF 中止中断标志位

一旦中止输入有效,由硬件对该位置'1'。如果中止输入无效,则该位可由软件清'0'。

0:无中止事件产生

1:中止输入上检测到有效电平

最低位UPIF 更新中断标志

此位在任何更新事件发生时由硬件置1,软件清0。

0:无更新中断发生

1:发生更新中断

四、定时器的配置

通过库函数进行配置,相关的库函数有gd32f20x_timer.h 和 stm32120x_timer.c 文件。

(1)时钟使能

TIMERx是挂在APB1之下的,通过APB1总线下的使能函数来使能TIMERx。例如:

c 复制代码
 rcu_periph_clock_enable(RCU_TIMER5)

(2)初始化定时器参数

如设置自动重装值、分频系数 、计数方式等。

定时器的初始化参数是通过初始化函数timer_init实现的。例如:

c 复制代码
timer_init(TIMER5, &timer_initpara);

第一个参数:确定定时器;

第二个参数:定时器初始化化参数结构体指针,结构体类型为 timer_parameter_struct,其结构体定义为:

c 复制代码
typedef struct  
{  
    uint32_t prescaler;       // 预分频值  
    uint32_t alignedmode;     // 对齐模式  
    uint32_t countermode;     // 计数器模式  
    uint32_t period;          // 自动重载值  
    uint32_t clockdivision;   // 时钟分频因子  
    uint32_t repetitioncounter; // 重复计数器值  
} timer_parameter_struct;

(3)设置允许更新中断。(即:TIMERx-DMAINTEN)

因为我们要使用TIMER5的更新中断,寄存器的相应位便可使能更新中断。在库函数里面定时器中断使能是通过timer-interrupt-enable()函数来实现的。

void timer_interrupt_enable(uint32_t timer_periph, uint32_t interrupt)

timer_periph:选择定时器,取值为TIMERx(x=0.13).

interrupt:用来指明使能的定时器中断的类型,定时器中断的类型有很多种,包括TIMER-INTUP、TIMER_INT_TRG、TIMER_INT_BRK、TIMER_INT_CH0等。

例如:

c 复制代码
timer_interrupt_enable(TIMER5,TIMER_INT_UP);

(4)中断优先级设置。

定时器中断使能后,因为要产生中断,所以要设置中断NVIC相关寄存器,设置中断优先级。

例如:

c 复制代码
void timer1_nvic_config(void)
{
    nvic_priority_group_set(NVIC_PRIGROUP_PRE1_SUB3);
    nvic_irq_enable(TIMER5_IRQn, 0, 1);
}

(5)使能定时器

允许定时器工作,即开启定时器。配置完定时器后要启动定时器,通过TIMER_CTL0的TIMER_CTL0_CEN来设置,通常是通过函数timer_enable(TIMERx)来实现。例如:

c 复制代码
timer_enable(TIMER5);

(6)编写中断服务函数

此函数的功能是用来处理定时器产生的相关中断。在中断产生后,通过状态寄存器的值去判断此次产生的中断属于什么类型。

然后,执行相关的操作,大多使用更新(溢出)中断,即中断标志寄存器TIMER_INTF的最低位。处理完中断之后应想TIMER_INTF最低位写零,来清楚中断标志。

来读取中断状态寄存器的值来判断中断类型的函数是:

timer_interrupt_flag_get

读取该函数的目的是:判断定时器的中断类型是否发生中断。例如:

判断定时器5是否发生更新(溢出)中断,方法为:

c 复制代码
if(SET == timer_interrupt_flag_get(TIMER1,TIMER_INT_UP))
{
    timer_interrupt_flag_clear(TIMER1,TIMER_INT_UP);//清除中断标志位
    
    //其它程序
    ......

}

五、定时器核心配置示例(定时1ms)

c 复制代码
/*
    brief      configure the TIMER peripheral
    param[in]  none
    param[out] none
    retval     none
  */
void timer5_init(void)
{
    /* TIMER5 configuration: generate 1ms
    SystemCoreClock =72MHZ
    TIMER1CLK = SystemCoreClock / 72= 1MHz */
    
    timer_parameter_struct timer_initpara; //定时器参数
     // 时钟使能 
    rcu_periph_clock_enable(RCU_TIMER1);  
    timer_deinit(TIMER5);

    /*初始化定时器参数*/
    /* TIMER1 configuration */
    timer_initpara.prescaler         = 71;
    timer_initpara.alignedmode       = TIMER_COUNTER_EDGE;
    timer_initpara.counterdirection  = TIMER_COUNTER_UP;
    timer_initpara.period            = 999;
    timer_initpara.clockdivision     = TIMER_CKDIV_DIV1;
    timer_init(TIMER5, &timer_initpara);

   timer_auto_reload_shadow_enable(TIME5);
   timer_interrupt_flag_clear(TIMER5,TIMER_INT_UP);
   
   /* 设置允许更新中断TIMERx-DMAINTEN*/ 
   timer_interrupt_enable(TIMER5,TIMER_INT_UP); 
  
    /*中断优先级设置*/
    //TIMER1 interrupt setting, preemptive priority 0, sub-priority 3
    nvic_priority_group_set(NVIC_PRIGROUP_PRE1_SUB3);
    nvic_irq_enable(TIMER5_IRQn, 0, 3); 
    
   /* 使能定时器*/
    timer_enable(TIMER5);   

   // rcu_periph_clock_disable(RCU_TIMER5);/*先关闭等待使用*/ 
}

/** 中断服务程序
  * @brief  This function handles TIMER1 interrupt request.
  * @param  None
  * @retval None
  */
void TIMER5_IRQHandler(void)
{
  if ( SET == timer_interrupt_flag_get(TIMER5 , TIMER_INT_UP) ) 
  {
     timer_interrupt_flag_clear(TIMER5 , TIMER_INT_UP);
     time++;
  }
}

/*主函数*/
volatile uint32_t time = 0; // ms 计时变量 
int main(void)
{
    //system clocks configuration
    systemclk_init();
 
    /* configure the TIMER peripheral */
    timer5_init();
    
    /* configure LED1 GPIO port */
    led_init(LED1);

    //Enable TIMER1 clock
   // rcu_periph_clock_enable(RCU_TIMER1);

    while(1) 
    {
         if ( time ==1000 ) /* 1000 * 1 ms = 1s 时间到 */
        {
              time = 0;
              /* LED 取反 */      
              led_toggle(LED1); 
           
        }        
    }
}

或者直接定时1s,进行使用。

c 复制代码
void TIME_Ms(uint32_t ms)
{
    timer_val = ms;
    while(timer_val);
}
void TIMER5_IRQHandler(void)
{
    if(SET == timer_interrupt_flag_get(TIMER5, TIMER_INT_FLAG_UP)){
        timer_interrupt_flag_clear(TIMER5, TIMER_INT_FLAG_UP);
        if(timer_val) --timer_val;
    }
}

/*主函数*/
volatile uint32_t  timer_val= 0; // ms 计时变量 
int main(void)
{
    //system clocks configuration
    systemclk_init();
 
    /* configure the TIMER peripheral */
    timer5_init();
    
    /* configure LED1 GPIO port */
    led_init(LED1);

    //Enable TIMER1 clock
   // rcu_periph_clock_enable(RCU_TIMER1);

    while(1) 
    {
         TIME_Ms(1000); /* 1000 * 1 ms = 1s 时间到 */
         led_toggle(LED1);    
    }
}

六、定时器定时(溢出)时间计算

1、定时器的时钟源。

(以下均以基本定时器time5为例,time5在APB1后分频所得)

定时器时钟CK_TIMER5经 APB1 预分频器后分频提供。

如果 APB1 预分频系数等于 1,则频率不变,否则频率乘以 2,库函数中 APB1 预分频的系数是 2,关注 RCU_APB1_CKAHB_DIV2;,即 PCLK1=36M,所以定时器时钟 CK_TIMER=36*2=72M,即和系统时钟的值相同。

参考:【GD32】_时钟架构及系统时钟频率配置

其时钟初始化代码在system_stm32f20x.c定义的,这里使用的默认配置,具体时钟设置函数是system_clock_72m_hxtal(),代码如下:

c 复制代码
static void system_clock_72m_hxtal(void)
{
    uint32_t timeout = 0U;
    uint32_t stab_flag = 0U;

    /* enable HXTAL */
    RCU_CTL |= RCU_CTL_HXTALEN;

    /* wait until HXTAL is stable or the startup time is longer than HXTAL_STARTUP_TIMEOUT */
    do {
        timeout++;
        stab_flag = (RCU_CTL & RCU_CTL_HXTALSTB);
    } while((0U == stab_flag) && (HXTAL_STARTUP_TIMEOUT != timeout));

    /* if fail */
    if(0U == (RCU_CTL & RCU_CTL_HXTALSTB)) {
        while(1) {
        }
    }

    /* HXTAL is stable */
    /* AHB = SYSCLK */
    RCU_CFG0 |= RCU_AHB_CKSYS_DIV1;
    /* APB2 = AHB/2 */
    RCU_CFG0 |= RCU_APB2_CKAHB_DIV2;
    /* APB1 = AHB/2 */
    RCU_CFG0 |= RCU_APB1_CKAHB_DIV2;

    /* CK_PLL = (CK_PREDIV0) * 9 = 72 MHz *///当晶振为8M时
    RCU_CFG0 &= ~(RCU_CFG0_PLLMF | RCU_CFG0_PLLDV | RCU_CFG0_PLLSEL);
    RCU_CFG0 |= (RCU_PLLSRC_HXTAL_IRC48M| RCU_PLL_MUL6);//选择外部晶振12M


    /* enable PLL1 */
    RCU_CTL |= RCU_CTL_PLL1EN;
    /* wait till PLL1 is ready */
    while((RCU_CTL & RCU_CTL_PLL1STB) == 0U) {
    }

    /* enable PLL */
    RCU_CTL |= RCU_CTL_PLLEN;

    /* wait until PLL is stable */
    while(0U == (RCU_CTL & RCU_CTL_PLLSTB)) {
    }

    /* select PLL as system clock */
    RCU_CFG0 &= ~RCU_CFG0_SCS;
    RCU_CFG0 |= RCU_CKSYSSRC_PLL;

    /* wait until PLL is selected as system clock */
    while(0U == (RCU_CFG0 & RCU_SCSS_PLL)) {
    }
}

2、定时器频率

TIMER5上的时钟为72Mhz,定时器分频系数为71,因此TIME5的频率为

CK_CNT=TIMERxCLK/(PSC+1)=1MHz

3、自动重装载值

Prtscaler (定时器分频系数) : 71

Counter Mode(计数模式) :Up(向上计数模式)

Counter Period(自动重装载值) : 999

CKD(时钟分频因子) : No Division 不分频

所以溢出时间Tout = 1/1MHZ *(999+1) =1ms

自动重装载值计算溢出时间要加1,这是因为自动重装载寄存器 TIMERx_CAR是从0开始计数的。

相关推荐
weixin_452600691 小时前
《青牛科技 GC6125:驱动芯片中的璀璨之星,点亮 IPcamera 和云台控制(替代 BU24025/ROHM)》
人工智能·科技·单片机·嵌入式硬件·新能源充电桩·智能充电枪
weixin_452600692 小时前
【青牛科技】14W 高保真音频放大电路——D2030
科技·单片机·嵌入式硬件·音视频·电动工具·智能电表
YuCaiH10 小时前
【STM32】USART串口数据包
笔记·stm32·单片机·嵌入式硬件
Kasen's experience12 小时前
STM32 GPIO 配置
stm32·单片机·嵌入式硬件
知行电子-12 小时前
Proteus中数码管动态扫描显示不全(已解决)
单片机·proteus·嵌入式
学习路上_write13 小时前
FPGA/Verilog,Quartus环境下if-else语句和case语句RT视图对比/学习记录
单片机·嵌入式硬件·qt·学习·fpga开发·github·硬件工程
非概念13 小时前
stm32学习笔记----51单片机和stm32单片机的区别
笔记·stm32·单片机·学习·51单片机
jjjxxxhhh12314 小时前
FPGA,使用场景,相比于单片机的优势
单片机·嵌入式硬件·fpga开发
无敌最俊朗@14 小时前
stm32学习之路——八种GPIO口工作模式
c语言·stm32·单片机·学习
EterNity_TiMe_15 小时前
【论文复现】STM32设计的物联网智能鱼缸
stm32·单片机·嵌入式硬件·物联网·学习·性能优化