标准定时器TIMER

发布于 2023-03-30  513 次阅读


定时器定时原理

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

介绍

定时器核心就是计数器

STM32定时器特性表

F1
H7

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

计数器模式溢出条件
递增计数模式CNT==ARR
递减计数模式CNT==0
中心对齐模式CNT==ARR-1、CNT==1

定时器时钟频率

STM32F1 系列的定时器 TIM2/TIM3/TIM4/TIM5/ TIM6/TIM7 都是挂载在 APB1 总线上,这
些定时器的内部时钟(CK_INT)实际上来自于 APB1 总线提供的时钟。但是这些定时器时钟不是
由 APB1 总线直接提供,而是要先经过一个倍频器
。在 HAL 库版本例程源码的 sys.c 文件中,
系统时钟初始化函数 sys_stm32_clock_init 已经设置 APB1 总线时钟频率为 36MHz,APB1 预分
频器的预分频系数为 2,所以这些定时器时钟源频率为 72MHz。因为当 APB1 预分频器的预分
频系数≥2 分频时,挂载在 APB1 总线上的定时器时钟频率是该总线时钟频率的两倍

定时器溢出时间计算方法

Tout =((ARR+1)∗(PSC+1))/Ft

Tout是定时器溢出时间

Ft是定时器的时钟源频率

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

PSC是预分频器寄存器的值

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

定时器类型主要功能
基本定时器没有输入输出通道,常用作时基,即定时功能
通用定时器具有多路独立通道,可用于输入捕获/输出比较,也可用作时基
高级定时器除具备通用定时器所有功能外,还具备带死区控制的互补信号输出、刹车输入等功能(可用于电机控制、数字电源设计等)

基本定时器

简介

基本定时器TIM6/TIM7:

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

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

可用于触发DAC

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

基本定时器框图:

配置步骤

1.配置工作参数

HAL_TIM_Base_Init()

TIM_HandleTypeDef	g_timx_handle;

void btim_timx_int_init(uint16_t arr, uint16_t psc)
{
	g_timx_handle.Instance = TIM6;
	g_timx_handle.Init.Prescaler = psc; /* 预分频 */
	g_timx_handle.Init.CounterMode = TIM_COUNTERMODE_UP; /* 递增计数器 */
	g_timx_handle.Init.Period = arr; /* 自动装载值 */
	HAL_TIM_Base_Init(&g_timx_handle);
}

2.MSP初始化

HAL_TIM_Base_MspInit()     配置NVIC、CLOCK等

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

3.使能更新中断并启动计数器

HAL_TIM_Base_Start_IT()

void btim_timx_int_init(uint16_t arr, uint16_t psc)
{
	g_timx_handle.Instance = TIM6;
	g_timx_handle.Init.Prescaler = psc; /* 预分频 */
	g_timx_handle.Init.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 和定时器 x 更新中断 */
}

4.编写中断服务函数

HAL_TIM_IRQHandler()

void TIM6_IRQHandler(void)
{
	HAL_TIM_IRQHandler(&g_timx_handle);
}

5.定时器更新中断回调函数

HAL_TIM_PeriodElapsedCallback()

void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim)
{
	if (htim->Instance == TIM6)
	{
		LED4_Togg;
	}
}

6.main函数

int main(void)
{
	HAL_Init();                                 /* 初始化HAL库 */
	Bsp_Led_Config();
	Bsp_Key_Config();
    sys_stm32_clock_init(RCC_PLL_MUL9);         /* 设置时钟, 72Mhz */
    delay_init(72);                             /* 延时初始化 */
    usart_init(115200);                         /* 串口初始化为115200 */
	btim_timx_int_init(5000 - 1, 7200 - 1);
	while (1)
	{
		LED5_Togg;
		delay_ms(500);
    }
}

在 main 函数里,先初始化系统和用户的外设代码,然后在 wilhe(1)里每 500ms 翻转一次
LED5。由前面的内容知道,定时器 6 的时钟频率为 72MHZ,而调用 btim_timx_int_init 初始化
函数之后,就相当于写入预分频寄存器的值为 7199,写入自动重载寄存器的值为 4999。由公式
得:
Tout = ((4999+1)*(7199+1))/72000000 = 0.5s = 500ms

通用定时器

简介

定时器框图

时钟源控制器时基单元输入捕获捕获/比较(公共)输出比较

时钟源

通用定时器时钟源设置方法

1.内部时钟

来自APB1总线上的时钟再通过一个倍频器后的频率,和基本定时器的时钟源差不多

2.外部时钟模式 1

时钟频率来自于芯片外部,时钟源进入定时器的流程如下:外部时钟源信号→IO→TIMx_CH1(或者 TIMx_CH2),这里需要注意的是:外部时钟模式 1 下,时钟源信号只能从 CH1 或者 CH2 输入到定时器,CH3 和 CH4 都是不可以的。从 IO到 TIMx_CH1(或者 TIMx_CH2),就需要我们配置 IO 的复用功能,才能使 IO 和定时器通道相连通

时钟源信号来到定时器 CH1 或 CH2 后,需要经过什么“关卡”才能到达计数器作为计数的时钟频率的,下面通过外部时钟模式 1 框图给大家解答。

外部时钟模式 1 框图

3.外部时钟模式 2

时钟源进入定时器的流程如下:外部时钟源信号→IO→TIMx_ETR。从 IO 到 TIMx_ETR,就需要我们配置 IO 的复用功能,才能使IO 和定时器相连通

时钟源信号来到定时器 TIMx_ETR 后,需要经过什么“关卡”才能到达计数器作为计数的时钟频率的,下面通过外部时钟模式 2 框图给大家解答。

外部时钟模式 2 框图

可以看到在外部时钟模式 2 下,定时器时钟信号首先从 ETR 引脚进来。接着经过外部触发极性选择器,由ETP 位来设置上升沿有效还是下降沿有效,选择下降沿有效的话,信号会经过反相器。

4.内部触发输入

内部触发输入是使用一个定时器作为另一个定时器的预分频器,即实现定时器的级联。下面以 TIM1 作为 TIM2 的预分频器为例,给大家介绍。

TIM1 作为 TIM2 的预分频器框图

上图中,TIM1 作为 TIM2 的预分频器,需要完成的配置步骤如下:
1.TIM1_CR2 寄存器的 MMS[2:0]位设置为 010,即 TIM1 的主模式选择为更新(选择更新事件作为触发输出 (TRGO))。
2.TIM2_SMCR 寄存器的 TS[2:0]位设置为 000,即使用 ITR1 作为内部触发。TS[2:0]位用于配置触发选择,除了 ITR1,还有其他的选择,详细描述如下图所示:

触发选择
TIMx 内部触发连接

中断配置步骤

1.配置工作参数

HAL_TIM_Base_Init()

void gtim_timx_int_init(uint16_t arr, uint16_t psc)
{
__HAL_RCC_TIM3_CLK_ENABLE();/* 使能 TIMx 时钟 */
g_timx_handle.Instance = TIM3; /* 通用定时器 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);
/* 设置中断优先级,抢占优先级 1,子优先级 3 */
HAL_NVIC_SetPriority(TIM3_IRQn, 1, 3);
HAL_NVIC_EnableIRQ(TIM3_IRQn); /* 开启 ITMx 中断 */

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

2.编写中断服务函数

TIM3_IRQHandler()

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

这里没有使用 HAL 库的定时器公共处理函数来处理中断部分的代码,而是通过自行判断中断标志位的方式来处理。只不过获取标志位的方式还是使用 HAL 库的函数宏__HAL_TIM_GET_FLAG(),也可以直接使用寄存器的方式来操作。通过__HAL_TIM_GET_FLAG()获取中断标志位并判断是否了中断,然后处理中断程序,最后通过__HAL_TIM_CLEAR_IT()将中断标志位清零,这样就完成了一次对中断的处理。这样的方式来处理中断。在一个项目中,用到多个定时器相关中断时,建议大家使用这种方式来处理代码,这样方便代码的管理

通用定时器使用PWM

会用到 3 个寄存器,来控制 PWM。这三个寄存器分别是:捕获/比较模式寄存器(TIMx_CCMR1/2)、捕获/比较使能寄存器(TIMx_CCER)、捕获/比较寄存器(TIMx_CCR1~4)

输出PWM原理

假设:递增计数模式

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

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

当CNT < CCRx,IO输出0

当CNT >= CCRx,IO输出1

周期/频率

PWM波的周期/频率 Tout=((ARR+1)∗(PSC+1))/Ft

PWM输出配置步骤

相关HAL库函数介绍

函数主要寄存器主要功能
HAL_TIM_PWM_Init()CR1、ARR、PSC初始化定时器基础参数
HAL_TIM_PWM_MspInit()存放NVIC、CLOCK、GPIO初始化代码
HAL_TIM_PWM_ConfigChannel()CCMRx、CCRx、CCER配置PWM模式、比较值、输出极性等
HAL_TIM_PWM_Start()CCER、CR1使能输出比较并启动计数器
__HAL_TIM_SET_COMPARE()CCRx修改比较值
__HAL_TIM_ENABLE_OCxPRELOAD()CCER使能通道预装载

1.配置工作参数

HAL_TIM_PWM_Init()

TIM_HandleTypeDef g_timx_pwm_chy_handle;
void gtim_timx_pwm_chy_init(uint16_t arr,uint16_t psc)
{
	TIM_OC_InitTypeDef g_timx_oc_pwm_chy_handle;
	
	g_timx_pwm_chy_handle.Instance = TIM3; /* 定时器 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 */

	g_timx_oc_pwm_chy_handle.OCMode = TIM_OCMODE_PWM1; /* 模式选择 PWM1 */
	/* 设置比较值,此值用来确定占空比, 默认比较值为自动重装载值的一半,即占空比为 50% */
	g_timx_oc_pwm_chy_handle.Pulse = arr/2;
	g_timx_oc_pwm_chy_handle.OCPolarity = TIM_OCPOLARITY_HIGH;/* 输出比较极性为低 */
	
	HAL_TIM_PWM_ConfigChannel(&g_timx_pwm_chy_handle, &g_timx_oc_pwm_chy_handle,TIM_CHANNEL_2); /* 配置 TIMx 通道 y */
	HAL_TIM_PWM_Start(&g_timx_pwm_chy_handle, TIM_CHANNEL_2);/*开启 PWM 通道*/
}

2.MSP初始化

HAL_TIM_PWM_MspInit()

void HAL_TIM_PWM_MspInit(TIM_HandleTypeDef *htim)
{
	if (htim->Instance == TIM3)
	{
		GPIO_InitTypeDef gpio_init_struct;
		__HAL_RCC_GPIOA_CLK_ENABLE(); /* 开启通道 y 的 CPIO 时钟 */
		__HAL_RCC_TIM3_CLK_ENABLE();
		gpio_init_struct.Pin = GPIO_PIN_7; /* 通道 y 的 CPIO 口 */
		gpio_init_struct.Mode = GPIO_MODE_AF_PP; /* 复用推完输出 */
		gpio_init_struct.Pull = GPIO_NOPULL; /* 下拉 */
		gpio_init_struct.Speed = GPIO_SPEED_FREQ_HIGH; /* 高速 */
		HAL_GPIO_Init(GPIOA, &gpio_init_struct);
		
		__HAL_RCC_AFIO_CLK_ENABLE();
		// __HAL_AFIO_REMAP_TIM3_PARTIAL(); /* IO 口 REMAP 设置,设置重映射 */
	}
}

重映射功能

TIMX REMAP 设置* 如果我们 LED0 接在 PB5 上, 必须通过开启 TIM3 的部分重映射功能, 才能将复用功能 TIM3_CH2 输出到 PB5 上* 因此, 必须实现 GTIM_TIMX_PWM_CHY_GPIO_REMAP* 对那些使用默认设置的定时器 PWM 输出脚, 不用设置重映射, 是不需要该函数的!

mian函数

int main(void)
{
uint16_t ledrpwmval = 0;
uint8_t dir = 1;
HAL_Init(); /* 初始化 HAL 库 */
sys_stm32_clock_init(RCC_PLL_MUL9); /* 设置时钟, 72Mhz */
delay_init(72); /* 延时初始化 */
usart_init(115200); /* 串口初始化为 115200 */
led_init(); /* 初始化 LED */
/* 72M/72=1M 的计数频率,自动重装载为 500,那么 PWM 频率为 1M/500=2kHZ */
gtim_timx_pwm_chy_init(500 - 1, 72 - 1);
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, TIM_CHANNEL_2,ledrpwmval);
}
}

我们假设比较值固定为 200

__HAL_TIM_SET_COMPARE(&g_timx_pwm_chy_handle, GTIM_TIMX_PWM_CHY, 200);

因为 LED是电平有效,所以我们在 gtim_timx_pwm_chy_init 函数中设置了输出比较极性为低,那么当比较值固定为 200 时, 占空比= CCR1 / (arr+1) = 200/500=40%。

输入捕获

测量脉宽

输入捕获脉宽测量原理

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

假设:递增计数模式

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

CCRx1:t1时间点CCRx的值

CCRx2:t2时间点CCRx的值

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

捕获测量高电平脉宽

输入捕配置步骤


相关HAL库函数介绍
函数主要寄存器主要功能
HAL_TIM_IC_Init()CR1、ARR、PSC初始化定时器基础参数
HAL_TIM_IC_MspInit()存放NVIC、CLOCK、GPIO初始化代码
HAL_TIM_IC_ConfigChannel()CCMRx、CCER配置通道映射、捕获边沿、分频、滤波等
__HAL_TIM_ENABLE_IT()DIER使能更新中断等
HAL_TIM_IC_Start_IT()CCER、DIER、CR1使能输入捕获、捕获中断并启动计数器
HAL_TIM_IRQHandler()SR定时器中断处理公用函数,处理各种中断
HAL_TIM_PeriodElapsedCallback()定时器更新中断回调函数,由用户重定义
HAL_TIM_IC_CaptureCallback()定时器输入捕获回调函数,由用户重定义

1.配置定时器基础工作参数

HAL_TIM_IC_Init()

TIM_HandleTypeDef g_timx_cap_chy_handle;     /* 定时器x句柄 */
void gtim_timx_cap_chy_init(uint16_t arr, uint16_t psc)
{
    g_timx_cap_chy_handle.Instance = TIM5;                              /* 定时器5 */
    g_timx_cap_chy_handle.Init.Prescaler = psc;                         /* 定时器分频 */
    g_timx_cap_chy_handle.Init.CounterMode = TIM_COUNTERMODE_UP;        /* 递增计数模式 */
    g_timx_cap_chy_handle.Init.Period = arr;                            /* 自动重装载值 */
    HAL_TIM_IC_Init(&g_timx_cap_chy_handle);
}
2.MSP初始化
/* 定时器 输入捕获 MSP初始化函数 */
void HAL_TIM_IC_MspInit(TIM_HandleTypeDef *htim)
{
    if (htim->Instance == TIM5)                             /*输入通道捕获*/
    {
        GPIO_InitTypeDef gpio_init_struct = {0};
        __HAL_RCC_TIM5_CLK_ENABLE();                        /* 使能TIM5时钟 */
        __HAL_RCC_GPIOA_CLK_ENABLE();                       /* 开启捕获IO的时钟 */

        gpio_init_struct.Pin = GPIO_PIN_0;
        gpio_init_struct.Mode = GPIO_MODE_INPUT;            /* 输入模式 */
        gpio_init_struct.Pull = GPIO_PULLDOWN;              /* 下拉 */
        HAL_GPIO_Init(GPIOA, &gpio_init_struct);

        HAL_NVIC_SetPriority(TIM5_IRQn, 1, 3);              /* 抢占1,子优先级3 */
        HAL_NVIC_EnableIRQ(TIM5_IRQn);                      /* 开启ITMx中断 */
    }
}
3.配置输入通道映射、捕获边沿

HAL_TIM_IC_ConfigChannel()

TIM_HandleTypeDef g_timx_cap_chy_handle;     /* 定时器x句柄 */
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 = TIM5;                              /* 定时器5 */
    g_timx_cap_chy_handle.Init.Prescaler = psc;                         /* 定时器分频 */
    g_timx_cap_chy_handle.Init.CounterMode = TIM_COUNTERMODE_UP;        /* 递增计数模式 */
    g_timx_cap_chy_handle.Init.Period = arr;                            /* 自动重装载值 */
    HAL_TIM_IC_Init(&g_timx_cap_chy_handle);

    timx_ic_cap_chy.ICPolarity = TIM_ICPOLARITY_RISING;                 /* 上升沿捕获 */
    timx_ic_cap_chy.ICSelection = TIM_ICSELECTION_DIRECTTI;             /* 映射到TI1上 */
    timx_ic_cap_chy.ICPrescaler = TIM_ICPSC_DIV1;                       /* 配置输入分频,不分频 */
    timx_ic_cap_chy.ICFilter = 0;                                       /* 配置输入滤波器,不滤波 */
    HAL_TIM_IC_ConfigChannel(&g_timx_cap_chy_handle, &timx_ic_cap_chy, TIM_CHANNEL_1);  /* 配置TIM5通道1 */

    __HAL_TIM_ENABLE_IT(&g_timx_cap_chy_handle, TIM_IT_UPDATE);         /* 使能更新中断 */
    HAL_TIM_IC_Start_IT(&g_timx_cap_chy_handle, TIM_CHANNEL_1);         /* 开始捕获TIM5的通道1 */
}
4.中断服务函数
/* 定时器5中断服务函数 */
void TIM5_IRQHandler(void)
{
    HAL_TIM_IRQHandler(&g_timx_cap_chy_handle);  /* 定时器HAL库共用处理函数 */
}
5.捕获回调函数
uint8_t g_timxchy_cap_sta = 0; /* 输入捕获状态 */
uint16_t g_timxchy_cap_val = 0; /* 输入捕获值 */

这两个变量用于辅助实现高电平捕获。其中 g_timxchy_cap_sta,是用来记录捕获状态, (这个变量,我们把它当成一个寄存器那样来使用)。对其各位赋予状态含义,描述如下表所示:

bit7bit6bit5~0
捕获完成标志捕获到高电平标志捕获高电平后定时器溢出的次数
g_timxchy_cap_sta各位描述

变量 g_timxchy_cap_sta 的位[5:0]是用于记录捕获高电平定时器溢出次数,总共 6 位,所以最多可以记录溢出的次数为 2 的 6 次方减一次,即 63 次

变量 g_timxchy_cap_val,则用来记录捕获到下降沿的时候, TIM5_CNT 寄存器的值

/* 输入捕获状态(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 HAL_TIM_IC_CaptureCallback(TIM_HandleTypeDef *htim)
{
	LED4_Togg;
    if (htim->Instance == TIM5)
    {
        if ((g_timxchy_cap_sta & 0X80) == 0)                /* 还没有成功捕获 */
        {
            if (g_timxchy_cap_sta & 0X40)                   /* 捕获到一个下降沿 */
            {
                g_timxchy_cap_sta |= 0X80;                  /* 标记成功捕获到一次高电平脉宽 */
                g_timxchy_cap_val = HAL_TIM_ReadCapturedValue(&g_timx_cap_chy_handle, TIM_CHANNEL_1);  /* 获取当前的捕获值 */
                TIM_RESET_CAPTUREPOLARITY(&g_timx_cap_chy_handle, TIM_CHANNEL_1);                      /* 一定要先清除原来的设置 */
                TIM_SET_CAPTUREPOLARITY(&g_timx_cap_chy_handle, TIM_CHANNEL_1, TIM_ICPOLARITY_RISING); /* 配置TIM5通道1上升沿捕获 */
            }
            else /* 还未开始,第一次捕获上升沿 */
            {
                g_timxchy_cap_sta = 0;                              /* 清空 */
                g_timxchy_cap_val = 0;
                g_timxchy_cap_sta |= 0X40;                          /* 标记捕获到了上升沿 */
                __HAL_TIM_DISABLE(&g_timx_cap_chy_handle);          /* 关闭定时器5 */
                __HAL_TIM_SET_COUNTER(&g_timx_cap_chy_handle, 0);   /* 定时器5计数器清零 */
                TIM_RESET_CAPTUREPOLARITY(&g_timx_cap_chy_handle, TIM_CHANNEL_1);   /* 一定要先清除原来的设置!! */
                TIM_SET_CAPTUREPOLARITY(&g_timx_cap_chy_handle, TIM_CHANNEL_1, TIM_ICPOLARITY_FALLING); /* 定时器5通道1设置为下降沿捕获 */
                __HAL_TIM_ENABLE(&g_timx_cap_chy_handle);           /* 使能定时器5 */
            }
        }
    }
}
6.更新中断函数
/* 定时器更新中断回调函数 */
void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim)
{
	//LED4_Togg;
    if (htim->Instance == TIM5)
    {
        if ((g_timxchy_cap_sta & 0X80) == 0)            /* 还未成功捕获 */
        {
            if (g_timxchy_cap_sta & 0X40)               /* 已经捕获到高电平了 */
            {
                if ((g_timxchy_cap_sta & 0X3F) == 0X3F) /* 高电平太长了 */
                {
                    TIM_RESET_CAPTUREPOLARITY(&g_timx_cap_chy_handle, TIM_CHANNEL_1);                     /* 一定要先清除原来的设置 */
                    TIM_SET_CAPTUREPOLARITY(&g_timx_cap_chy_handle, TIM_CHANNEL_1, TIM_ICPOLARITY_RISING);/* 配置TIM5通道1上升沿捕获 */
                    g_timxchy_cap_sta |= 0X80;          /* 标记成功捕获了一次 */
                    g_timxchy_cap_val = 0XFFFF;
                }
                else      /* 累计定时器溢出次数 */
                {
                    g_timxchy_cap_sta++;
                }
            }
        }
    }
}
main函数
extern uint8_t  g_timxchy_cap_sta;  /* 输入捕获状态 */
extern uint16_t g_timxchy_cap_val;  /* 输入捕获值 */
int main(void)
{	
	uint32_t temp = 0;
	
	HAL_Init();                                 /* 初始化HAL库 */
	Bsp_Led_Config();
	//Bsp_Key_Config();
    sys_stm32_clock_init(RCC_PLL_MUL9);         /* 设置时钟, 72Mhz */
    delay_init(72);                             /* 延时初始化 */
    usart_init(115200);                         /* 串口初始化为115200 */
	gtim_timx_cap_chy_init(0XFFFF, 72 - 1); /* 以1Mhz的频率计数 捕获 */
	printf("脉宽捕获实验");
	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;          /* 开启下一次捕获*/
        }
	}
}

gtim_timx_cap_chy_init(0XFFFF, 72 - 1)这个语句,这两个形参分别设置自动重载寄存器的值为 65535,以及预分频器寄存器的值为 71。定时器 5 是 16 位的计数器, 这里设置为最大值 65535。预分频系数,我们设置为 72 分频, 定时器 5 的时钟频率是 2 倍的 APB1 总线时钟频率,即 72MHz,可以得到计数器的计数频率是 1MHz,即 1us 计数一次,所以我们的捕获时间精度是 1us。这里可以知道定时器的溢出时间是 65536us。

脉冲计数

脉冲计数器原理

外部时钟模式 1 的外部输入引脚只能是通道 1 或者通道 2 对应的 IO
如果想时钟源信号的上升沿和下降沿计数器都计数,可以选择 TI1F_ED 作为触发输入选择器的触发源

配置步骤

相关函数库介绍
函数主要寄存器主要功能
HAL_TIM_IC_Init()CR1、ARR、PSC初始化定时器基础参数
HAL_TIM_IC_MspInit()存放NVIC、CLOCK、GPIO初始化代码
HAL_TIM_SlaveConfigSynchro()SMCR、CCMRx、CCER配置定时器从模式、触发选择、分频、滤波等
HAL_TIM_IC_Start()CCER、CR1使能输入捕获、启动计数器
__HAL_TIM_GET_COUNTER()CNT获取计数器当前值
__HAL_TIM_SET_COUNTER()CNT设置计数器的值
配置定时器基础工作参数
/**
* @brief 通用定时器 TIMX 通道 Y 脉冲计数 初始化函数
* @note
* 本函数选择通用定时器的时钟选择: 外部时钟源模式 1(SMS[2:0] = 111)
* CNT 的计数时钟源就来自 TIMX_CH1/CH2, 可以实现外部脉冲计数(脉冲接入 CH1/CH2)
*
* 时钟分频数 = psc, 一般设置为 0, 表示每一个时钟都会计数一次, 以提高精度.
* 通过读取 CNT 和溢出次数, 经过简单计算, 可以得到当前的计数值, 从而实现脉冲计数
* @param arr: 自动重装值
* @retval 无
*/
TIM_HandleTypeDef g_timx_cnt_chy_handle; 
void gtim_timx_cnt_chy_init(uint16_t psc)
{
	GPIO_InitTypeDef gpio_init_struct;
	TIM_SlaveConfigTypeDef tim_slave_config = {0};
	__HAL_RCC_GPIOA_CLK_ENABLE();/* 开启 GPIOA 时钟 */
	__HAL_RCC_TIM2_CLK_ENABLE();/* 使能 TIMx 时钟 */
	g_timx_cnt_chy_handle.Instance = TIM2; /* 定时器 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 = GPIO_PIN_0; /* 输入捕获的 GPIO 口 */
	gpio_init_struct.Mode = GPIO_MODE_AF_PP; /* 复用推挽输出 */
	gpio_init_struct.Pull = GPIO_PULLDOWN; /* 下拉 */
	gpio_init_struct.Speed = GPIO_SPEED_FREQ_HIGH; /* 高速 */
	HAL_GPIO_Init(GPIOA, &gpio_init_struct);
	 
	/* 从模式:外部触发模式 1 */
	tim_slave_config.SlaveMode = TIM_SLAVEMODE_EXTERNAL1;/*从模式:外部触发模式 1*/
	tim_slave_config.InputTrigger = TIM_TS_TI1FP1; /* TI1FP1 作为触发输入源 */
	tim_slave_config.TriggerPolarity = TIM_TRIGGERPOLARITY_RISING;/* 上升沿 */
	tim_slave_config.TriggerPrescaler = TIM_TRIGGERPRESCALER_DIV1;/* 不分频*/
	tim_slave_config.TriggerFilter = 0x0; /* 滤波:本例中不需要任何滤波 */
	HAL_TIM_SlaveConfigSynchro(&g_timx_cnt_chy_handle, &tim_slave_config);
	 
	HAL_TIM_IC_Start(&g_timx_cnt_chy_handle,TIM_CHANNEL_1);/* 使能通道输入 */
}
main函数
int main(void)
{
	uint32_t curcnt = 0;
	uint32_t oldcnt = 0;
	
	HAL_Init();                                 /* 初始化HAL库 */
	Bsp_Led_Config();
	Bsp_Key_Config();
    sys_stm32_clock_init(RCC_PLL_MUL9);         /* 设置时钟, 72Mhz */
    delay_init(72);                             /* 延时初始化 */
    usart_init(115200);                         /* 串口初始化为115200 */
	gtim_timx_cnt_chy_init(0);
	printf("SSSSS\n");
	while (1)
	{
		if (Key2_Scan() == KEY_ON) /* KEY0 按键按下,重启计数 */
		{	
			printf("重置次数\n");
			 __HAL_TIM_SET_COUNTER(&g_timx_cnt_chy_handle, 0);/* 重新启动计数 */
		}
			curcnt = __HAL_TIM_GET_COUNTER(&g_timx_cnt_chy_handle); /* 获取计数值 */
		if (oldcnt != curcnt)
		{
			oldcnt = curcnt;
			printf("CNT:%d\r\n", oldcnt); /* 打印脉冲个数 */
		}
    }
}

高级定时器

简介

TIM1/TIM8

主要特性:

  • 16位递增、递减、中心对齐计数器(计数值:0~65535)
  • 16位预分频器(分频系数:1~65536)
  • 可用于触发DAC、ADC
  • 在更新事件、触发事件、输入捕获、输出比较时,会产生中断/DMA请求
  • 4个独立通道,可用于:输入捕获、输出比较、输出PWM、单脉冲模式
  • 使用外部信号控制定时器且可实现多个定时器互连的同步电路
  • 支持编码器和霍尔传感器电路等
  • 重复计数器
  • 死区时间带可编程的互补输出
  • 断路输入,用于将定时器的输出信号置于用户可选的安全配置中

高级定时器框图

高级定时器的框图和通用定时器框图很类似,只是添加了其它的一些功能,如:重复计数器、带死区控制的互补输出通道、断路输入等。这些功能在高级定时器框图的位置如下:

重复计数器

我们知道定时器发生上溢或者下溢时,会直接生成更新事件。但是有重复计数器的定时器并不完全是这样的,定时器每次发生上溢或下溢时,重复计数器的值会减一,当重复计数器的值为 0 时,再发生一次上溢或者下溢才会生成定时器更新事件。如果我们设置重复计数器寄存器 RCR 的值为 N,那么更新事件将在定时器发生 N+1 次上溢或下溢时发生。

输出比较

高级定时器输出比较部分和通用定时器相比,多了带死区控制的互补输出功能。 上图第②部分的 TIMx_CH1N、 TIMx_CH2N 和 TIMx_CH3N 分别是定时器通道 1、通道 2 和通道 3的互补输出通道,通道 4 是没有互补输出通道的。 DTG 是死区发生器,死区时间由 DTG[7:0]位来配置。 如果不使用互补通道和死区时间控制,那么高级定时器 TIM1 和 TIM8 和通用定时器的输出比较部分使用方法基本一样,只是要注意 MOE 位得置 1 定时器才能输出。

断路功能

断路功能也称刹车功能,一般用于电机控制的刹车。 F1 系列有一个断路通道,断路源可以是刹车输入引脚(TIMx_BKIN),也可以是一个时钟失败事件。时钟失败事件由复位时钟控制器中的时钟安全系统产生。系统复位后,断路功能默认被禁止, MOE 位为低。使能断路功能的方法:将 TIMx_BDTR 的位 BKE 置 1。断路输入引脚 TIMx_BKIN 的输入有效电平可通过 TIMx_BDTR 寄存器的位 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 的状态:由相关控制位状态决定,当使用互补输出时:根据情况自动控制输出电平,参考《STM32F10xxx 参考手册_V10(中文版) .pdf》手册第 245 页的表 75 带刹车功能的互补通道 Ocx 和 OcxN 的控制位。3, BIF 位置 1,如果使能了 BIE 位,还会产生刹车中断;如果使能了 TDE 位,会产生 DMA请求。4,如果 AOE 位置 1,在下一个 更新事件 UEV 时, MOE 位被自动置 1。

输出指定个数 PWM

关于更多重复计数计数器计数原理上面已经介绍过一遍了

重复计数器特性,设置重复计数器寄存器 RCR 的值为 N,那么更新事件将在定时器发生 N+1 次上溢或下溢时发生。换句话来说就是,想要指定输出 N 个 PWM,只需要把N-1 写入 RCR 寄存器。因为在边沿对齐模式下,定时器溢出周期对应着 PWM 周期,我们只要在更新事件发生时,停止输出 PWM 就行。

为了保证定时器输出指定个数的 PWM 后,定时器马上停止继续输出,我们使能更新中断,并在定时器中断里关闭计数器。

需要把 MOE 位置 1,这样高级定时器的通道才能输出。

配置步骤

  • 配置边沿对齐模式输出PWM(如递增,递减模式)
  • 指定输出N个PWM,则将N-1写入RCR
  • 在更新中断内关闭计数器

相关HAL库函数介绍

函数主要寄存器主要功能
HAL_TIM_PWM_Init()CR1、ARR、PSC初始化定时器基础参数
HAL_TIM_PWM_MspInit()存放NVIC、CLOCK、GPIO初始化代码
HAL_TIM_PWM_ConfigChannel()CCMRx、CCRx、CCER配置PWM模式、比较值、输出极性等
__HAL_TIM_ENABLE_IT()CCER使能更新中断等
HAL_TIM_PWM_Start()CCER、CR1使能输出、主输出、启动计数器
HAL_TIM_IRQHandler()SR定时器中断处理公用函数,处理各种中断
HAL_TIM_PeriodElapsedCallback()定时器更新中断回调函数,由用户重定义
HAL_TIM_GenerateEvent()EGR通过软件产生事件
__HAL_TIM_ENABLE()CR1启动计数器

配置定时器基础工作参数


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

 /* 高级定时器TIMX 通道Y 输出指定个数PWM 初始化函数 */
void atim_timx_npwm_chy_init(uint16_t arr, uint16_t psc)
{
    TIM_OC_InitTypeDef timx_oc_npwm_chy = {0};
	__HAL_RCC_GPIOC_CLK_ENABLE();
	__HAL_RCC_TIM8_CLK_ENABLE();

    g_timx_npwm_chy_handle.Instance = TIM8;                            /* 定时器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.RepetitionCounter = 0;                 /* 重复计数器初始值 */
    HAL_TIM_PWM_Init(&g_timx_npwm_chy_handle);                         /* 初始化PWM */


    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, TIM_CHANNEL_1);

    __HAL_TIM_ENABLE_IT(&g_timx_npwm_chy_handle, TIM_IT_UPDATE);/* 允许更新中断 */
    HAL_TIM_PWM_Start(&g_timx_npwm_chy_handle, TIM_CHANNEL_1);/* 使能输出 */
	
	GPIO_InitTypeDef gpio_init_struct;
	gpio_init_struct.Pin = GPIO_PIN_6;
	gpio_init_struct.Mode = GPIO_MODE_AF_PP;        /* 推挽式复用功能 */
	gpio_init_struct.Pull = GPIO_PULLDOWN;            /* 上拉 */
	gpio_init_struct.Speed = GPIO_SPEED_FREQ_HIGH;  /* 高速 */
	HAL_GPIO_Init(GPIOC, &gpio_init_struct);
	
	HAL_NVIC_SetPriority(TIM8_UP_IRQn, 1, 3);
	HAL_NVIC_EnableIRQ(TIM8_UP_IRQn);
}

使能定时器更新中断

/* g_npwm_remain 表示当前还剩下多少个脉冲要发送
* 每次最多发送 256 个脉冲
*/
static uint32_t g_npwm_remain = 0;
/**
* @brief 高级定时器 TIMX NPWM 设置 PWM 个数
* @param rcr: PWM 的个数, 1~2^32 次方个
* @retval 无
*/
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 */
}

编写中断服务函数

/* 定时器8中断服务函数 */
void TIM8_UP_IRQHandler(void)
{
    HAL_TIM_IRQHandler(&g_timx_npwm_chy_handle);
}

编写更新中断回调函数

/* 定时器更新中断回调函数 */
void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim)
{
    if (htim->Instance == TIM8)
    {
        if(g_npwm_remain)
        {
            TIM8->RCR = g_npwm_remain - 1;//设置重复计数寄存器
            /*产生一个软件更新事件,将RCR的值缓冲进影子寄存器*/
            HAL_TIM_GenerateEvent(&g_timx_npwm_chy_handle, TIM_EVENTSOURCE_UPDATE);
            __HAL_TIM_ENABLE(&g_timx_npwm_chy_handle);
            g_npwm_remain = 0;
        }
        else
        {
            TIM8->CR1 &= ~(1 << 0);//关闭计数器
            // _HAL_TIM_DISABLE()要判断两个位0是否为0才能关闭,我们不需要关闭那两个,所以直接使用寄存器
        }
    }
}

main函数

int main(void)
{
	HAL_Init();                                 /* 初始化HAL库 */
	Bsp_Led_Config();
	Bsp_Key_Config();
    sys_stm32_clock_init(RCC_PLL_MUL9);         /* 设置时钟, 72Mhz */
    delay_init(72);                             /* 延时初始化 */
    usart_init(115200);                         /* 串口初始化为115200 */
	atim_timx_npwm_chy_init(5000 - 1, 7200 - 1);
	
	printf("hellow\n");
	while (1)
	{
		if(Key1_Scan() == KEY_ON)
		{
			atim_timx_npwm_chy_set(2);//输出2个PWM
		}	
		if(Key2_Scan() == KEY_ON)
		{
			atim_timx_npwm_chy_set(5);//输出5个PWM
		}
    }
}

输出比较模式

输出比较模式原理

输出比较模式:翻转

当CNT = CCRx,OCxREF电平翻转

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

配置步骤

相关HAL库函数介绍

函数主要寄存器主要功能
HAL_TIM_OC_Init()CR1、ARR、PSC初始化定时器基础参数
HAL_TIM_OC_MspInit存放NVIC、CLOCK、GPIO初始化代码
HAL_TIM_OC_ConfigChannel()CCMRx、CCRx、CCER设置输出比较模式、比较值、输出极性等
__HAL_TIM_ENABLE_OCxPRELOAD()CCMRx使能通道预装载
HAL_TIM_OC_Start()CR1、CCER、BDTR使能输出比较、主输出、启动计数器
__HAL_TIM_SET_COMPARE()CCRx修改捕获/比较寄存器的值

实现目标

  • 通过定时器8通道1/2/3/4输出相位分别为25%、50%、75%、100%的PWM
    • 1.确定PWM波的周期/频率Tout =((ARR+1)∗(PSC+1))/Ft
    • 1KHz为例,PSC=71,ARR=999
    • 2.配置输出比较模式为:翻转
    • 通道输出极性为:高电平有效

配置代码

TIM_HandleTypeDef g_timx_comp_pwm_handle;       /* 定时器x句柄 */
/* 高级定时器 输出比较模式 初始化函数 */
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 = TIM8;                       /* 定时器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;                     /* 自动重装载值 */
    HAL_TIM_OC_Init(&g_timx_comp_pwm_handle);                     /* 输出比较模式初始化 */

    timx_oc_comp_pwm.OCMode = TIM_OCMODE_TOGGLE;				/* 比较输出模式翻转功能 */
    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~4 */
    HAL_TIM_OC_ConfigChannel(&g_timx_comp_pwm_handle, &timx_oc_comp_pwm, TIM_CHANNEL_2);
    HAL_TIM_OC_ConfigChannel(&g_timx_comp_pwm_handle, &timx_oc_comp_pwm, TIM_CHANNEL_3);
    HAL_TIM_OC_ConfigChannel(&g_timx_comp_pwm_handle, &timx_oc_comp_pwm, TIM_CHANNEL_4);
    
    __HAL_TIM_ENABLE_OCxPRELOAD(&g_timx_comp_pwm_handle, TIM_CHANNEL_1);/* CCR1~4 寄存器预装载使能 */
    __HAL_TIM_ENABLE_OCxPRELOAD(&g_timx_comp_pwm_handle, TIM_CHANNEL_2);
    __HAL_TIM_ENABLE_OCxPRELOAD(&g_timx_comp_pwm_handle, TIM_CHANNEL_3);
    __HAL_TIM_ENABLE_OCxPRELOAD(&g_timx_comp_pwm_handle, TIM_CHANNEL_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 == TIM8)
    {
        GPIO_InitTypeDef gpio_init_struct;

        __HAL_RCC_TIM8_CLK_ENABLE();
        __HAL_RCC_GPIOC_CLK_ENABLE();

        gpio_init_struct.Pin = GPIO_PIN_6;
        gpio_init_struct.Mode = GPIO_MODE_AF_PP;
        gpio_init_struct.Pull = GPIO_NOPULL;
        gpio_init_struct.Speed = GPIO_SPEED_FREQ_HIGH;
        HAL_GPIO_Init(GPIOC, &gpio_init_struct);

        gpio_init_struct.Pin = GPIO_PIN_7;
        HAL_GPIO_Init(GPIOC, &gpio_init_struct);

        gpio_init_struct.Pin = GPIO_PIN_8;
        HAL_GPIO_Init(GPIOC, &gpio_init_struct);

        gpio_init_struct.Pin = GPIO_PIN_9;
        HAL_GPIO_Init(GPIOC, &gpio_init_struct);
    }
}

main函数

int main(void)
{
	HAL_Init();                                 /* 初始化HAL库 */
	Bsp_Led_Config();
	Bsp_Key_Config();
    sys_stm32_clock_init(RCC_PLL_MUL9);         /* 设置时钟, 72Mhz */
    delay_init(72);                             /* 延时初始化 */
    usart_init(115200);                         /* 串口初始化为115200 */
	atim_timx_comp_pwm_init(1000 - 1, 72 - 1);
	printf("hellow\n");
	__HAL_TIM_SET_COMPARE(&g_timx_comp_pwm_handle, TIM_CHANNEL_1, 250 - 1);/* 通道 1 相位 25% */
    __HAL_TIM_SET_COMPARE(&g_timx_comp_pwm_handle, TIM_CHANNEL_2, 500 - 1);/* 通道 1 相位 50% */
    __HAL_TIM_SET_COMPARE(&g_timx_comp_pwm_handle, TIM_CHANNEL_3, 750 - 1);/* 通道 1 相位 75% */
    __HAL_TIM_SET_COMPARE(&g_timx_comp_pwm_handle, TIM_CHANNEL_4, 1000 - 1);/* 通道 1 相位 100% */
	while (1)
	{
		
    }
}

互补输出带死区控制

简介

应用

该功能常应用于H桥来控制电机正反转

Q1和Q4高电平电机正转,Q3和Q2高电平电机反转

Q1和Q2如果同时为高电平,则会发生短路

由于元器件是有延迟特性,为了防止短路或者电机从正转立马切换为反转产生一瞬间短路,对电机也会有损伤,所以需要加上死区时间控制

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

死区时间计算

1, 确定tDTS的值

fDTS=Ft/2^CKD[1:0]

tDTS = 1/fDTS

TIMx_CR1

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

TIMx_BDTR

3, 代入选择的公式计算

F1为例:DTG[7:0]=250 预分频系数为4

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

tDTS = 1/72000000/4 s

DT = (32+26)*16*55.56 ns=51.55968us

刹车(断路)功能

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

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

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

发生刹车后

  • MOE位被清零,OCx和OCxN为无效、空闲或复位状态(OSSI位选择)
  • OCx和OCxN的状态:由相关控制位状态决定,当使用互补输出时:根据情况自动控制输出电平,参考参考手册使用刹车(断路)功能小节
  • BIF位置1,如果使能了BIE位,还会产生刹车中断;如果使能了TDE位,会产生DMA请求
  • 如果AOE位置 1,在下一个 更新事件UEV时,MOE位被自动置 1

配置步骤

实现目标

  • 通过定时器1通道1输出频率为1KHz,占空比为70%的PWM,使用PWM模式1
  • 使能互补输出并设置死区时间控制:设置DTG为100(5.56us),进行验证死区时间是否正确
  • 使能刹车功能:刹车输入信号高电平有效,配置输出空闲状态等,最后用示波器验证
    • 1.确定PWM波的周期/频率Tout =((ARR+1)∗(PSC+1))/Ft
    • 1KHz为例,PSC=71,ARR=999
    • 2.配置通道输出极性以及互补输出极性

相关HAL库函数介绍

函数主要寄存器主要功能
HAL_TIM_PWM_Init()CR1、ARR、PSC初始化定时器基础参数
HAL_TIM_PWM_MspInit()存放NVIC、CLOCK、GPIO初始化代码
HAL_TIM_PWM_ConfigChannel()CCMRx、CCRx、CCER配置PWM模式、比较值、输出极性等
HAL_TIMEx_ConfigBreakDeadTime()BDTR配置刹车功能、死区时间等
HAL_TIM_PWM_Start()CCER、CR1使能输出、主输出、启动计数器
HAL_TIMEx_PWMN_Start()CCER、CR1使能互补输出、主输出、启动计数器

1.配置工作参数

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

/* 高级定时器 互补输出 初始化函数(使用PWM模式1) */
void atim_timx_cplm_pwm_init(uint16_t arr, uint16_t psc)
{
    TIM_OC_InitTypeDef tim_oc_cplm_pwm = {0};

    g_timx_cplm_pwm_handle.Instance = TIM1;                                 /* 定时器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 = 18Mhz */
    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_OCNPOLARITY_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, TIM_CHANNEL_1);

    /* 设置死区参数,开启死区中断 */
    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, TIM_CHANNEL_1);              /* OCy 输出使能 */
    HAL_TIMEx_PWMN_Start(&g_timx_cplm_pwm_handle,TIM_CHANNEL_1);            /* OCyN 输出使能 */
}

2.MSP初始化

/* 定时器 PWM输出 MSP初始化函数 */
void HAL_TIM_PWM_MspInit(TIM_HandleTypeDef *htim)
{
    if (htim->Instance == TIM1)
    {
        GPIO_InitTypeDef gpio_init_struct = {0};

        __HAL_RCC_TIM1_CLK_ENABLE();
        __HAL_RCC_GPIOB_CLK_ENABLE();
		__HAL_RCC_GPIOA_CLK_ENABLE();

         gpio_init_struct.Pin = GPIO_PIN_8;
        gpio_init_struct.Mode = GPIO_MODE_AF_PP; 
        gpio_init_struct.Pull = GPIO_PULLDOWN;
        gpio_init_struct.Speed = GPIO_SPEED_FREQ_HIGH ;
        HAL_GPIO_Init(GPIOA, &gpio_init_struct);

        gpio_init_struct.Pin = GPIO_PIN_13;
        HAL_GPIO_Init(GPIOB, &gpio_init_struct);

		gpio_init_struct.Mode = GPIO_MODE_INPUT;
        gpio_init_struct.Pin = GPIO_PIN_12;
        HAL_GPIO_Init(GPIOB, &gpio_init_struct);

        __HAL_RCC_AFIO_CLK_ENABLE();
        //__HAL_AFIO_REMAP_TIM1_ENABLE();
    }
}

3.设置输出比较值 & 死区时间

/**
 * @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 / 18M = 55.56ns
 * @retval      无
 */
void atim_timx_cplm_pwm_set(uint16_t ccr, uint8_t dtg)
{
    __HAL_TIM_SET_COMPARE(&g_timx_cplm_pwm_handle, TIM_CHANNEL_1, ccr);
    g_sbreak_dead_time_config.DeadTime = dtg;
    HAL_TIMEx_ConfigBreakDeadTime(&g_timx_cplm_pwm_handle, &g_sbreak_dead_time_config);
}

4.main函数

int main(void)
{
	HAL_Init();                                 /* 初始化HAL库 */
	Bsp_Led_Config();
	Bsp_Key_Config();
        sys_stm32_clock_init(RCC_PLL_MUL9);         /* 设置时钟, 72Mhz */
        delay_init(72);                             /* 延时初始化 */
        usart_init(115200);                         /* 串口初始化为115200 */
        atim_timx_cplm_pwm_init(1000 - 1, 72 - 1);
        atim_timx_cplm_pwm_set(700 - 1, 100);
        while (1)
        {
   
        }

}

PWM输入模式

工作原理

PWM 信号输入捕获通道只能使用CH1,CH2。

然后确定 IC1 和 IC2 的捕获边沿,设置 IC1 捕获边沿为上升沿捕获, IC2 捕获边沿为下降沿捕获。

选择触发输入信号(TRGI)。这里也是以通道 1(CH1)输入 PWM 为例,那么我们就应该选择 TI1FP1 为触发输入信号。如果是通道 2(CH2)输入 PWM,那就选择 TI2FP2 为触发输入信号。可以看到这里并没有对应通道 3(CH3)或者通道 4(CH4)的触发输入信号,所以我们只选择通道 1 或者通道 2 作为 PWM 输入的通道。

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

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

配置步骤

实现目标

  • 通过定时器3通道2(PB5)输出PWM
  • 将PWM输入到定时器1通道1(PA8),测量PWM的频率/周期、占空比等信息
  • 定时器1的采样时钟频率固定72MHz,Tout =((ARR+1)∗(PSC+1))/Ft
  • 72MHz采样频率( 精度约13.8ns ),PSC=0,ARR=65535,不考虑溢出情况下,测量的最长PWM周期为910.2us

相关HAL库函数介绍

函数主要寄存器主要功能
HAL_TIM_IC_Init()CR1、ARR、PSC初始化定时器基础参数
HAL_TIM_IC_MspInit()存放NVIC、CLOCK、GPIO初始化代码
HAL_TIM_IC_ConfigChannel()CCMRx、CCER配置通道映射、捕获边沿、分频、滤波等
HAL_TIM_SlaveConfigSynchro()SMCR、CCER配置从模式、触发源、触发边沿等
HAL_TIM_IC_Start_IT()CCER、DIER、CR1使能输入捕获、捕获中断并启动计数器
HAL_TIM_IRQHandler()SR定时器中断处理公用函数,处理各种中断
HAL_TIM_IC_CaptureCallback()定时器输入捕获回调函数,由用户重定义

1.配置工作参数

产生一个PWM波用于测量

TIM_HandleTypeDef g_timx_pwm_chy_handle;
/* 通用定时器PWM输出初始化函数 */
void gtim_timx_pwm_chy_init(uint16_t arr, uint16_t psc)
{
    TIM_OC_InitTypeDef timx_oc_pwm_chy;
    
    g_timx_pwm_chy_handle.Instance = TIM3;
    g_timx_pwm_chy_handle.Init.Prescaler = psc;
    g_timx_pwm_chy_handle.Init.Period = arr;
    g_timx_pwm_chy_handle.Init.CounterMode = TIM_COUNTERMODE_UP;
    HAL_TIM_PWM_Init(&g_timx_pwm_chy_handle);
    
    timx_oc_pwm_chy.OCMode = TIM_OCMODE_PWM1;
    timx_oc_pwm_chy.Pulse =1;
    timx_oc_pwm_chy.OCPolarity = TIM_OCPOLARITY_HIGH;
    HAL_TIM_PWM_ConfigChannel(&g_timx_pwm_chy_handle, &timx_oc_pwm_chy, TIM_CHANNEL_2);
    HAL_TIM_PWM_Start(&g_timx_pwm_chy_handle, TIM_CHANNEL_2);
}
/* 定时器输出PWM MSP初始化函数 */
void HAL_TIM_PWM_MspInit(TIM_HandleTypeDef *htim)
{
    if(htim->Instance == TIM3)
    {
        GPIO_InitTypeDef gpio_init_struct;
        __HAL_RCC_GPIOB_CLK_ENABLE();
        __HAL_RCC_TIM3_CLK_ENABLE();

        gpio_init_struct.Pin = GPIO_PIN_5;
        gpio_init_struct.Mode = GPIO_MODE_AF_PP;            /* 推挽复用 */
        gpio_init_struct.Pull = GPIO_PULLUP;                /* 上拉 */
        gpio_init_struct.Speed = GPIO_SPEED_FREQ_HIGH;      /* 高速 */
        HAL_GPIO_Init(GPIOB, &gpio_init_struct);
        
        __HAL_RCC_AFIO_CLK_ENABLE();
        __HAL_AFIO_REMAP_TIM3_PARTIAL();
    }
}

PWM输入模式 初始化函数

TIM_HandleTypeDef g_timx_pwmin_chy_handle;   /* 定时器x句柄 */
/* PWM输入模式 初始化函数,采样时钟频率为72Mhz,精度约13.8ns */
void atim_timx_pwmin_chy_init(void)
{
    TIM_SlaveConfigTypeDef slave_config = {0};
    TIM_IC_InitTypeDef tim_ic_pwmin_chy = {0};

    g_timx_pwmin_chy_handle.Instance = TIM1;                        /* 定时器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_TRIGGERPOLARITY_RISING;      /* 上升沿检测 */
    slave_config.TriggerFilter = 0;                                 /* 不滤波 */
    HAL_TIM_SlaveConfigSynchro(&g_timx_pwmin_chy_handle, &slave_config);

    /* IC1捕获:上升沿触发TI1FP1 */
    tim_ic_pwmin_chy.ICPolarity = TIM_ICPOLARITY_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_ICPOLARITY_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_TIM_IC_Start_IT(&g_timx_pwmin_chy_handle, TIM_CHANNEL_1);//上升沿触发捕获中断,下降沿无需触发中断
    HAL_TIM_IC_Start(&g_timx_pwmin_chy_handle, TIM_CHANNEL_2);
}

2.MSP初始化

/* 定时器 输入捕获 MSP初始化函数 */
void HAL_TIM_IC_MspInit(TIM_HandleTypeDef *htim)
{
    if(htim->Instance == TIM1)
    {
        GPIO_InitTypeDef gpio_init_struct = {0};

        __HAL_RCC_TIM1_CLK_ENABLE();
        __HAL_RCC_GPIOA_CLK_ENABLE();

        gpio_init_struct.Pin = GPIO_PIN_8;
        gpio_init_struct.Mode = GPIO_MODE_AF_PP;
        gpio_init_struct.Pull = GPIO_PULLDOWN;
        gpio_init_struct.Speed = GPIO_SPEED_FREQ_HIGH;
        HAL_GPIO_Init(GPIOC, &gpio_init_struct);
        
        /* TIM1/TIM8有独立的输入捕获中断服务函数 */
        HAL_NVIC_SetPriority(TIM1_CC_IRQn, 3, 4);
        HAL_NVIC_EnableIRQ(TIM1_CC_IRQn);
    }
}

3.中断服务函数

/* 定时器8 输入捕获 中断服务函数,仅TIM1/TIM8有这个函数,其他普通定时器没有这个中断服务函数! */
void TIM1_CC_IRQHandler(void)
{
    HAL_TIM_IRQHandler(&g_timx_pwmin_chy_handle); /* 定时器共用处理函数 */
}

4.中断处理回调函数

/* 定时器输入捕获中断处理回调函数 */
void HAL_TIM_IC_CaptureCallback(TIM_HandleTypeDef *htim)
{
    if(htim->Instance == TIM1)
    {
        if(g_timxchy_pwmin_sta == 0)
        {
            if(htim->Channel == HAL_TIM_ACTIVE_CHANNEL_1)
            {
                g_timxchy_pwmin_hval = HAL_TIM_ReadCapturedValue(&g_timx_pwmin_chy_handle, TIM_CHANNEL_2) + 1 + 1;
                g_timxchy_pwmin_cval = HAL_TIM_ReadCapturedValue(&g_timx_pwmin_chy_handle, TIM_CHANNEL_1) + 1 + 1;
                g_timxchy_pwmin_sta = 1;
            }
        }
    }
}

5.重新启动捕获

/* PWM输入模式 重新启动捕获 */
void atim_timx_pwmin_chy_restart(void)
{
//    sys_intx_disable();                     /* 关闭中断 */
	__ASM volatile("cpsid i");		 /* 关闭中断 */
    g_timxchy_pwmin_sta = 0;                /* 清零状态,重新开始检测 */
    g_timxchy_pwmin_hval=0;
    g_timxchy_pwmin_cval=0;
	__ASM volatile("cpsie i");		/* 打开中断 */
//    sys_intx_enable();                      /* 打开中断 */
}

main函数

extern uint16_t g_timxchy_pwmin_sta;    /* PWM输入状态 */
extern uint16_t g_timxchy_pwmin_psc;    /* 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库 */
	Bsp_Led_Config();
	Bsp_Key_Config();
    sys_stm32_clock_init(RCC_PLL_MUL9);         /* 设置时钟, 72Mhz */
    delay_init(72);                             /* 延时初始化 */
    usart_init(115200);                         /* 串口初始化为115200 */
	LED1_ON;
	LED2_ON;
	gtim_timx_pwm_chy_init(10 - 1, 72 - 1);
	atim_timx_pwmin_chy_init();
    while (1)
    {
		delay_ms(10);
		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) / 72;      /* 得到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输入检测 */
		} 
    }

}
  • alipay_img
  • wechat_img
想法不去做终究就只是想法
最后更新于 2023-05-25