定时器定时原理
使用精准的时基,通过硬件的方式,实现定时功能
介绍
定时器核心就是计数器
STM32定时器特性表
定时器计数模式及溢出条件
计数器模式 | 溢出条件 |
递增计数模式 | 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 框图给大家解答。
3.外部时钟模式 2
时钟源进入定时器的流程如下:外部时钟源信号→IO→TIMx_ETR。从 IO 到 TIMx_ETR,就需要我们配置 IO 的复用功能,才能使IO 和定时器相连通。
时钟源信号来到定时器 TIMx_ETR 后,需要经过什么“关卡”才能到达计数器作为计数的时钟频率的,下面通过外部时钟模式 2 框图给大家解答。
可以看到在外部时钟模式 2 下,定时器时钟信号首先从 ETR 引脚进来。接着经过外部触发极性选择器,由ETP 位来设置上升沿有效还是下降沿有效,选择下降沿有效的话,信号会经过反相器。
4.内部触发输入
内部触发输入是使用一个定时器作为另一个定时器的预分频器,即实现定时器的级联。下面以 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,还有其他的选择,详细描述如下图所示:
中断配置步骤
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,是用来记录捕获状态, (这个变量,我们把它当成一个寄存器那样来使用)。对其各位赋予状态含义,描述如下表所示:
bit7 | bit6 | bit5~0 |
捕获完成标志 | 捕获到高电平标志 | 捕获高电平后定时器溢出的次数 |
变量 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
2, 判断DTG[7:5],选择计算公式
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输入检测 */
}
}
}
Comments NOTHING