【8】STM32·HAL库·定时器

目录

一、定时器概述

1.1、软件定时原理

1.2、定时器定时原理

1.3、STM32定时器分类

1.4、STM32定时器特性表

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

二、基本定时器(TIM6和TIM7)

2.1、基本定时器简介

2.2、基本定时器框图

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

2.3.1、递增计数模式

2.3.2、递减计数模式

2.3.3、中心对齐模式

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

2.4.1、控制寄存器1(TIMx_CR1)

2.4.2、DMA/中断使能寄存器(TIMx_DIER)

2.4.3、状态寄存器(TIMx_SR)

2.4.4、计数器(TIMx_CNT)

2.4.5、预分频器(TIMx_PSC)

2.4.6、自动重装载寄存器(TIMx_ARR)

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

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

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

三、通用定时器

3.1、通用定时器简介

3.2、通用定时器框图

3.3、计数器时钟源

3.3.1、计数器时钟源寄存器设置方法(F4)

3.3.2、外部时钟模式1

3.3.3、外部时钟模式2

3.3.4、使用一个定时器作为另一个定时器的预分频器

3.4、通用定时器中断实验

3.5、通用定时器PWM输出实验

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

3.5.2、通用定时器输出PWM原理

3.5.3、PWM模式

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

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

3.6、通用定时器输入捕获实验

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

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

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

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

3.7、通用定时器脉冲计数实验

3.7.1、脉冲计数实验原理

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

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

四、高级定时器

4.1、高级定时器简介

4.2、高级定时器框图

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

4.3.1、重复计数器特性

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

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

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

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

4.4.1、高级定时器输出比较模式实验原理

4.4.2、高级定时器输出比较模式实验配置步骤

4.4.3、编程实战:高级定时器输出比较模式实验

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

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

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

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

4.5.4、死区时间计算

4.5.5、刹车(断路)功能

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

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

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

4.6.1、PWM输入模式工作原理

4.6.2、PWM输入模式时序

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

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


一、定时器概述

1.1、软件定时原理

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

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

缺点

1、延时不精准

2、CPU 死等

1.2、定时器定时原理

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

定时器核心就是计数器

1.3、STM32定时器分类

1.4、STM32定时器特性表

F1

H7

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

二、基本定时器(TIM6和TIM7)

2.1、基本定时器简介

基本定时器为 TIM6 和 TIM7,主要特性有:

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

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

可用于触发 DAC

在更新事件(计数器溢出)时,会产生 中断/DMA 请求

2.2、基本定时器框图

① 时钟源

② 控制器

③ 计数器(时基单元)

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

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

2.3.1、递增计数模式

递增计数模式实例说明

当 PSC=1,ARR=36 时有:

2.3.2、递减计数模式

递减计数模式实例说明

当 PSC=1,ARR=36 时有:

2.3.3、中心对齐模式

中心对齐模式实例说明

当 PSC=0,ARR=6 时有:

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

2.4.1、控制寄存器1(TIMx_CR1)

用于设置 ARR 寄存器是否具有缓冲,使能/关闭计数器

2.4.2、DMA/中断使能寄存器(TIMx_DIER)

用于使能更新中断

2.4.3、状态寄存器(TIMx_SR)

用于判断是否发生了更新中断,由硬件置 1,软件清零

2.4.4、计数器(TIMx_CNT)

计数器实时数值,可用于设置计时器初始值,范围:0~65535

2.4.5、预分频器(TIMx_PSC)

用于设置预分频系数,范围:0~65535,实际预分频系数等于 PSC+1

2.4.6、自动重装载寄存器(TIMx_ARR)

用于设置自动重装载值,范围:0~65535

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

定时器溢出时间计算公式

T_{out} 是定时器溢出时间

F_{t} 是定时器的时钟源频率

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

PSC 是预分频器寄存器的值

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

1、配置定时器基础工作参数:使用 HAL_TIM_Base_Init()

2、定时器基础 MSP 初始化:使用 HAL_TIM_Base_MspInit(),配置 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()

相关 HAL 库函数介绍

关键结构体介绍

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;

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

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

main.c

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

int main(void)
{
    HAL_Init();                             /* 初始化HAL库 */
    sys_stm32_clock_init(336, 8, 2, 7);     /* 设置时钟,168Mhz */
    delay_init(168);                        /* 延时初始化 */
    usart_init(115200);                     /* 串口初始化为115200 */
    led_init();                             /* 初始化LED */
    btim_timx_int_init(5000 - 1, 8400 - 1); /* 84 000 000 / 84 00 = 10 000 10Khz的计数频率,计数5K次为500ms */

    while (1)
    {
        delay_ms(500);
    }
}

btim.c

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

TIM_HandleTypeDef g_timx_handler; /* 定时器参数句柄 */

/**
 * @brief       基本定时器TIMX定时中断初始化函数
 * @note
 *              基本定时器的时钟来自APB1,当PPRE1 ≥ 2分频的时候
 *              基本定时器的时钟为APB1时钟的2倍, 而APB1为42M, 所以定时器时钟 = 84Mhz
 *              定时器溢出时间计算方法: Tout = ((arr + 1) * (psc + 1)) / Ft us.
 *              Ft=定时器工作频率,单位:Mhz
 *
 * @param       arr : 自动重装值。
 * @param       psc : 时钟预分频数
 * @retval      无
 */
void btim_timx_int_init(uint16_t arr, uint16_t psc)
{
    g_timx_handler.Instance = BTIM_TIMX_INT;              /* 定时器x */
    g_timx_handler.Init.Prescaler = psc;                  /* 分频 */
    g_timx_handler.Init.CounterMode = TIM_COUNTERMODE_UP; /* 递增计数模式 */
    g_timx_handler.Init.Period = arr;                     /* 自动装载值 */
    HAL_TIM_Base_Init(&g_timx_handler);

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

/* 定时器底层驱动,开启时钟,设置中断优先级 */
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中断 */
    }
}

/* 基本定时器TIMX中断服务函数 */
void BTIM_TIMX_INT_IRQHandler(void)
{
    HAL_TIM_IRQHandler(&g_timx_handler); /* 定时器回调函数 */
}

/* 回调函数,定时器中断服务函数调用 */
void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim)
{
    if (htim->Instance == BTIM_TIMX_INT)
    {
        LED1_TOGGLE(); /* LED1反转 */
    }
}

btim.h

#ifndef _BTIM_H
#define _BTIM_H

#include "./SYSTEM/sys/sys.h"
#include "./BSP/LED/led.h"

/******************************************************************************************/
/* 基本定时器 定义 */

/* TIMX 中断定义 
 * 默认是针对TIM6/TIM7
 * 注意: 通过修改这4个宏定义,可以支持TIM1~TIM8任意一个定时器.
 */
 
#define BTIM_TIMX_INT                       TIM6
#define BTIM_TIMX_INT_IRQn                  TIM6_DAC_IRQn
#define BTIM_TIMX_INT_IRQHandler            TIM6_DAC_IRQHandler
#define BTIM_TIMX_INT_CLK_ENABLE()          do{ __HAL_RCC_TIM6_CLK_ENABLE(); }while(0)  /* TIM6 时钟使能 */

/******************************************************************************************/

void btim_timx_int_init(uint16_t arr, uint16_t psc);    /* 基本定时器 定时中断初始化函数 */

#endif

三、通用定时器

3.1、通用定时器简介

通用定时器为 TIM2 到 TIM5(在 F4 中 TIM9 到 TIM14 为阉割版通用定时器),主要特性有:

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

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

可用于触发 DAC、ADC

在更新事件、触发事件输入捕获输出比较时,会产生 中断/DMA 请求
4 个独立通道,可用于:输入捕获、输出比较、输出 PWM、单脉冲模式
使用外部信号控制定时器且可实现多个定时器互连的同步电路
支持编码器和霍尔传感器电路等

3.2、通用定时器框图

① 时钟源

② 控制器

③ 时基单元

④ 输入捕获

⑤ 捕获/比较(公共)

⑥ 输出比较

3.3、计数器时钟源

① 内部时钟(CK_INT),来自外设总线APB提供的时钟

② 外部时钟模式1:外部输入引脚(TIx),来自定时器通道1或者通道2引脚的信号

③ 外部时钟模式2:外部触发输入(ETR),来自可以复用为 TIMx_ETR 的 IO 引脚

④ 内部触发输入(ITRx),用于与芯片内部其它通用/高级定时器级联

3.3.1、计数器时钟源寄存器设置方法(F4)

3.3.2、外部时钟模式1

3.3.3、外部时钟模式2

3.3.4、使用一个定时器作为另一个定时器的预分频器

3.4、通用定时器中断实验

与基本定时器不同点:基本定时器只能递增计数,而通用定时器计数模式有三种

main.c

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

int main(void)
{
    HAL_Init();                             /* 初始化HAL库 */
    sys_stm32_clock_init(336, 8, 2, 7);     /* 设置时钟,168Mhz */
    delay_init(168);                        /* 延时初始化 */
    usart_init(115200);                     /* 串口初始化为115200 */
    led_init();                             /* 初始化LED */
    gtim_timx_int_init(5000 - 1, 8400 - 1); /* 84 000 000 / 84 00 = 10 000 10Khz的计数频率,计数5K次为500ms */

    while (1)
    {
        delay_ms(500);
    }
}

gtim.c

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

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

/**
 * @brief       通用定时器TIMX定时中断初始化函数
 * @note
 *              通用定时器的时钟来自APB1,当PPRE1 ≥ 2分频的时候
 *              通用定时器的时钟为APB1时钟的2倍, 而APB1为42M, 所以定时器时钟 = 84Mhz
 *              定时器溢出时间计算方法: Tout = ((arr + 1) * (psc + 1)) / Ft us.
 *              Ft=定时器工作频率,单位:Mhz
 *
 * @param       arr: 自动重装值
 * @param       psc: 预分频系数
 * @retval      无
 */
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库共用处理函数来处理,而是直接通过判断中断标志位的方式 */
    if (__HAL_TIM_GET_FLAG(&g_timx_handle, TIM_FLAG_UPDATE) != RESET)
    {
        LED1_TOGGLE();
        __HAL_TIM_CLEAR_IT(&g_timx_handle, TIM_IT_UPDATE); /* 清除定时器溢出中断标志位 */
    }
}

gtim.h

#ifndef __GTIM_H
#define __GTIM_H

#include "./SYSTEM/sys/sys.h"
#include "./BSP/LED/led.h"

/****************************************************************************************************/
/* 通用定时器 定义 */

/**
 * 默认是针对TIM2~TIM5.
 * 注意: 通过修改这4个宏定义,可以支持TIM1~TIM8任意一个定时器.
 */
 
#define GTIM_TIMX_INT                       TIM3
#define GTIM_TIMX_INT_IRQn                  TIM3_IRQn
#define GTIM_TIMX_INT_IRQHandler            TIM3_IRQHandler
#define GTIM_TIMX_INT_CLK_ENABLE()          do{ __HAL_RCC_TIM3_CLK_ENABLE(); }while(0)  /* TIM3 时钟使能 */

/****************************************************************************************************/

void gtim_timx_int_init(uint16_t arr, uint16_t psc);        /* 通用定时器 定时中断初始化函数 */

#endif

3.5、通用定时器PWM输出实验

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

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

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

3.5.2、通用定时器输出PWM原理

假设:递增计数模式

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

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

当 CNT < CCRx,IO 输出0

当 CNT >= CCRx,IO 输出1

总结:PWM 波周期或频率由 ARR 决定,PWM 波占空比由 CCRx 决定

3.5.3、PWM模式

PWM模式1

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

           CNT >= CCRx,输出无效电平

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

           CNT <= CCRx,输出有效

有/无效状态由 TIMx_CCER 决定

           CCxP=0:OCx高电平有效

           CCxP=1:Ocx低电平有效

PWM模式2

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

           CNT >= CCRx,输出有效电平

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

           CNT <= CCRx,输出无效

有/无效状态由 TIMx_CCER 决定

           CCxP=0:OCx高电平有效

           CCxP=1:Ocx低电平有效

3.5.4、通用定时器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()

相关 HAL 库函数介绍

关键结构体介绍

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;

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

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

main.c

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

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

int main(void)
{
    uint16_t ledrpwmval = 0;
    uint8_t dir = 1;

    HAL_Init();                              /* 初始化HAL库 */
    sys_stm32_clock_init(336, 8, 2, 7);      /* 设置时钟,168Mhz */
    delay_init(168);                         /* 延时初始化 */
    usart_init(115200);                      /* 串口初始化为115200 */
    led_init();                              /* 初始化LED */
    gtim_timx_pwm_chy_init(500 - 1, 84 - 1); /* 84 000 000 / 84 = 1 000 000 1Mhz的计数频率,2Khz的PWM */

    while (1)
    {
        delay_ms(2);

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

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

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

gtim.c

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

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

/**
 * @brief       通用定时器TIMX 通道Y PWM输出 初始化函数(使用PWM模式1)
 * @note
 *              通用定时器的时钟来自APB1,当PPRE1 ≥ 2分频的时候
 *              通用定时器的时钟为APB1时钟的2倍, 而APB1为42M, 所以定时器时钟 = 84Mhz
 *              定时器溢出时间计算方法: Tout = ((arr + 1) * (psc + 1)) / Ft us.
 *              Ft = 定时器工作频率,单位:Mhz
 *
 * @param       arr: 自动重装值
 * @param       psc: 预分频系数
 * @retval      无
 */
void gtim_timx_pwm_chy_init(uint16_t arr, uint16_t psc)
{
    TIM_OC_InitTypeDef timx_oc_pwm_chy = {0}; /* 定时器输出句柄 */

    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通道 */
}

/* 定时器底层驱动,时钟使能,引脚配置 */
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的CPIO时钟 */
        GTIM_TIMX_PWM_CHY_CLK_ENABLE();      /* 使能定时器时钟 */

        gpio_init_struct.Pin = GTIM_TIMX_PWM_CHY_GPIO_PIN;      /* 通道y的CPIO口 */
        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; /* IO口REMAP设置, 是否必要查看头文件配置的说明! */
        HAL_GPIO_Init(GTIM_TIMX_PWM_CHY_GPIO_PORT, &gpio_init_struct);
    }
}

gtim.h

#ifndef __GTIM_H
#define __GTIM_H

#include "./SYSTEM/sys/sys.h"
#include "./BSP/LED/led.h"

/*********************************以下是通用定时器PWM输出实验相关宏定义*************************************/

/* TIMX PWM输出定义 
 * 这里输出的PWM控制LED0(RED)的亮度
 * 默认是针对TIM2~TIM5
 * 注意: 通过修改这几个宏定义,可以支持TIM1~TIM8任意一个定时器,任意一个IO口输出PWM
 */

#define GTIM_TIMX_PWM_CHY_GPIO_PORT         GPIOF
#define GTIM_TIMX_PWM_CHY_GPIO_PIN          GPIO_PIN_9
#define GTIM_TIMX_PWM_CHY_GPIO_CLK_ENABLE() do{ __HAL_RCC_GPIOF_CLK_ENABLE(); }while(0)  /* PF口时钟使能 */
#define GTIM_TIMX_PWM_CHY_GPIO_AF           GPIO_AF9_TIM14                               /* 端口复用到TIM14 */

/* TIMX REMAP设置
 * 因为我们LED0接在PF9上, 必须通过开启TIM14的部分重映射功能, 才能将TIM14_CH1输出到PF9上
 */

#define GTIM_TIMX_PWM                       TIM14                                        /* TIM14 */
#define GTIM_TIMX_PWM_CHY                   TIM_CHANNEL_1                                /* 通道Y,  1<= Y <=4 */
#define GTIM_TIMX_PWM_CHY_CCRX              TIM14->CCR1                                  /* 通道Y的输出比较寄存器 */
#define GTIM_TIMX_PWM_CHY_CLK_ENABLE()      do{ __HAL_RCC_TIM14_CLK_ENABLE(); }while(0)  /* TIM14 时钟使能 */

/****************************************************************************************************/

void gtim_timx_pwm_chy_init(uint16_t arr, uint16_t psc);    /* 通用定时器 PWM初始化函数 */

#endif

3.6、通用定时器输入捕获实验

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

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

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

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

以捕获测量高电平脉宽为例

假设:递增计数模式

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

CCRx1:t1 时间点 CCRx 的值

CCRx2:t2 时间点 CCRx 的值

高电平期间,计时器计数的个数:N * (ARR+1) + CCRx2

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

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()

相关 HAL 库函数介绍

关键结构体介绍

typedef struct
{ 
    uint32_t ICPolarity;    /* 输入捕获触发方式选择,比如上升、下降沿捕获 */ 
    uint32_t ICSelection;   /* 输入捕获选择,用于设置映射关系 */ 
    uint32_t ICPrescaler;   /* 输入捕获分频系数 */ 
    uint32_t ICFilter;      /* 输入捕获滤波器设置 */ 
} TIM_IC_InitTypeDef;

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

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

main.c

#include "./SYSTEM/sys/sys.h"
#include "./SYSTEM/usart/usart.h"
#include "./SYSTEM/delay/delay.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 = 1;

    HAL_Init();                             /* 初始化HAL库 */
    sys_stm32_clock_init(336, 8, 2, 7);     /* 设置时钟,168Mhz */
    delay_init(168);                        /* 延时初始化 */
    usart_init(115200);                     /* 串口初始化为115200 */
    led_init();                             /* 初始化LED */
    gtim_timx_cap_chy_init(0xFFFF, 84 - 1); /* 以1Mhz的频率计数 捕获 */

    while (1)
    {
        if (g_timxchy_cap_sta & 0x80) /* 成功捕获到了一次高电平 */
        {
            temp = g_timxchy_cap_sta & 0x3F;
            temp *= 0xFFFF;                                                                     /* 溢出时间总和 */
            temp += g_timxchy_cap_val;                                                          /* 得到总的高电平时间 */
            printf("HIGH:%.2f s, %.2f ms, %d us\r\n", temp / 1000000.0f, temp / 1000.0f, temp); /* 打印总的高电平时间 */
            g_timxchy_cap_sta = 0;                                                              /* 开启下一次捕获 */
        }
        if (++t > 20) /* 200ms进入一次 */
        {
            t = 0;
            LED0_TOGGLE(); /* LED0闪烁 ,提示程序运行 */
        }
        delay_ms(10);
    }
}

gtim.c

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

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

/**
 * @brief       通用定时器TIMX 通道Y 输入捕获 初始化函数
 * @note
 *              通用定时器的时钟来自APB1,当PPRE1 ≥ 2分频的时候
 *              通用定时器的时钟为APB1时钟的2倍, 而APB1为42M, 所以定时器时钟 = 84Mhz
 *              定时器溢出时间计算方法: Tout = ((arr + 1) * (psc + 1)) / Ft us.
 *              Ft=定时器工作频率,单位:Mhz
 *
 * @param       arr: 自动重装值
 * @param       psc: 预分频系数
 * @retval      无
 */
void gtim_timx_cap_chy_init(uint32_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 */
}

/* 通用定时器输入捕获初始化接口 */
void HAL_TIM_IC_MspInit(TIM_HandleTypeDef *htim)
{
    if (htim->Instance == GTIM_TIMX_CAP) /* 输入通道捕获 */
    {
        GPIO_InitTypeDef gpio_init_struct;
        GTIM_TIMX_CAP_CHY_CLK_ENABLE();      /* 使能TIMx时钟 */
        GTIM_TIMX_CAP_CHY_GPIO_CLK_ENABLE(); /* 开启捕获IO的时钟 */

        gpio_init_struct.Pin = GTIM_TIMX_CAP_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_CAP_CHY_GPIO_AF; /* 复用为捕获TIM5的通道1 */
        HAL_GPIO_Init(GTIM_TIMX_CAP_CHY_GPIO_PORT, &gpio_init_struct);

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

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

/* 定时器中断服务函数 */
void GTIM_TIMX_CAP_IRQHandler(void)
{
    HAL_TIM_IRQHandler(&g_timx_cap_chy_handle); /* 定时器共用处理函数 */
}

/* 定时器输入捕获中断处理回调函数 */
void HAL_TIM_IC_CaptureCallback(TIM_HandleTypeDef *htim)
{
    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, GTIM_TIMX_CAP_CHY);  /* 获取当前的捕获值 */
            TIM_RESET_CAPTUREPOLARITY(&g_timx_cap_chy_handle, GTIM_TIMX_CAP_CHY);                      /* 一定要先清除原来的设置 */
            TIM_SET_CAPTUREPOLARITY(&g_timx_cap_chy_handle, GTIM_TIMX_CAP_CHY, TIM_ICPOLARITY_RISING); /* 配置TIM5通道1上升沿捕获 */
        }
        else /* 还未开始,第一次捕获上升沿 */
        {
            g_timxchy_cap_sta = 0; /* 清空 */
            g_timxchy_cap_val = 0;
            g_timxchy_cap_sta |= 0x40;                                                                  /* 标记捕获到了上升沿 */
            __HAL_TIM_SET_COUNTER(&g_timx_cap_chy_handle, 0);                                           /* 定时器5计数器清零 */
            TIM_RESET_CAPTUREPOLARITY(&g_timx_cap_chy_handle, GTIM_TIMX_CAP_CHY);                       /* 一定要先清除原来的设置!! */
            TIM_SET_CAPTUREPOLARITY(&g_timx_cap_chy_handle, GTIM_TIMX_CAP_CHY, TIM_ICPOLARITY_FALLING); /* 定时器5通道1设置为下降沿捕获 */
        }
    }
}

/* 定时器更新中断回调函数 */
void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim)
{
    if (htim->Instance == GTIM_TIMX_CAP)
    {
        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, GTIM_TIMX_CAP_CHY);                      /* 一定要先清除原来的设置 */
                    TIM_SET_CAPTUREPOLARITY(&g_timx_cap_chy_handle, GTIM_TIMX_CAP_CHY, TIM_ICPOLARITY_RISING); /* 配置TIM5通道1上升沿捕获 */
                    g_timxchy_cap_sta |= 0x80;                                                                 /* 标记成功捕获了一次 */
                    g_timxchy_cap_val = 0xFFFF;
                }
                else /* 累计定时器溢出次数 */
                {
                    g_timxchy_cap_sta++;
                }
            }
        }
    }
}

gtim.h

#ifndef __GTIM_H
#define __GTIM_H

#include "./SYSTEM/sys/sys.h"
#include "./BSP/LED/led.h"

/*********************************以下是通用定时器输入捕获实验相关宏定义*************************************/

/* TIMX 输入捕获定义 
 * 这里的输入捕获使用定时器TIM5_CH1,捕获WK_UP按键的输入
 * 默认是针对TIM2~TIM5. 
 * 注意: 通过修改这几个宏定义,可以支持TIM1~TIM8任意一个定时器,任意一个IO口做输入捕获
 *       特别要注意:默认用的PA0,设置的是下拉输入!如果改其他IO,对应的上下拉方式也得改!
 */
#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_CCRX              TIM5->CCR1                                    /* 通道Y的输出比较寄存器 */
#define GTIM_TIMX_CAP_CHY_CLK_ENABLE()      do{ __HAL_RCC_TIM5_CLK_ENABLE(); }while(0)    /* TIM5 时钟使能 */

/****************************************************************************************************/

void gtim_timx_cap_chy_init(uint32_t arr, uint16_t psc);    /* 通用定时器 输入捕获初始化函数 */

#endif

3.7、通用定时器脉冲计数实验

3.7.1、脉冲计数实验原理

外部时钟模式1

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

1、配置定时器基础工作参数:使用 HAL_TIM_IC_Init()

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

3、配置定时器从模式等:使用 HAL_TIM_SlaveConfigSynchro()

4、使能输入捕获并启动计数器:使用 HAL_TIM_IC_Start()

5、获取计数器的值:使用 __HAL_TIM_GET_COUNTER()

6、设置计数器的值:使用 __HAL_TIM_SET_COUNTER()

相关 HAL 库函数介绍

关键结构体介绍

typedef struct 
{ 
    uint32_t SlaveMode;          /* 从模式选择 */ 
    uint32_t InputTrigger;       /* 输入触发源选择 */ 
    uint32_t TriggerPolarity;    /* 输入触发极性 */ 
    uint32_t TriggerPrescaler;   /* 输入触发预分频 */ 
    uint32_t TriggerFilter;      /* 输入滤波器设置 */ 
} TIM_SlaveConfigTypeDef;

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

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

main.c

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

int main(void)
{
    uint32_t curcnt = 0;
    uint32_t oldcnt = 0;
    uint8_t key = 0;
    uint8_t t = 0;

    HAL_Init();                         /* 初始化HAL库 */
    sys_stm32_clock_init(336, 8, 2, 7); /* 设置时钟,168Mhz */
    delay_init(168);                    /* 延时初始化 */
    usart_init(115200);                 /* 串口初始化为115200 */
    led_init();                         /* 初始化LED */
    key_init();                         /* 初始化按键 */
    gtim_timx_cnt_chy_init(0);          /* 定时器计数初始化, 不分频 */
    gtim_timx_cnt_chy_restart();        /* 重启计数 */

    while (1)
    {
        key = key_scan(0);    /* 扫描按键 */
        if (key == KEY0_PRES) /* KEY0按键按下,重启计数 */
        {
            printf("key0 press \r\n");
            gtim_timx_cnt_chy_restart(); /* 重新启动计数 */
        }

        curcnt = gtim_timx_cnt_chy_get_count(); /* 获取计数值 */
        if (oldcnt != curcnt)
        {
            oldcnt = curcnt;
            printf("CNT:%d\r\n", oldcnt); /* 打印脉冲个数 */
        }

        t++;
        if (t > 40) /* 200ms进入一次 */
        {
            t = 0;
            LED0_TOGGLE(); /* LED0闪烁, 提示程序运行 */
        }

        delay_ms(10);
    }
}

gtim.c

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

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

/* 记录定时器计数器的溢出次数, 方便计算总脉冲个数 */
uint32_t g_timxchy_cnt_ofcnt = 0; /* 计数溢出次数 */

/**
 * @brief       通用定时器TIMX 通道Y 脉冲计数 初始化函数
 * @note
 *              本函数选择通用定时器的时钟选择: 外部时钟源模式1(SMS[2:0] = 111)
 *              这样CNT的计数时钟源就来自 TIMX_CH1/CH2, 可以实现外部脉冲计数(脉冲接入CH1/CH2)
 *
 *              时钟分频数 = psc, 一般设置为0, 表示每一个时钟都会计数一次, 以提高精度.
 *              通过读取CNT和溢出次数, 经过简单计算, 可以得到当前的计数值, 从而实现脉冲计数
 *
 * @param       arr: 自动重装值
 * @retval      无
 */
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 */
}

/* 通用定时器TIMX 通道Y 获取当前计数值 */
uint32_t gtim_timx_cnt_chy_get_count(void)
{
    uint32_t count = 0;
    count = g_timxchy_cnt_ofcnt * 65536;                    /* 计算溢出次数对应的计数值 */
    count += __HAL_TIM_GET_COUNTER(&g_timx_cnt_chy_handle); /* 加上当前CNT的值 */
    return count;
}

/* 通用定时器TIMX 通道Y 重启计数器 */
void gtim_timx_cnt_chy_restart(void)
{
    __HAL_TIM_DISABLE(&g_timx_cnt_chy_handle);        /* 关闭定时器TIMX */
    g_timxchy_cnt_ofcnt = 0;                          /* 累加器清零 */
    __HAL_TIM_SET_COUNTER(&g_timx_cnt_chy_handle, 0); /* 计数器清零 */
    __HAL_TIM_ENABLE(&g_timx_cnt_chy_handle);         /* 使能定时器TIMX */
}

/* 通用定时器TIMX 脉冲计数 更新中断服务函数 */
void GTIM_TIMX_CNT_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);
}

gtim.h

#ifndef __GTIM_H
#define __GTIM_H

#include "./SYSTEM/sys/sys.h"
#include "./BSP/LED/led.h"

/*********************************以下是通用定时器脉冲计数实验相关宏定义*************************************/

/* TIMX 输入计数定义
* 这里的输入计数使用定时器TIM2_CH1,捕获WK_UP按键的输入
* 默认是针对TIM2~TIM5, 只有CH1和CH2通道可以用做输入计数, CH3/CH4不支持!
* 注意: 通过修改这几个宏定义,可以支持TIM1~TIM8任意一个定时器,CH1/CH2对应IO口做输入计数
*       特别要注意:默认用的PA0,设置的是下拉输入!如果改其他IO,对应的上下拉方式也得改!
*/
#define GTIM_TIMX_CNT_CHY_GPIO_PORT            GPIOA
#define GTIM_TIMX_CNT_CHY_GPIO_PIN             GPIO_PIN_0
#define GTIM_TIMX_CNT_CHY_GPIO_AF              GPIO_AF1_TIM2                                /* AF功能选择 */
#define GTIM_TIMX_CNT_CHY_GPIO_CLK_ENABLE()    do{ __HAL_RCC_GPIOA_CLK_ENABLE(); }while(0)  /* PA口时钟使能 */

#define GTIM_TIMX_CNT                          TIM2
#define GTIM_TIMX_CNT_IRQn                     TIM2_IRQn
#define GTIM_TIMX_CNT_IRQHandler               TIM2_IRQHandler
#define GTIM_TIMX_CNT_CHY                      TIM_CHANNEL_1                                /* 通道Y,  1<= Y <=2 */
#define GTIM_TIMX_CNT_CHY_CLK_ENABLE()         do{ __HAL_RCC_TIM2_CLK_ENABLE(); }while(0)   /* TIM2 时钟使能 */

/****************************************************************************************************/

void gtim_timx_cnt_chy_init(uint16_t psc);                  /* 通用定时器 脉冲计数初始化函数 */
uint32_t gtim_timx_cnt_chy_get_count(void);                 /* 通用定时器 获取脉冲计数 */
void gtim_timx_cnt_chy_restart(void);                       /* 通用定时器 重启计数器 */

#endif

四、高级定时器

4.1、高级定时器简介

高级定时器为 TIM1 到 TIM8,主要特性有:

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

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

可用于触发 DAC、ADC

在更新事件、触发事件输入捕获输出比较时,会产生 中断/DMA 请求
4 个独立通道,可用于:输入捕获、输出比较、输出 PWM、单脉冲模式
使用外部信号控制定时器且可实现多个定时器互连的同步电路
支持编码器和霍尔传感器电路等
重复计数器
死区时间带可编程的互补输出
断路输入,用于将定时器的输出信号置于用户可选的安全配置中

4.2、高级定时器框图

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

4.3.1、重复计数器特性

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

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

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

1,配置边沿对齐模式输出 PWM

2,指定输出 N 个 PWM,则把 N-1 写入 RCR

3,在更新中断内,关闭计数器

注意:高级定时器通道输出必须把 MOE 位置 1

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

1、配置定时器基础工作参数:使用 HAL_TIM_PWM_Init()

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

3、配置 PWM 模式/比较值等:使用 HAL_TIM_PWM_ConfigChannel()

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

5、使能定时器更新中断:使用 __HAL_TIM_ENABLE_IT()

6、使能输出、主输出、计数器:使用 HAL_TIM_PWM_Start()

7、编写中断服务函数:使用 TIMx_IRQHandler() 等 —> HAL_TIM_IRQHandler()

8、编写更新中断回调函数:HAL_TIM_PeriodElapsedCallback()

相关 HAL 库函数介绍

关键结构体介绍

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;

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

通过定时器 8 通道 1(PC6)实现指定个数 PWM 输出,用于控制 LED1 的亮灭

main.c

#include "./SYSTEM/sys/sys.h"
#include "./SYSTEM/usart/usart.h"
#include "./SYSTEM/delay/delay.h"
#include "./BSP/LED/led.h"
#include "./BSP/KEY/key.h"
#include "./BSP/TIMER/atim.h"

int main(void)
{
    uint8_t key = 0;
    uint8_t t = 0;
    GPIO_InitTypeDef gpio_init_struct;

    HAL_Init();                         /* 初始化HAL库 */
    sys_stm32_clock_init(336, 8, 2, 7); /* 设置时钟,168Mhz */
    delay_init(168);                    /* 延时初始化 */
    usart_init(115200);                 /* 串口初始化为115200 */
    led_init();                         /* 初始化LED */
    key_init();                         /* 初始化按键 */

    /* 将 LED1 引脚设置为输入模式, 避免和 PF10 冲突 */
    gpio_init_struct.Pin = LED1_GPIO_PIN;               /* LED1引脚 */
    gpio_init_struct.Mode = GPIO_MODE_INPUT;            /* 设置为输入 */
    gpio_init_struct.Pull = GPIO_PULLUP;                /* 上拉 */
    gpio_init_struct.Speed = GPIO_SPEED_FREQ_VERY_HIGH; /* 高速模式 */
    HAL_GPIO_Init(LED1_GPIO_PORT, &gpio_init_struct);   /* 初始化LED1引脚 */

    atim_timx_npwm_chy_init(10000 - 1, 8400 - 1); /* 20Khz的计数频率,2Hz的PWM频率. */

    ATIM_TIMX_NPWM_CHY_CCRX = 5000; /* 设置PWM占空比,50%,这样可以控制每一个PWM周期,LED1(GREEN)
                                     * 有一半时间是亮的,一半时间是灭的,LED1亮灭一次,表示一个PWM波
                                     */
    atim_timx_npwm_chy_set(5);      /* 输出5个PWM波(控制LED1(GREEN)闪烁5次) */

    while (1)
    {
        key = key_scan(0);

        if (key == KEY0_PRES) /* KEY0按下 */
        {
            atim_timx_npwm_chy_set(5); /* 输出5个PWM波(控制TIM8_CH1, 即PC6输出5个脉冲) */
        }

        t++;
        delay_ms(10);

        if (t > 50) /* 控制LED0闪烁, 提示程序运行状态 */
        {
            t = 0;
            LED0_TOGGLE();
        }
    }
}

atim.c

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

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

/* g_npwm_remain表示当前还剩下多少个脉冲要发送
 * 每次最多发送256个脉冲
 */
static uint32_t g_npwm_remain = 0;

/**
 * @brief       高级定时器TIMX 通道Y 输出指定个数PWM 初始化函数
 * @note
 *              高级定时器的时钟来自APB2, 而PCLK2 = 168Mhz, 我们设置PPRE2不分频, 因此
 *              高级定时器时钟 = 168Mhz
 *              定时器溢出时间计算方法: Tout = ((arr + 1) * (psc + 1)) / Ft us.
 *              Ft=定时器工作频率,单位:Mhz
 *
 * @param       arr: 自动重装值
 * @param       psc: 预分频系数
 * @retval      无
 */
void atim_timx_npwm_chy_init(uint16_t arr, uint16_t psc)
{
    GPIO_InitTypeDef gpio_init_struct;
    TIM_OC_InitTypeDef timx_oc_npwm_chy = {0}; /* 定时器输出 */
    ATIM_TIMX_NPWM_CHY_GPIO_CLK_ENABLE();      /* TIMX 通道IO口时钟使能 */
    ATIM_TIMX_NPWM_CHY_CLK_ENABLE();           /* TIMX 时钟使能 */

    g_timx_npwm_chy_handle.Instance = ATIM_TIMX_NPWM;                              /* 定时器x */
    g_timx_npwm_chy_handle.Init.Prescaler = psc;                                   /* 定时器分频 */
    g_timx_npwm_chy_handle.Init.CounterMode = TIM_COUNTERMODE_UP;                  /* 递增计数模式 */
    g_timx_npwm_chy_handle.Init.Period = arr;                                      /* 自动重装载值 */
    g_timx_npwm_chy_handle.Init.AutoReloadPreload = TIM_AUTORELOAD_PRELOAD_ENABLE; /* 使能TIMx_ARR进行缓冲 */
    g_timx_npwm_chy_handle.Init.RepetitionCounter = 0;                             /* 重复计数器初始值 */
    HAL_TIM_PWM_Init(&g_timx_npwm_chy_handle);                                     /* 初始化PWM */

    gpio_init_struct.Pin = ATIM_TIMX_NPWM_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 = ATIM_TIMX_NPWM_CHY_GPIO_AF; /* 端口复用 */
    HAL_GPIO_Init(ATIM_TIMX_NPWM_CHY_GPIO_PORT, &gpio_init_struct);

    timx_oc_npwm_chy.OCMode = TIM_OCMODE_PWM1;                                                 /* 模式选择PWM 1 */
    timx_oc_npwm_chy.Pulse = arr / 2;                                                          /* 设置比较值,此值用来确定占空比 */
                                                                                               /* 这里默认设置比较值为自动重装载值的一半,即占空比为50% */
    timx_oc_npwm_chy.OCPolarity = TIM_OCPOLARITY_HIGH;                                         /* 输出比较极性为高 */
    HAL_TIM_PWM_ConfigChannel(&g_timx_npwm_chy_handle, &timx_oc_npwm_chy, ATIM_TIMX_NPWM_CHY); /* 配置TIMx通道y */

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

    __HAL_TIM_ENABLE_IT(&g_timx_npwm_chy_handle, TIM_IT_UPDATE);    /* 允许更新中断 */
    HAL_TIM_PWM_Start(&g_timx_npwm_chy_handle, ATIM_TIMX_NPWM_CHY); /* 开启对应PWM通道 */
}

/* 高级定时器TIMX NPWM设置PWM个数, 1~2^32次方个 */
void atim_timx_npwm_chy_set(uint32_t npwm)
{
    if (npwm == 0)
        return;

    g_npwm_remain = npwm;                                                   /* 保存脉冲个数 */
    HAL_TIM_GenerateEvent(&g_timx_npwm_chy_handle, TIM_EVENTSOURCE_UPDATE); /* 产生一次更新事件,在中断里面处理脉冲输出 */
    __HAL_TIM_ENABLE(&g_timx_npwm_chy_handle);                              /* 使能定时器TIMX */
}

/* 高级定时器TIMX NPWM中断服务函数 */
void ATIM_TIMX_NPWM_IRQHandler(void)
{
    uint16_t npwm = 0;

    /* 以下代码没有使用定时器HAL库共用处理函数来处理,而是直接通过判断中断标志位的方式 */
    if (__HAL_TIM_GET_FLAG(&g_timx_npwm_chy_handle, TIM_FLAG_UPDATE) != RESET)
    {
        if (g_npwm_remain >= 256) /* 还有大于256个脉冲需要发送 */
        {
            g_npwm_remain = g_npwm_remain - 256;
            npwm = 256;
        }
        else if (g_npwm_remain % 256) /* 还有位数(不到256)个脉冲要发送 */
        {
            npwm = g_npwm_remain % 256;
            g_npwm_remain = 0; /* 没有脉冲了 */
        }

        if (npwm) /* 有脉冲要发送 */
        {
            ATIM_TIMX_NPWM->RCR = npwm - 1;                                         /* 设置重复计数寄存器值为npwm-1, 即npwm个脉冲 */
            HAL_TIM_GenerateEvent(&g_timx_npwm_chy_handle, TIM_EVENTSOURCE_UPDATE); /* 产生一次更新事件,在中断里面处理脉冲输出 */
            __HAL_TIM_ENABLE(&g_timx_npwm_chy_handle);                              /* 使能定时器TIMX */
        }
        else
        {
            ATIM_TIMX_NPWM->CR1 &= ~(1 << 0); /* 关闭定时器TIMX,使用HAL Disable会清除PWM通道信息,此处不用 */
        }

        __HAL_TIM_CLEAR_IT(&g_timx_npwm_chy_handle, TIM_IT_UPDATE); /* 清除定时器溢出中断标志位 */
    }
}

atim.h

#ifndef __ATIM_H
#define __ATIM_H

#include "./SYSTEM/sys/sys.h"
#include "./BSP/LED/led.h"

/******************************************************************************************/
/* 高级定时器 定义 */

 /* TIMX 输出指定个数PWM 定义 
 * 这里输出的PWM通过PC6(TIM8_CH1)输出, 我们用杜邦线连接PC6和PF10, 然后在程序里面将PF10设置成浮空输入
 * 就可以 看到TIM8_CH1控制LED1(GREEN)的亮灭, 亮灭一次表示一个PWM波
 * 默认使用的是TIM8_CH1.
 * 注意: 通过修改这几个宏定义, 可以支持TIM1/TIM8定时器, 任意一个IO口输出指定个数的PWM
 */
#define ATIM_TIMX_NPWM_CHY_GPIO_PORT            GPIOC
#define ATIM_TIMX_NPWM_CHY_GPIO_PIN             GPIO_PIN_6
#define ATIM_TIMX_NPWM_CHY_GPIO_CLK_ENABLE()    do{ __HAL_RCC_GPIOC_CLK_ENABLE(); }while(0) /* PC口时钟使能 */
#define ATIM_TIMX_NPWM_CHY_GPIO_AF              GPIO_AF3_TIM8

#define ATIM_TIMX_NPWM                          TIM8
#define ATIM_TIMX_NPWM_IRQn                     TIM8_UP_TIM13_IRQn
#define ATIM_TIMX_NPWM_IRQHandler               TIM8_UP_TIM13_IRQHandler
#define ATIM_TIMX_NPWM_CHY                      TIM_CHANNEL_1                               /* 通道Y,  1<= Y <=4 */
#define ATIM_TIMX_NPWM_CHY_CCRX                 TIM8->CCR1                                  /* 通道Y的输出比较寄存器 */
#define ATIM_TIMX_NPWM_CHY_CLK_ENABLE()         do{ __HAL_RCC_TIM8_CLK_ENABLE(); }while(0)  /* TIM8 时钟使能 */

/******************************************************************************************/

void atim_timx_npwm_chy_init(uint16_t arr, uint16_t psc);   /* 高级定时器 输出指定个数PWM初始化函数 */
void atim_timx_npwm_chy_set(uint32_t npwm);                 /* 高级定时器 设置输出PWM的个数 */

#endif

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

4.4.1、高级定时器输出比较模式实验原理

输出比较模式:翻转

当 CNT = CCRx,OCxREF 电平翻转

总结:PWM 波周期或频率由 ARR 决定,占空比固定 50%,相位由 CCRx 决定

4.4.2、高级定时器输出比较模式实验配置步骤

1、配置定时器基础工作参数:使用 HAL_TIM_OC_Init()

2、定时器输出比较 MSP 初始化:使用 HAL_TIM_OC_MspInit(),配置 NVIC、CLOCK、GPIO 等

3、配置输出比较模式等:使用 HAL_TIM_OC_ConfigChannel()

4、使能通道预装载:使用 __HAL_TIM_ENABLE_OCxPRELOAD()

5、使能输出、主输出、计数器:使用 HAL_TIM_OC_Start()

6、修改捕获/比较寄存器的值:使用 __HAL_TIM_SET_COMPARE()

相关 HAL 库函数介绍

关键结构体介绍

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;

4.4.3、编程实战:高级定时器输出比较模式实验

通过定时器 8 通道 1/2/3/4 输出相位分别为 25%、50%、75%、100% 的 PWM

main.c

#include "./SYSTEM/sys/sys.h"
#include "./SYSTEM/usart/usart.h"
#include "./SYSTEM/delay/delay.h"
#include "./BSP/LED/led.h"
#include "./BSP/KEY/key.h"
#include "./BSP/TIMER/atim.h"

int main(void)
{
    uint8_t t = 0;

    HAL_Init();                         /* 初始化HAL库 */
    sys_stm32_clock_init(336, 8, 2, 7); /* 设置时钟,168Mhz */
    delay_init(168);                    /* 延时初始化 */
    usart_init(115200);                 /* 串口初始化为115200 */
    led_init();                         /* 初始化LED */
    key_init();                         /* 初始化按键 */

    atim_timx_comp_pwm_init(1000 - 1, 168 - 1); /* 1Mhz的计数频率, 1Khz的周期. */

    ATIM_TIMX_COMP_CH1_CCRX = 250 - 1;  /* 通道1 相位25% */
    ATIM_TIMX_COMP_CH2_CCRX = 500 - 1;  /* 通道2 相位50% */
    ATIM_TIMX_COMP_CH3_CCRX = 750 - 1;  /* 通道3 相位75% */
    ATIM_TIMX_COMP_CH4_CCRX = 1000 - 1; /* 通道4 相位100% */

    while (1)
    {
        t++;
        delay_ms(10);

        if (t > 50) /* 控制LED0闪烁, 提示程序运行状态 */
        {
            t = 0;
            LED0_TOGGLE();
        }
    }
}

atim.c

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

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

/**
 * @brief       高级定时器TIMX 输出比较模式 初始化函数(使用输出比较模式)
 * @note
 *              配置高级定时器TIMX 4路输出比较模式PWM输出,实现50%占空比,不同相位控制
 *              注意,本例程输出比较模式,每2个计数周期才能完成一个PWM输出,因此输出频率减半
 *              另外,我们还可以开启中断在中断里面修改CCRx,从而实现不同频率/不同相位的控制
 *              但是我们不推荐这么使用,因为这可能导致非常频繁的中断,从而占用大量CPU资源
 *
 *              高级定时器的时钟来自APB2, 而PCLK2 = 168Mhz, 我们设置PPRE2不分频, 因此
 *              高级定时器时钟 = 168Mhz
 *              定时器溢出时间计算方法: Tout = ((arr + 1) * (psc + 1)) / Ft us.
 *              Ft=定时器工作频率,单位:Mhz
 *
 * @param       arr: 自动重装值。
 * @param       psc: 预分频系数
 * @retval      无
 */
void atim_timx_comp_pwm_init(uint16_t arr, uint16_t psc)
{
    TIM_OC_InitTypeDef timx_oc_comp_pwm = {0};

    g_timx_comp_pwm_handle.Instance = ATIM_TIMX_COMP;                              /* 定时器8 */
    g_timx_comp_pwm_handle.Init.Prescaler = psc;                                   /* 预分频系数 */
    g_timx_comp_pwm_handle.Init.CounterMode = TIM_COUNTERMODE_UP;                  /* 向上计数模式 */
    g_timx_comp_pwm_handle.Init.Period = arr;                                      /* 自动重装载值 */
    g_timx_comp_pwm_handle.Init.AutoReloadPreload = TIM_AUTORELOAD_PRELOAD_ENABLE; /* 不使能影子寄存器TIMx_ARR */
    HAL_TIM_OC_Init(&g_timx_comp_pwm_handle);                                      /* 输出比较模式初始化 */

    timx_oc_comp_pwm.OCMode = TIM_OCMODE_TOGGLE;                                         /* 比较输出模式翻转功能 */
    timx_oc_comp_pwm.Pulse = 250 - 1;                                                    /* 设置输出比较寄存器的值 */
    timx_oc_comp_pwm.OCPolarity = TIM_OCPOLARITY_HIGH;                                   /* 输出比较极性为高 */
    HAL_TIM_OC_ConfigChannel(&g_timx_comp_pwm_handle, &timx_oc_comp_pwm, TIM_CHANNEL_1); /* 初始化定时器的输出比较通道1 */
    __HAL_TIM_ENABLE_OCxPRELOAD(&g_timx_comp_pwm_handle, TIM_CHANNEL_1);                 /* 通道1 预装载使能 */

    timx_oc_comp_pwm.Pulse = 500 - 1;
    HAL_TIM_OC_ConfigChannel(&g_timx_comp_pwm_handle, &timx_oc_comp_pwm, TIM_CHANNEL_2); /* 初始化定时器的输出比较通道2 */
    __HAL_TIM_ENABLE_OCxPRELOAD(&g_timx_comp_pwm_handle, TIM_CHANNEL_2);                 /* 通道2 预装载使能 */

    timx_oc_comp_pwm.Pulse = 750 - 1;
    HAL_TIM_OC_ConfigChannel(&g_timx_comp_pwm_handle, &timx_oc_comp_pwm, TIM_CHANNEL_3); /* 初始化定时器的输出比较通道3 */
    __HAL_TIM_ENABLE_OCxPRELOAD(&g_timx_comp_pwm_handle, TIM_CHANNEL_3);                 /* 通道3 预装载使能 */

    timx_oc_comp_pwm.Pulse = 1000 - 1;
    timx_oc_comp_pwm.OCIdleState = TIM_OCIDLESTATE_RESET;
    HAL_TIM_OC_ConfigChannel(&g_timx_comp_pwm_handle, &timx_oc_comp_pwm, TIM_CHANNEL_4); /* 初始化定时器的输出比较通道4 */
    __HAL_TIM_ENABLE_OCxPRELOAD(&g_timx_comp_pwm_handle, TIM_CHANNEL_4);                 /* 通道4 预装载使能 */

    HAL_TIM_OC_Start(&g_timx_comp_pwm_handle, TIM_CHANNEL_1);
    HAL_TIM_OC_Start(&g_timx_comp_pwm_handle, TIM_CHANNEL_2);
    HAL_TIM_OC_Start(&g_timx_comp_pwm_handle, TIM_CHANNEL_3);
    HAL_TIM_OC_Start(&g_timx_comp_pwm_handle, TIM_CHANNEL_4);
}

/* 定时器底层驱动,时钟使能,引脚配置 */
void HAL_TIM_OC_MspInit(TIM_HandleTypeDef *htim)
{
    if (htim->Instance == ATIM_TIMX_COMP)
    {
        GPIO_InitTypeDef gpio_init_struct;

        ATIM_TIMX_COMP_CLK_ENABLE(); /* 使能定时器时钟 */

        ATIM_TIMX_COMP_CH1_GPIO_CLK_ENABLE();
        ATIM_TIMX_COMP_CH2_GPIO_CLK_ENABLE();
        ATIM_TIMX_COMP_CH3_GPIO_CLK_ENABLE();
        ATIM_TIMX_COMP_CH4_GPIO_CLK_ENABLE();

        gpio_init_struct.Pin = ATIM_TIMX_COMP_CH1_GPIO_PIN;
        gpio_init_struct.Mode = GPIO_MODE_AF_PP;
        gpio_init_struct.Pull = GPIO_NOPULL;
        gpio_init_struct.Speed = GPIO_SPEED_FREQ_HIGH;
        gpio_init_struct.Alternate = ATIM_TIMX_COMP_GPIO_AF;
        HAL_GPIO_Init(ATIM_TIMX_COMP_CH1_GPIO_PORT, &gpio_init_struct);

        gpio_init_struct.Pin = ATIM_TIMX_COMP_CH2_GPIO_PIN;
        HAL_GPIO_Init(ATIM_TIMX_COMP_CH2_GPIO_PORT, &gpio_init_struct);

        gpio_init_struct.Pin = ATIM_TIMX_COMP_CH3_GPIO_PIN;
        HAL_GPIO_Init(ATIM_TIMX_COMP_CH3_GPIO_PORT, &gpio_init_struct);

        gpio_init_struct.Pin = ATIM_TIMX_COMP_CH4_GPIO_PIN;
        HAL_GPIO_Init(ATIM_TIMX_COMP_CH4_GPIO_PORT, &gpio_init_struct);
    }
}

atim.h

#ifndef __ATIM_H
#define __ATIM_H

#include "./SYSTEM/sys/sys.h"
#include "./BSP/LED/led.h"

/******************************************************************************************/
/* TIMX 输出比较模式 定义 
 * 这里通过TIM8的输出比较模式,控制PC6,PC7,PC8,PC9输出4路PWM,占空比50%,并且每一路PWM之间的相位差为25%
 * 修改CCRx可以修改相位.
 * 默认是针对TIM8
 * 注意: 通过修改这些宏定义,可以支持TIM1/TIM8任意一个定时器,任意一个IO口使用输出比较模式,输出PWM
 */
#define ATIM_TIMX_COMP_CH1_GPIO_PORT            GPIOC
#define ATIM_TIMX_COMP_CH1_GPIO_PIN             GPIO_PIN_6
#define ATIM_TIMX_COMP_CH1_GPIO_CLK_ENABLE()    do{ __HAL_RCC_GPIOC_CLK_ENABLE(); }while(0)   /* PC口时钟使能 */

#define ATIM_TIMX_COMP_CH2_GPIO_PORT            GPIOC
#define ATIM_TIMX_COMP_CH2_GPIO_PIN             GPIO_PIN_7
#define ATIM_TIMX_COMP_CH2_GPIO_CLK_ENABLE()    do{ __HAL_RCC_GPIOC_CLK_ENABLE(); }while(0)   /* PC口时钟使能 */

#define ATIM_TIMX_COMP_CH3_GPIO_PORT            GPIOC
#define ATIM_TIMX_COMP_CH3_GPIO_PIN             GPIO_PIN_8
#define ATIM_TIMX_COMP_CH3_GPIO_CLK_ENABLE()    do{ __HAL_RCC_GPIOC_CLK_ENABLE(); }while(0)   /* PC口时钟使能 */

#define ATIM_TIMX_COMP_CH4_GPIO_PORT            GPIOC
#define ATIM_TIMX_COMP_CH4_GPIO_PIN             GPIO_PIN_9
#define ATIM_TIMX_COMP_CH4_GPIO_CLK_ENABLE()    do{ __HAL_RCC_GPIOC_CLK_ENABLE(); }while(0)   /* PC口时钟使能 */

#define ATIM_TIMX_COMP_GPIO_AF                  GPIO_AF3_TIM8

#define ATIM_TIMX_COMP                          TIM8
#define ATIM_TIMX_COMP_CH1_CCRX                 ATIM_TIMX_COMP->CCR1                          /* 通道1的输出比较寄存器 */
#define ATIM_TIMX_COMP_CH2_CCRX                 ATIM_TIMX_COMP->CCR2                          /* 通道2的输出比较寄存器 */
#define ATIM_TIMX_COMP_CH3_CCRX                 ATIM_TIMX_COMP->CCR3                          /* 通道3的输出比较寄存器 */
#define ATIM_TIMX_COMP_CH4_CCRX                 ATIM_TIMX_COMP->CCR4                          /* 通道4的输出比较寄存器 */
#define ATIM_TIMX_COMP_CLK_ENABLE()             do{ __HAL_RCC_TIM8_CLK_ENABLE(); }while(0)    /* TIM8 时钟使能 */

/******************************************************************************************/

void atim_timx_comp_pwm_init(uint16_t arr, uint16_t psc);   /* 高级定时器 输出比较模式输出PWM 初始化函数 */

#endif

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

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

互补输出                                                                          带死区控制的互补输出

互补输出:是指通过两个输出引脚同时输出相反电平信号。其中一个引脚输出高电平时,另一个引脚输出低电平;反之亦然。这种互补输出的设计可以用于控制电机驱动、PWM信号生成等应用场景

死区控制:PWM的上下桥臂的三极管是不能同时导通的。如果同时导通就会是电源两端短路。所以,两路触发信号要在一段时间内都是使三极管断开的。这个区域就叫做“死区”

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

由于元器件是有延迟特性,所以需要加上死区时间控制

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

4.5.4、死区时间计算

1、确定 t_{DTS} 的值:f_{DTS}=\frac{F_{t}}{2^{CKD[1:0]}}

2、判断 DTG[7:5],选择计算公式

3、代入选择的公式计算

控制寄存器1 TIMx_CR1

断路和死区寄存器 TIMx_BDTR

举个栗子(F4为例):DTG[7:0]=250

250,即二进制:1111 1010,选第四条

DT = (32+26)*16*23.81ns=22.09568us

4.5.5、刹车(断路)功能

使能刹车功能:将 TIMx_BDTR的BKE 位置 1,刹车输入信号极性由 BKP 位设置

使能刹车功能后:由 TIMx_BDTR 的 MOE、OSSI、OSSR 位,TIMx_CR2 的 OISx、OISxN 位,TIMx_CCER 的 CCxE、CCxNE 位控制 OCx 和 OCxN 输出状态

无论何时,OCx 和 OCxN 输出都不能同时处在有效电平

在发生刹车后

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

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

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

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

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

1、配置定时器基础工作参数:使用 HAL_TIM_PWM_Init()

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

3、配置 PWM 模式/比较值等:使用 HAL_TIM_PWM_ConfigChannel()

4、配置刹车功能、死区时间等:使用 HAL_TIMEx_ConfigBreakDeadTime()

5、使能输出、主输出、计数器:使用 HAL_TIM_PWM_Start()

6、使能互补输出、主输出、计数器:使用 HAL_TIMEx_PWMN_Start()

相关 HAL 库函数介绍

关键结构体介绍

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;

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

通过定时器 1 通道 1 输出频率为 1KHz,占空比为 70% 的 PWM,使用 PWM 模式 1

使能互补输出并设置死区时间控制:设置 DTG 为 100(2.38us),进行验证死区时间是否正确

使能刹车功能:刹车输入信号高电平有效,配置输出空闲状态等,最后用示波器验证

main.c

#include "./SYSTEM/sys/sys.h"
#include "./SYSTEM/usart/usart.h"
#include "./SYSTEM/delay/delay.h"
#include "./BSP/LED/led.h"
#include "./BSP/KEY/key.h"
#include "./BSP/TIMER/atim.h"

int main(void)
{
    uint8_t t = 0;

    HAL_Init();                                 /* 初始化HAL库 */
    sys_stm32_clock_init(336, 8, 2, 7);         /* 设置时钟,168Mhz */
    delay_init(168);                            /* 延时初始化 */
    usart_init(115200);                         /* 串口初始化为115200 */
    led_init();                                 /* 初始化LED */
    key_init();                                 /* 初始化按键 */
    atim_timx_cplm_pwm_init(1000 - 1, 168 - 1); /* 1Mhz的计数频率 1Khz波形輸出. */
    atim_timx_cplm_pwm_set(300, 100);           /* 占空比:7:3, 死区时间 100 * tDTS */

    while (1)
    {
        t++;
        delay_ms(10);

        if (t > 50) /* 控制LED0闪烁, 提示程序运行状态 */
        {
            t = 0;
            LED0_TOGGLE();
        }
    }
}

atim.c

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

TIM_HandleTypeDef g_timx_cplm_pwm_handle;                       /* 定时器x句柄 */
TIM_BreakDeadTimeConfigTypeDef g_sbreak_dead_time_config = {0}; /* 死区时间设置 */

/**
 * @brief       高级定时器TIMX 互补输出 初始化函数(使用PWM模式1)
 * @note
 *              配置高级定时器TIMX 互补输出, 一路OCy 一路OCyN, 并且可以设置死区时间
 *
 *              高级定时器的时钟来自APB2, 而PCLK2 = 168Mhz, 我们设置PPRE2不分频, 因此
 *              高级定时器时钟 = 168Mhz
 *              定时器溢出时间计算方法: Tout = ((arr + 1) * (psc + 1)) / Ft us.
 *              Ft=定时器工作频率, 单位 : Mhz
 *
 * @param       arr: 自动重装值。
 * @param       psc: 预分频系数
 * @retval      无
 */
void atim_timx_cplm_pwm_init(uint16_t arr, uint16_t psc)
{
    GPIO_InitTypeDef gpio_init_struct = {0};
    TIM_OC_InitTypeDef tim_oc_cplm_pwm = {0};

    ATIM_TIMX_CPLM_CLK_ENABLE();           /* TIMx 时钟使能 */
    ATIM_TIMX_CPLM_CHY_GPIO_CLK_ENABLE();  /* 通道X对应IO口时钟使能 */
    ATIM_TIMX_CPLM_CHYN_GPIO_CLK_ENABLE(); /* 通道X互补通道对应IO口时钟使能 */
    ATIM_TIMX_CPLM_BKIN_GPIO_CLK_ENABLE(); /* 通道X刹车输入对应IO口时钟使能 */

    gpio_init_struct.Pin = ATIM_TIMX_CPLM_BKIN_GPIO_PIN;
    gpio_init_struct.Mode = GPIO_MODE_AF_PP;
    gpio_init_struct.Pull = GPIO_PULLDOWN;
    gpio_init_struct.Speed = GPIO_SPEED_FREQ_HIGH;
    gpio_init_struct.Alternate = ATIM_TIMX_CPLM_CHY_GPIO_AF; /* 端口复用 */
    HAL_GPIO_Init(ATIM_TIMX_CPLM_BKIN_GPIO_PORT, &gpio_init_struct);

    gpio_init_struct.Pin = ATIM_TIMX_CPLM_CHY_GPIO_PIN;
    HAL_GPIO_Init(ATIM_TIMX_CPLM_CHY_GPIO_PORT, &gpio_init_struct);

    gpio_init_struct.Pin = ATIM_TIMX_CPLM_CHYN_GPIO_PIN;
    HAL_GPIO_Init(ATIM_TIMX_CPLM_CHYN_GPIO_PORT, &gpio_init_struct);

    g_timx_cplm_pwm_handle.Instance = ATIM_TIMX_CPLM;                              /* 定时器x */
    g_timx_cplm_pwm_handle.Init.Prescaler = psc;                                   /* 预分频系数 */
    g_timx_cplm_pwm_handle.Init.CounterMode = TIM_COUNTERMODE_UP;                  /* 向上计数模式 */
    g_timx_cplm_pwm_handle.Init.Period = arr;                                      /* 自动重装载值 */
    g_timx_cplm_pwm_handle.Init.ClockDivision = TIM_CLOCKDIVISION_DIV4;            /* CKD[1:0] = 10, tDTS = 4 * tCK_INT = Ft / 4 = 42Mhz*/
    g_timx_cplm_pwm_handle.Init.AutoReloadPreload = TIM_AUTORELOAD_PRELOAD_ENABLE; /* 使能影子寄存器TIMx_ARR */

    HAL_TIM_PWM_Init(&g_timx_cplm_pwm_handle);

    tim_oc_cplm_pwm.OCMode = TIM_OCMODE_PWM1;              /* PWM模式1 */
    tim_oc_cplm_pwm.OCPolarity = TIM_OCPOLARITY_HIGH;      /* OCy 高电平有效 */
    tim_oc_cplm_pwm.OCNPolarity = TIM_OCPOLARITY_HIGH;     /* OCyN 高电平有效 */
    tim_oc_cplm_pwm.OCIdleState = TIM_OCIDLESTATE_RESET;   /* 当MOE=0,OCx=0 */
    tim_oc_cplm_pwm.OCNIdleState = TIM_OCNIDLESTATE_RESET; /* 当MOE=0,OCxN=0 */
    HAL_TIM_PWM_ConfigChannel(&g_timx_cplm_pwm_handle, &tim_oc_cplm_pwm, ATIM_TIMX_CPLM_CHY);

    /* 设置死区参数,开启死区中断 */
    g_sbreak_dead_time_config.OffStateRunMode = TIM_OSSR_DISABLE;           /* 运行模式的关闭输出状态 */
    g_sbreak_dead_time_config.OffStateIDLEMode = TIM_OSSI_DISABLE;          /* 空闲模式的关闭输出状态 */
    g_sbreak_dead_time_config.LockLevel = TIM_LOCKLEVEL_OFF;                /* 不用寄存器锁功能 */
    g_sbreak_dead_time_config.BreakState = TIM_BREAK_ENABLE;                /* 使能刹车输入 */
    g_sbreak_dead_time_config.BreakPolarity = TIM_BREAKPOLARITY_HIGH;       /* 刹车输入有效信号极性为高 */
    g_sbreak_dead_time_config.AutomaticOutput = TIM_AUTOMATICOUTPUT_ENABLE; /* 使能AOE位,允许刹车结束后自动恢复输出 */
    HAL_TIMEx_ConfigBreakDeadTime(&g_timx_cplm_pwm_handle, &g_sbreak_dead_time_config);

    HAL_TIM_PWM_Start(&g_timx_cplm_pwm_handle, ATIM_TIMX_CPLM_CHY);    /* OCy 输出使能 */
    HAL_TIMEx_PWMN_Start(&g_timx_cplm_pwm_handle, ATIM_TIMX_CPLM_CHY); /* OCyN 输出使能 */
}

/**
 * @brief       定时器TIMX 设置输出比较值 & 死区时间
 * @param       ccr: 输出比较值
 * @param       dtg: 死区时间
 *   @arg       dtg[7:5]=0xx时, 死区时间 = dtg[7:0] * tDTS
 *   @arg       dtg[7:5]=10x时, 死区时间 = (64 + dtg[6:0]) * 2  * tDTS
 *   @arg       dtg[7:5]=110时, 死区时间 = (32 + dtg[5:0]) * 8  * tDTS
 *   @arg       dtg[7:5]=111时, 死区时间 = (32 + dtg[5:0]) * 16 * tDTS
 *   @note      tDTS = 1 / (Ft /  CKD[1:0]) = 1 / 42M = 23.8ns
 * @retval      无
 */
void atim_timx_cplm_pwm_set(uint16_t ccr, uint8_t dtg)
{
    g_sbreak_dead_time_config.DeadTime = dtg;
    HAL_TIMEx_ConfigBreakDeadTime(&g_timx_cplm_pwm_handle, &g_sbreak_dead_time_config); /*重设死区时间*/
    __HAL_TIM_MOE_ENABLE(&g_timx_cplm_pwm_handle);                                      /* MOE=1,使能主输出 */
    ATIM_TIMX_CPLM_CHY_CCRY = ccr;                                                      /* 设置比较寄存器 */
}

atim.h

#ifndef __ATIM_H
#define __ATIM_H

#include "./SYSTEM/sys/sys.h"
#include "./BSP/LED/led.h"

/******************************************************************************************/
/* TIMX 互补输出模式 定义 
 * 这里设置互补输出相关硬件配置, CHY即正常输出, CHYN即互补输出
 * 修改CCRx可以修改占空比.
 * 默认是针对TIM1
 * 注意: 通过修改这些宏定义,可以支持TIM1/TIM8定时器, 任意一个IO口输出互补PWM(前提是必须有互补输出功能)
 */

/* 输出通道引脚 */
#define ATIM_TIMX_CPLM_CHY_GPIO_PORT            GPIOE
#define ATIM_TIMX_CPLM_CHY_GPIO_PIN             GPIO_PIN_9
#define ATIM_TIMX_CPLM_CHY_GPIO_CLK_ENABLE()    do{ __HAL_RCC_GPIOE_CLK_ENABLE(); }while(0)   /* PE口时钟使能 */

/* 互补输出通道引脚 */
#define ATIM_TIMX_CPLM_CHYN_GPIO_PORT           GPIOE
#define ATIM_TIMX_CPLM_CHYN_GPIO_PIN            GPIO_PIN_8
#define ATIM_TIMX_CPLM_CHYN_GPIO_CLK_ENABLE()   do{ __HAL_RCC_GPIOE_CLK_ENABLE(); }while(0)   /* PE口时钟使能 */

/* 刹车输入引脚 */
#define ATIM_TIMX_CPLM_BKIN_GPIO_PORT           GPIOE
#define ATIM_TIMX_CPLM_BKIN_GPIO_PIN            GPIO_PIN_15
#define ATIM_TIMX_CPLM_BKIN_GPIO_CLK_ENABLE()   do{ __HAL_RCC_GPIOE_CLK_ENABLE(); }while(0)   /* PE口时钟使能 */

/* TIMX REMAP设置
 * 因为PE8/PE9/PE15, 默认并不是TIM1的复用功能脚, 必须开启完全重映射, 才可以将: TIM1_CH1->PE9; TIM1_CH1N->PE8; TIM1_BKIN->PE15;
 * 这样, PE8/PE9/PE15, 才能用作TIM1的CH1N/CH1/BKIN功能.
 * 因此, 必须实现ATIM_TIMX_CPLM_CHYN_GPIO_AF, 
 * 如果我们使用默认的复用功能输出, 则不用设置重映射, 是可以不需要该函数的! 根据具体需要来实现.
 */
#define ATIM_TIMX_CPLM_CHY_GPIO_AF             GPIO_AF1_TIM1

/* 互补输出使用的定时器 */
#define ATIM_TIMX_CPLM                          TIM1
#define ATIM_TIMX_CPLM_CHY                      TIM_CHANNEL_1
#define ATIM_TIMX_CPLM_CHY_CCRY                 ATIM_TIMX_CPLM->CCR1
#define ATIM_TIMX_CPLM_CLK_ENABLE()             do{ __HAL_RCC_TIM1_CLK_ENABLE(); }while(0)    /* TIM1 时钟使能 */

/******************************************************************************************/

void atim_timx_cplm_pwm_init(uint16_t arr, uint16_t psc);   /* 高级定时器 互补输出 初始化函数 */
void atim_timx_cplm_pwm_set(uint16_t ccr, uint8_t dtg);     /* 高级定时器 互补输出 设置输出比较值 & 死区时间 */

#endif

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

4.6.1、PWM输入模式工作原理

4.6.2、PWM输入模式时序

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

1、配置定时器基础工作参数:使用 HAL_TIM_IC_Init()

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

3、配置 IC1/2 映射、捕获边沿等:使用 HAL_TIM_IC_ConfigChannel()

4、配置从模式,触发源等:使用 HAL_TIM_SlaveConfigSynchro()

5、设置优先级、使能中断:使用 HAL_NVIC_SetPriority()HAL_NVIC_EnableIRQ()

6、使能捕获、捕获中断及计数器:使用 HAL_TIM_IC_Start_IT()HAL_TIM_IC_Start()

7、编写中断服务函数:使用 TIMx_IRQHandler() 等 —> HAL_TIM_IRQHandler()

8、编写输入捕获回调函数:HAL_TIM_IC_CaptureCallback()

相关 HAL 库函数介绍

关键结构体介绍

typedef struct
{ 
    uint32_t ICPolarity;  /* 输入捕获触发方式选择,比如上升、下降沿捕获 */ 
    uint32_t ICSelection; /* 输入捕获选择,用于设置映射关系 */ 
    uint32_t ICPrescaler; /* 输入捕获分频系数 */ 
    uint32_t ICFilter;    /* 输入捕获滤波器设置 */ 
} TIM_IC_InitTypeDef;

typedef struct 
{ 
    uint32_t SlaveMode;          /* 从模式选择 */ 
    uint32_t InputTrigger;       /* 输入触发源选择 */ 
    uint32_t TriggerPolarity;    /* 输入触发极性 */ 
    uint32_t TriggerPrescaler;   /* 输入触发预分频 */ 
    uint32_t TriggerFilter;      /* 输入滤波器设置 */ 
} TIM_SlaveConfigTypeDef;

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

通过定时器 14 通道 1(PF9)输出 PWM

将 PWM 输入到定时器 8 通道 1(PC6),测量 PWM 的频率/周期、占空比等信息

168MHz 采样频率(精度约5.95ns),PSC=0,ARR=65535

不考虑溢出情况下,测量的最长 PWM 周期为 389.9us

main.c

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

extern uint16_t g_timxchy_pwmin_psc;  /* PWM输入状态 */
extern uint16_t g_timxchy_pwmin_sta;  /* PWM输入状态 */
extern uint32_t g_timxchy_pwmin_hval; /* PWM的高电平脉宽 */
extern uint32_t g_timxchy_pwmin_cval; /* PWM的周期宽度 */

int main(void)
{
    uint8_t t = 0;
    double ht, ct, f, tpsc;

    HAL_Init();                         /* 初始化HAL库 */
    sys_stm32_clock_init(336, 8, 2, 7); /* 设置时钟,168Mhz */
    delay_init(168);                    /* 延时初始化 */
    usart_init(115200);                 /* 串口初始化为115200 */
    led_init();                         /* 初始化LED */
    key_init();                         /* 初始化按键 */

    gtim_timx_pwm_chy_init(10 - 1, 84 - 1); /* 1Mhz的计数频率, 100Khz PWM */
    atim_timx_pwmin_chy_init();             /* 初始化PWM输入捕获 */

    GTIM_TIMX_PWM_CHY_CCRX = 2; /* 低电平宽度20,高电平宽度80 */

    while (1)
    {
        delay_ms(10);
        t++;

        if (t >= 20) /* 每200ms输出一次结果,并闪烁LED0,提示程序运行 */
        {
            if (g_timxchy_pwmin_sta) /* 捕获了一次数据 */
            {
                printf("\r\n");                                   /* 输出空,另起一行 */
                printf("PWM PSC  :%d\r\n", g_timxchy_pwmin_psc);  /* 打印分频系数 */
                printf("PWM Hight:%d\r\n", g_timxchy_pwmin_hval); /* 打印高电平脉宽 */
                printf("PWM Cycle:%d\r\n", g_timxchy_pwmin_cval); /* 打印周期 */
                tpsc = ((double)g_timxchy_pwmin_psc + 1) / 168;   /* 得到PWM采样时钟周期时间 */
                ht = g_timxchy_pwmin_hval * tpsc;                 /* 计算高电平时间 */
                ct = g_timxchy_pwmin_cval * tpsc;                 /* 计算周期长度 */
                f = (1 / ct) * 1000000;                           /* 计算频率 */
                printf("PWM Hight time:%.3fus\r\n", ht);          /* 打印高电平脉宽长度 */
                printf("PWM Cycle time:%.3fus\r\n", ct);          /* 打印周期时间长度 */
                printf("PWM Frequency :%.3fHz\r\n", f);           /* 打印频率 */
                atim_timx_pwmin_chy_restart();                    /* 重启PWM输入检测 */
            }

            LED1_TOGGLE(); /* DS1闪烁 */
            t = 0;
        }
    }
}

atim.c

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

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

/* PWM输入状态(g_timxchy_cap_sta)
 * 0,没有成功捕获.
 * 1,已经成功捕获了
 */
uint8_t g_timxchy_pwmin_sta = 0;   /* PWM输入状态 */
uint16_t g_timxchy_pwmin_psc = 0;  /* PWM输入分频系数 */
uint32_t g_timxchy_pwmin_hval = 0; /* PWM的高电平脉宽 */
uint32_t g_timxchy_pwmin_cval = 0; /* PWM的周期宽度 */

/**
 * @brief       定时器TIMX 通道Y PWM输入模式 初始化函数
 * @note
 *              高级定时器的时钟来自APB2, 而PCLK2 = 168Mhz, 我们设置PPRE2不分频, 因此
 *              高级定时器时钟 = 168Mhz
 *              定时器溢出时间计算方法: Tout = ((arr + 1) * (psc + 1)) / Ft us.
 *              Ft=定时器工作频率,单位:Mhz
 *
 * @param       无
 * @retval      无
 */
void atim_timx_pwmin_chy_init(void)
{
    GPIO_InitTypeDef gpio_init_struct = {0};
    TIM_SlaveConfigTypeDef slave_config = {0};
    TIM_IC_InitTypeDef tim_ic_pwmin_chy = {0};

    ATIM_TIMX_PWMIN_CHY_CLK_ENABLE();
    ATIM_TIMX_PWMIN_CHY_GPIO_CLK_ENABLE();

    gpio_init_struct.Pin = ATIM_TIMX_PWMIN_CHY_GPIO_PIN;
    gpio_init_struct.Mode = GPIO_MODE_AF_PP;
    gpio_init_struct.Pull = GPIO_PULLDOWN;
    gpio_init_struct.Speed = GPIO_SPEED_FREQ_HIGH;
    gpio_init_struct.Alternate = ATIM_TIMX_PWMIN_CHY_GPIO_AF;
    HAL_GPIO_Init(ATIM_TIMX_PWMIN_CHY_GPIO_PORT, &gpio_init_struct);

    g_timx_pwmin_chy_handle.Instance = ATIM_TIMX_PWMIN;            /* 定时器8 */
    g_timx_pwmin_chy_handle.Init.Prescaler = 0;                    /* 定时器预分频系数 */
    g_timx_pwmin_chy_handle.Init.CounterMode = TIM_COUNTERMODE_UP; /* 递增计数模式 */
    g_timx_pwmin_chy_handle.Init.Period = 65535;                   /* 自动重装载值 */
    HAL_TIM_IC_Init(&g_timx_pwmin_chy_handle);

    /* 从模式配置,IT1触发更新 */
    slave_config.SlaveMode = TIM_SLAVEMODE_RESET;                   /* 从模式:复位模式 */
    slave_config.InputTrigger = TIM_TS_TI1FP1;                      /* 定时器输入触发源:TI1FP1 */
    slave_config.TriggerPolarity = TIM_INPUTCHANNELPOLARITY_RISING; /* 上升沿检测 */
    slave_config.TriggerFilter = 0;                                 /* 不滤波 */
    HAL_TIM_SlaveConfigSynchro(&g_timx_pwmin_chy_handle, &slave_config);

    /* IC1捕获:上升沿触发TI1FP1 */
    tim_ic_pwmin_chy.ICPolarity = TIM_INPUTCHANNELPOLARITY_RISING; /* 上升沿检测 */
    tim_ic_pwmin_chy.ICSelection = TIM_ICSELECTION_DIRECTTI;       /* 选择输入端IC1映射到TI1 */
    tim_ic_pwmin_chy.ICPrescaler = TIM_ICPSC_DIV1;                 /* 不分频 */
    tim_ic_pwmin_chy.ICFilter = 0;                                 /* 不滤波 */
    HAL_TIM_IC_ConfigChannel(&g_timx_pwmin_chy_handle, &tim_ic_pwmin_chy, TIM_CHANNEL_1);

    /* IC2捕获:上升沿触发TI1FP2 */
    tim_ic_pwmin_chy.ICPolarity = TIM_INPUTCHANNELPOLARITY_FALLING; /* 下降沿检测 */
    tim_ic_pwmin_chy.ICSelection = TIM_ICSELECTION_INDIRECTTI;      /* 选择输入端IC2映射到TI1 */
    HAL_TIM_IC_ConfigChannel(&g_timx_pwmin_chy_handle, &tim_ic_pwmin_chy, TIM_CHANNEL_2);

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

    /* TIM1/TIM8有独立的输入捕获中断服务函数 */
    if (ATIM_TIMX_PWMIN == TIM1 || ATIM_TIMX_PWMIN == TIM8)
    {
        HAL_NVIC_SetPriority(ATIM_TIMX_PWMIN_CC_IRQn, 1, 3); /* 设置中断优先级,抢占优先级1,子优先级3 */
        HAL_NVIC_EnableIRQ(ATIM_TIMX_PWMIN_CC_IRQn);         /* 开启TIMx中断 */
    }

    __HAL_TIM_ENABLE_IT(&g_timx_pwmin_chy_handle, TIM_IT_UPDATE);
    HAL_TIM_IC_Start_IT(&g_timx_pwmin_chy_handle, TIM_CHANNEL_1);
    HAL_TIM_IC_Start_IT(&g_timx_pwmin_chy_handle, TIM_CHANNEL_2);
}

/* 定时器TIMX PWM输入模式 重新启动捕获 */
void atim_timx_pwmin_chy_restart(void)
{
    sys_intx_disable(); /* 关闭中断 */

    g_timxchy_pwmin_sta = 0; /* 清零状态,重新开始检测 */
    g_timxchy_pwmin_psc = 0; /* 分频系数清零 */

    __HAL_TIM_SET_PRESCALER(&g_timx_pwmin_chy_handle, 0); /* 以最大的计数频率采集,以得到最好的精度 */
    __HAL_TIM_SET_COUNTER(&g_timx_pwmin_chy_handle, 0);   /* 计数器清零 */

    __HAL_TIM_ENABLE_IT(&g_timx_pwmin_chy_handle, TIM_IT_CC1);    /* 使能通道1捕获中断 */
    __HAL_TIM_ENABLE_IT(&g_timx_pwmin_chy_handle, TIM_IT_UPDATE); /* 使能溢出中断 */
    __HAL_TIM_ENABLE(&g_timx_pwmin_chy_handle);                   /* 使能定时器TIMX */

    __HAL_TIM_CLEAR_FLAG(&g_timx_pwmin_chy_handle, TIM_FLAG_CC1);    /* 清零捕获/比较1中断标志 */
    __HAL_TIM_CLEAR_FLAG(&g_timx_pwmin_chy_handle, TIM_FLAG_CC2);    /* 清零捕获/比较2中断标志 */
    __HAL_TIM_CLEAR_FLAG(&g_timx_pwmin_chy_handle, TIM_FLAG_UPDATE); /* 清零更新中断标志 */

    sys_intx_enable(); /* 打开中断 */
}

/* 定时器TIMX 通道Y PWM输入模式 中断处理函数 */
static void atim_timx_pwmin_chy_process(void)
{
    static uint8_t sflag = 0; /* 启动PWMIN输入检测标志 */

    if (g_timxchy_pwmin_sta)
    {
        g_timxchy_pwmin_psc = 0;
        __HAL_TIM_CLEAR_FLAG(&g_timx_pwmin_chy_handle, TIM_FLAG_CC1);    /* 清零捕获/比较1中断标志 */
        __HAL_TIM_CLEAR_FLAG(&g_timx_pwmin_chy_handle, TIM_FLAG_CC2);    /* 清零捕获/比较2中断标志 */
        __HAL_TIM_CLEAR_FLAG(&g_timx_pwmin_chy_handle, TIM_FLAG_UPDATE); /* 清零更新中断标志 */
        __HAL_TIM_SET_COUNTER(&g_timx_pwmin_chy_handle, 0);              /* 计数器清零 */
        return;
    }

    if (__HAL_TIM_GET_FLAG(&g_timx_pwmin_chy_handle, TIM_FLAG_UPDATE)) /* 发生了溢出中断 */
    {
        __HAL_TIM_CLEAR_FLAG(&g_timx_pwmin_chy_handle, TIM_FLAG_UPDATE); /* 清除溢出中断标记 */

        if (__HAL_TIM_GET_FLAG(&g_timx_pwmin_chy_handle, TIM_FLAG_CC1) == 0) /* 没有发生周期捕获中断,且捕获未完成 */
        {
            sflag = 0;
            if (g_timxchy_pwmin_psc == 0) /* 从0 到 1 */
            {
                g_timxchy_pwmin_psc++;
            }
            else
            {
                if (g_timxchy_pwmin_psc == 65535) /* 已经最大了,可能是无输入状态 */
                {
                    g_timxchy_pwmin_psc = 0; /* 重新恢复不分频 */
                }
                else if (g_timxchy_pwmin_psc > 32767) /* 不能倍增了 */
                {
                    g_timxchy_pwmin_psc = 65535; /* 直接等于最大分频系数 */
                }
                else
                {
                    g_timxchy_pwmin_psc += g_timxchy_pwmin_psc; /* 倍增 */
                }
            }

            __HAL_TIM_SET_PRESCALER(&g_timx_pwmin_chy_handle, g_timxchy_pwmin_psc); /* 设置定时器预分频系数 */
            __HAL_TIM_SET_COUNTER(&g_timx_pwmin_chy_handle, 0);                     /* 计数器清零 */
            __HAL_TIM_CLEAR_FLAG(&g_timx_pwmin_chy_handle, TIM_FLAG_CC1);           /* 清零捕获/比较1中断标志 */
            __HAL_TIM_CLEAR_FLAG(&g_timx_pwmin_chy_handle, TIM_FLAG_CC2);           /* 清零捕获/比较2中断标志 */
            __HAL_TIM_CLEAR_FLAG(&g_timx_pwmin_chy_handle, TIM_FLAG_UPDATE);        /* 清零更新中断标志 */
            return;
        }
    }

    if (sflag == 0) /* 第一次采集到捕获中断 */
    {
        if (__HAL_TIM_GET_FLAG(&g_timx_pwmin_chy_handle, TIM_FLAG_CC1)) /* 检测到了第一次周期捕获中断 */
        {
            sflag = 1; /* 标记第一次周期已经捕获, 第二次周期捕获可以开始了 */
        }

        __HAL_TIM_CLEAR_FLAG(&g_timx_pwmin_chy_handle, TIM_FLAG_CC1);    /* 清零捕获/比较1中断标志 */
        __HAL_TIM_CLEAR_FLAG(&g_timx_pwmin_chy_handle, TIM_FLAG_CC2);    /* 清零捕获/比较2中断标志 */
        __HAL_TIM_CLEAR_FLAG(&g_timx_pwmin_chy_handle, TIM_FLAG_UPDATE); /* 清零更新中断标志 */
        return;                                                          /* 完成此次操作 */
    }

    if (g_timxchy_pwmin_sta == 0) /* 还没有成功捕获 */
    {
        if (__HAL_TIM_GET_FLAG(&g_timx_pwmin_chy_handle, TIM_FLAG_CC1)) /* 检测到了周期捕获中断 */
        {
            g_timxchy_pwmin_hval = HAL_TIM_ReadCapturedValue(&g_timx_pwmin_chy_handle, TIM_CHANNEL_2) + 1; /* 高定平脉宽捕获值 */
            g_timxchy_pwmin_cval = HAL_TIM_ReadCapturedValue(&g_timx_pwmin_chy_handle, TIM_CHANNEL_1) + 1; /* 周期捕获值 */

            if (g_timxchy_pwmin_hval < g_timxchy_pwmin_cval) /* 高电平脉宽必定小于周期长度 */
            {
                g_timxchy_pwmin_sta = 1; /* 标记捕获成功 */

                g_timxchy_pwmin_psc = ATIM_TIMX_PWMIN->PSC; /* 获取PWM输入分频系数 */

                if (g_timxchy_pwmin_psc == 0) /* 分频系数为0的时候, 修正读取数据 */
                {
                    g_timxchy_pwmin_hval++; /* 修正系数为1, 加1 */
                    g_timxchy_pwmin_cval++; /* 修正系数为1, 加1 */
                }

                sflag = 0;
                /* 每次捕获PWM输入成功后, 停止捕获, 避免频繁中断影响系统正常代码运行 */
                ATIM_TIMX_PWMIN->CR1 &= ~(1 << 0);                             /* 关闭定时器TIMX */
                __HAL_TIM_DISABLE_IT(&g_timx_pwmin_chy_handle, TIM_IT_CC1);    /* 使能通道1捕获中断 */
                __HAL_TIM_DISABLE_IT(&g_timx_pwmin_chy_handle, TIM_IT_CC2);    /* 使能通道2捕获中断 */
                __HAL_TIM_DISABLE_IT(&g_timx_pwmin_chy_handle, TIM_IT_UPDATE); /* 使能溢出中断 */

                __HAL_TIM_CLEAR_FLAG(&g_timx_pwmin_chy_handle, TIM_FLAG_CC1);    /* 清零捕获/比较1中断标志 */
                __HAL_TIM_CLEAR_FLAG(&g_timx_pwmin_chy_handle, TIM_FLAG_CC2);    /* 清零捕获/比较2中断标志 */
                __HAL_TIM_CLEAR_FLAG(&g_timx_pwmin_chy_handle, TIM_FLAG_UPDATE); /* 清零更新中断标志 */
            }
            else
            {
                atim_timx_pwmin_chy_restart();
            }
        }
    }

    /* 清除捕获/比较1中断标志\捕获/比较2中断标志/更新中断标志 */
    __HAL_TIM_CLEAR_FLAG(&g_timx_pwmin_chy_handle, TIM_FLAG_CC1);
    __HAL_TIM_CLEAR_FLAG(&g_timx_pwmin_chy_handle, TIM_FLAG_CC2);
    __HAL_TIM_CLEAR_FLAG(&g_timx_pwmin_chy_handle, TIM_FLAG_UPDATE);
}

/* 定时器TIMX 更新/溢出 中断服务函数 */
void ATIM_TIMX_PWMIN_IRQHandler(void)
{
    atim_timx_pwmin_chy_process();
}

/* 定时器TIMX 输入捕获 中断服务函数 */
void ATIM_TIMX_PWMIN_CC_IRQHandler(void)
{
    atim_timx_pwmin_chy_process();
}

atim.h

#ifndef __ATIM_H
#define __ATIM_H

#include "./SYSTEM/sys/sys.h"
#include "./BSP/LED/led.h"

/******************************************************************************************/
/* TIMX PWM输入模式 定义 
 * 这里的输入捕获使用定时器TIM1_CH1,捕获WK_UP按键的输入
 * 默认是针对TIM1/TIM8等高级定时器
 * 注意: 通过修改这几个宏定义,可以支持TIM1~TIM8任意一个定时器的通道1/通道2
 */
#define ATIM_TIMX_PWMIN_CHY_GPIO_PORT           GPIOC
#define ATIM_TIMX_PWMIN_CHY_GPIO_PIN            GPIO_PIN_6
#define ATIM_TIMX_PWMIN_CHY_GPIO_AF             GPIO_AF3_TIM8
#define ATIM_TIMX_PWMIN_CHY_GPIO_CLK_ENABLE()   do{ __HAL_RCC_GPIOC_CLK_ENABLE(); }while(0)   /* PC口时钟使能 */

#define ATIM_TIMX_PWMIN                         TIM8
#define ATIM_TIMX_PWMIN_IRQn                    TIM8_UP_TIM13_IRQn
#define ATIM_TIMX_PWMIN_IRQHandler              TIM8_UP_TIM13_IRQHandler
#define ATIM_TIMX_PWMIN_CHY                     TIM_CHANNEL_1                           /* 通道Y,  1<= Y <=2*/ 
#define ATIM_TIMX_PWMIN_CHY_CLK_ENABLE()        do{ __HAL_RCC_TIM8_CLK_ENABLE(); }while(0)  /* TIM8 时钟使能 */

 /* TIM1 / TIM8 有独立的捕获中断服务函数,需要单独定义,对于TIM2~5等,则不需要以下定义 */
#define ATIM_TIMX_PWMIN_CC_IRQn                 TIM8_CC_IRQn
#define ATIM_TIMX_PWMIN_CC_IRQHandler           TIM8_CC_IRQHandler

/******************************************************************************************/

void atim_timx_pwmin_chy_init(void);                        /* 高级定时器 PWM输入模式初始化 */
void atim_timx_pwmin_chy_restart(void);                     /* 高级定时器 重启PWM输入模式检测 */

#endif
  • 20
    点赞
  • 33
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

HZU_Puzzle

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值