上一篇文章讲到调整关键代码的位置可以解决MCU编码器对应引脚电平始终为低而导致的读不到脉冲的问题,本篇文章深入探究其背后原因。
出错时代码被放置在了void HAL_TIM_Encoder_MspInit(TIM_HandleTypeDef* tim_encoderHandle)中,而正确的时候代码则是被放置在了void MX_TIM3_Init(void)和void MX_TIM5_Init(void)函数的最后。代码是同样的代码,功能是相同的,相同的代码最终由于放置位置的差异导致了功能上的天壤之别。这就很明显地是一个顺序问题。
先来看各个代码的调用顺序,以Tim3为例。MX_TIM3_Init函数源码如下:
/* TIM3 init function */
void MX_TIM3_Init(void)
{
TIM_Encoder_InitTypeDef sConfig = {0};
TIM_MasterConfigTypeDef sMasterConfig = {0};
htim3.Instance = TIM3;
htim3.Init.Prescaler = 0;
htim3.Init.CounterMode = TIM_COUNTERMODE_UP;
htim3.Init.Period = 65535;
htim3.Init.ClockDivision = TIM_CLOCKDIVISION_DIV1;
htim3.Init.AutoReloadPreload = TIM_AUTORELOAD_PRELOAD_DISABLE;
sConfig.EncoderMode = TIM_ENCODERMODE_TI12;
sConfig.IC1Polarity = TIM_ICPOLARITY_RISING;
sConfig.IC1Selection = TIM_ICSELECTION_DIRECTTI;
sConfig.IC1Prescaler = TIM_ICPSC_DIV1;
sConfig.IC1Filter = 0;
sConfig.IC2Polarity = TIM_ICPOLARITY_FALLING;
sConfig.IC2Selection = TIM_ICSELECTION_DIRECTTI;
sConfig.IC2Prescaler = TIM_ICPSC_DIV1;
sConfig.IC2Filter = 0;
if (HAL_TIM_Encoder_Init(&htim3, &sConfig) != HAL_OK)
{
Error_Handler();
}
sMasterConfig.MasterOutputTrigger = TIM_TRGO_RESET;
sMasterConfig.MasterSlaveMode = TIM_MASTERSLAVEMODE_DISABLE;
if (HAL_TIMEx_MasterConfigSynchronization(&htim3, &sMasterConfig) != HAL_OK)
{
Error_Handler();
}
}
MX_TIM3_Init函数中调用了HAL_TIM_Encoder_Init函数,该函数在Drivers\STM32F4xx_HAL_Driver\Src\stm32f4xx_hal_tim.c中:
/**
* @brief Initializes the TIM Encoder Interface and initialize the associated handle.
* @note Switching from Center Aligned counter mode to Edge counter mode (or reverse)
* requires a timer reset to avoid unexpected direction
* due to DIR bit readonly in center aligned mode.
* Ex: call @ref HAL_TIM_Encoder_DeInit() before HAL_TIM_Encoder_Init()
* @note Encoder mode and External clock mode 2 are not compatible and must not be selected together
* Ex: A call for @ref HAL_TIM_Encoder_Init will erase the settings of @ref HAL_TIM_ConfigClockSource
* using TIM_CLOCKSOURCE_ETRMODE2 and vice versa
* @param htim TIM Encoder Interface handle
* @param sConfig TIM Encoder Interface configuration structure
* @retval HAL status
*/
HAL_StatusTypeDef HAL_TIM_Encoder_Init(TIM_HandleTypeDef *htim, TIM_Encoder_InitTypeDef *sConfig)
{
uint32_t tmpsmcr;
uint32_t tmpccmr1;
uint32_t tmpccer;
/* Check the TIM handle allocation */
if (htim == NULL)
{
return HAL_ERROR;
}
/* Check the parameters */
assert_param(IS_TIM_COUNTER_MODE(htim->Init.CounterMode));
assert_param(IS_TIM_CLOCKDIVISION_DIV(htim->Init.ClockDivision));
assert_param(IS_TIM_AUTORELOAD_PRELOAD(htim->Init.AutoReloadPreload));
assert_param(IS_TIM_CC2_INSTANCE(htim->Instance));
assert_param(IS_TIM_ENCODER_MODE(sConfig->EncoderMode));
assert_param(IS_TIM_IC_SELECTION(sConfig->IC1Selection));
assert_param(IS_TIM_IC_SELECTION(sConfig->IC2Selection));
assert_param(IS_TIM_ENCODERINPUT_POLARITY(sConfig->IC1Polarity));
assert_param(IS_TIM_ENCODERINPUT_POLARITY(sConfig->IC2Polarity));
assert_param(IS_TIM_IC_PRESCALER(sConfig->IC1Prescaler));
assert_param(IS_TIM_IC_PRESCALER(sConfig->IC2Prescaler));
assert_param(IS_TIM_IC_FILTER(sConfig->IC1Filter));
assert_param(IS_TIM_IC_FILTER(sConfig->IC2Filter));
if (htim->State == HAL_TIM_STATE_RESET)
{
/* Allocate lock resource and initialize it */
htim->Lock = HAL_UNLOCKED;
#if (USE_HAL_TIM_REGISTER_CALLBACKS == 1)
/* Reset interrupt callbacks to legacy weak callbacks */
TIM_ResetCallback(htim);
if (htim->Encoder_MspInitCallback == NULL)
{
htim->Encoder_MspInitCallback = HAL_TIM_Encoder_MspInit;
}
/* Init the low level hardware : GPIO, CLOCK, NVIC */
htim->Encoder_MspInitCallback(htim);
#else
/* Init the low level hardware : GPIO, CLOCK, NVIC and DMA */
HAL_TIM_Encoder_MspInit(htim);
#endif /* USE_HAL_TIM_REGISTER_CALLBACKS */
}
/* Set the TIM state */
htim->State = HAL_TIM_STATE_BUSY;
/* Reset the SMS and ECE bits */
htim->Instance->SMCR &= ~(TIM_SMCR_SMS | TIM_SMCR_ECE);
/* Configure the Time base in the Encoder Mode */
TIM_Base_SetConfig(htim->Instance, &htim->Init);
/* Get the TIMx SMCR register value */
tmpsmcr = htim->Instance->SMCR;
/* Get the TIMx CCMR1 register value */
tmpccmr1 = htim->Instance->CCMR1;
/* Get the TIMx CCER register value */
tmpccer = htim->Instance->CCER;
/* Set the encoder Mode */
tmpsmcr |= sConfig->EncoderMode;
/* Select the Capture Compare 1 and the Capture Compare 2 as input */
tmpccmr1 &= ~(TIM_CCMR1_CC1S | TIM_CCMR1_CC2S);
tmpccmr1 |= (sConfig->IC1Selection | (sConfig->IC2Selection << 8U));
/* Set the Capture Compare 1 and the Capture Compare 2 prescalers and filters */
tmpccmr1 &= ~(TIM_CCMR1_IC1PSC | TIM_CCMR1_IC2PSC);
tmpccmr1 &= ~(TIM_CCMR1_IC1F | TIM_CCMR1_IC2F);
tmpccmr1 |= sConfig->IC1Prescaler | (sConfig->IC2Prescaler << 8U);
tmpccmr1 |= (sConfig->IC1Filter << 4U) | (sConfig->IC2Filter << 12U);
/* Set the TI1 and the TI2 Polarities */
tmpccer &= ~(TIM_CCER_CC1P | TIM_CCER_CC2P);
tmpccer &= ~(TIM_CCER_CC1NP | TIM_CCER_CC2NP);
tmpccer |= sConfig->IC1Polarity | (sConfig->IC2Polarity << 4U);
/* Write to TIMx SMCR */
htim->Instance->SMCR = tmpsmcr;
/* Write to TIMx CCMR1 */
htim->Instance->CCMR1 = tmpccmr1;
/* Write to TIMx CCER */
htim->Instance->CCER = tmpccer;
/* Initialize the TIM state*/
htim->State = HAL_TIM_STATE_READY;
return HAL_OK;
}
可以看到,HAL_TIM_Encoder_Init函数中调用了HAL_TIM_Encoder_MspInit函数。这个HAL_TIM_Encoder_MspInit函数正是错误的情况下关键代码放置的位置,源码如下:
void HAL_TIM_Encoder_MspInit(TIM_HandleTypeDef* tim_encoderHandle)
{
GPIO_InitTypeDef GPIO_InitStruct = {0};
if(tim_encoderHandle->Instance==TIM3)
{
/* USER CODE BEGIN TIM3_MspInit 0 */
/* USER CODE END TIM3_MspInit 0 */
/* TIM3 clock enable */
__HAL_RCC_TIM3_CLK_ENABLE();
__HAL_RCC_GPIOA_CLK_ENABLE();
/**TIM3 GPIO Configuration
PA6 ------> TIM3_CH1
PA7 ------> TIM3_CH2
*/
GPIO_InitStruct.Pin = GPIO_PIN_6|GPIO_PIN_7;
GPIO_InitStruct.Mode = GPIO_MODE_AF_PP;
GPIO_InitStruct.Pull = GPIO_NOPULL;
GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_LOW;
GPIO_InitStruct.Alternate = GPIO_AF2_TIM3;
HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);
/* TIM3 interrupt Init */
HAL_NVIC_SetPriority(TIM3_IRQn, 0, 0);
HAL_NVIC_EnableIRQ(TIM3_IRQn);
/* USER CODE BEGIN TIM3_MspInit 1 */
#if 0
//清零计数器
__HAL_TIM_SET_COUNTER(&htim3, 0);
//清零中断标志位
__HAL_TIM_CLEAR_IT(&htim3, TIM_IT_UPDATE);
//使能定时器的更新事件中断
__HAL_TIM_ENABLE_IT(&htim3, TIM_IT_UPDATE);
//设置更新事件请求源为:计数器溢出
__HAL_TIM_URS_ENABLE(&htim3);
//设置中断优先级
HAL_NVIC_SetPriority(TIM3_IRQn, 5, 0);
//使能定时器中断
HAL_NVIC_EnableIRQ(TIM3_IRQn);
//使能编码器接口
HAL_TIM_Encoder_Start(&htim3, TIM_CHANNEL_ALL);
#endif
/* USER CODE END TIM3_MspInit 1 */
}
……
}
可以看到,如果将关键代码(上面代码中#if 0和#endif中间的内容)放在这里,则先使能了编码器接口,而后经过了HAL_TIM_Encoder_Init函数中下边的一大段初始化函数:
/* Set the TIM state */
htim->State = HAL_TIM_STATE_BUSY;
/* Reset the SMS and ECE bits */
htim->Instance->SMCR &= ~(TIM_SMCR_SMS | TIM_SMCR_ECE);
/* Configure the Time base in the Encoder Mode */
TIM_Base_SetConfig(htim->Instance, &htim->Init);
/* Get the TIMx SMCR register value */
tmpsmcr = htim->Instance->SMCR;
/* Get the TIMx CCMR1 register value */
tmpccmr1 = htim->Instance->CCMR1;
/* Get the TIMx CCER register value */
tmpccer = htim->Instance->CCER;
/* Set the encoder Mode */
tmpsmcr |= sConfig->EncoderMode;
/* Select the Capture Compare 1 and the Capture Compare 2 as input */
tmpccmr1 &= ~(TIM_CCMR1_CC1S | TIM_CCMR1_CC2S);
tmpccmr1 |= (sConfig->IC1Selection | (sConfig->IC2Selection << 8U));
/* Set the Capture Compare 1 and the Capture Compare 2 prescalers and filters */
tmpccmr1 &= ~(TIM_CCMR1_IC1PSC | TIM_CCMR1_IC2PSC);
tmpccmr1 &= ~(TIM_CCMR1_IC1F | TIM_CCMR1_IC2F);
tmpccmr1 |= sConfig->IC1Prescaler | (sConfig->IC2Prescaler << 8U);
tmpccmr1 |= (sConfig->IC1Filter << 4U) | (sConfig->IC2Filter << 12U);
/* Set the TI1 and the TI2 Polarities */
tmpccer &= ~(TIM_CCER_CC1P | TIM_CCER_CC2P);
tmpccer &= ~(TIM_CCER_CC1NP | TIM_CCER_CC2NP);
tmpccer |= sConfig->IC1Polarity | (sConfig->IC2Polarity << 4U);
/* Write to TIMx SMCR */
htim->Instance->SMCR = tmpsmcr;
/* Write to TIMx CCMR1 */
htim->Instance->CCMR1 = tmpccmr1;
/* Write to TIMx CCER */
htim->Instance->CCER = tmpccer;
/* Initialize the TIM state*/
htim->State = HAL_TIM_STATE_READY;
这段代码具体有何作用,是否会重置已经启动的编码器计数动作设置,就是这个问题的关键所在!
仔细分析一下各个代码的实际功能。
1.?HAL_TIM_Encoder_Start
HAL_TIM_Encoder_Start函数在Drivers\STM32F4xx_HAL_Driver\Src\stm32f4xx_hal_tim.c中,源码如下:
/**
* @brief Starts the TIM Encoder Interface.
* @param htim TIM Encoder Interface handle
* @param Channel TIM Channels to be enabled
* This parameter can be one of the following values:
* @arg TIM_CHANNEL_1: TIM Channel 1 selected
* @arg TIM_CHANNEL_2: TIM Channel 2 selected
* @arg TIM_CHANNEL_ALL: TIM Channel 1 and TIM Channel 2 are selected
* @retval HAL status
*/
HAL_StatusTypeDef HAL_TIM_Encoder_Start(TIM_HandleTypeDef *htim, uint32_t Channel)
{
/* Check the parameters */
assert_param(IS_TIM_CC2_INSTANCE(htim->Instance));
/* 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;
}
1.1?TIM_CCxChannelCmd函数
同样在Drivers\STM32F4xx_HAL_Driver\Src\stm32f4xx_hal_tim.c中,源码如下:
/**
* @brief Enables or disables the TIM Capture Compare Channel x.
* @param TIMx to select the TIM peripheral
* @param Channel specifies the TIM Channel
* This parameter can be one of the following values:
* @arg TIM_CHANNEL_1: TIM Channel 1
* @arg TIM_CHANNEL_2: TIM Channel 2
* @arg TIM_CHANNEL_3: TIM Channel 3
* @arg TIM_CHANNEL_4: TIM Channel 4
* @param ChannelState specifies the TIM Channel CCxE bit new state.
* This parameter can be: TIM_CCx_ENABLE or TIM_CCx_DISABLE.
* @retval None
*/
void TIM_CCxChannelCmd(TIM_TypeDef *TIMx, uint32_t Channel, uint32_t ChannelState)
{
uint32_t tmp;
/* Check the parameters */
assert_param(IS_TIM_CC1_INSTANCE(TIMx));
assert_param(IS_TIM_CHANNELS(Channel));
tmp = TIM_CCER_CC1E << (Channel & 0x1FU); /* 0x1FU = 31 bits max shift */
/* Reset the CCxE Bit */
TIMx->CCER &= ~tmp;
/* Set or reset the CCxE Bit */
TIMx->CCER |= (uint32_t)(ChannelState << (Channel & 0x1FU)); /* 0x1FU = 31 bits max shift */
}
这个函数的功能是使能/禁止定时器的捕获比较通道。涉及到CCER寄存器,手册中对应的资料为:
?
1.2?__HAL_TIM_ENABLE函数
__HAL_TIM_ENABLE()在Drivers\STM32F4xx_HAL_Driver\Inc\stm32f4xx_hal_tim.h中,实际上是宏定义:
/**
* @brief Enable the TIM peripheral.
* @param __HANDLE__ TIM handle
* @retval None
*/
#define __HAL_TIM_ENABLE(__HANDLE__) ((__HANDLE__)->Instance->CR1|=(TIM_CR1_CEN))
这个函数的功能是使能定时器外设。涉及到CR1寄存器,手册中对应的资料为:
?
2.??SMCR寄存器
/* Reset the SMS and ECE bits */ ? htim->Instance->SMCR &= ~(TIM_SMCR_SMS | TIM_SMCR_ECE);
SMCR寄存器在手册中对应的资料为:
?
3.?TIM_Base_SetConfig
/* Configure the Time base in the Encoder Mode */ ? TIM_Base_SetConfig(htim->Instance, &htim->Init);
TIM_Base_SetConfig函数同样在Drivers\STM32F4xx_HAL_Driver\Src\stm32f4xx_hal_tim.c中,源码如下:
/**
* @brief Time Base configuration
* @param TIMx TIM peripheral
* @param Structure TIM Base configuration structure
* @retval None
*/
void TIM_Base_SetConfig(TIM_TypeDef *TIMx, TIM_Base_InitTypeDef *Structure)
{
uint32_t tmpcr1;
tmpcr1 = TIMx->CR1;
/* Set TIM Time Base Unit parameters ---------------------------------------*/
if (IS_TIM_COUNTER_MODE_SELECT_INSTANCE(TIMx))
{
/* Select the Counter Mode */
tmpcr1 &= ~(TIM_CR1_DIR | TIM_CR1_CMS);
tmpcr1 |= Structure->CounterMode;
}
if (IS_TIM_CLOCK_DIVISION_INSTANCE(TIMx))
{
/* Set the clock division */
tmpcr1 &= ~TIM_CR1_CKD;
tmpcr1 |= (uint32_t)Structure->ClockDivision;
}
/* Set the auto-reload preload */
MODIFY_REG(tmpcr1, TIM_CR1_ARPE, Structure->AutoReloadPreload);
TIMx->CR1 = tmpcr1;
/* Set the Autoreload value */
TIMx->ARR = (uint32_t)Structure->Period ;
/* Set the Prescaler value */
TIMx->PSC = Structure->Prescaler;
if (IS_TIM_REPETITION_COUNTER_INSTANCE(TIMx))
{
/* Set the Repetition Counter value */
TIMx->RCR = Structure->RepetitionCounter;
}
/* Generate an update event to reload the Prescaler
and the repetition counter (only for advanced timer) value immediately */
TIMx->EGR = TIM_EGR_UG;
}
3.1??IS_TIM_COUNTER_MODE_SELECT_INSTANCE
IS_TIM_COUNTER_MODE_SELECT_INSTANCE宏定义在Drivers\CMSIS\Device\ST\STM32F4xx\Include\stm32f405xx.h中:
/****************** TIM Instances : supporting counting mode selection ********/
#define IS_TIM_COUNTER_MODE_SELECT_INSTANCE(INSTANCE) (((INSTANCE) == TIM1) || \
((INSTANCE) == TIM2) || \
((INSTANCE) == TIM3) || \
((INSTANCE) == TIM4) || \
((INSTANCE) == TIM5) || \
((INSTANCE) == TIM8))
3.2?IS_TIM_CLOCK_DIVISION_INSTANCE宏定义同样在Drivers\CMSIS\Device\ST\STM32F4xx\Include\stm32f405xx.h中:
/****************** TIM Instances : supporting clock division *****************/
#define IS_TIM_CLOCK_DIVISION_INSTANCE(INSTANCE) (((INSTANCE) == TIM1) || \
((INSTANCE) == TIM2) || \
((INSTANCE) == TIM3) || \
((INSTANCE) == TIM4) || \
((INSTANCE) == TIM5) || \
((INSTANCE) == TIM8) || \
((INSTANCE) == TIM9) || \
((INSTANCE) == TIM10)|| \
((INSTANCE) == TIM11)|| \
((INSTANCE) == TIM12)|| \
((INSTANCE) == TIM13)|| \
((INSTANCE) == TIM14))
|