STM32 HAL库开发入门篇(2):定时器详解

目录

一、TIMER定时器概述

1.1 软件定时原理

1.2 定时器定时原理

1.3 STM32定时器分类

1.4 STM32定时器特性表

1.5 STM32基本、通用、高级定时器功能整体的区别

二、基本定时器

2.1 基本定时器简介

2.3 STM32定时器计数模式及溢出条件

2.4 定时器中断实验相关寄存器

2.5 定时器溢出时间计算方法

2.6 定时器中断实验配置步骤

2.7 编程实战:定时器中断实验

三、通用定时器

3.1 通用定时器简介

3.2 通用定时器框图 

3.3 计数器时钟源

3.3.1 外部时钟模式1

3.3.2 外部时钟模式2

3.3.3 内部触发输入 

3.4 通用定时器PWM输出实验

3.4.1 通用定时器输出比较部分框图

3.4.2 通用定时器输出PWM原理

3.4.3 PWM模式

3.4.4 通用定时器PWM输出实验配置步骤

3.4.5 编程实战:通用定时器PWM输出实验

3.5 通用定时器输入捕获实验

3.5.1 通用定时器输入捕获部分框图

3.5.2 通用定时器输入捕获脉宽测量原理

3.5.4  编程实战:通用定时器输入捕获实验

3.6 通用定时器脉冲计数实验

3.6.1 脉冲计数实验原理

3.6.2 通用定时器脉冲计数实验配置步骤

3.6.3 编程实战:通用定时器脉冲计数实验

四、高级定时器

4.1 高级定时器简介

4.2 高级定时器框图

4.3 高级定时器输出指定个数PWM实验

4.3.1 重复计数器特性 

4.3.2 高级定时器输出指定个数PWM个数


使用正点原子stm32f103精英版 

一、TIMER定时器概述

1.1 软件定时原理

软件延时过程中,程序就进入延时函数中,无法执行其他程序

下图的72,是因为f103的主频是72MHz,这样调用延时肯定是不精准的

1.2 定时器定时原理

1.3 STM32定时器分类

实时时钟就是 RTC 

1.4 STM32定时器特性表

1.5 STM32基本、通用、高级定时器功能整体的区别

二、基本定时器

2.1 基本定时器简介

基本定时器的时钟来源只能是内部时钟

       通过程序的方式无法直接访问影子寄存器,但可以通过写入arr自动重载寄存器,它再转移到影子寄存器生效,此时ARR寄存器就起到一个缓冲或缓存的作用

       事件是默认产生;中断和DMA输出是默认不产生,但可以配置产生 

        ARPE位的作用:设置有缓冲作用后,ARR的值要等事件发生后写入影子寄存器;设置没有缓冲作用的话,ARR的值就马上转移到影子寄存器中生效

       虽然最大总线的最大时钟频率是72、36MHz,但定时器的最大时钟频率不一定有这么大 

       可以看到例程中 APB2 预分频系数是1,所以最大还是72MHz 

       APB1 预分频系数是2,APB1时钟是从AHB 2分频过来的,所以APB1给到定时器的时钟要乘2,定时器时钟就也是72Mhz了

2.3 STM32定时器计数模式及溢出条件

 

2.4 定时器中断实验相关寄存器

如一个例子,控制led亮1s,灭2s

(1)当没有缓冲时,假设先设置arr=99,1s后更新灭,然后重新写入arr=199灭2s,这其中写arr的值也需要时间

(2)而有缓冲时,假设先设置arr=99,1s后更新灭,然后在这1s内写入arr=199,此时因为有缓冲所以arr的值不会立即写入要等更新后写入,所以就节省了写入arr值的时间,减小了误差

 

2.5 定时器溢出时间计算方法

ARR也是同理,寄存器最小值为0,我从0数到3,你不能说我就数了3个数吧,当然是3+1=4个数 

2.6 定时器中断实验配置步骤

基地址:

基本定时器技术模式固定递增,无分频因子

重复计数器寄存器只有高级定时器才有 

自动重载预装载使能就是CR1寄存器的位7:APRE

基本定时器只有更新中断 

2.7 编程实战:定时器中断实验

       更新中断产生的方式:

       (1)定时器溢出时伴随更新时间和更新中断的产生

       (2)通过软件设置UG位产生软件的更新时间从而产生更新中断

       这里使用定时器溢出的方式 

#include "./BSP/TIMER/timer.h"
#include "./BSP/LED/led.h"

TIM_HandleTypeDef g_timx_handle;

/* 定时器中断初始化函数 */
void btim_timx_int_init(uint16_t arr, uint16_t psc)
{
    g_timx_handle.Instance = TIM6;
    g_timx_handle.Init.Prescaler = psc;
    g_timx_handle.Init.Period = arr;
    HAL_TIM_Base_Init(&g_timx_handle);
    
    HAL_TIM_Base_Start_IT(&g_timx_handle);

}



/* 定时器基础MSP初始化函数 */
void HAL_TIM_Base_MspInit(TIM_HandleTypeDef *htim)
{
    if(htim->Instance == TIM6)
    {
        __HAL_RCC_TIM6_CLK_ENABLE();
        HAL_NVIC_SetPriority(TIM6_IRQn, 2, 3);
        HAL_NVIC_EnableIRQ(TIM6_IRQn);
    
    }

}



/* 定时器6中断服务函数 */
void TIM6_IRQHandler()
{
    HAL_TIM_IRQHandler(&g_timx_handle);
}



/* 定时器溢出中断回调函数 */
void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim)
{
    if(htim->Instance == TIM6)  /* 判断是定时器6,否则多个定时器进来会混乱 */
    {
        LED0_TOGGLE();
    
    }


}

三、通用定时器

3.1 通用定时器简介

       通用定时器时钟来自RCC内部时钟,有4类:

       (1)APB总线上的时钟

       (2)ITR0-4内部触发输入时钟部分

       (3)可以复用为ETR引脚的IO口

       (4)定时器的通道1、通道2

如:

3.2 通用定时器框图 

 

3.3 计数器时钟源

3.3.1 外部时钟模式1

TI1_ED 来自通道1,双边沿

TI1FP1、TI2FP2来自通道1、2,单边沿 

 

3.3.2 外部时钟模式2

3.3.3 内部触发输入 

只有通用和高级定时器才可以参与

 

3.4 通用定时器PWM输出实验

3.4.1 通用定时器输出比较部分框图

3.4.2 通用定时器输出PWM原理

3.4.3 PWM模式

 

3.4.4 通用定时器PWM输出实验配置步骤

 

 

3.4.5 编程实战:通用定时器PWM输出实验

 

#include "./BSP/TIMER/gtim.h"

TIM_HandleTypeDef g_timx_pwm_chy_handle;


/* 通用定时器PWM输出初始化函数 */
void gtim_timx_pwm_chy_init(uint16_t arr, uint16_t psc)
{
    TIM_OC_InitTypeDef timx_oc_pwm_chy;
    
    g_timx_pwm_chy_handle.Instance = TIM3;
    g_timx_pwm_chy_handle.Init.Prescaler = psc;
    g_timx_pwm_chy_handle.Init.Period = arr;
    g_timx_pwm_chy_handle.Init.CounterMode = TIM_COUNTERMODE_UP; // 计数模式
    HAL_TIM_PWM_Init(&g_timx_pwm_chy_handle);
    
    timx_oc_pwm_chy.OCMode = TIM_OCMODE_PWM1;
    timx_oc_pwm_chy.Pulse = arr / 2;  // 比较值,就是CCRx的值
    timx_oc_pwm_chy.OCPolarity = TIM_OCPOLARITY_LOW; // 输出极性为低,低电平有效
    HAL_TIM_PWM_ConfigChannel(&g_timx_pwm_chy_handle, &timx_oc_pwm_chy, TIM_CHANNEL_2);
    
    HAL_TIM_PWM_Start(&g_timx_pwm_chy_handle, TIM_CHANNEL_2);


}

/* 定时器输出PWM MSP初始化函数 */
void HAL_TIM_PWM_MspInit(TIM_HandleTypeDef *htim)
{
    if(htim->Instance == TIM3)
    {
        GPIO_InitTypeDef gpio_init_struct;
        __HAL_RCC_GPIOB_CLK_ENABLE();                                 /* LED0时钟使能 */
        __HAL_RCC_TIM3_CLK_ENABLE();                                 /* LED1时钟使能 */

        gpio_init_struct.Pin = GPIO_PIN_5;                   /* LED0引脚 */
        gpio_init_struct.Mode = GPIO_MODE_AF_PP;            /* 复用推挽输出 */
        gpio_init_struct.Pull = GPIO_PULLUP;                    /* 上拉 */
        gpio_init_struct.Speed = GPIO_SPEED_FREQ_HIGH;          /* 高速 */
        HAL_GPIO_Init(GPIOB, &gpio_init_struct);       /* 初始化LED0引脚 */

        /* 重映射 */
        __HAL_RCC_AFIO_CLK_ENABLE();
        __HAL_AFIO_REMAP_TIM3_PARTIAL();
    
    }

}



#include "./SYSTEM/sys/sys.h"
#include "./SYSTEM/delay/delay.h"
#include "./SYSTEM/usart/usart.h"
#include "./BSP/LED/led.h"
#include "./BSP/TIMER/gtim.h"

extern TIM_HandleTypeDef g_timx_pwm_chy_handle;

int main(void)
{
    uint16_t ledpwm = 0;
    uint8_t dir = 1;
    
    HAL_Init();                                 /* 初始化HAL库 */
    sys_stm32_clock_init(RCC_PLL_MUL9);         /* 设置时钟,72M */
    delay_init(72);                             /* 初始化延时函数 */
    led_init();                                 /* 初始化LED */
    usart_init(115200);
    gtim_timx_pwm_chy_init(499, 71);   // 2KHz
    
    
    
    while(1)
    {
        
        delay_ms(10);
        
        if(dir) ledpwm++;
        else ledpwm--;
        
        if(ledpwm > 300) dir = 0;
        if(ledpwm == 0) dir = 1;
        
        __HAL_TIM_SET_COMPARE(&g_timx_pwm_chy_handle, TIM_CHANNEL_2, ledpwm);
    }
}

3.5 通用定时器输入捕获实验

3.5.1 通用定时器输入捕获部分框图

只要 CC1S 不是00就是输入模式 

CCR1读操作完成后,影子寄存器的值才会被转移到预装载寄存器中  

3.5.2 通用定时器输入捕获脉宽测量原理

       t1 处产生一个捕获事件,将计数器的计数值转移到CCR1中 ,此时将计数器的值清零,将上升沿检测改为下降沿检测,

输入捕获分频系数:捕获几个上升沿触发 

Polarity

Selection 

Prescaler 

Filter

3.5.4  编程实战:通用定时器输入捕获实验

72Mhz / 72 =1MHz 就是1us(1微秒)计数一次

#include "./BSP/TIMER/gtim.h"



TIM_HandleTypeDef g_timx_cap_chy_handle;

void gtim_timx_cap_chy_init(uint16_t arr, uint16_t psc)
{
    TIM_IC_InitTypeDef timx_ic_cap_chy = {0};                       /* 初始化结构体变量建议给个初值0 */
    
    g_timx_cap_chy_handle.Instance = TIM5;                          /* 定时器5 */
    g_timx_cap_chy_handle.Init.Prescaler = psc;                     /* 定时器分频 */
    g_timx_cap_chy_handle.Init.CounterMode = TIM_COUNTERMODE_UP;    /* 递增计数模式 */
    g_timx_cap_chy_handle.Init.Period = arr;                        /* 自动重装载值 */
    HAL_TIM_IC_Init(&g_timx_cap_chy_handle);
    
    timx_ic_cap_chy.ICPolarity = TIM_ICPOLARITY_RISING;             /* 上升沿捕获 */
    timx_ic_cap_chy.ICSelection = TIM_ICSELECTION_DIRECTTI;         /* 映射到TI1 */
    timx_ic_cap_chy.ICPrescaler = TIM_ICPSC_DIV1;                   /* 配置输入分频 */
    timx_ic_cap_chy.ICFilter = 0;                                   /* 配置输入滤波器 */
    HAL_TIM_IC_ConfigChannel(&g_timx_cap_chy_handle, &timx_ic_cap_chy, TIM_CHANNEL_1);

    __HAL_TIM_ENABLE_IT(&g_timx_cap_chy_handle, TIM_IT_UPDATE);     /* 使能更新中断 */
    HAL_TIM_IC_Start_IT(&g_timx_cap_chy_handle, TIM_CHANNEL_1);     /* 开始捕获TIM5的通道1,捕获中断使能 */

}


void HAL_TIM_IC_MspInit(TIM_HandleTypeDef *htim)
{
    if(htim->Instance == TIM5)
    {
        GPIO_InitTypeDef gpio_init_struct;
        __HAL_RCC_TIM5_CLK_ENABLE();
        __HAL_RCC_GPIOA_CLK_ENABLE();
        
        gpio_init_struct.Pin = GPIO_PIN_0;
        gpio_init_struct.Mode = GPIO_MODE_AF_PP; // 复用功能是可以读取io的电平情况的,输出也能读值
        gpio_init_struct.Pull = GPIO_PULLDOWN;
        gpio_init_struct.Speed = GPIO_SPEED_FREQ_HIGH;
        HAL_GPIO_Init(GPIOA, &gpio_init_struct);
        
        HAL_NVIC_SetPriority(TIM5_IRQn, 1, 3);
        HAL_NVIC_EnableIRQ(TIM5_IRQn);
       
    }
}    

/* 输入捕获状态(g_timxchy_cap_sta)
 * [7]  :0,没有成功的捕获;1,成功捕获1次
 * [6]  :0,还没捕获到高电平;1,已经捕获到高电平
 * [5:0]:捕获高电平后溢出的次数,最多溢出63次,所以最长捕获值 = 63*65536 + 65535 = 4194303
 *        注意:为了通用,我们默认ARR和CCRy都是16位寄存器,对于32位的定时器(如TIM5),也只按16位计算
 *        F1系列定时器都是16位,把g_timxchy_cap_sta定义为uint16_t,还可以测的时间更长
 *        按1us的计数频率,最长溢时间为:4194303 us,约4.19s
 *
 *        说明一下:正常32位的定时器来说,1us计数器加1,溢出时间为4294秒,2^32 = 4294967296
 *
 */

uint8_t g_timxchy_cap_sta = 0;  /* 输入捕获状态 */
uint16_t g_timxchy_cap_val = 0; /* 输入捕获值 */

/* 定时器5中断服务函数 */
void TIM5_IRQHandler(void)
{
    HAL_TIM_IRQHandler(&g_timx_cap_chy_handle);
}

/* 定时器输入捕获中断回调函数 */
void HAL_TIM_IC_CaptureCallback(TIM_HandleTypeDef *htim)
{
    if(htim->Instance == TIM5)
    {
        if((g_timxchy_cap_sta & 0x80) == 0) // 还没有成功捕获
        {
            if(g_timxchy_cap_sta & 0x40)    // 捕获下降沿
            {
                g_timxchy_cap_sta |= 0x80;   // 标记捕获到一次高电平
                g_timxchy_cap_val = HAL_TIM_ReadCapturedValue(&g_timx_cap_chy_handle, TIM_CHANNEL_1);
                TIM_RESET_CAPTUREPOLARITY(&g_timx_cap_chy_handle, TIM_CHANNEL_1);  // 清除原来的设置
                TIM_SET_CAPTUREPOLARITY(&g_timx_cap_chy_handle, TIM_CHANNEL_1, TIM_ICPOLARITY_RISING);// 开启新一次捕获上升沿
            }
            else /* 还未开始,第一次捕获上升沿 */
            {
                g_timxchy_cap_sta = 0;
                g_timxchy_cap_val = 0;
                g_timxchy_cap_sta |= 0x40;   // 标记捕获到了上升沿
                __HAL_TIM_DISABLE(&g_timx_cap_chy_handle);              // 关闭定时器5
                __HAL_TIM_SET_COUNTER(&g_timx_cap_chy_handle, 0);       // 定时器5计数器清零
                TIM_RESET_CAPTUREPOLARITY(&g_timx_cap_chy_handle, TIM_CHANNEL_1);// 清除原来的设置
                TIM_SET_CAPTUREPOLARITY(&g_timx_cap_chy_handle, TIM_CHANNEL_1, TIM_ICPOLARITY_FALLING); // 捕获下降沿
                __HAL_TIM_ENABLE(&g_timx_cap_chy_handle);               // 使能定时器5
            }
        }
    }
}

/* 定时器更新中断回调函数 */
void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim)
{
    if(htim->Instance == TIM5)
    {
        if((g_timxchy_cap_sta & 0x80) == 0) // 还没有成功捕获
        {
            if(g_timxchy_cap_sta & 0x40)    // 捕获下降沿
            {
                if((g_timxchy_cap_sta & 0x3F) == 0x3F) /* 高电平太长了 */
                {
                    TIM_RESET_CAPTUREPOLARITY(&g_timx_cap_chy_handle, TIM_CHANNEL_1);// 清除原来的设置
                    TIM_SET_CAPTUREPOLARITY(&g_timx_cap_chy_handle, TIM_CHANNEL_1, TIM_ICPOLARITY_RISING); // 设置捕获上升沿
                    g_timxchy_cap_sta |= 0X80;
                    g_timxchy_cap_val = 0XFFFF;
                }
                else   /* 累计定时器溢出次数 */
                {
                    g_timxchy_cap_sta++;
                }
            }
        }
    }

}


#include "./SYSTEM/sys/sys.h"
#include "./SYSTEM/delay/delay.h"
#include "./SYSTEM/usart/usart.h"
#include "./BSP/LED/led.h"
#include "./BSP/TIMER/gtim.h"

extern uint8_t g_timxchy_cap_sta;  /* 输入捕获状态 */
extern uint16_t g_timxchy_cap_val; /* 输入捕获值 */

int main(void)
{
    uint32_t temp = 0;
    uint8_t t = 0;
    
    HAL_Init();                                 /* 初始化HAL库 */
    sys_stm32_clock_init(RCC_PLL_MUL9);         /* 设置时钟,72M */
    delay_init(72);                             /* 初始化延时函数 */
    led_init();                                 /* 初始化LED */
    usart_init(115200);
    gtim_timx_cap_chy_init(65535, 71);
    
    while(1)
    {
        if(g_timxchy_cap_sta & 0x80) // 还没有成功捕获
        {
            temp = g_timxchy_cap_sta & 0x3F;
            temp *= 65536;               /* 溢出时间总和 */
            temp += g_timxchy_cap_val;   /* 总的高电平时间 */
            printf("HIGH:%d us\r\n", temp);
            g_timxchy_cap_sta = 0;       /* 开启下一次捕获 */
        }
        
        t++;
        
        if(t > 20)                       /* 200ms进入一次 */
        {
            t = 0;
            LED0_TOGGLE();               /* LED0闪烁,提是程序在运行 */
        }
        delay_ms(10);
    }
}

3.6 通用定时器脉冲计数实验

4个时钟源:(1)内部时钟;(2)外部触发1:由CH1、CH2;(3)外部触发2:由IO的ETR;(4)内部触发输入:一般用于级联 

3.6.1 脉冲计数实验原理

3.6.2 通用定时器脉冲计数实验配置步骤

SlaveMode:


 (1)DISABLE:不使用从模式就是使用内部时钟

 (2)RESET:复位 

 (3)GATED:门电路

 (4)TRIGGER:触发模式 

 (5)EXTERNAL1:外部时钟模式1

触发源: 

 

边沿检测器: 

边沿检测器有2个, 上2个是上面的检测器,下3个是下面的检测器

BOTHEDGE用处暂不明

分频器:

外2会用到分频器,外1没用到 

滤波器:

滤波器外1和外2都用到了,实验里没用

3.6.3 编程实战:通用定时器脉冲计数实验

#include "./BSP/TIMER/gtim.h"

TIM_HandleTypeDef g_timx_cap_chy_handle;

void gtim_timx_cap_chy_init(uint16_t arr, uint16_t psc)
{
    TIM_SlaveConfigTypeDef tim_slave_config = {0};
    
    g_timx_cap_chy_handle.Instance = TIM2;                          /* 定时器5 */
    g_timx_cap_chy_handle.Init.Prescaler = psc;                     /* 定时器分频 */
    g_timx_cap_chy_handle.Init.CounterMode = TIM_COUNTERMODE_UP;    /* 递增计数模式 */
    g_timx_cap_chy_handle.Init.Period = arr;                        /* 自动重装载值 */
    HAL_TIM_IC_Init(&g_timx_cap_chy_handle);
    
    tim_slave_config.SlaveMode = TIM_SLAVEMODE_EXTERNAL1;
    tim_slave_config.InputTrigger = TIM_TS_TI1FP1;
    tim_slave_config.TriggerPolarity = TIM_TRIGGERPOLARITY_RISING;
    tim_slave_config.TriggerFilter = 0;
    HAL_TIM_SlaveConfigSynchro(&g_timx_cap_chy_handle, &tim_slave_config);
    
    HAL_TIM_IC_Start(&g_timx_cap_chy_handle, TIM_CHANNEL_1);

}

void HAL_TIM_IC_MspInit(TIM_HandleTypeDef *htim)
{
    if(htim->Instance == TIM2)
    {
        GPIO_InitTypeDef gpio_init_struct;
        __HAL_RCC_TIM2_CLK_ENABLE();
        __HAL_RCC_GPIOA_CLK_ENABLE();
        
        gpio_init_struct.Pin = GPIO_PIN_0;
        gpio_init_struct.Mode = GPIO_MODE_AF_PP; // 复用功能是可以读取io的电平情况的,输出也能读值
        gpio_init_struct.Pull = GPIO_PULLDOWN;
        gpio_init_struct.Speed = GPIO_SPEED_FREQ_HIGH; // 输入可以不设置速度
        HAL_GPIO_Init(GPIOA, &gpio_init_struct);
       
    }
}  
#include "./SYSTEM/sys/sys.h"
#include "./SYSTEM/delay/delay.h"
#include "./SYSTEM/usart/usart.h"
#include "./BSP/LED/led.h"
#include "./BSP/TIMER/gtim.h"
#include "./BSP/KEY/key.h"

extern TIM_HandleTypeDef g_timx_cap_chy_handle;

int main(void)
{
    uint16_t curcnt;
    uint16_t oldcnt;
    uint8_t key;
    uint8_t t = 0;
    
    HAL_Init();                                 /* 初始化HAL库 */
    sys_stm32_clock_init(RCC_PLL_MUL9);         /* 设置时钟,72M */
    delay_init(72);                             /* 初始化延时函数 */
    led_init();                                 /* 初始化LED */
    usart_init(115200);
    gtim_timx_cap_chy_init(65535, 0);
    key_init();
    
    while(1)
    {
        key = key_scan(0);
        if(key == KEY0_PRES)
        {
            __HAL_TIM_SET_COUNTER(&g_timx_cap_chy_handle, 0);
        }            
        
        curcnt = __HAL_TIM_GET_COUNTER(&g_timx_cap_chy_handle);
        if(oldcnt != curcnt)
        {
            oldcnt = curcnt;
            printf("CNT:%d\r\n", oldcnt);
        }
        t ++;
        
        if(t > 20)
        {
            t = 0;
            LED0_TOGGLE();
        }
        delay_ms(10);
    }
}

       此时定时器计数cnt == arr 的时候会溢出,因为设置了 arr = 65535,所以最大计数值即最大捕获数量就是 65535,如果要增大计数量,可以添加定时器更新中断

四、高级定时器

4.1 高级定时器简介

重复计数器,对REP寄存器写一个值,每次溢出就减1,指导减为0后,就产生更新事件

4.2 高级定时器框图

通道1、2、3都有CHxN就是互补通道 

4.3 高级定时器输出指定个数PWM实验

4.3.1 重复计数器特性 

       再同步就是软件手动产生更新事件后,RCR寄存器的值会再缓冲到它的影子寄存器中,影子寄存器就被重置了

  

4.3.2 高级定时器输出指定个数PWM个数

 p105  5:43

物联沃分享整理
物联沃-IOTWORD物联网 » STM32 HAL库开发入门篇(2):定时器详解

发表评论