Timer的配置和应用

一、引言

在嵌入式开发中,定时器(Timer) 是最常用、也是最容易出问题的外设之一。它看似简单,却几乎贯穿了所有系统:任务调度、PWM 控制、时间测量、信号捕获……只要涉及“时间”或“频率”的地方,就离不开定时器。

二、定时器常见的使用场景

1. 系统时基(定时中断)

  • 周期性中断,用于操作系统的心跳(如 1ms Tick)。

  • 应用案例:RTOS 调度、定时刷新 UI、传感器采样。

2. pwm波形输出

  • 通过调整占空比,灵活实现功率控制。

  • 应用案例:电机调速、LED 调光、舵机控制。

3. 输入捕获

  • 用于测量信号频率、周期或脉宽。

  • 应用案例:测速风扇转速、超声波测距

4. 输出比较

  • 定时器计数达到设定值时触发事件。

  • 应用案例:精确定时触发 DAC 输出波形。

5. 外部事件计数

  • 把定时器当作脉冲计数器使用。

  • 应用案例:计数生产线上传感器触发次数。

6. 编码器接口

  • 部分定时器支持直接连接增量型编码器。

  • 应用案例:电机转角、位移检测

三、定时器调试过程中常见问题与经验

1. 时钟配置问题

  • 定时器的时钟源来自 APB 总线。需要确认:

    • 系统时钟频率设置正确;

    • APB 分频不为 1 时,定时器时钟会自动加倍(容易忽略)。

  • 经验:调试定时器前,先打印一下 SystemCoreClockrcu_clock_freq_get(CK_APB1),确认实际频率,或者在datasheet里看使用的定时器属于哪一个时钟树上的,例如下面的时钟树,能够清楚知道是每个总线的频率。

2. PSC 和 ARR 配置错误

    定时周期公式:

T = \frac{(PSC + 1)X(ARR + 1)}{F_{timer}}

  • 常见错误:PSC 或 ARR 设置过小,导致溢出过快。

  • 经验:计算好目标定时时间后,用 printf 打印 PSC 和 ARR,避免配置偏差。

3. 中断优先级设置

  • 如果定时中断优先级过高,可能打断关键业务逻辑;过低则可能延迟执行。

  • 经验:RTOS 系统中,建议定时器中断优先级略高于普通外设中断,但低于系统关键中断(如 Systick)。

4. PWM 波形畸变

  • 高速 PWM 时,GPIO 驱动不足可能导致波形失真。

  • 经验

    • 使用高速 IO 模式;

    • 必要时加缓冲驱动电路

5. 输入捕获抖动

  • 外部信号毛刺可能引起误触发。

  • 经验:开启定时器输入滤波,或在硬件加 RC 滤波。

6. 编码器模式

  • 编码器 A/B 相位需要对应定时器输入通道,否则解码出错。

  • 经验:先用示波器确认编码器信号波形,再接入 MCU。

四、代码示例

1. 定时中断 1ms (LED 翻转)

void timer2_init(void)
{
    /* 开启定时器时钟 */
    rcu_periph_clock_enable(RCU_TIMER2);

    /* 定时器配置 */
    timer_parameter_struct timer_initpara;
    timer_deinit(TIMER2);
    timer_struct_para_init(&timer_initpara);
    timer_initpara.prescaler         = 60 -1;     // 分频系数 (60MHz / 60 = 1MHz)
    timer_initpara.alignedmode       = TIMER_COUNTER_EDGE;
    timer_initpara.counterdirection  = TIMER_COUNTER_UP;
    timer_initpara.period            = 1000-1;     // 自动重装载值 (1MHz / 1000 = 1kHz -> 1ms)
    timer_initpara.clockdivision     = TIMER_CKDIV_DIV1;
    timer_init(TIMER2, &timer_initpara);

    /* 开启中断 */
    timer_interrupt_enable(TIMER2, TIMER_INT_UP);
    nvic_irq_enable(TIMER2_IRQn, 1, 1);

    /* 启动定时器 */
    timer_enable(TIMER2);
}

/* 定时器2中断函数 */
void TIMER2_IRQHandler(void)
{
    if(RESET != timer_interrupt_flag_get(TIMER2, TIMER_INT_UP)){
        timer_interrupt_flag_clear(TIMER2, TIMER_INT_UP);

        /* 用户代码: 每 1ms 执行一次 */
        gpio_bit_toggle(GPIOC, GPIO_PIN_13); // 翻转 LED
    }
}

2. PWM 输出控制舵机

运行效果:舵机保持在中位(1.5ms 脉宽),通过改变 pulse_value (如 1000~2000)即可控制舵机转动角度。

void pwm_init(void)
{
    /* 开启时钟 */
    rcu_periph_clock_enable(RCU_GPIOA);
    rcu_periph_clock_enable(RCU_TIMER1);

    /* GPIO 配置为复用功能 */
    gpio_init(GPIOA, GPIO_MODE_AF_PP, GPIO_OSPEED_50MHZ, GPIO_PIN_1);

    /* 定时器配置 (50Hz -> 周期 20ms) */
    timer_parameter_struct timer_initpara;
    timer_deinit(TIMER1);
    timer_struct_para_init(&timer_initpara);
    timer_initpara.prescaler         = 120 -1;     // 120MHz / 120 = 1MHz
    timer_initpara.alignedmode       = TIMER_COUNTER_EDGE;
    timer_initpara.counterdirection  = TIMER_COUNTER_UP;
    timer_initpara.period            = 19999;   // 1MHz / 20000 = 50Hz
    timer_init(TIMER1, &timer_initpara);

    /* PWM 配置 (CH1) */
    timer_oc_parameter_struct timer_ocinitpara;
    timer_channel_output_struct_para_init(&timer_ocinitpara);
    timer_ocinitpara.outputstate = TIMER_CCX_ENABLE;
    timer_channel_output_config(TIMER1, TIMER_CH_1, &timer_ocinitpara);

    timer_channel_output_pulse_value_config(TIMER1, TIMER_CH_1, 1500); // 默认1.5ms占空比
    timer_channel_output_mode_config(TIMER1, TIMER_CH_1, TIMER_OC_MODE_PWM0);
    timer_channel_output_shadow_config(TIMER1, TIMER_CH_1, TIMER_OC_SHADOW_DISABLE);

    timer_enable(TIMER1);
}

3、定时器CH0上升沿输入捕获,CH2重置计数器输出pwm波

    timer_oc_parameter_struct timer_ocinitpara;
    timer_parameter_struct timer_initpara;
    timer_ic_parameter_struct timer_icinitpara;

    rcu_periph_clock_enable(RCU_TIMER1);

    timer_deinit(TIMER1);

    /* TIMER1 configuration */
    timer_initpara.prescaler         = 600-1;
    timer_initpara.alignedmode       = TIMER_COUNTER_EDGE;
    timer_initpara.counterdirection  = TIMER_COUNTER_UP;
    timer_initpara.period            = 100000;
    timer_initpara.clockdivision     = TIMER_CKDIV_DIV1;
    timer_initpara.repetitioncounter = 0;
    timer_init(TIMER1,&timer_initpara);

     /* CH2 configuration in OC PWM0 mode */
    timer_ocinitpara.ocpolarity   = TIMER_OC_POLARITY_HIGH;
    timer_ocinitpara.ocnpolarity  = TIMER_OCN_POLARITY_HIGH;
    timer_ocinitpara.outputstate  = TIMER_CCX_ENABLE;
    timer_ocinitpara.outputnstate = TIMER_CCXN_DISABLE;
    timer_ocinitpara.ocidlestate  = TIMER_OC_IDLE_STATE_LOW;
    timer_ocinitpara.ocnidlestate = TIMER_OCN_IDLE_STATE_LOW;
    timer_channel_output_config(TIMER1,TIMER_CH_2,&timer_ocinitpara);

    timer_channel_output_pulse_value_config(TIMER1,TIMER_CH_2,3999);
    timer_channel_output_mode_config(TIMER1,TIMER_CH_2,TIMER_OC_MODE_PWM0);
    timer_channel_output_shadow_config(TIMER1,TIMER_CH_2,TIMER_OC_SHADOW_DISABLE);

    /* TIMER1 CH0 input capture configuration */
    timer_icinitpara.icpolarity  = TIMER_IC_POLARITY_RISING;
    timer_icinitpara.icselection = TIMER_IC_SELECTION_DIRECTTI;
    timer_icinitpara.icprescaler = TIMER_IC_PSC_DIV1;
    timer_icinitpara.icfilter    = 0x02;
    timer_input_capture_config(TIMER1,TIMER_CH_0,&timer_icinitpara);

    /* slave mode selection : TIMER1 */
    /* TIMER1 input trigger : external trigger connected to CI0 */
    timer_input_trigger_source_select(TIMER1,TIMER_SMCFG_TRGSEL_CI0FE0);
    timer_slave_mode_select(TIMER1,TIMER_SLAVE_MODE_RESTART);

    /* auto-reload preload enable */
    timer_auto_reload_shadow_enable(TIMER1);
	
	timer_enable(TIMER1);

测试结果:可以发现,精度还是挺高的小于100ns,可以应用于一些高精度项目。

4. 定时器联动功能

定时器联动是需要根据手册来进行设计的,并不是任意两个都可以进行联动,比如我这边需要联动timer0和timer2,如下图有对应关系表:

timer0产生溢出信号后,会触发边沿信号到timer2

void timer0_timer2(void)
{
	timer_parameter_struct timer_initpara;
	rcu_periph_clock_enable(RCU_TIMER0);

    timer_deinit(TIMER0);

    /* TIMER0 configuration */
    timer_initpara.prescaler         = 0;
    timer_initpara.alignedmode       = TIMER_COUNTER_CENTER_UP;
    timer_initpara.counterdirection  = TIMER_COUNTER_UP;
    timer_initpara.period            = 12000-1;
    timer_initpara.clockdivision     = TIMER_CKDIV_DIV1;
    timer_initpara.repetitioncounter = 0;
    timer_init(TIMER0,&timer_initpara);
	
	   /* auto-reload preload enable */
    timer_auto_reload_shadow_enable(TIMER0);
    /* select the master slave mode */
    timer_master_slave_mode_config(TIMER0,TIMER_MASTER_SLAVE_MODE_ENABLE);
    /* TIMER0 update event is used as trigger output */
    timer_master_output_trigger_source_select(TIMER0,TIMER_TRI_OUT_SRC_UPDATE); 
	
	rcu_periph_clock_enable(RCU_TIMER2);
	    /* TIMER2  configuration */
    timer_deinit(TIMER2);

    timer_initpara.prescaler         = 0;
    timer_initpara.alignedmode       = TIMER_COUNTER_EDGE;
    timer_initpara.counterdirection  = TIMER_COUNTER_UP;
    timer_initpara.period            = 10000-1;
    timer_initpara.clockdivision     = TIMER_CKDIV_DIV1;
    timer_initpara.repetitioncounter = 0;
    timer_init(TIMER2,&timer_initpara);
	
	   /* auto-reload preload enable */
    timer_auto_reload_shadow_enable(TIMER2);
    /* slave mode selection: TIMER2 */
    timer_slave_mode_select(TIMER2,TIMER_SLAVE_MODE_EXTERNAL0);
    timer_input_trigger_source_select(TIMER2,TIMER_SMCFG_TRGSEL_ITI0);
	
	timer_enable(TIMER0);
	timer_enable(TIMER2);
	
	
	//timer_interrupt_enable(TIMER0,TIMER_INT_UP);
	timer_interrupt_enable(TIMER2,TIMER_INT_UP);

	 
	 nvic_config(TIMER2_IRQn,0,1);
}

GD32F105是GigaDevice公司推出的一款基于ARM Cortex-M3内核的32位通用微控制器。配置GD32F105的Timer定时器)涉及到几个关键步骤,主要包括定时器的初始化设置、计数器的启动中断处理等。以下是配置GD32F105 Timer的基本步骤: 1. 时钟配置:首先需要开启定时器的时钟。这通常是通过配置RCC(Reset and Clock Control)寄存器来实现的。 2. 定时器模式设置:根据需要选择定时器的工作模式,如定时器计数器模式(向上计数或向下计数)、PWM模式等。 3. 预分频器设置:通过设置预分频器值来确定定时器的时钟频率。预分频器值越大,定时器的计数频率就越低。 4. 自动重装载值设置:设置自动重装载寄存器的值,这个值决定了定时器溢出的时间点。定时器每次计数到这个值时会发生溢出事件。 5. 中断配置:如果需要使用定时器中断,需要配置相应的中断优先级,并在NVIC(Nested Vectored Interrupt Controller)中使能定时器的中断。 6. 定时器启动:最后,将定时器的控制寄存器中的使能位(例如,CEN位)设置为1,启动定时器计数。 以下是一个配置GD32F105 Timer的基础代码框架(假设使用的是GD32F10x标准库函数): ```c #include "gd32f10x.h" // 定时器时钟使能 void timer_clock_enable(void) { rcu_periph_clock_enable(RCU_TIMER); } // 定时器初始化函数 void timer_init(void) { // 设置定时器时钟分频 timer.prescaler = ...; // 设置定时器自动重装载值 timer.arr = ...; // 初始化定时器参数 timer_initpara_struct_init(&timer_initpara); timer_initpara.prescaler = timer.prescaler; timer_initpara.alignedmode = TIMER_COUNTER_EDGE; timer_initpara.period = timer.arr; timer_initpara.clockdivision = TIMER_CKDIV_DIV1; timer_initpara.repetitioncounter = 0; timer_init(TIMERx, &timer_initpara); // 定时器中断配置 nvic_irq_enable(TIMERx_IRQn, ... , ...); // 定时器启动 timer_enable(TIMERx); } int main(void) { // 时钟配置 timer_clock_enable(); // 定时器初始化 timer_init(); // 其他应用代码... while(1) { // 循环体代码... } } // 定时器中断服务函数 void TIMERx_IRQHandler(void) { if (timer_interrupt_flag_get(TIMERx, TIMER_INT_FLAG_UP) != RESET) { // 用户代码 // ... // 清除溢出中断标志位 timer_interrupt_flag_clear(TIMERx, TIMER_INT_FLAG_UP); } } ``` 在实际应用中,需要根据具体的项目需求来配置上述参数。代码中的`TIMERx`需要替换为实际使用的定时器编号,如`TIMER0`、`TIMER1`等。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值