STM32HAL库-TIMER定时器篇

定时器概述

软件定时原理

使用纯软件的方式(CPU死等)的方式实现定时(延时)功能

void delay_us(uint32_t us) { us *= 72;     while(us--); }

缺点:延时不精准,CPU死等浪费资源

函数的调用有压栈和出栈的过程,STM32是ARM架构,有流水线,一条指令会分为多个步骤在不同时间片内由CPU决定执行顺序,导致函数延时不准确

定时器定时原理

使用精准的时钟源,通过硬件的方式,实现定时功能

定时器的核心就是计数器

时钟源信号经过预分频器分频后传递给计数器,计数器溢出则产生中断/事件,然后重装载值

定时器定时原理

STM32定时器分类

定时器分类

STM32定时器主要分为 常规定时器专用定时器内核定时器 三种

常规定时器 包括 基本定时器、通用定时器、高级定时器

专用定时器 包括 独立看门狗 IWDG、窗口看门狗 WWDG、实时时钟 RTC、低功耗定时器

内核定时器 包括 SysTick系统滴答定时器

STM32定时器特性表

F4系列芯片有14个常规定时器,包括

2 个基本定时器(TIM6~7),

10 个通用定时器(TIM9~14、TIM3~4、TIM2/5),

2 个高级定时器(TIM2/5)

基本定时器不具备 捕获/比较通道 和 死区互补输出 功能

通用定时器不具备互补输出功能

高级定时器兼具 捕获/比较通道 和 互补输出功能

STM32基本、通用、高级定时器功能区别

基本定时器没有输入输出通道

意味着没有 外部GPIO引脚 与之相连,只能用来完成定时中断等基本共功能

通用定时器具有 输入输出通道,可与外部设备交互

高级定时器具有 输入输出通道 和 死区互补信号输出、刹车输入等功能

基本定时器

基本定时器简介

1、基本定时器TIM6/TIM7

2、主要特性

16位递增计数器(计数值0~65535)

16位预分频器(分频系数1~65536)

可用于触发DAC

在更新事件会产生中断/DMA请求

基本定时器TIM6/TIM7简介

基本定时器框图

F4系列基本定时器框图

计数器(时基单元)

基本定时器的时钟源来自于内部时钟RCC模块配置的APB总线时钟

PSC预分频器自动重装载寄存器影子寄存器的概念

影子寄存器是实际上起作用的寄存器,不可直接访问

与影子寄存器对应的预装载寄存器其实是起一个缓存的作用,产生中断/事件以后才转移过去

溢出条件是CNT计数器的值 = 影子寄存器的值,溢出后可产生 事件/中断/DMA输出请求

事件是默认产生,可以设置为不产生

中断和DMA输出是默认不产生,可以设置为产生

除了计数器CNT溢出以外,还可以设置 UG位 来软件产生更新事件,更新事件产生以后,会把"预加载寄存器"的值转移到影子寄存器中去

ARPE位决定ARR寄存器是否具有缓冲作用,如果设置"预加载寄存器"为具有缓冲作用,那么ARR的值不是立即生效,而是要等到更新事件产生才会把ARR的值转移到影子寄存器中去;如果设置预加载寄存器无缓冲作用,那么预加载寄存器会立刻把值加载到影子寄存器中去,从而立刻生效

Auto-reload preload enable,ARPE 自动重装载预装载使能

触发控制器

                当计数器溢出时,触发控制器会产生TRGO触发输出信号,触发控制器产生一次数模转换,也就是ADC信号。Triggle Output,触发输出

时钟源

        F4系列的基本定时器时钟挂载在 APB1 总线,所以它的时钟来自于 APB1 总线,但是基本定时器时钟不是直接由 APB1 总线直接提供,而是先经过一个倍频器。

         在 TIMPRE 位默认设置为 0 的情况下,当 APB1 的预分频器预的分频系数为 1 时,这个倍频器系数就为 1,即定时器的时钟源频率等于 APB1 总线时钟频率;当 APB1 的预分频器的预分频系数系数 ≥2 分频时,这个倍频器系数就为 2,即定时器的时钟源频率等于 APB1 总线时钟频率的两倍。我们在时钟设置函数 sys_stm32_clock_init 已经设置 APB1 总线时钟频率为 45MHz,预分频器的预分频系数为 2,所以定时器时钟源频率为90Mhz。

在正点原子的例程中,有sys_stm32_clock_init(360, 25, 2, 8);     /* 设置时钟,180Mhz */

/**
 * @brief       时钟设置函数
 * @param       plln: PLL1倍频系数(PLL倍频), 取值范围: 64~432.
 * @param       pllm: PLL1预分频系数(进PLL之前的分频), 取值范围: 2~63.
 * @param       pllp: PLL1的p分频系数(PLL之后的分频), 分频后作为系统时钟, 取值范围: 2,4,6,8.(仅限这4个值!)
 * @param       pllq: PLL1的q分频系数(PLL之后的分频), 取值范围: 2~15.
 * @note
 *
 *              Fvco: VCO频率
 *              Fsys: 系统时钟频率, 也是PLL1的p分频输出时钟频率
 *              Fq:   PLL1的q分频输出时钟频率
 *              Fs:   PLL输入时钟频率, 可以是HSI, HSE等.
 *              Fvco = Fs * (plln / pllm);
 *              Fsys = Fvco / pllp = Fs * (plln / (pllm * pllp));
 *              Fq   = Fvco / pllq = Fs * (plln / (pllm * pllq));
 *
 *              外部晶振为25M的时候, 推荐值: plln = 360, pllm = 25, pllp = 2, pllq = 8.
 *              得到:Fvco = 25 * (360 / 25) = 360Mhz
 *                   Fsys = pll1_p_ck = 360 / 2 = 180Mhz
 *                   Fq   = pll1_q_ck = 360 / 8 = 45
 *
 *              F429默认需要配置的频率如下:
 *              CPU频率(HCLK) = pll_p_ck = 180Mhz
 *              AHB1/2/3(rcc_hclk1/2/3) = 180Mhz
 *              APB1(rcc_pclk1) = pll_p_ck / 4 = 45Mhz
 *              APB2(rcc_pclk2) = pll_p_ck / 2 = 90Mhz
 *
 * @retval      错误代码: 0, 成功; 1, 错误;
 */
uint8_t sys_stm32_clock_init(uint32_t plln, uint32_t pllm, uint32_t pllp, uint32_t pllq)
{
    HAL_StatusTypeDef ret = HAL_OK;
    RCC_ClkInitTypeDef rcc_clk_init = {0};
    RCC_OscInitTypeDef rcc_osc_init = {0};
    
    __HAL_RCC_PWR_CLK_ENABLE();                                     /* 使能PWR时钟 */
    
    /* 下面这个设置用来设置调压器输出电压级别,以便在器件未以最大频率工作时使性能与功耗实现平衡 */
    __HAL_PWR_VOLTAGESCALING_CONFIG(PWR_REGULATOR_VOLTAGE_SCALE1);  /* 调压器输出电压级别选择:级别1模式 */

    /* 使能HSE,并选择HSE作为PLL时钟源,配置PLL1,开启USB时钟 */
    rcc_osc_init.OscillatorType = RCC_OSCILLATORTYPE_HSE;           /* 时钟源为HSE */
    rcc_osc_init.HSEState = RCC_HSE_ON;                             /* 打开HSE */
    rcc_osc_init.PLL.PLLState = RCC_PLL_ON;                         /* 打开PLL */
    rcc_osc_init.PLL.PLLSource = RCC_PLLSOURCE_HSE;                 /* PLL时钟源选择HSE */
    rcc_osc_init.PLL.PLLN = plln;
    rcc_osc_init.PLL.PLLM = pllm;
    rcc_osc_init.PLL.PLLP = pllp;
    rcc_osc_init.PLL.PLLQ = pllq;
    ret = HAL_RCC_OscConfig(&rcc_osc_init);                         /* 初始化RCC */
    if (ret != HAL_OK)
    {
        return 1;                                                   /* 时钟初始化失败,可以在这里加入自己的处理 */
    }

    ret = HAL_PWREx_EnableOverDrive();                              /* 开启Over-Driver功能 */
    if (ret != HAL_OK)
    {
        return 1;
    }

    /* 选中PLL作为系统时钟源并且配置HCLK,PCLK1和PCLK2*/
    rcc_clk_init.ClockType = ( RCC_CLOCKTYPE_SYSCLK \
                                    | RCC_CLOCKTYPE_HCLK \
                                    | RCC_CLOCKTYPE_PCLK1 \
                                    | RCC_CLOCKTYPE_PCLK2);

    rcc_clk_init.SYSCLKSource = RCC_SYSCLKSOURCE_PLLCLK;            /* 设置系统时钟时钟源为PLL */
    rcc_clk_init.AHBCLKDivider = RCC_SYSCLK_DIV1;                   /* AHB分频系数为1 */
    rcc_clk_init.APB1CLKDivider = RCC_HCLK_DIV4;                    /* APB1分频系数为4 */
    rcc_clk_init.APB2CLKDivider = RCC_HCLK_DIV2;                    /* APB2分频系数为2 */
    ret = HAL_RCC_ClockConfig(&rcc_clk_init, FLASH_LATENCY_5);      /* 同时设置FLASH延时周期为5WS,也就是6个CPU周期 */
    if (ret != HAL_OK)
    {
        return 1;                                                   /* 时钟初始化失败 */
    }

    return 0;
}

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

假设定时器的分频系数PSC=1,那么就是2分频,实际的分频 = 分频系数+1,将自动重装载寄存器ARR的值设置为36,就可以得到如下的时序图

CK_PSC是定时器的是时钟源

CNT_EN是计数器的使能位

CK_CNT是经过分频后的定时器时钟

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

基本定时器 TIM6/TIM7 主要使用的寄存器有

控制寄存器1                 TIMx_CR1

DMA/中断使能寄存器   TIMx_DIER

状态寄存器                   TIMx_SR

计数器寄存器               TIMx_CNT

预分频寄存器               TIMx_PSC

自动重载寄存器           TIMx_ARR

控制寄存器 TIMx_CR1

16位寄存器,高8位保留

位0 CEN    计数器使能

位7 ARPE 自动重装载预装载使能 重装载预装载使能则具有缓冲,计数器以影子寄存器为条件

比如一个灯,1s亮一次,2s灭一次,如果不用缓冲寄存器,就只能在熄灭的那短暂时间内去修改ARR寄存器的值,但修改操作需要us级别的时间,因此会造成误差

而使用缓冲寄存器的话,可以在任意区间内去修改影子寄存器的值,因为影子寄存器是事件触发后才生效

OPM,One-Pulse Mode 单脉冲模式,接收到触发信号后只运行一次

URS,Update Request Source 更新请求来源,软件触发还是硬件触发

Update Disable 更新中断失能

DMA/中断使能寄存器 TIMx_DIER

16位寄存器,高8位保留

位0 UIE   更新中断使能

位8 UDE 更新DMA请求使能

通常只用到更新中断使能   UDIS,

状态寄存器 TIMx_SR

16位寄存器,仅位0有效

位0 UIF 更新中断标志

Event Generation Register,EGR 事件生成寄存器,通过向 TIMx_EGR 寄存器写入特定的值,可以触发定时器产生不同的事件,如更新事件(UG)、触发事件(TG)、捕获/比较事件(CG)等

计数器寄存器 TIMx_CNT

16位寄存器,存储计数值

预分频寄存器 TIMx_PSC

16位寄存器,预分频值可设置为0~65535

实际预分频系数 = PSC+1

自动重载寄存器器 TIMx_ARR

16位寄存器,装自动重载值

定时器溢出时间计算方法

定时器溢出时间 Tout = (自动重装载寄存器+1)/定时器计数频率

                                  = (自动重装载寄存器+1)*(预分频寄存器PSC+1) / 时钟源频率Ft

定时器中断实验配置步骤

1,配置定时器基础工作参数           HAL_TIM_Base_Init()

2,定时器基础MSP初始化              HAL_TIM_Base_MspInit()   (weak)  配置NVIC、CLOCK等

3,使能更新中断并启动计数器       HAL_TIM_Base_Start_IT()

4,设置优先级,使能中断              HAL_NVIC_SetPriority()、 HAL_NVIC_EnableIRQ()

5,编写中断服务函数                     TIMx_IRQHandler()等  -> HAL_TIM_IRQHandler()

6,编写定时器更新中断回调函数   HAL_TIM_PeriodElapsedCallback()  (weak)

在 TIM 初始化结构体中,

基本定时器只允许递增计数模式,因此CounterMode无效

基本定时器没有时钟分频因子,因此ClockDivision位无效

基本定时器没有重复计数器寄存器,因此RepetitionCounter无效

/*关键结构体*/

/*TIM句柄*/
struct typedef{
  TIM_TypeDef              *Instance;   /*外设寄存器基地址*/
  TIM_Base_InitTypeDef     Init;        /*定时器初始化结构体*/
  ……
}TIM_HandleTypeDef;

/*定时器初始化结构体*/
typedef struct
{
  uint32_t Prescaler;          //预分频系数
  uint32_t CounterMode;        //计数模式
  uint32_t Period;             //自动重装载值ARR
  uint32_t AutoReloadPreload;  //自动重装载寄存器使能
  ……
} TIM_Base_InitTypeDef;
//初始化定时器基础参数
HAL_StatusTypeDef HAL_TIM_Base_Init(TIM_HandleTypeDef *htim)
{
    ……
  /* Set the Time Base configuration */
  TIM_Base_SetConfig(htim->Instance, &htim->Init);
    ……
  return HAL_OK;
}
//使能TIM更新中断并启动计数器
HAL_StatusTypeDef HAL_TIM_Base_Start_IT(TIM_HandleTypeDef *htim)
{
     ……
  /* Enable the TIM Update interrupt */
  __HAL_TIM_ENABLE_IT(htim, TIM_IT_UPDATE);
     ……
  return HAL_OK;
}

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

例程:使用定时器6,实现500ms定时器中断更新,在中断里翻转LED0

产生更新中断有两种方式,一种是计数器溢出(软件触发),一种是事件生成寄存器(EGR)的UG位触发。例程讲解计数器溢出方式。

前文讲解过F4系列的基本定时器 TIM6/TIM7 挂载在APB1总线上,定时器时钟频率90MHz

定时器溢出时间 Tout=500ms=0.5s,

为了便于计数器的值给整数,(定时器时钟频率/定时器预分频值)一般取整数

因此 PSC+1=9000 , 因此 PSC = 8999      图中为F1的计数值计算

故而ARR+1=0.5*90 000 000/9000=5000    72MHz=72000KHz=72 000 000Hz

在btim.c和btim.h文件中编写

1. 定时器中断初始化函数 btim_timx_int_init

#define BTIM_TIMX_INT                       TIM6

TIM_HandleTypeDef g_timx_handle;

//基本定时器定时中断初始化函数
// * @param       arr : 自动重装值。
// * @param       psc : 时钟预分频数
void btim_timx_int_init(uint16_t arr, uint16_t psc)
{
    g_timx_handle.Instance = BTIM_TIMX_INT;                      /* 定时器x */
    g_timx_handle.Init.Prescaler = psc;                          /* 分频 */
    g_timx_handle.Init.CounterMode = TIM_COUNTERMODE_UP;         /* 递增计数模式 */
    g_timx_handle.Init.Period = arr;                             /* 自动装载值 */
    HAL_TIM_Base_Init(&g_timx_handle);   //初始化定时器基础参数,如模式和通道状态、分频值、重装载值
    
    HAL_TIM_Base_Start_IT(&g_timx_handle);            /* 使能定时器x和定时器更新中断 */
}

 2. 重写MSP初始化函数 HAL_TIM_Base_MspInit

void HAL_TIM_Base_MspInit(TIM_HandleTypeDef *htim)
{
    if (htim->Instance == BTIM_TIMX_INT)
    {
        BTIM_TIMX_INT_CLK_ENABLE();                     /* 使能TIMx时钟 */
        HAL_NVIC_SetPriority(BTIM_TIMX_INT_IRQn, 1, 3); /* 抢占1,子优先级3 */
        HAL_NVIC_EnableIRQ(BTIM_TIMX_INT_IRQn);         /* 开启ITMx中断 */
    }
}

 3. 定时器中断服务 

//定时器服务函数
void BTIM_TIMX_INT_IRQHandler(void)
{
    HAL_TIM_IRQHandler(&g_timx_handle);    /* 中断公共服务函数,会调用中断服务回调函数*/
}

4. 中断服务回调函数

void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim)
{
    if (htim->Instance == BTIM_TIMX_INT)
    {
        LED1_TOGGLE();                                  /* LED1反转 */
    }
}

int main(void)
{
    HAL_Init();                              /* 初始化HAL库 */
    sys_stm32_clock_init(360, 25, 2, 8);     /* 设置时钟,180Mhz */
    delay_init(180);                         /* 延时初始化 */
    led_init();                              /* 初始化LED */
    btim_timx_int_init(5000 - 1, 9000 - 1);  /* 90 000 000 / 9000 = 10KHz 10KHz的计数频率,计数5K次为500ms */

    while(1)
    {
        LED0_TOGGLE();                       /* LED0(红灯) 翻转 */
        delay_ms(2000);
    }
}

通用定时器

通用定时器简介

1、基本定时器 TIM2~5 && TIM8~14 共10个

2、主要特性

16位递增、递减、中心对齐计数器(计数值0~65535)

16位预分频器(分频系数1~65536)

可用于触发DAC、ADC

在更新事件、触发事件、输入捕获、输出比较时,会产生中断/DMA请求

4个独立通道,可用于:输入捕获、输出比较、输出PWM、单脉冲模式

使用外部信号控制定时器且可实现定时器级联

支持编码器和霍尔传感器电路等

通用定时器框图

定时器的核心是时基单元,也就是计数器,不管是基本、通用还是高级定时器,它们的框图都是在时基单元的基础上去扩展的

计数器时钟源

计数器时钟源框图

基本定时器的时钟源只能来自于内部时钟RCC模块配置的APB总线

而通用定时器的时钟源总共有 4 类,

1) 内部时钟(CK_INT),来自外设总线APB时钟,RCC模块负责负责配置系统时钟,包括选择时钟源、设置时钟频率以及为各个外设提供时钟

2) 外部时钟模式1,外部输入引脚(TIx)来源于输入捕获通道 TIMx_CHx

TI1F_ED,Edge Detector 边沿检测   TIxFPx、TIxFPx 定时器x滤波输入通道x  

3)  外部时钟模式2,外部触发输入(ETR),IO口复用为TIMx_ETR外部触发引脚  External Trigger Register,ETR,外部触发寄存器

当计数器溢出时,触发控制器会产生TRGO触发输出信号,触发控制器产生ADC/DAC信号

4) 内部触发输入(ITRx),即上图时钟源的内部触发寄存器  ITR0、ITR1、ITR2、ITR3 Internal Trigger,可用于级联

Timer Input 1 Filtered by Input 2    FP1 输入捕获通道1   FP2 输入捕获通道2

计数器时钟源寄存器设置方法

SMCR,Slave Mode Controller Register,从模式控制寄存器 

SMS,Slave Mode Selection,从模式选择

ECE,Encoder Counter Enable,编码器计数器使能

设置ECE模式位与选择外部时钟模式1并将TRGI连接到ETRF(SMS=111和TS=111)具有相同功效,都是设置时钟源来自ETR引脚,经过从模式控制器到达计数器

TIMx_SMCR寄存器的触发选择TS功能

控制器

触发控制器 会输出TRGO信号,到其他定时器,或者到ADC/DAC

TRGO信号可以连接到其他定时器的内部触发寄存器(上图ITR0~4),也即是定时器级联,

或者产生ADC/DAC信号

时基单元

通用定时器的时基单元与基本定时器相同

PSC预分频器自动重装载寄存器影子寄存器的概念

影子寄存器是实际上起作用的寄存器,不可直接访问

与影子寄存器对应的预装载寄存器其实是起一个缓存的作用,产生中断/事件以后才转移过去

溢出条件是CNT计数器的值 = 影子寄存器的值,溢出后可产生 事件/中断/DMA输出请求

事件是默认产生,可以设置为不产生

中断和DMA输出是默认不产生,可以设置为产生

除了计数器CNT溢出以外,还可以设置 UG位 来软件产生更新事件,更新事件产生以后,会把"预加载寄存器"的值转移到影子寄存器中去

ARPE位决定ARR寄存器是否具有缓冲作用,如果设置"预加载寄存器"为具有缓冲作用,那么ARR的值不是立即生效,而是要等到更新事件产生才会把ARR的值转移到影子寄存器中去;如果设置预加载寄存器无缓冲作用,那么预加载寄存器会立刻把值加载到影子寄存器中去,从而立刻生效

Auto-reload preload enable,ARPE 自动重装载预装载使能

输入捕获、输出比较、捕获/比较公共部分

输入捕获 和 输出比较 其实是一种分时复用的关系 ,比如IO引脚复用为一种,另一种则失效

输入捕获:

IO口复用为外部捕获通道 TIMx_CHx,外部信号进入 IO 口,

捕获信号进来以后

1. 可以经过 XOR异或门,异或门通常在电机AB相编码器测速使用,A相或者B相分别输入到两个CHx捕获通道,经过异或可得知有无脉冲

2. 直接到达 输入滤波器和边沿检测器,由于外部信号可能带毛刺,因此经过滤波来确定是高电平还是低电平,经过边沿检测后 得到两个信号, TI1FP1 和 TI1FP2 

这两个信号是由用户配置的,如果配置 TI1 映射到 输入捕获通道FP1,信号就走FP1的线,配置 TI2映射到FP2,信号就走 FP2 的线 Timer Input 1 Filtered Input,TI1FP1,滤波输入通道

之后信号经过预分频器,每来一个上升沿/下降沿,

1. 都会产生一个事件信号,这时捕获/比较寄存器CCR1会把计数器CNT的值读到自己这里,由此得到脉冲的间隔时间比如用捕获通道1,就是计数器CNT的值转移到FP1里面,用到捕获通道2,就是CNT的值转移到 FP2 里面

2. 除了产生捕获事件之外,还可以产生捕获中断   CC1I

输出比较:

用户向 捕获/比较寄存器 写入比较值

配置好计数器的重载值、预分频器、计数模式,知道计数一个值需要多少时间

每产生一个更新事件,捕获/比较计数器的比较值会写入影子寄存器

当 计数器的值 和 捕获/比较寄存器影子寄存器 的值相等后,会让 输出控制参考信号OC1REF 改变

输出控制参考信号OC1REF 是高电平有效。开启中断的话,还会产生一个捕获比较中断CC1I

输出控制参考信号进行输出控制的话,有 8 种输出模式,比如 PWM 输出模式等等

输出控制参考信号经过 输出控制器 成为 某种输出模式下的输出信号后,也就是OC1信号,最后来到 捕获比较通道TIMx_CHx

输出比较信号OCx 除了受 输出控制参考信号OC1REF 和 输出控制器的影响,还会受到ETRF信号的影响,ETRF信号可以将 输出控制参考信号OC1REF 强制清零

外部时钟模式1

来源于输入捕获通道 TIMx_CHx

从模式选择SMS位设置为111,将编码器计数器选择ECE设置为0,就选中了外部时钟模式1

触发输入信号TRGI 的一个上升沿来了以后,经过预分频就可以进入到计数器计一个数

TRGI,Trigger Input,触发输入

在外部时钟模式1下,从模式控制器TIMx_SMCR的触发选择TS[2:0]功能,

可以选择 TI1_EDFP1FP2 三种触发输入方式,其中TI1_ED对应双边沿、捕获通道1/2对应上升沿或者下降沿触发。

以上图的TI2FP2为例,从TimerxCH2来一个信号,经过滤波器边沿检测器后进入触发选择器,产生触发输入信号TRGI,进入计数器

CCMRx,Capture/Compare Mode Register x,捕获/比较模式寄存器  ICF[3:0]

输入捕获模式下CCMR的 ICF 位域可设置采样频率和滤波带宽

外部时钟模式2

来源于外部触发引脚 TIMx_ETR

ETRF,External Trigger Filter,外部触发滤波信号 

ETP,External Trigger Polarity,外部触发极性

ETPS,External Trigger Prescaler,外部触发预分频器

从ETR引脚输入一个信号,由于ETRF信号是上升沿触发,因此上升沿直接进入分频器,下降沿则反向后进入预分频器由,ETPS[1:0]位来设置预分频系数,系数范围:1、2、4、8。 紧接着经过滤波器,由 ETF[3:0]位来设置滤波方式,也可以设置不使用滤波器。时钟源信号 fDTS 由TIMx_CR1 寄存器的 CKD 位设置。 最后经过从模式选择器,由 ECE 位和 SMS[2:0]位来选择定时器的时钟源。这里我们介绍的是外部时钟模式 2,直接把 ECE 位置 1 即可。CK_PSC 需要经过定时器的预分频器分频后,最终就能到达计数器进行计数了。

级联:使用一个定时器作为另一个定时器的预分频器

上图中,TIM3 作为 TIM2 的预分频器,需要完成的配置步骤如下:

1,TIM3_CR2 寄存器的 MMS[2:0] 位设置为 010,即 TIM3 的主模式选择为更新(选择更新事件作为触发输出(TRGO))

2,TIM2_SMCR 寄存器的 TS[2:0] 位设置为 010,即使用 ITR2 作为内部触发。TS[2:0]位用

于配置触发选择,除了 ITR2,还有其他的选择,详细描述如下图所示

 解读通用定时器中断实验

定时器中断实验配置步骤

typedef struct 
{ 
    TIM_TypeDef *Instance;            /* 外设寄存器基地址 */ 
    TIM_Base_InitTypeDef Init;     /* 定时器初始化结构体*/
     ...
}TIM_HandleTypeDef;
typedef struct 
{ 
    uint32_t Prescaler;                      /* 预分频系数 */ 
    uint32_t CounterMode;             /* 计数模式 */ 
    uint32_t Period;                           /* 自动重载值 ARR */ 
    uint32_t ClockDivision;             /* 时钟分频因子 */ 
    uint32_t RepetitionCounter;   /* 重复计数器寄存器的值 */ 
    uint32_t AutoReloadPreload; /* 自动重载预装载使能 */
} TIM_Base_InitTypeDef;

 main.c内容

int main(void)
{
    HAL_Init();                              /* 初始化HAL库 */
    sys_stm32_clock_init(360, 25, 2, 8);     /* 设置时钟,180Mhz */
    delay_init(180);                         /* 延时初始化 */
    led_init();                              /* 初始化LED */
    gtim_timx_int_init(5000 - 1, 9000 - 1);  /* 90 000 000 / 9000 = 10KHz 10KHz的计数频率,计数5K次为500ms */

    while (1)
    {
        LED0_TOGGLE();                       /* LED0(红灯) 翻转*/
        delay_ms(200);
    }
}

 gtim.c内容

/**通用定时器TIMX定时中断初始化函数*/
 * @param       arr: 自动重装值    psc: 预分频系数  */
void gtim_timx_int_init(uint16_t arr, uint16_t psc)
{
    GTIM_TIMX_INT_CLK_ENABLE();                             /* 使能TIMx时钟 */

    g_timx_handle.Instance = GTIM_TIMX_INT;                 /* 通用定时器x */
    g_timx_handle.Init.Prescaler = psc;                     /* 预分频系数 */
    g_timx_handle.Init.CounterMode = TIM_COUNTERMODE_UP;    /* 递增计数模式 */
    g_timx_handle.Init.Period = arr;                        /* 自动装载值 */
    HAL_TIM_Base_Init(&g_timx_handle);

    HAL_NVIC_SetPriority(GTIM_TIMX_INT_IRQn, 1, 3);         /* 设置中断优先级,抢占优先级1,子优先级3 */
    HAL_NVIC_EnableIRQ(GTIM_TIMX_INT_IRQn);                 /* 开启ITMx中断 */

    HAL_TIM_Base_Start_IT(&g_timx_handle);                  /* 使能定时器x和定时器x更新中断 */
}

 // 定时器中断服务函数
void GTIM_TIMX_INT_IRQHandler(void)
{
    /* 以下代码没有使用定时器HAL库共用处理函数来处理,而是直接通过判断中断标志位的方式     */
    //SR寄存器的位0就是更新中断标志位
    if (__HAL_TIM_GET_FLAG(&g_timx_handle, TIM_FLAG_UPDATE) != RESET)//获取更新中断标志位
    {
        LED1_TOGGLE();
        __HAL_TIM_CLEAR_IT(&g_timx_handle, TIM_IT_UPDATE);  /* 清除定时器溢出中断标志位 */
    }
}

stm32fxxx_hal_tim.c

HAL_StatusTypeDef HAL_TIM_Base_Init(TIM_HandleTypeDef *htim)
{
#if (USE_HAL_TIM_REGISTER_CALLBACKS == 1)
    /* Reset interrupt callbacks to legacy weak callbacks */
    TIM_ResetCallback(htim);

    if (htim->Base_MspInitCallback == NULL)
    {
      htim->Base_MspInitCallback = HAL_TIM_Base_MspInit;
    }
    /* Init the low level hardware : GPIO, CLOCK, NVIC */
    htim->Base_MspInitCallback(htim);
#else
    /* Init the low level hardware : GPIO, CLOCK, NVIC */
    HAL_TIM_Base_MspInit(htim);
#endif /* USE_HAL_TIM_REGISTER_CALLBACKS */
  }


  /* Set the Time Base configuration */
  TIM_Base_SetConfig(htim->Instance, &htim->Init);
                    ……
  return HAL_OK;
}

通用定时器PWM输出实验

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

CCR1,Capture/Compare Register 1,捕获/比较预装载寄存器1。在捕获模式下,CCR1 寄存器用于存储捕获到的定时器计数器的数值;在比较模式下,CCR1 寄存器用于存储比较值,与定时器计数器的值进行比较。通过配置 CCR1 寄存器,可以实现定时器的捕获功能或比较功能,用于测量时间间隔、生成脉冲、控制PWM输出等应用。

OC1PE,Output compare 1 preload enable,输出比较1预装载使能,禁止CCR1寄存器的预装载功能

输出比较时,用户将捕获/比较值写入CCR1,

当CCR1不处于写入操作,

且CCMR寄存器的CC1S域=00,也就是配置通道1处于输出比较模式,

同时预装载使能OC1PE或触发更新事件UEV时,

CCR1的值被转移至影子寄存器,计数器值与影子寄存器进行比较,

满足则信号进入输出模式控制器,得到输出控制参考信号oc1ref,由CCER寄存器的CC1P位域来设置参考信号的极性,之后到达输出使能电路,再往后输出就到CH1通道1了。

ETRF 强制清零

OC1CE,Output Compare 1 Clear Enable 输出比较1清零使能

捕获/比较模式寄存器 1 TIMx_CCMR1:

TIM2/TIM3/TIM4/TIM5 的捕获/比较模式寄存器(TIMx_CCMR1/2),该寄存器一般有 2 个:
TIMx _CCMR1 和 TIMx _CCMR2。TIMx_CCMR1 控制 CH1 和 CH2,而 TIMx_CCMR2 控制
CH3 和 CH4。

捕获/比较使能寄存器 TIMx_CCER:

通用定时器输出PWM原理

假设:递增计数模式

ARR:自动重装载寄存器的值

CCRx:捕获/比较寄存器x的值

计数值从零开始递增,当计数值到达CCRx寄存器的比较值之前,IO口输出低电平,到达CCRx寄存器的比较值之后,IO口输出高电平

IO口输出为高/低对应就是OCx输出高/低(OCx指的是IO口复用为CHx)

ARR寄存器的自动重装值决定PWM周期

CCRx寄存器的比较值决定占空比

PWM模式

当使用比较/模式寄存器时,在输出模式下有8种模式,其中PWM模式有两种

PWM模式1:

        递增:CNT < CCRx,输出有效电平              CNT计数值   CCRx比较值

                  CNT >= CCRx,输出无效电平

        递减:CNT > CCRx,输出无效电平              

                   CNT <= CCRx,输出有效电平

PWM模式2:

        递增:CNT < CCRx,输出无效电平              

                  CNT >= CCRx,输出有效电平

        递减:CNT > CCRx,输出有效电平              

                   CNT <= CCRx,输出无效电平

有/无效状态由TIMx_CCER决定。CCxP=0:OCx高电平有效; CCxP=1:Ocx低电平有效

TIMx_CCER,捕获/比较使能寄存器     CCxP,捕获/比较极性设置位域

OCx高电平有效就是指OCx输出高电平

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

1,配置定时器基础工作参数          HAL_TIM_PWM_Init()

2,定时器PWM输出MSP初始化    HAL_TIM_PWM_MspInit()     配置NVIC、CLOCK、GPIO等

3,配置PWM模式/比较值等           HAL_TIM_PWM_ConfigChannel()

4,使能输出并启动计数器              HAL_TIM_PWM_Start()

5,修改比较值控制占空比(可选)    __HAL_TIM_SET_COMPARE()

6,使能通道预装载(可选)              __HAL_TIM_ENABLE_OCxPRELOAD()

main.c

int main(void)
{

    HAL_Init();                                 /* 初始化HAL库 */
    sys_stm32_clock_init(360, 25, 2, 8);        /* 设置时钟,180Mhz */
    ……
    //led_init();                                 /* 初始化LED */
    gtim_timx_pwm_chy_init(500 - 1, 90 - 1);    /* 1Mhz的计数频率,2Khz的PWM */

    while (1)
    {
        ……
        __HAL_TIM_SET_COMPARE(&g_timx_pwm_chy_handle, GTIM_TIMX_PWM_CHY, ledrpwmval);
    }
}

//操作捕获比较寄存器CCR1 CCR2 CCR3 CCR4
#define __HAL_TIM_SET_COMPARE(__HANDLE__, __CHANNEL__, __COMPARE__) \
  (((__CHANNEL__) == TIM_CHANNEL_1) ? ((__HANDLE__)->Instance->CCR1 = (__COMPARE__)) :\
   ((__CHANNEL__) == TIM_CHANNEL_2) ? ((__HANDLE__)->Instance->CCR2 = (__COMPARE__)) :\
   ((__CHANNEL__) == TIM_CHANNEL_3) ? ((__HANDLE__)->Instance->CCR3 = (__COMPARE__)) :\
   ((__HANDLE__)->Instance->CCR4 = (__COMPARE__)))

gtim.c

#define GTIM_TIMX_PWM                       TIM3    

TIM_HandleTypeDef g_timx_pwm_chy_handle;     /* 定时器x句柄 */    

/**
 * @brief       通用定时器TIMX 通道Y PWM输出 初始化函数(使用PWM模式1)
 * @param       arr: 自动重装值
 * @param       psc: 预分频系数
 */
void gtim_timx_pwm_chy_init(uint16_t arr, uint16_t psc)
{
    TIM_OC_InitTypeDef timx_oc_pwm_chy = {0};  /* 定时器PWM输出配置句柄 */
    
    g_timx_pwm_chy_handle.Instance = GTIM_TIMX_PWM;   /* 定时器x */
    g_timx_pwm_chy_handle.Init.Prescaler = psc;      /* 预分频系数 */
    g_timx_pwm_chy_handle.Init.CounterMode = TIM_COUNTERMODE_UP;    /* 递增计数模式 */
    g_timx_pwm_chy_handle.Init.Period = arr;       /* 自动重装载值 */
    HAL_TIM_PWM_Init(&g_timx_pwm_chy_handle);      /* 初始化PWM */

    timx_oc_pwm_chy.OCMode = TIM_OCMODE_PWM1;       /* PWM1模式 */
    timx_oc_pwm_chy.Pulse = arr / 2;               /* 设置比较值,此值用来确定占空比 */

    timx_oc_pwm_chy.OCPolarity = TIM_OCPOLARITY_LOW;    /* 输出比较极性为低 */
    HAL_TIM_PWM_ConfigChannel(&g_timx_pwm_chy_handle, &timx_oc_pwm_chy, GTIM_TIMX_PWM_CHY); /* 配置TIMx通道y */
    HAL_TIM_PWM_Start(&g_timx_pwm_chy_handle, GTIM_TIMX_PWM_CHY); /* 开启对应PWM通道 */
}

typedef struct
{

  uint32_t OCMode;      //输出比较模式选择  8种  //CCMR 
  uint32_t Pulse;       //设置比较值        //CCRx
  uint32_t OCPolarity;  //输出比较极性     //CCxP
  uint32_t OCNPolarity; //互补输出比较极性(高级定时器才有)
  uint32_t OCFastMode;  //快速模式使能
  uint32_t OCIdleState; //空闲状态下OC1输出
  uint32_t OCNIdleState; //空闲状态下OC1互补输出
} TIM_OC_InitTypeDef;

 stm32f4xx_hal_tim.c

//TIM句柄
{
  TIM_TypeDef                        *Instance; //定时器实例        
  TIM_Base_InitTypeDef               Init;      //存储定时器的基本初始化配置,如时钟分频、计数模式等。       
  HAL_TIM_ActiveChannel              Channel;   //激活的通道,指示当前定时器的哪个通道是活跃的                      
  …… 
} TIM_HandleTypeDef; 

//TIM_Base_Init句柄
typedef struct
{
  uint32_t Prescaler;      //预分频系数
  uint32_t CounterMode;    //计数模式   
  uint32_t Period;         //周期      
  uint32_t ClockDivision;  //时钟分频
  uint32_t RepetitionCounter;//重复计数器 
  uint32_t AutoReloadPreload; //自动重装载预装载寄存器
} TIM_Base_InitTypeDef;

HAL_StatusTypeDef HAL_TIM_PWM_Init(TIM_HandleTypeDef *htim)
{
                    ……
    /* 初始化低级硬件: GPIO, CLOCK, NVIC and DMA */
    HAL_TIM_PWM_MspInit(htim);
                    ……
  return HAL_OK;
}
TIM输出比较初始化句柄   TIM_OC_InitTypeDef

/*开始生成PWM信号.  参数:定时器句柄 通道 */
HAL_StatusTypeDef HAL_TIM_PWM_Start(TIM_HandleTypeDef *htim, uint32_t Channel)
{
                ……
   //使能捕获/比较通道  CCER寄存器
  TIM_CCxChannelCmd(htim->Instance, Channel, TIM_CCx_ENABLE);
                ……
      //定时器使能(使能计数器开始计数)  CR1寄存器 
      __HAL_TIM_ENABLE(htim);
                ……
  /* Return function status */
  return HAL_OK;
}

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

例程:通过定时器输出的PWM控制LED0,实现类似手机呼吸灯的效果

1. 确定PWM波的周期/频率

既然要实现PWM,首先要确定PWM的周期以及频率

TIM的时钟频率Ft = 90MHz 

预分频值PSC 一般取时钟频率整除的数 90

周期 T = (计数值ARR+1)*(预分频值PSC+1)/时钟频率Ft

以频率2KHz为例,周期为0.0005s,可得 在PSC=89时,ARR应设置为499

2 000Hz = 90 000 000Hz/((ARR+1)*90)=1 000 000/(ARR+1) ,得到arr+1=500

2. 配置输出比较模式为:PWM模式1,通道输出极性为:低电平有效,也就是CNT<CCRx时输出低电平,CNT>=CCRx时输出高电平

 看原理图得知LED0使用PB1口,看芯片数据手册得知PB1可复用为TIM1_CH2N、TIM3_CH4、TIM8_CH3N,因此只有TIM3_CH4适用

#define GTIM_TIMX_PWM                       TIM3   
#define GTIM_TIMX_PWM_CHY                   TIM_CHANNEL_4    /* 通道Y,1<= Y <=4 */ 

TIM_HandleTypeDef g_timx_pwm_chy_handle;     /* 定时器x句柄 */    

/**
 * @brief       通用定时器TIMX 通道Y PWM输出 初始化函数(使用PWM模式1)
 * @param       arr: 自动重装值
 * @param       psc: 预分频系数
 */
void gtim_timx_pwm_chy_init(uint16_t arr, uint16_t psc)
{
    TIM_OC_InitTypeDef timx_oc_pwm_chy = {0};  /* 定时器PWM输出配置句柄 */
    
    g_timx_pwm_chy_handle.Instance = GTIM_TIMX_PWM;   /* 定时器x */
    g_timx_pwm_chy_handle.Init.Prescaler = psc;      /* 预分频系数 */
    g_timx_pwm_chy_handle.Init.CounterMode = TIM_COUNTERMODE_UP;    /* 递增计数模式 */
    g_timx_pwm_chy_handle.Init.Period = arr;       /* 自动重装载值 */
    HAL_TIM_PWM_Init(&g_timx_pwm_chy_handle);      /* 初始化PWM */

    timx_oc_pwm_chy.OCMode = TIM_OCMODE_PWM1;       /* PWM1模式 */
    timx_oc_pwm_chy.Pulse = arr / 2;               /* 设置比较值,此值用来确定占空比 */

    timx_oc_pwm_chy.OCPolarity = TIM_OCPOLARITY_LOW;    /* 输出比较极性为低 */
    HAL_TIM_PWM_ConfigChannel(&g_timx_pwm_chy_handle, &timx_oc_pwm_chy, GTIM_TIMX_PWM_CHY); /* 配置TIMx通道y */
    HAL_TIM_PWM_Start(&g_timx_pwm_chy_handle, GTIM_TIMX_PWM_CHY); /* 开启对应PWM通道 */
}

/**
 * @brief       定时器底层驱动,时钟使能,引脚配置此函数会被HAL_TIM_PWM_Init()调用
 * @param       htim:定时器句柄
 */
void HAL_TIM_PWM_MspInit(TIM_HandleTypeDef *htim)
{
    if (htim->Instance == GTIM_TIMX_PWM)
    {
        GPIO_InitTypeDef gpio_init_struct;
        GTIM_TIMX_PWM_CHY_GPIO_CLK_ENABLE();     /* 使能通道y的GPIO时钟 */
        GTIM_TIMX_PWM_CHY_CLK_ENABLE();          /* 使能定时器时钟 */

        gpio_init_struct.Pin = GTIM_TIMX_PWM_CHY_GPIO_PIN;  /* 使能通道y的GPIO口 */
        gpio_init_struct.Mode = GPIO_MODE_AF_PP;      /* 复用推挽输出 */
        gpio_init_struct.Pull = GPIO_PULLUP;                            /* 上拉 */
        gpio_init_struct.Speed = GPIO_SPEED_FREQ_HIGH;                  /* 高速 */
        gpio_init_struct.Alternate = GTIM_TIMX_PWM_CHY_GPIO_AF;/*定时器x通道y的GPIO复用*/
        HAL_GPIO_Init(GTIM_TIMX_PWM_CHY_GPIO_PORT, &gpio_init_struct);
    }
}
int main(void)
{
    uint16_t ledrpwmval = 0;
    uint8_t dir = 1;

    HAL_Init();                                 /* 初始化HAL库 */
    sys_stm32_clock_init(360, 25, 2, 8);        /* 设置时钟,180Mhz */
    delay_init(180);                            /* 延时初始化 */
    led_init();                                 /* 初始化LED */
    gtim_timx_pwm_chy_init(500 - 1, 90 - 1);    /* 1Mhz的计数频率,2Khz的PWM */

    while (1)
    {
        delay_ms(10);

        if (dir)
        {ledrpwmval++;} /* dir==1,ledrpwmval递增 */
        else {ledrpwmval--;}/* dir==0,ledrpwmval递减 */

        if (ledrpwmval > 300)
        { dir = 0;}   /* ledrpwmval到达300后,方向为递减 */
        if (ledrpwmval == 0)
        {dir = 1;} /* ledrpwmval递减到0后,方向改为递增 */

        /* 修改比较值控制占空比 */
        __HAL_TIM_SET_COMPARE(&g_timx_pwm_chy_handle, GTIM_TIMX_PWM_CHY, ledrpwmval);
    }
}

通用定时器输入捕获实验

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

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

捕获/比较通道的输入部分

要使用通用定时器的捕获/比较通道的输入功能,需要将捕获/比较模式寄存器CCMR的捕获/比较选择域CC1S配置为输入捕获,并设置捕获分频系数 ICPS,同时设置CCER使能输入捕获

如果CC1S配置为01,则信号走TI1FP1滤波通道

如果CC1S配置为10,则信号走TI2FP1滤波通道

信号从CHx通道输入进来,经过滤波器采样滤波,到达边沿检测器,配置极性之后,来到分频器,使能输入后得到捕获输入IC1PS

捕获/比较通道的主电路---输入部分

CC1S域配置通道为输入

CC1E使能捕获,也就是配置为输入

IC1PS为经过分频后的信号,CC1E为捕获输入使能(硬件产生捕获事件)

TIMx_EGR的CC1G域也可设置软件产生捕获事件

产生捕获事件后会把计数器的值转移到影子寄存器中,当CCR1寄存器不处在读操作下,也就是CCR1寄存器空闲时,影子寄存器的值会被转移到CCR1寄存器,因此用户就可通过读取CCR1寄存器得到捕获到的计数器的值

举个例子,比如刚开始把通道设置为上升沿触发,当捕获到上升沿的时候,会产生一个捕获事件,把计数器CNT的值捕获到CCR的影子寄存器当CCR空闲的时候,CNT的值从影子寄存器转移到CCR用户读取CCR的值就可以知道CNT的值(命名为CNT1)

然后马上把通道设置为下降沿触发,得到CNT2,CNT2-CNT1就可知高电平计数个数(高电平时间)

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

先把通道配置为上升沿检测,通道上一个上升沿到来时,读取CCR捕获CNT的值(记得CNT清零)

然后通道配置为下降沿检测,信号下降沿的时候,读取CCR捕获CNT的值

此时下降沿有两种情况,一种是在计数溢出时到来,一种是在计数溢出后到来(用更新中断记录溢出次数)

计数器溢出条件是 CNT = ARR,之后再计数就是到0,再到1,因此要注意第一次溢出后实际计数为ARR+1

通用定时器输入捕获实验配置步骤

1,配置定时器基础工作参数               HAL_TIM_IC_Init()

2,定时器输入捕获MSP初始化           HAL_TIM_IC_MspInit()     配置NVIC、CLOCK、GPIO等

3,配置输入通道映射、边沿检测等    HAL_TIM_IC_ConfigChannel()

4,设置优先级,使能中断                   HAL_NVIC_SetPriority()、 HAL_NVIC_EnableIRQ()

5,使能定时器更新中断                      __HAL_TIM_ENABLE_IT()

6,使能捕获、捕获中断及计数器        HAL_TIM_IC_Start_IT()

7,编写中断服务函数                          TIMx_IRQHandler()等   ->  HAL_TIM_IRQHandler()

8,编写更新中断和捕获回调函数        HAL_TIM_PeriodElapsedCallback()                                                             HAL_TIM_IC_CaptureCallback()

TIM输入捕获相关HAL库函数介绍
TIM输入捕获初始化结构体  TIM_IC_InitTypeDef
typedef struct
{
  /*CCER寄存器 CC1P位域 极性设置 输入模式下,0不反向,上升沿有效,1反向,下降沿有效*/
  uint32_t  ICPolarity; //输入捕获触发方式,比如上升、下降沿捕获 8种触发方式

  /*CCMR寄存器 
    CC1S位域[1:0] 捕获/比较模式选择 
                    00 CC1通道被配置为输出 
                    01 CC1通道被配置为输入,IC1映射在TI1上
                    10 CC1通道被配置为输入,IC1映射在TI2上
                    11 CC1通道被配置为输入,IC1映射在TRC上*/
  uint32_t ICSelection; //输入捕获选择,用于设置映射关系        3种映射关系

  /*CCMR寄存器 IC1PSC[1:0]域 输入捕获1预分频 
                                        00无预分频 
                                        01每2个事件触发一次捕获
                                        10每4个事件触发一次捕获
                                        11每8个事件触发一次捕获*/
  uint32_t ICPrescaler; //输入捕获分频系数,几个上升沿或下降沿产生一次捕获事件

  /*CCMR寄存器 IC1F域[3:0] 输入捕获1滤波设置
                                    0000 无滤波器,以工作频率采样
                                    0001 采样频率=工作频率,N=2 ……*/
  uint32_t ICFilter;    //输入捕获滤波器设置
} TIM_IC_InitTypeDef;  //TIM输入捕获初始化结构体
void gtim_timx_cap_chy_init(uint16_t arr, uint16_t psc)
{
    TIM_IC_InitTypeDef timx_ic_cap_chy = {0};
    
    g_timx_cap_chy_handle.Instance = GTIM_TIMX_CAP; /* 定时器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, GTIM_TIMX_CAP_CHY); /* 配置TIM5通道1 */

    __HAL_TIM_ENABLE_IT(&g_timx_cap_chy_handle, TIM_IT_UPDATE);    /* 使能更新中断 */
    HAL_TIM_IC_Start_IT(&g_timx_cap_chy_handle, GTIM_TIMX_CAP_CHY); /* 开始捕获TIM5的通道1 */
}
HAL_StatusTypeDef HAL_TIM_IC_Init(TIM_HandleTypeDef *htim)
{
    /* 初始化基础外设: GPIO, CLOCK, NVIC and DMA */
    HAL_TIM_IC_MspInit(htim);
            ……
  return HAL_OK;
}
HAL_StatusTypeDef HAL_TIM_IC_Start_IT(TIM_HandleTypeDef *htim, uint32_t Channel)
{
        ……
  switch (Channel) //判断通道
  {
    case TIM_CHANNEL_1:
    {
      /* 打开通道的捕获中断 */
      __HAL_TIM_ENABLE_IT(htim, TIM_IT_CC1);
      break;
    }
        ……
  }
      TIM_CCxChannelCmd(htim->Instance, Channel, TIM_CCx_DISABLE);//使能通道输入
       ……
      /*配置时禁用定时器 CR1寄存器 CEN位域 0禁止计数器 1使能计数器*/
      __HAL_TIM_DISABLE(htim);
}

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

例程:通过定时器5通道1来捕获按键高电平脉宽时间,通过串口打印出来

阿波罗开发板定时器5通道1外接IO口PA0

PA0处按键按下的时候产生上升沿,按键松开的时候产生下降沿

1,确定计数器工作频率

        1MHz计数频率,ARR最大值65535,PSC=89

2,配置输入捕获方式:上升沿捕获、输入捕获通道1映射在通道1(TI1)上、不分频、不滤波

 

 输入模式下配不配置GPIO的速度无所谓

 

gtim.h

#define GTIM_TIMX_CAP_CHY_GPIO_PORT         GPIOA
#define GTIM_TIMX_CAP_CHY_GPIO_PIN          GPIO_PIN_0
#define GTIM_TIMX_CAP_CHY_GPIO_CLK_ENABLE() do{ __HAL_RCC_GPIOA_CLK_ENABLE(); }while(0)   /* PA口时钟使能 */
#define GTIM_TIMX_CAP_CHY_GPIO_AF           GPIO_AF2_TIM5                                 /* AF功能选择 */

#define GTIM_TIMX_CAP                       TIM5                       
#define GTIM_TIMX_CAP_IRQn                  TIM5_IRQn
#define GTIM_TIMX_CAP_IRQHandler            TIM5_IRQHandler
#define GTIM_TIMX_CAP_CHY                   TIM_CHANNEL_1                                 /* 通道Y,1<= Y <=4 */
#define GTIM_TIMX_CAP_CHY_CLK_ENABLE()      do{ __HAL_RCC_TIM5_CLK_ENABLE(); }while(0)    /* TIM5时钟使能 */

main.c 

#define __HAL_TIM_ENABLE_IT(__HANDLE__, __INTERRUPT__)    ((__HANDLE__)->Instance->DIER |= (__INTERRUPT__))
int main(void)
{
    uint32_t temp = 0;
    uint8_t t = 0;

    HAL_Init();                              /* 初始化HAL库 */
    sys_stm32_clock_init(360, 25, 2, 8);     /* 设置时钟,180Mhz */
    usart_init(115200);                      /* 初始化USART */
    delay_init(180);                         /* 延时初始化 */
    led_init();                              /* 初始化LED */
    gtim_timx_cap_chy_init(0xFFFF, 90 - 1);  /* 以1Mhz的频率计数 捕获 */

    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);
    }
}

通用定时器脉冲计数实验

内部时钟,来自APB1总线上的时钟

外部时钟1,来自CH1和CH2

外部时钟2,来自ETR引脚(IO复用)

内部触发输入ITR0~3,用于定时器级联

脉冲计数实验原理

外部时钟1模式:

常用外部时钟1作为通用定时器的CNT时钟源,也就是来自CH1和CH2的时钟信号(CH3和CH4不能作为时钟源,因为从模式控制器只有TI1FP1和TI2FP2的信号,也就是CH1和CH2的信号来源)

TI1F_ED是双边沿触发信号。来一个CHx通道的时钟信号(脉冲信号)计一个数,因此直接读取CNT的值就能达到脉冲计数效果。

外部时钟2模式:

外部时钟2时钟信号来源于ETR引脚,经过极性选择、边沿检测、预分频,到达滤波器,到达从模式控制器,最后到达CNT

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

 关于定时器的从模式:

void gtim_timx_cnt_chy_init(uint16_t psc)
{
    GPIO_InitTypeDef gpio_init_struct;
    TIM_SlaveConfigTypeDef tim_slave_config = {0};
    GTIM_TIMX_CNT_CHY_CLK_ENABLE();     /* 使能TIMx时钟 */
    GTIM_TIMX_CNT_CHY_GPIO_CLK_ENABLE();     /* 开启GPIOA时钟 */
    
    g_timx_cnt_chy_handle.Instance = GTIM_TIMX_CNT;  /* 定时器x */
    g_timx_cnt_chy_handle.Init.Prescaler = psc;     /* 预分频系数 */
    g_timx_cnt_chy_handle.Init.CounterMode = TIM_COUNTERMODE_UP;  /* 递增计数模式 */
    g_timx_cnt_chy_handle.Init.Period = 65535;       /* 自动重装载值 */
    HAL_TIM_IC_Init(&g_timx_cnt_chy_handle);

    gpio_init_struct.Pin = GTIM_TIMX_CNT_CHY_GPIO_PIN;   /* 输入捕获的GPIO口 */
    gpio_init_struct.Mode = GPIO_MODE_AF_PP;       /* 复用推挽输出 */
    gpio_init_struct.Pull = GPIO_PULLDOWN;        /* 下拉 */
    gpio_init_struct.Speed = GPIO_SPEED_FREQ_VERY_HIGH;      /* 高速 */
    gpio_init_struct.Alternate = GTIM_TIMX_CNT_CHY_GPIO_AF;  /* 复用为捕获TIMx的通道 */
    HAL_GPIO_Init(GTIM_TIMX_CNT_CHY_GPIO_PORT, &gpio_init_struct);

    /* 从模式:外部触发模式1 */
    tim_slave_config.SlaveMode = TIM_SLAVEMODE_EXTERNAL1;     /* 从模式:外部触发模式1 */
    tim_slave_config.InputTrigger = TIM_TS_TI1FP1;         /* 输入触发:选择 TI1FP1(TIMX_CH1) 作为输入源 */
    tim_slave_config.TriggerPolarity = TIM_TRIGGERPOLARITY_RISING;  /* 触发极性:上升沿 */
    tim_slave_config.TriggerPrescaler = TIM_TRIGGERPRESCALER_DIV1;   /* 触发预分频:不分频 */
    tim_slave_config.TriggerFilter = 0x0;      /* 滤波:本例中不需要任何滤波 */

    //从模式配置
    HAL_TIM_SlaveConfigSynchronization(&g_timx_cnt_chy_handle, &tim_slave_config);

    HAL_NVIC_SetPriority(GTIM_TIMX_CNT_IRQn, 1, 3);      /* 设置中断优先级,抢占优先级1,子优先级3 */
    HAL_NVIC_EnableIRQ(GTIM_TIMX_CNT_IRQn);     /* 开启ITMx中断 */

    __HAL_TIM_ENABLE_IT(&g_timx_cnt_chy_handle, TIM_IT_UPDATE);    /* 使能更新中断 */
    HAL_TIM_IC_Start(&g_timx_cnt_chy_handle, GTIM_TIMX_CNT_CHY); /* 开启捕获TIMx的通道y */
}

TIM从模式配置结构体
//针对ETR触发源
#define TIM_TRIGGERPOLARITY_INVERTED          TIM_ETRPOLARITY_INVERTED   //反转            
#define TIM_TRIGGERPOLARITY_NONINVERTED       TIM_ETRPOLARITY_NONINVERTED //不反转           
//针对TI1FP1和TI2FP2触发源
#define TIM_TRIGGERPOLARITY_RISING            TIM_INPUTCHANNELPOLARITY_RISING //上升      
#define TIM_TRIGGERPOLARITY_FALLING           TIM_INPUTCHANNELPOLARITY_FALLING//下降     
#define TIM_TRIGGERPOLARITY_BOTHEDGE          TIM_INPUTCHANNELPOLARITY_BOTHEDGE//双边  


typedef struct
{
  /*从模式选择   
              可选参数:
                        不使用从模式 预分频器直接由内部时钟驱动
                        复位模式  选中的触发输入(TRGI)的上升沿重新初始化计数器
                        门控模式  当触发输入TRGI为高时计数器开启(不复位),为低时停止
                        触发模式  计数器在触发输入TRGI的上升沿启动
                        */
  uint32_t  SlaveMode;   //   

  /*CCMR寄存器  可选的就是TI1_ED TI1FP1 TI2FP2这三种触发源*/
  uint32_t  InputTrigger;     //输入触发源选择 
  uint32_t  TriggerPolarity;  //输入触发极性
  uint32_t  TriggerPrescaler;  //输入触发预分频(ETR线路的预分频器,仅外2模式生效)
  uint32_t  TriggerFilter;   //输入触发滤波器设置
} TIM_SlaveConfigTypeDef;

HAL_StatusTypeDef HAL_TIM_SlaveConfigSynchro(TIM_HandleTypeDef *htim, TIM_SlaveConfigTypeDef *sSlaveConfig)
{
    ……
  if (TIM_SlaveTimer_SetConfig(htim, sSlaveConfig) != HAL_OK)
  {
    htim->State = HAL_TIM_STATE_READY;
    ……
  }
}
SMCR寄存器的SMS[2:0]位域 从模式选择

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

例程:

将定时器2通道1输入的高电平脉冲作为定时器2的时钟,并通过串口打印脉冲数

定时器 2,使用 TIM2 通道 1PA0 复用为 TIM2_CH1

1.  确定计数器工作频率

 2. 配置从模式:外部i时钟模式1、触发选择、上升沿触发、不分配、不滤波

定时器更新中断服务函数里面做计数

通用定时器总结(掌握)

通用定时器一共有四种功能,分别是

定时器中断功能

PWM输出功能

输入捕获功能

脉冲计数功能 

定时器中断功能配置

/*关键结构体*/

/*TIM句柄*/
struct typedef{
  TIM_TypeDef              *Instance;   /*外设寄存器基地址*/
  TIM_Base_InitTypeDef     Init;        /*定时器初始化结构体*/
  ……
}TIM_HandleTypeDef;

/*定时器初始化结构体*/
typedef struct
{
  uint32_t Prescaler;          //预分频系数 PSCR
  uint32_t CounterMode;        //计数模式 CR1->CMS  上升/下降/对齐
  uint32_t Period;             //自动重装载值ARR
  uint32_t AutoReloadPreload;  //自动重装载预装载寄存器使能CR1->ARPE
      // uint32_t RepetitionCounter;//重复计数器RCR的值
} TIM_Base_InitTypeDef;

聊定时器的中断功能首先要聊TIM_HandleTyepDef这个句柄,句柄包含了TypeDefBase_InitTypeDef两个成员,分别是定时器基地址定时器初始化结构体,初始化结构体里面主要包括预分频系数Prescaler计数模式CounterModeARR自动重装载值PeriodCR1->ARPE使能

HAL_TIM_BaseInit(&HandleTypeDef)会调用

HAL_TIM_MSPInit(&HandlerTypeDef)来使能TIM时钟(APB2),设置中断优先级、使能中断

TIM_Base_SetConfig(&Instance, &Init);来配置CR1的CMS、ARPE位域,ARR重装载值,PSC预分频值,EGR寄存器的UG位

HAL_TIM_Base_Start_IT(&HandleTypeDef);

会设置DIER寄存器来使能更新中断

TIMx_IRQHandler()会调用

公有中断服务函数HAL_TIM_IRQHandler(&HandleTypeDef),公有中断服务函数会根据ERG的UG位状态来决定是否调用

溢出中断回调函数HAL_TIM_PeriodElapsedCallback(&HandleTypeDef)

PWM输出功能配置:

typedef struct
{
  uint32_t OCMode;       //输出比较模式
  uint32_t Pulse;        //比较值
  uint32_t OCPolarity;   //输出极性设置
                            //uint32_t OCNPolarity;  
  uint32_t OCFastMode;   //快速模式
  uint32_t OCIdleState;  //空闲状态
                             //uint32_t OCNIdleState; 
                             //uint32_t ICPolarity;
                             //uint32_t ICSelection;
                             // uint32_t ICFilter;
  
} TIM_OC_InitTypeDef; //定时器OC初始化结构体

定时器中断功能用到了定时器初始化句柄TIM_HandleTypedef,里面有定时器基地址TIM_TypeDef和初始化结构体TIM_Base_InitTypeDef,

配置PWM输出功能需要多用到一个定时器OC初始化结构体TIM_OC_InitTypeDef来配置输出比较的相关参数,如输出比较模式比较值输出极性快速模式空闲状态

HAL_TIM_PWM_Init(&HandleTypeDef)会调用

HAL_TIM_PWM_MspInit(&HandleTypeDef)来使能

        GPIO时钟和TIM时钟,初始化GPIO口,使能复用时钟和重映射(若需计数PWM可配置NVIC)

TIM_Base_SetConfig(&HandleTypeDef, &OC_InitTypeDef)来配置

        CR1的CMS、ARPE位域,ARR重装载值,PSC预分频值,EGR寄存器的UG位

HAL_TIM_PWM_ConfigChannel(HandleTypeDef, OC_InitTypeDef, Channel)会根据通道号调用TIM_OCx_SetConfig(HandleTypeDef, OC_InitTypeDef)配置CCMR1/2寄存器

HAL_TIM_PWM_Start(HandleTypeDef, Channel)会设置CCER寄存器使能通道(高级定时器可使能主输出)

        最后若是编写指定个数PWM可编写中断服务函数(TIMx_IRQHandler)和更新中断回调函数HAL_TIM_PeriodElapsedCallback()

输入捕获功能配置:

typedef struct
{
  uint32_t  ICPolarity; //捕获通道极性 上升/下降/双边 
  uint32_t ICSelection; //捕获通道选择 
  uint32_t ICPrescaler; //捕获通道预分频  多少个事件触发次捕获
  uint32_t ICFilter;    //捕获通道滤波
} TIM_IC_InitTypeDef;

定时器中断用到了初始化句柄HandleTypedef,句柄有成员TIM_TypeDef指向定时器基地址,TIM_Base_InitTypeDef作为初始化结构体,用来设置预分频系数PSC、计数模式、ARR重装载值、ARPE自动重装载预装载值等。

PWM输出模式用到了PWM_InitTypeDef结构体来初始化,包括OC模式、比较值、输出极性、夸苏模式、空闲状态等。

输入捕获IC功能,则用到了IC_InitTypeDef结构体来初始化,包括通道极性IC通道选择捕获通道预分频系数捕获通道滤波等。

HAL_TIM_IC_Init(&HandleTypeDef)会调用

HAL_TIM_IC_MspInit()使能TIM、GPIO时钟、初始化GPIO、设置NVIC分组和优先级并开启中断

TIM_Base_SetConfig(Instance, &Init)来配置CR1的CMS、ARPE位域,ARR重装载值,PSC预分频值,EGR寄存器的UG位

HAL_TIM_IC_ConfigChannel(&HandleTypeDef, &IC_InitTypeDef, Channel)会使用

__HAL_TIM_ENABLE_IT(&HandleTypeDef, TIM_IT_UPDATE)控制DIER使能更新中断

HAL_TIM_IC_Start_IT(&HandleTypeDef, Channle)会根据通道号控制DIER使能该通道的捕获中断

通过定时器中断服务函数TIMx_IRQHandler调用公用中断处理函数HAL_TIM_IRQHandler(&HandleTypeDef),再调用输入捕获回调函数HAL_TIM_IC_CaptureCallback(&HandleTypeDef)

脉冲计数功能配置

typedef struct
{
  uint32_t  SlaveMode;        //指定定时器作为从设备时的工作模式,例如触发模式、外部时钟模式等
  uint32_t  TriggerPolarity;  //触发信号极性
  uint32_t  TriggerPrescaler; //触发信号预分频系数
  uint32_t  TriggerFilter;    //触发信号输入滤波
} TIM_SlaveConfigTypeDef;//TIM从模式配置结构体

定时器中断用到了初始化句柄HandleTypedef,句柄有成员TIM_TypeDef指向定时器基地址,TIM_Base_InitTypeDef作为初始化结构体,用来设置预分频系数PSC、计数模式、ARR重装载值、ARPE自动重装载预装载值等。

PWM输出模式用到了PWM_InitTypeDef结构体来初始化,包括OC模式、比较值、输出极性、夸苏模式、空闲状态等。

输入捕获IC功能用IC_InitTypeDef结构体来初始化,包括通道极性、IC通道选择、捕获通道预分频系数、捕获通道滤波等。

输入脉冲计数功能使用从模式初始化结构体Slave_InitTypeDef来初始化,包括从设备的工作模式触发信号极性触发信号预分频系数触发信号输入滤

HAL_TIM_IC_Init(&HandleTypeDef)会调用

HAL_TIM_IC_MspInit()使能TIM、GPIO时钟、初始化GPIO、设置NVIC分组和优先级并开启中断

TIM_Base_SetConfig(Instance, &Init)来配置CR1的CMS、ARPE位域,ARR重装载值,PSC预分频值,EGR寄存器的UG位

HAL_TIM_SlaveConfigSynchro(&HandleTypeDef, &SlaveConfigTypeDef)

TIM_SlaveTimer_SetConfig(&SlaveConfigTypeDef)会设置从模式控制寄存器SMCR

HAL_TIM_IC_Start_IT(&HandleTypeDef, Channle)会根据通道号控制DIER使能该通道的捕获中断

 __HAL_TIM_SET_COUNTER(&g_timx_cnt_chy_handle, 0);可以设置计数器CNT的值

__HAL_TIM_GET_COUNTER(&g_timx_cnt_chy_handle, 0);可以读取计数器CNT的值

可以直接在HAL_TIMx_IRQHandler()里面通过判断中断标志位TIM_SR_UIF来累计溢出次数,每次更新中断后清除DIER的更新中断使能标志位UIE

#define TIM_FLAG_UPDATE                    TIM_SR_UIF 
#define TIM_IT_UPDATE                      TIM_DIER_UIE 

void TIM2_IRQHandler(void)
{
    /* 以下代码没有使用定时器HAL库共用处理函数来处理,而是直接通过判断中断标志位的方式 */
    if(__HAL_TIM_GET_FLAG(&g_timx_cnt_chy_handle, TIM_FLAG_UPDATE) != RESET)
    {
        g_timxchy_cnt_ofcnt++;          /* 累计溢出次数 */
    }
    __HAL_TIM_CLEAR_IT(&g_timx_cnt_chy_handle, TIM_IT_UPDATE);
}

高级定时器

高级定时器简介

高级定时器:TIM1/TIM8

主要特性:

16位递增、递减、中心对齐计数器

16位预分频器

可用于触发DAC、ADC

在更新事件、触发事件、输入捕获、输出比较时,会产生中断/DMA请求

4个独立通道,可用于:输入捕获、输出比较、输出PWM、单脉冲模式

使用外部信号控制定时器且可实现多个定时器互连的同步电路

支持编码器和霍尔传感器电路等

重复计数器

死区时间带可编程的互补输出

断路输入(刹车信号),用于将定时器的输出信号置于用户可选的安全配置中

高级定时器框图

高级定时器时钟源部分和通用定时器是一样的

不同一:重复计数器

通用定时器CNT溢出的时候是伴随着更新事件以及更新中断的产生,而高级定时器CNT溢出后,要先经过重复计数器RER的REP位域,REP的值减到0,才会产生更新事件和更新中断

REP[7:0],Repetition,重复寄存器 RCR的位域,CNT溢出一次减一

不同二:输出比较

输出比较部分,除了CH1~CH4,还有CH1N~CH3N作为互补通道。同时有DTG[7:0]寄存器用来设置死区时间

不同三:断路功能

TIMx_BKIN称为刹车输入引脚,BKIN输入信号进来后,先经过极性选择,是高电平有效还是低电平有效,与时钟安全系统CSS的故障信号经过或门,产生一个刹车中断BI,之后信号到达输出控制模块

BKIN,BreakInput,刹车输入

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

重复计数器特性

计数器每次溢出都能使重复计数器减1,减到0时再发生一次溢出就会产生更新事件

再同步的意思就是,产生了一个更新事件后,RCR寄存器的值会缓冲到影子寄存器里面,相当于重置了影子寄存器的值。因此最后一行中间,软件产生中断后,还要产生四次溢出才触发更新事件

如果设置RCR为N,更新事件将在N+1次溢出时发生

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

BDTR,Break and Dead-Time Register,断路和死区寄存器

MOE位,主输出使能,使能输出通道OC和互补输出通道OCN。是高级定时器输出的强制性开关,置1才可输出

高级定时器输出指定个数PWM实验配置步骤

主输出只有高级定时器才有,由BDTR寄存器的MOE位使能

typedef struct 
{ 
   uint32_t OCMode; 	  /* 输出比较模式选择 */
   uint32_t Pulse; 	            /* 设置比较值 */
   uint32_t OCPolarity;       /* 设置输出比较极性 */
   uint32_t OCNPolarity;    /* 设置互补输出比较极性 */
   uint32_t OCFastMode;   /* 使能或失能输出比较快速模式 */
   uint32_t OCIdleState;     /* 空闲状态下OC1输出 */
   uint32_t OCNIdleState;  /* 空闲状态下OC1N输出 */ 
} TIM_OC_InitTypeDef;

编程实战:高级定时器输出指定个数PWM实验

例程:定时器8通道1实现指定个数PWM输出,用于控制LED1的亮灭

查芯片数据手册,灯是PB0,输出通道是PC6

灯和输出通道不在同一个接口,可以通过杜邦线,连接起来。然后灯引脚设置为输入,避免冲突。

其实就是通用定时器PWM输出基础上多了个BDER的MOE主输出使能和中断计数,直接参考通用定时器的综合章节做对比来看

高级定时器输出比较模式实验

翻转就是设置输出比较极性 

高级定时器互补输出带死区控制实验

互补输出,还带死区控制,什么意思?

电机极简图

互补输出意思就是,通道输出正波形,互补通道输出反波形,类似这种波形互补

死区指的就是俩互补波形存在的相位间隔

死区控制用的最多的就是H桥上,是指留出很短的时间间隔控制开关,来避免两个MOS管同时导通导致的短路现象。H桥一般用来控制电机正反转

带死区控制的互补输出应用之H桥

要控制电机正反转也就是控制电流从左往右或者从右往左流过电机。图上的四个三极管都是NPN型高电平导通。Q1和Q4接的输出通道OC1,Q3和Q2接的互补输出通道OC1N。假设输出通道导通是正转,那么互补通道导通就是反转输出通道和互补输出通道不能同时置于有效电平。如果OC1和OC1N都导通,电源就接地短路了。

真实的H桥还要经过很多元器件,而元器件有延迟特性,如果不加死区时间控制,通道变高,而互补通道变低,很可能由于延迟特性存在瞬间短路,所以需要加上死区时间控制。

元器件延迟特性,波形存在滞后
死区时间控制针对元器件延时特性

捕获/比较通道的输出部分(通道1~3)

死区时间计算

其实就是TIM的时钟源来除以分频系数得到工作频率 Ft就 是时钟源频率,CKD就是分频系数

刹车(断路功能)

TIMx_BKIN引脚是复用引脚。

来自BKIN引脚和时钟安全系统CSS的故障事件信号都可以触发刹车功能

发生刹车后会怎样?

1,MOE位被清零,OCx和OCxN为无效、空闲或复位状态(OSSI位选择)

2, OCx和OCxN的状态:由相关控制位状态决定,当使用互补输出时:根据情况自动控制输出电平,参考参考手册使用刹车(断路)功能小节

3,BIF位置1,如果使能了BIE位,还会产生刹车中断;如果使能了TDE位,会产生DMA请求

4,如果AOE位置 1,在下一个 更新事件UEV时,MOE位被自动置 1

高级定时器互补输出带死区实验配置步骤

typedef struct 
{ 
   uint32_t OCMode; 	  /* 输出比较模式选择 */
   uint32_t Pulse; 	            /* 设置比较值 */
   uint32_t OCPolarity;       /* 设置输出比较极性 */
   uint32_t OCNPolarity;    /* 设置互补输出比较极性 */
   uint32_t OCFastMode;   /* 使能或失能输出比较快速模式 */
   uint32_t OCIdleState;     /* 空闲状态下OCx输出 */
   uint32_t OCNIdleState;  /* 空闲状态下OCxN输出 */ 
} TIM_OC_InitTypeDef;
typedef struct 
{
    uint32_t OffStateRunMode;    /* 运行模式下的关闭状态选择 */ 
    uint32_t OffStateIDLEMode;   /* 空闲模式下的关闭状态选择 */ 
    uint32_t LockLevel; 		 /* 寄存器锁定设置 */ 
    uint32_t DeadTime; 	          /* 死区时间设置 */ 
    uint32_t BreakState; 	          /* 是否使能刹车功能 */ 
    uint32_t BreakPolarity;		 /* 刹车输入极性 */ 
    uint32_t BreakFilter; 		 /* 刹车输入滤波器(F1/F4系列没有) */ 
    uint32_t AutomaticOutput; 	/* 自动恢复输出使能,即使能AOE位 */
} TIM_BreakDeadTimeConfigTypeDef;

编程实战:高级定时器互补输出带死区控制实验

例程功能:

1,利用 TIM1_CH1(PE9)输出 70%占空比的 PWM,它的互补输出通道(PE8)则是输出 30% 占空比的 PWM

2,刹车功能,当给刹车输入引脚(PE15)输入低电平时,进行刹车,即 PE9 和 PE8 停止输出PWM

3,LED0 闪烁指示程序运行。

F1战舰频率计算


硬件资源:

1)LED 灯 LED:LED0 – PB1

2)定时器 1

        TIM1 正常输出通道 PE9

        TIM1 互补输出通道 PE8

        TIM1 刹车输入通道 PE15

可通过示波器观察PE9和PE8引脚PWM输出的情况,还可以给PE15引脚接入低电平进行刹车

高级定时器PWM输入模式实验

PWM输入模式是输入捕获模式的特例,常被用于测量PWM脉宽和频率

PWM输入模式工作原理

PWM输入模式工作原理示意图

第一,确定定时器时钟源。

本实验中我们使用内部时钟(CK_INT),高级定时器挂载在APB2总线上。

按照 sys_stm32_clock_init 函数的配置,主时钟频率360MHz,APB2总线时钟频率分频系数为2,是主时钟频率的一半,即 180MHz。计数器的计数频率确定了测量的精度。

第二,确定 PWM 输入的通道。

PWM 输入模式下测量 PWM,信号只能从通道 1(CH1)或者通道 2(CH2)输入。

第三,确定 IC1 和 IC2 的捕获边沿。

这里以通道 1(CH1)输入 PWM 为例,一般我们习惯设置 IC1 捕获边沿为上升沿捕获,IC2 捕获边沿为下降沿捕获。

第四,选择触发输入信号(TRGI)。

这里也是以通道 1(CH1)输入 PWM 为例,那么我 们就应该选择 TI1FP1为触发输入信号。如果是通道 2(CH2)输入 PWM,那就选择 TI2FP2为 触发输入信号。

第五,从模式选择:复位模式。

复位模式的作用是:在出现所选触发输入 (TRGI) 上升沿 时,重新初始化计数器并生成一个寄存器更新事件。 

第六,读取一个 PWM 周期内计数器的计数个数,以及高电平期间的计数个数,再结合计数器的计数周期(即计一个数的时间),最终计算得到输入的 PWM 周期和占空比等参数。

以通道 1(CH1)输入 PWM,设置 IC1 捕获边沿为上升沿捕获,IC2 捕获边沿为下降沿捕获为 例,那么 CCR1 寄存器的值+1 就是 PWM 周期内计数器的计数个数,CCR2 寄存器的值+1 就是 PWM 高电平期间计数器的计数个数。通过这两个值就可以计算出 PWM 的周期或者占空比等参数

PWM输入模式时序

PWM输入模式时序图

        上图是以通道 1(CH1)输入 PWM,设置 IC1 捕获边沿为上升沿捕获,IC2 捕获边沿为下降沿捕获为例的 PWM 输入模式时序图。

        从时序图可以看出,计数器的计数模式是递增计数模式。从左边开始看,当 TI1 来了上升沿时,计数器的值被复位为 0(原因是从模式选择为复位模式),IC1 和 IC2 都发生捕获事件。然后计数器的值计数到 2 的时候,IC2 发生了下降沿捕获,捕获事件会导致这时候的计数器的 值被锁存到 CCR2寄存器中,该值+1就是高电平期间计数器的计数个数。最后计数器的值计数 到 4 的时候,IC1 发生了上升沿捕获,捕获事件会导致这时候的计数器的值被锁存到 CCR1 寄 存器中,该值+1 就是 PWM 周期内计数器的计数个数。

        假设计数器的计数频率是 180MHz,那我们就可以计算出这个 PWM 的周期、频率和占空比等参数了。下面就以这个为例给大家计算一下。由计数器的计数频率为 180MHz,可以得到计数器计一个数的时间是 5.556ns(即测量的精度是 5.556ns)。知道了测量精度,再来计算 PWM 的周期,PWM 周期 =(4+1)*(1/180000000) = 27.78ns,那么 PWM 的频率就是 36MHz。占空比 = (2+1)/(4+1) =3/5(即占空比为 60%)。

高级定时器PWM输入模式实验配置步骤

编程实战:高级定时器PWM输入模式实验

例程:

        首先通过 TIM3_CH4(PB1)输出 PWM 波。然后把 PB1 输出的 PWM 波用杜邦线接入 PC6 (定时器 8 通道 1),最后通过串口打印 PWM 波的脉宽和频率等信息。LED1 闪烁来提示程序正在运行。

硬件资源:

1)LED 灯 LED: LED1– PB0

2)定时器 3 通道 4(PB1)输出 PWM 波 定时器 8 通道 1(PC6)输入 PWM 波

  • 21
    点赞
  • 14
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
STM32 HAL库中,使用定时器进行计时的方法如下: 1. 初始化定时器:首先需要初始化定时器,选择所需的定时器(TIM1~TIM17),以及时钟源、预分频系数、计数器模式等参数。例如,以下代码初始化了TIM2定时器: ```c TIM_HandleTypeDef htim2; htim2.Instance = TIM2; htim2.Init.Prescaler = 0; htim2.Init.CounterMode = TIM_COUNTERMODE_UP; htim2.Init.Period = 0xFFFF; htim2.Init.ClockDivision = TIM_CLOCKDIVISION_DIV1; if (HAL_TIM_Base_Init(&htim2) != HAL_OK) { Error_Handler(); } ``` 2. 启动定时器:启动定时器后,它将开始计时。例如,以下代码启动了TIM2定时器: ```c if (HAL_TIM_Base_Start(&htim2) != HAL_OK) { Error_Handler(); } ``` 3. 获取计数器值:可以使用以下代码获取定时器的计数器值,以得到经过的时间: ```c uint32_t timer_value = __HAL_TIM_GET_COUNTER(&htim2); ``` 其中,`__HAL_TIM_GET_COUNTER()`是HAL库提供的获取计数器值的宏定义。 4. 停止定时器:计时结束后,可以停止定时器。例如,以下代码停止了TIM2定时器: ```c if (HAL_TIM_Base_Stop(&htim2) != HAL_OK) { Error_Handler(); } ``` 以上就是在STM32 HAL库中使用定时器进行计时的基本方法。需要注意的是,定时器的计数器是一个16位或32位的寄存器,计数器的最大值取决于所使用的定时器定时器的时钟源、预分频系数等参数。在计算经过的时间时,需要考虑到计数器溢出的情况。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值