STM32 CubeMX TIMx编码器模式中断踩坑记录及HAL_TIM_Encoder_Start和_IT函数解析
目录
问题的开始
今天在用STM32F103VET6实现定时器TIM2编码器功能的时候,预采用中断查询方式对于编码器计数值进行查询,发现程序不能进入中断。工程使用CubeMX生成初始化代码。Main函数中是这样写的:
MX_GPIO_Init();
MX_TIM2_Init();
MX_USART1_UART_Init();
/* USER CODE BEGIN 2 */
printf("ready!");
HAL_TIM_Encoder_Start(&htim2,TIM_CHANNEL_3);/* USER CODE END 2 */
在此,已经确认开启了TIM2中断的NVIC通道,但函数执行没法进入TIM2中断。原因是应该使用:
HAL_TIM_Encoder_Start_IT(&htim2,TIM_CHANNEL_ALL);
函数来作为定时器编码器功能开启函数。一般来说,HAL库函数中,函数末尾加入_IT的函数才会触发中断,没有加_IT的函数往往没有中断功能,而常常是使用轮询方式使用。像类似的函数组还有:HAL_UART_Transmit();HAL_UART_Transmit_IT();等等。
至于为什么HAL_TIM_Encoder_Start_IT函数可以触发中断而HAL_TIM_Encoder_Start函数没有触发中断的功能,还是要看一下HAL库中关于这两个函数的定义有什么不同。其实这两个函数前半部分区别不大,有区别的主要是以下部分(请注意红色部分):
首先是IT函数:
………………
/* Enable the encoder interface channels */
/* Enable the capture compare Interrupts 1 and/or 2 */
switch (Channel)
{
case TIM_CHANNEL_1:
{
TIM_CCxChannelCmd(htim->Instance, TIM_CHANNEL_1, TIM_CCx_ENABLE);
__HAL_TIM_ENABLE_IT(htim, TIM_IT_CC1);
break;
}case TIM_CHANNEL_2:
{
TIM_CCxChannelCmd(htim->Instance, TIM_CHANNEL_2, TIM_CCx_ENABLE);
__HAL_TIM_ENABLE_IT(htim, TIM_IT_CC2);
break;
}default :
{
TIM_CCxChannelCmd(htim->Instance, TIM_CHANNEL_1, TIM_CCx_ENABLE);
TIM_CCxChannelCmd(htim->Instance, TIM_CHANNEL_2, TIM_CCx_ENABLE);
__HAL_TIM_ENABLE_IT(htim, TIM_IT_CC1);
__HAL_TIM_ENABLE_IT(htim, TIM_IT_CC2);
break;
}
}/* Enable the Peripheral */
__HAL_TIM_ENABLE(htim);/* Return function status */
return HAL_OK;
之后是轮询函数:
/* Enable the encoder interface channels */
switch (Channel)
{
case TIM_CHANNEL_1:
{
TIM_CCxChannelCmd(htim->Instance, TIM_CHANNEL_1, TIM_CCx_ENABLE);
break;
}case TIM_CHANNEL_2:
{
TIM_CCxChannelCmd(htim->Instance, TIM_CHANNEL_2, TIM_CCx_ENABLE);
break;
}default :
{
TIM_CCxChannelCmd(htim->Instance, TIM_CHANNEL_1, TIM_CCx_ENABLE);
TIM_CCxChannelCmd(htim->Instance, TIM_CHANNEL_2, TIM_CCx_ENABLE);
break;
}
}
/* Enable the Peripheral */
__HAL_TIM_ENABLE(htim);/* Return function status */
return HAL_OK;
很明显_IT函数比轮询函数多了个开启中断的功能。如果在调用轮询之后将中断打开,也可以得到正确的结果,所以以下上下两段代码的功能其实是一样的。
// 开启TIM2编码器模式(注意,这里用的是中断方式)
HAL_TIM_Encoder_Start_IT(&htim2,TIM_CHANNEL_ALL);
// 开启TIM2编码器模式(注意,这里用的是轮询方式)
HAL_TIM_Encoder_Start(&htim2,TIM_CHANNEL_ALL);
__HAL_TIM_ENABLE_IT(&htim2,TIM_IT_CC1);
__HAL_TIM_ENABLE_IT(&htim2,TIM_IT_CC2);
额……既然都看到这里了,不如把这个函数的底层看了吧……
HAL_TIM_Encoder_Start及其_IT函数解析
这里以HAL_TIM_Encoder_Start_IT为例(其实前面都差不多看懂一个就能看懂另一个)
HAL_StatusTypeDef HAL_TIM_Encoder_Start_IT(TIM_HandleTypeDef *htim, uint32_t Channel)
{
HAL_TIM_ChannelStateTypeDef channel_1_state = TIM_CHANNEL_STATE_GET(htim, TIM_CHANNEL_1);// 拿到htim定时器的Channel1通道状态
HAL_TIM_ChannelStateTypeDef channel_2_state = TIM_CHANNEL_STATE_GET(htim, TIM_CHANNEL_2);// 拿到htim定时器的Channel2通道状态
HAL_TIM_ChannelStateTypeDef complementary_channel_1_state = TIM_CHANNEL_N_STATE_GET(htim, TIM_CHANNEL_1);// 拿到htim定时器的Channel1互补通道状态
HAL_TIM_ChannelStateTypeDef complementary_channel_2_state = TIM_CHANNEL_N_STATE_GET(htim, TIM_CHANNEL_2);// 拿到htim定时器的Channel2互补通道状态
…………
这里出现了TIM_CHANNEL_STATE_GET()和TIM_CHANNEL_N_STATE_GET(),这两个什么意思呢,跳转一下看到:
#define TIM_CHANNEL_STATE_GET(__HANDLE__, __CHANNEL__)\
(((__CHANNEL__) == TIM_CHANNEL_1) ? (__HANDLE__)->ChannelState[0] :\
((__CHANNEL__) == TIM_CHANNEL_2) ? (__HANDLE__)->ChannelState[1] :\
((__CHANNEL__) == TIM_CHANNEL_3) ? (__HANDLE__)->ChannelState[2] :\
(__HANDLE__)->ChannelState[3])
#define TIM_CHANNEL_N_STATE_GET(__HANDLE__, __CHANNEL__)\
(((__CHANNEL__) == TIM_CHANNEL_1) ? (__HANDLE__)->ChannelNState[0] :\
((__CHANNEL__) == TIM_CHANNEL_2) ? (__HANDLE__)->ChannelNState[1] :\
((__CHANNEL__) == TIM_CHANNEL_3) ? (__HANDLE__)->ChannelNState[2] :\
(__HANDLE__)->ChannelNState[3])
这里出现__HANDLE__里的成员ChannelState[]数组,跳转看一下TIM总控句柄里面都有什么,然后看到这一段代码:
TIM_TypeDef *Instance; /*!< Register base address*/
TIM_Base_InitTypeDef Init; /*!< TIM Time Base required parameters*/
HAL_TIM_ActiveChannel Channel; /*!< Active channel*/
DMA_HandleTypeDef *hdma[7]; /*!< DMA Handlers array */
HAL_LockTypeDef Lock; /*!< Locking object*/
__IO HAL_TIM_StateTypeDef State; /*!< TIM operation state*/
__IO HAL_TIM_ChannelStateTypeDef ChannelState[4];/*!< TIM channel operation state*/
__IO HAL_TIM_ChannelStateTypeDef ChannelNState[4];/*!< TIM complementary channel operation state*/
__IO HAL_TIM_DMABurstStateTypeDef DMABurstState;/*!< DMA burst operation state */
…………
可以看见里面有个__IO HAL_TIM_ChannelStateTypeDef ChannelState[4]; 以及 __IO HAL_TIM_ChannelStateTypeDef ChannelNState[4]; 一个通用或者高级定时器有四个捕获比较通道及其互补通道,这个存储的其实是这四个通道及其互补通道的占用状态,这个状态的取值可以跳转到HAL_TIM_DMABurstStateTypeDef枚举中查看,可以看到有释放、就绪、占用三种状态:
所以这两个很长的宏定义这里想表达的意思其实是,传入句柄__HANDLE__,待查询 __CHANNEL__,然后类似一问一答的模式:
你想查询TIM_CHANNEL_1吗?是的,返回句柄__HANDLE__中的ChannelState[0](也就是通道1的占用情况):不是 你想查询TIM_CHANNEL_2吗?是的,返回句柄__HANDLE__中的ChannelState[1](也就是通道2的占用情况):不是 你想查询TIM_CHANNEL_3吗?是的,返回句柄__HANDLE__中的ChannelState[2](也就是通道3的占用情况):不是 返回句柄__HANDLE__中的ChannelState[3](也就是通道4的占用情况)
这部分就比较好看了,校验总控结构体实例正确性后,就是判断用于编码器模式的通道Channel1以及Channel2占用情况,如果被占用,就返回设置错误的信息;如果通道空闲,那么编码器模式将占用他们,函数其占用情况设置为被占用。
/* Check the parameters */
assert_param(IS_TIM_ENCODER_INTERFACE_INSTANCE(htim->Instance));//校验总控结构体实例正确性
/* Set the TIM channel(s) state */
if (Channel == TIM_CHANNEL_1)
{
if ((channel_1_state != HAL_TIM_CHANNEL_STATE_READY)
|| (complementary_channel_1_state != HAL_TIM_CHANNEL_STATE_READY))
{
return HAL_ERROR;
}
else
{
TIM_CHANNEL_STATE_SET(htim, TIM_CHANNEL_1, HAL_TIM_CHANNEL_STATE_BUSY);
TIM_CHANNEL_N_STATE_SET(htim, TIM_CHANNEL_1, HAL_TIM_CHANNEL_STATE_BUSY);
}
}
else if (Channel == TIM_CHANNEL_2)
{
if ((channel_2_state != HAL_TIM_CHANNEL_STATE_READY)
|| (complementary_channel_2_state != HAL_TIM_CHANNEL_STATE_READY))
{
return HAL_ERROR;
}
else
{
TIM_CHANNEL_STATE_SET(htim, TIM_CHANNEL_2, HAL_TIM_CHANNEL_STATE_BUSY);
TIM_CHANNEL_N_STATE_SET(htim, TIM_CHANNEL_2, HAL_TIM_CHANNEL_STATE_BUSY);
}
}
else
{
if ((channel_1_state != HAL_TIM_CHANNEL_STATE_READY)
|| (channel_2_state != HAL_TIM_CHANNEL_STATE_READY)
|| (complementary_channel_1_state != HAL_TIM_CHANNEL_STATE_READY)
|| (complementary_channel_2_state != HAL_TIM_CHANNEL_STATE_READY))
{
return HAL_ERROR;
}
else
{
TIM_CHANNEL_STATE_SET(htim, TIM_CHANNEL_1, HAL_TIM_CHANNEL_STATE_BUSY);
TIM_CHANNEL_STATE_SET(htim, TIM_CHANNEL_2, HAL_TIM_CHANNEL_STATE_BUSY);
TIM_CHANNEL_N_STATE_SET(htim, TIM_CHANNEL_1, HAL_TIM_CHANNEL_STATE_BUSY);
TIM_CHANNEL_N_STATE_SET(htim, TIM_CHANNEL_2, HAL_TIM_CHANNEL_STATE_BUSY);
}
}
最后这一部分就是到底层寄存器的设置操作了。这个 TIM_CCxChannelCmd(TIM_TypeDef *TIMx, uint32_t Channel, uint32_t ChannelState)函数可以跳转一下,根据手册寄存器验证其正确性。最后就是因为这个函数是使用中断方式打开的编码器,所以在最后使用宏__HAL_TIM_ENABLE_IT开启了中断,再到最后用宏__HAL_TIM_ENABLE使能了定时器,返回设置成功的状态信息。
/* Enable the encoder interface channels */
/* Enable the capture compare Interrupts 1 and/or 2 */
switch (Channel)
{
case TIM_CHANNEL_1:
{
TIM_CCxChannelCmd(htim->Instance, TIM_CHANNEL_1, TIM_CCx_ENABLE);
__HAL_TIM_ENABLE_IT(htim, TIM_IT_CC1);
break;
}
case TIM_CHANNEL_2:
{
TIM_CCxChannelCmd(htim->Instance, TIM_CHANNEL_2, TIM_CCx_ENABLE);
__HAL_TIM_ENABLE_IT(htim, TIM_IT_CC2);
break;
}
default :
{
TIM_CCxChannelCmd(htim->Instance, TIM_CHANNEL_1, TIM_CCx_ENABLE);
TIM_CCxChannelCmd(htim->Instance, TIM_CHANNEL_2, TIM_CCx_ENABLE);
__HAL_TIM_ENABLE_IT(htim, TIM_IT_CC1);
__HAL_TIM_ENABLE_IT(htim, TIM_IT_CC2);
break;
}
}
/* Enable the Peripheral */
__HAL_TIM_ENABLE(htim);
/* Return function status */
return HAL_OK;
}
一个比较好玩的事情
一个比较好玩的事情是我发现虽然ST对这两个个函数的简介说Channel这个参数取值有三:
但是这两个函数里面都有这样的一段代码:
switch (Channel)
{
case TIM_CHANNEL_1:
{
TIM_CCxChannelCmd(htim->Instance, TIM_CHANNEL_1, TIM_CCx_ENABLE);
break;
}case TIM_CHANNEL_2:
{
TIM_CCxChannelCmd(htim->Instance, TIM_CHANNEL_2, TIM_CCx_ENABLE);
break;
}default :
{
TIM_CCxChannelCmd(htim->Instance, TIM_CHANNEL_1, TIM_CCx_ENABLE);
TIM_CCxChannelCmd(htim->Instance, TIM_CHANNEL_2, TIM_CCx_ENABLE);
break;
}
}
这个switch结构没有对Channel这个参数进行参数验证,这就是说只要我不传入Channel1以及Channel2,其他传进去任何参数都可以设置编码器双通道边沿检测模式,即如下代码段可以使得程序逻辑成立。
HAL_TIM_Encoder_Start_IT(&htim2,1651);//当然也可以是其他什么的任何值