对于速度信号,脉冲信号的检测使用外部中断的方法效率极低还影响程序的时效性,STM32的定时器功能强大,满足常见的测速计数需求(高级定时器、通用定时器),纯硬件操作,不用CPU的参与,效率高,测试精准,本文介绍STM32H743定时器测速的基本配置。
编码器测速
编码器信号与计数器方向和计数位置之间的关系,如下表
这个表格将编码器接口所有可能出现的工作情况全都列了出来,包括它是如何实现方向检测和
倍频的。虽然信息很全面但是乍看上去却不容易看懂。首先需要解释一下,表中的 TI1 和 TI2 对
应编码器的通道 A 和通道 B,而 TI1FP1 和 TI2FP2 则对应反相以后的 TI1、 TI2。 STM32 的编码器
接口在计数的时候,并不是单纯采集某一通道信号的上升沿或下降沿,而是需要综合另一个通道
信号的电平。表中“相反信号的电平”指的就是在计数的时候所参考的另一个通道信号的电平,
这些电平决定了计数器的计数方向。
为了便于大家理解 STM32 编码器接口的计数原理,我们将表中的信息提出转换成一系列图像。
首先看下图,下图所展示的信息对应表格中“仅在 TI1 处计数”。图中包含 TI1、 TI2 两通道的信
号,以及计数器的计数方向,其中 TI1 比 TI2 提前 1/4 个周期,以 TI1 的信号边沿作为有效边沿。
当检测到 TI1 的上升沿时, TI2 为低电平,此时计数器向上计数 1 次,下一时刻检测到 TI1 的下
降沿时, TI2 为高电平,此时计数器仍然向上计数一次,以此类推。这样就能把 TI1 的上升沿和
下降沿都用来计数,即实现了对原始信号的 2 倍频。
接下来看如下图像,图中同样包含 TI1、 TI2 两通道的信号,以及计数器的计数方向,其中 TI1 比
TI2 滞后 1/4 个周期,以 TI1 的信号边沿作为有效边沿。当检测到 TI1 的上升沿时, TI2 为高电平,
此时计数器向下计数 1 次,下一时刻检测到 TI1 的下降沿时, TI2 为低电平,此时计数器仍然向
下计数一次,以此类推。这样同样是把 TI1 的上升沿和下降沿都用来计数,同样实现了对原始信
号的 2 倍频,只不过变成向下计数了。
以上两幅图像都是只以 TI1 的信号边沿作为有效边沿,并且根据 TI2 的电平决定各自的计数方
向,然后判断计数方向就能得到编码器的旋转方向,向上计数正向,向下计数反向。“仅在 TI2
处计数”也是同样的原理,在这里就不重复讲了。
最后如下图所示,下图所展示的信息对应表格中“在 TI1 和 TI2 处均计数”。这种采样方式可以
把两个通道的上升沿和下降沿都用来计数,计数方向也是两个通道同时参考,相当于原来仅在一
个通道处计数的 2 倍,所以这种就能实现对原始信号的 4 倍频。
TIM_HandleTypeDef TIM2_EncoderHandle;
//定时器2 编码器初始化 速度慢不使用中断 记录数据溢出
void TIM2_ENCODR_Init()
{
GPIO_InitTypeDef GPIO_InitStruct;
TIM_Encoder_InitTypeDef Encoder_ConfigStructure;
/* 定时器通道引脚端口时钟使能 */
__HAL_RCC_GPIOB_CLK_ENABLE();
__HAL_RCC_GPIOA_CLK_ENABLE();
/* 使能编码器接口时钟 */
__HAL_RCC_TIM2_CLK_ENABLE();
/**TIM2 GPIO Configuration
PA15 ------> TIM2_CH1
PB3 ------> TIM2_CH2
*/
/* 设置输入类型 */
GPIO_InitStruct.Mode = GPIO_MODE_AF_PP;
/* 设置上拉 */
GPIO_InitStruct.Pull = GPIO_PULLUP;
/* 设置引脚速率 */
GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_HIGH;
/* 选择要控制的GPIO引脚 */
GPIO_InitStruct.Pin = GPIO_PIN_15;
/* 设置复用 */
GPIO_InitStruct.Alternate = GPIO_AF1_TIM2;
/* 调用库函数,使用上面配置的GPIO_InitStructure初始化GPIO */
HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);
/* 选择要控制的GPIO引脚 */
GPIO_InitStruct.Pin = GPIO_PIN_3;
/* 设置复用 */
GPIO_InitStruct.Alternate = GPIO_AF1_TIM2;
/* 调用库函数,使用上面配置的GPIO_InitStructure初始化GPIO */
HAL_GPIO_Init(GPIOB, &GPIO_InitStruct);
/* 定时器初始化设置 */
TIM2_EncoderHandle.Instance = TIM2;
TIM2_EncoderHandle.Init.Prescaler = 0; //预分频器
TIM2_EncoderHandle.Init.CounterMode = TIM_COUNTERMODE_UP; //向上计数
TIM2_EncoderHandle.Init.Period = ENCODER_TIM_PERIOD; //自动重装载值
TIM2_EncoderHandle.Init.ClockDivision = TIM_CLOCKDIVISION_DIV1; //时钟不分频
TIM2_EncoderHandle.Init.AutoReloadPreload = TIM_AUTORELOAD_PRELOAD_DISABLE; //加载不缓冲
/* 设置编码器倍频数 */
Encoder_ConfigStructure.EncoderMode = TIM_ENCODERMODE_TI12; //4倍频
/* 编码器接口通道1设置 */
Encoder_ConfigStructure.IC1Polarity = TIM_ICPOLARITY_RISING; //TIM_ICPOLARITY_BOTHEDGE //极性
Encoder_ConfigStructure.IC1Selection = TIM_ICSELECTION_DIRECTTI;
Encoder_ConfigStructure.IC1Prescaler = TIM_ICPSC_DIV1;
Encoder_ConfigStructure.IC1Filter = 10;
/* 编码器接口通道2设置 */
Encoder_ConfigStructure.IC2Polarity = TIM_ICPOLARITY_RISING;
Encoder_ConfigStructure.IC2Selection = TIM_ICSELECTION_DIRECTTI;
Encoder_ConfigStructure.IC2Prescaler = TIM_ICPSC_DIV1;
Encoder_ConfigStructure.IC2Filter = 10;
/* 初始化编码器接口 */
HAL_TIM_Encoder_Init(&TIM2_EncoderHandle, &Encoder_ConfigStructure);
/* 清零计数器 */
__HAL_TIM_SET_COUNTER(&TIM2_EncoderHandle, 0);
/* 清零中断标志位 */
// __HAL_TIM_CLEAR_IT(&TIM3_EncoderHandle,TIM_IT_UPDATE);
/* 使能定时器的更新事件中断 */
// __HAL_TIM_ENABLE_IT(&TIM3_EncoderHandle,TIM_IT_UPDATE);
/* 设置更新事件请求源为:计数器溢出 */
// __HAL_TIM_URS_ENABLE(&TIM3_EncoderHandle);
/* 设置中断优先级 */
// HAL_NVIC_SetPriority(TIM3_IRQn, 10, 1);
/* 使能定时器中断 */
// HAL_NVIC_EnableIRQ(TIM3_IRQn);
TIM2->CNT = 0x7fff;
/* 使能编码器接口 */
HAL_TIM_Encoder_Start(&TIM2_EncoderHandle, TIM_CHANNEL_ALL);
}
读取计数器的数据
s16 getTIMx_DetaCnt(TIM_TypeDef * TIMx)
{
s16 cnt;
cnt = TIMx->CNT-0x7fff;
TIMx->CNT = 0x7fff;
return cnt;
}
通过定时器固定周期查看即可。 这里对编码器的脉冲数进行了4倍频
使用定时器ETR引脚对外部脉冲计数
如果使用单脉冲的霍尔传感器或者是对传感的输出信号进行计数的时候就适合使用定时器的外部时钟输入
高级定时器 和通用定时器都有指定的ETR 时钟输入引脚,可以通过CubeMX进行配置 以定时器2为例
//定时2配置 外部ETR输入口检测脉冲数量
//PA15
void TIM2_ETR_Init(void)
{
GPIO_InitTypeDef GPIO_InitStruct;
TIM_ClockConfigTypeDef sClockSourceConfig;
TIM_MasterConfigTypeDef sMasterConfig;
TIM_HandleTypeDef htim2;
__HAL_RCC_TIM2_CLK_ENABLE();
__HAL_RCC_GPIOA_CLK_ENABLE();
GPIO_InitStruct.Pin = GPIO_PIN_15;
GPIO_InitStruct.Mode = GPIO_MODE_AF_PP;
GPIO_InitStruct.Pull = GPIO_NOPULL;
GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_LOW;
GPIO_InitStruct.Alternate = GPIO_AF1_TIM2;
HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);
htim2.Instance = TIM2;
htim2.Init.Prescaler = 0; //预分频器
htim2.Init.CounterMode = TIM_COUNTERMODE_UP; //TIM向上计数模式
htim2.Init.Period = 0XFFFFFFFF; //设定计数器自动重装值
htim2.Init.ClockDivision = TIM_CLOCKDIVISION_DIV1; //设置时钟分割:TDTS = Tck_tim
htim2.Init.AutoReloadPreload = TIM_AUTORELOAD_PRELOAD_DISABLE; //加载不缓冲
if (HAL_TIM_Base_Init(&htim2) != HAL_OK)
{
Error_Handler();
}
sClockSourceConfig.ClockSource = TIM_CLOCKSOURCE_ETRMODE2;
sClockSourceConfig.ClockPolarity = TIM_CLOCKPOLARITY_NONINVERTED;
sClockSourceConfig.ClockPrescaler = TIM_CLOCKPRESCALER_DIV1;
sClockSourceConfig.ClockFilter = 0;
if (HAL_TIM_ConfigClockSource(&htim2, &sClockSourceConfig) != HAL_OK)
{
Error_Handler();
}
sMasterConfig.MasterOutputTrigger = TIM_TRGO_RESET;
sMasterConfig.MasterSlaveMode = TIM_MASTERSLAVEMODE_DISABLE;
if (HAL_TIMEx_MasterConfigSynchronization(&htim2, &sMasterConfig) != HAL_OK)
{
Error_Handler();
}
/* 清零计数器 */
__HAL_TIM_SET_COUNTER(&htim2, 0);
HAL_TIM_Base_Start(&htim2);
}
读取计数值
s16 GET_TIMx_DetaCnt(TIM_TypeDef * TIMx)
{
s16 cnt;
cnt = TIMx->CNT;
TIMx->CNT = 0;
return cnt;
}
定时器的ETR引脚是固定的在实际应用的时候会存在引脚被占用的情况。
使用定器的输入捕获通道1、2作为时钟灵活方便,引脚可以随意复用。
外部信号模式1——脉冲计数模式
使用定时器的TI1FP1 或者TI2FP2 信号
// 定时器4 配置成 外部信号触发脉冲计数模式
void TIM4_External_Clock_Init(void)
{
GPIO_InitTypeDef GPIO_InitStruct;
TIM_SlaveConfigTypeDef sSlaveConfig;
TIM_MasterConfigTypeDef sMasterConfig;
TIM_HandleTypeDef TIM4_Handler;
__HAL_RCC_TIM4_CLK_ENABLE();
__HAL_RCC_GPIOD_CLK_ENABLE();
GPIO_InitStruct.Pin = GPIO_PIN_13;
GPIO_InitStruct.Mode = GPIO_MODE_AF_PP;
GPIO_InitStruct.Pull = GPIO_NOPULL;
GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_LOW;
GPIO_InitStruct.Alternate = GPIO_AF2_TIM4;
HAL_GPIO_Init(GPIOD, &GPIO_InitStruct);
TIM4_Handler.Instance = TIM4;
TIM4_Handler.Init.Prescaler = 0;
TIM4_Handler.Init.CounterMode = TIM_COUNTERMODE_UP;
TIM4_Handler.Init.Period = 65535;
TIM4_Handler.Init.ClockDivision = TIM_CLOCKDIVISION_DIV1;
TIM4_Handler.Init.AutoReloadPreload = TIM_AUTORELOAD_PRELOAD_DISABLE;
if (HAL_TIM_Base_Init(&TIM4_Handler) != HAL_OK)
{
Error_Handler();
}
sSlaveConfig.SlaveMode = TIM_SLAVEMODE_EXTERNAL1;
sSlaveConfig.InputTrigger = TIM_TS_TI2FP2;
sSlaveConfig.TriggerPolarity = TIM_TRIGGERPOLARITY_RISING;
sSlaveConfig.TriggerFilter = 0;
if (HAL_TIM_SlaveConfigSynchro(&TIM4_Handler, &sSlaveConfig) != HAL_OK)
{
Error_Handler();
}
sMasterConfig.MasterOutputTrigger = TIM_TRGO_RESET;
sMasterConfig.MasterSlaveMode = TIM_MASTERSLAVEMODE_DISABLE;
if (HAL_TIMEx_MasterConfigSynchronization(&TIM4_Handler, &sMasterConfig) != HAL_OK)
{
Error_Handler();
}
/* 清零计数器 */
__HAL_TIM_SET_COUNTER(&TIM4_Handler, 0);
//开启计数模式
HAL_TIM_Base_Start(&TIM4_Handler);
}
可以注意到定时器的初始数据的同,查看数据手册
通用不同的通用定时器也存在一定的差异。