本文共 36038 字,大约阅读时间需要 120 分钟。
本博客主要介绍F429时钟功能中的捕获功能,其程序源代码以正点原子的实验代码为依据,主要涉及以下两个方面:
本文只介绍捕获相关的内容,其余的内容在之前的博客中已经介绍,本文不能详细介绍。
由于定时器基本不涉及硬件电路,本文不再介绍硬件相关设计。
HAL库版本:
STM32Cube_FW_F4_V1.25.0
本实验的主要功能为:
通过TIM3通道4产生PWM波,且通过TIM5通道1捕获PWM波的高电平持续时间。高电平持续时间(PWM占空比)在不断的变化中。若不连接PWM波输出与捕获输入,同样,可以用来你测量按键KEY_UP按下时间。本文重新实现的代码下载以及下载链接,见博客<>。
/** * @brief TIM Input Capture Configuration Structure definition */typedef struct{ uint32_t ICPolarity; /*!< Specifies the active edge of the input signal. This parameter can be a value of @ref TIM_Input_Capture_Polarity */ uint32_t ICSelection; /*!< Specifies the input. This parameter can be a value of @ref TIM_Input_Capture_Selection */ uint32_t ICPrescaler; /*!< Specifies the Input Capture Prescaler. This parameter can be a value of @ref TIM_Input_Capture_Prescaler */ uint32_t ICFilter; /*!< Specifies the input capture filter. This parameter can be a number between Min_Data = 0x0 and Max_Data = 0xF */} TIM_IC_InitTypeDef;
/** @defgroup TIM_Input_Capture_Polarity TIM Input Capture Polarity * @{ */#define TIM_ICPOLARITY_RISING TIM_INPUTCHANNELPOLARITY_RISING /*!< Capture triggered by rising edge on timer input */#define TIM_ICPOLARITY_FALLING TIM_INPUTCHANNELPOLARITY_FALLING /*!< Capture triggered by falling edge on timer input */#define TIM_ICPOLARITY_BOTHEDGE TIM_INPUTCHANNELPOLARITY_BOTHEDGE /*!< Capture triggered by both rising and falling edges on timer input*//** * @} *//** @defgroup TIM_Input_Channel_Polarity TIM Input Channel polarity * @{ */#define TIM_INPUTCHANNELPOLARITY_RISING 0x00000000U /*!< Polarity for TIx source */#define TIM_INPUTCHANNELPOLARITY_FALLING TIM_CCER_CC1P /*!< Polarity for TIx source */#define TIM_INPUTCHANNELPOLARITY_BOTHEDGE (TIM_CCER_CC1P | TIM_CCER_CC1NP) /*!< Polarity for TIx source *//** * @} */
/** @defgroup TIM_Input_Capture_Selection TIM Input Capture Selection * @{ */#define TIM_ICSELECTION_DIRECTTI TIM_CCMR1_CC1S_0 /*!< TIM Input 1, 2, 3 or 4 is selected to be connected to IC1, IC2, IC3 or IC4, respectively */#define TIM_ICSELECTION_INDIRECTTI TIM_CCMR1_CC1S_1 /*!< TIM Input 1, 2, 3 or 4 is selected to be connected to IC2, IC1, IC4 or IC3, respectively */#define TIM_ICSELECTION_TRC TIM_CCMR1_CC1S /*!< TIM Input 1, 2, 3 or 4 is selected to be connected to TRC *//** * @} */
/** @defgroup TIM_Input_Capture_Prescaler TIM Input Capture Prescaler * @{ */#define TIM_ICPSC_DIV1 0x00000000U /*!< Capture performed each time an edge is detected on the capture input */#define TIM_ICPSC_DIV2 TIM_CCMR1_IC1PSC_0 /*!< Capture performed once every 2 events */#define TIM_ICPSC_DIV4 TIM_CCMR1_IC1PSC_1 /*!< Capture performed once every 4 events */#define TIM_ICPSC_DIV8 TIM_CCMR1_IC1PSC /*!< Capture performed once every 8 events *//** * @} */
取值范围为:[0x0-0xF]。
注意:
f D T S f_{DTS} fDTS的配置方式通过TIMx_CR1寄存器实现,在HAL库中,通过基本时钟配置实现。程序的总体架构如下:
int main(void){ long long temp = 0; HAL_Init(); //初始化HAL库 Stm32_Clock_Init(360, 25, 2, 8); //设置时钟,180Mhz delay_init(180); //初始化延时函数 uart_init(115200); //初始化USART LED_Init(); //初始化LED TIM3_PWM_Init(500 - 1, 90 - 1); //90M/90=1M的计数频率,自动重装载为500,那么PWM频率为1M/500=2kHZ TIM5_CH1_Cap_Init(0XFFFFFFFF, 90 - 1); //以1MHZ的频率计数 while (1) { delay_ms(10); TIM_SetTIM3Compare4(TIM_GetTIM3Capture4() + 1); if (TIM_GetTIM3Capture4() == 300) TIM_SetTIM3Compare4(0); if (TIM5CH1_CAPTURE_STA & 0X80) //成功捕获到了一次高电平 { temp = TIM5CH1_CAPTURE_STA & 0X3F; //获得溢出次数 temp *= 0XFFFFFFFF; //溢出时间总和 temp += TIM5CH1_CAPTURE_VAL; //得到总的高电平时间 printf("HIGH:%lld us\r\n", temp); //打印总的高点平时间 TIM5CH1_CAPTURE_STA = 0; //开启下一次捕获 } }}
在主函数中,大致分成两个部分:
在外设配置中,本文只关注TIM5_CH1_Cap_Init
的外设,其余的外设前文已经介绍过。
在while循环中,实现本实验的主要功能:
/** * @brief 时钟5初始化 * @note TIM2和TIM5是32位的 * @param {u32} arr 自动重载值 * @param {u16} psc 预分频 * @retval 无 */void TIM5_CH1_Cap_Init(u32 arr, u16 psc){ /* 1.RCC时钟使能 */ __HAL_RCC_TIM5_CLK_ENABLE(); //使能TIM5时钟 /* 2.通用时钟初始化 */ TIM5_Handler.Instance = TIM5; //通用定时器5 TIM5_Handler.Init.Prescaler = psc; //时钟预分频 TIM5_Handler.Init.CounterMode = TIM_COUNTERMODE_UP; //向上计数器 TIM5_Handler.Init.Period = arr; //自动装载值 TIM5_Handler.Init.ClockDivision = TIM_CLOCKDIVISION_DIV1; //时钟分频因子 HAL_TIM_IC_Init(&TIM5_Handler); //初始化输入捕获时基参数 /* 3.输入通道配置 */ TIM_IC_InitTypeDef TIM5_CH1Config; TIM5_CH1Config.ICPolarity = TIM_ICPOLARITY_RISING; //上升沿捕获 TIM5_CH1Config.ICSelection = TIM_ICSELECTION_DIRECTTI; //映射到TI1上 TIM5_CH1Config.ICPrescaler = TIM_ICPSC_DIV1; //配置输入分频,不分频 TIM5_CH1Config.ICFilter = 0; //配置输入滤波器,不滤波 HAL_TIM_IC_ConfigChannel(&TIM5_Handler, &TIM5_CH1Config, TIM_CHANNEL_1); //配置TIM5通道1 /* 4.启动定时器 */ HAL_TIM_IC_Start_IT(&TIM5_Handler, TIM_CHANNEL_1); //开启TIM5的捕获通道1,并且开启捕获中断 __HAL_TIM_ENABLE_IT(&TIM5_Handler, TIM_IT_UPDATE); //使能更新中断}
该程序大致分成四个部分:
该函数与PWM配置的流程基本一致,可以参考博客<>。
其中涉及到两个结构体:
TIM_HandleTypeDef
TIM_IC_InitTypeDef
其中,第一个结构体在之前博客中已经详细介绍,本文不再详细介绍,更多详细内容参考<>。
第二个结构体可以参考本文结构体部分:。
最后的宏定义为:
/** @brief Enable the specified TIM interrupt. * @param __HANDLE__ specifies the TIM Handle. * @param __INTERRUPT__ specifies the TIM interrupt source to enable. * This parameter can be one of the following values: * @arg TIM_IT_UPDATE: Update interrupt * @arg TIM_IT_CC1: Capture/Compare 1 interrupt * @arg TIM_IT_CC2: Capture/Compare 2 interrupt * @arg TIM_IT_CC3: Capture/Compare 3 interrupt * @arg TIM_IT_CC4: Capture/Compare 4 interrupt * @arg TIM_IT_COM: Commutation interrupt * @arg TIM_IT_TRIGGER: Trigger interrupt * @arg TIM_IT_BREAK: Break interrupt * @retval None */#define __HAL_TIM_ENABLE_IT(__HANDLE__, __INTERRUPT__) ((__HANDLE__)->Instance->DIER |= (__INTERRUPT__))
通过将DIER寄存器对应位置1,使能对应的中断。
/** * @brief Initializes the TIM Input Capture Time base according to the specified * parameters in the TIM_HandleTypeDef and initializes 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_IC_DeInit() before HAL_TIM_IC_Init() * @param htim TIM Input Capture handle * @retval HAL status */HAL_StatusTypeDef HAL_TIM_IC_Init(TIM_HandleTypeDef *htim){ /* 1.检测参数 */ /* Check the TIM handle allocation */ if (htim == NULL) { return HAL_ERROR; } /* Check the parameters */ assert_param(IS_TIM_INSTANCE(htim->Instance)); 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)); /* 2.底层初始化 */ 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->IC_MspInitCallback == NULL) { htim->IC_MspInitCallback = HAL_TIM_IC_MspInit; } /* Init the low level hardware : GPIO, CLOCK, NVIC */ htim->IC_MspInitCallback(htim);#else /* Init the low level hardware : GPIO, CLOCK, NVIC and DMA */ HAL_TIM_IC_MspInit(htim);#endif /* USE_HAL_TIM_REGISTER_CALLBACKS */ } /* 3.时钟的基本配置 */ /* Set the TIM state */ htim->State = HAL_TIM_STATE_BUSY; /* Init the base time for the input capture */ TIM_Base_SetConfig(htim->Instance, &htim->Init); /* Initialize the TIM state*/ htim->State = HAL_TIM_STATE_READY; return HAL_OK;}
该函数分成三个部分:
该函数与PWM波部分基本一致,可以参考博客<>。
/** * @brief 输入通道底层初始化 * @note 该函数在HAL_TIM_IC_Init函数中被调用 * @param {TIM_HandleTypeDef} *htim 时钟句柄 * @retval 无 */void HAL_TIM_IC_MspInit(TIM_HandleTypeDef *htim){ /* 1.RCC时钟使能 */ __HAL_RCC_GPIOA_CLK_ENABLE(); //开启GPIOA时钟 /* 2.GPIO配置 */ GPIO_InitTypeDef GPIO_Initure; GPIO_Initure.Pin = GPIO_PIN_0; //PIN0 GPIO_Initure.Mode = GPIO_MODE_AF_PP; //复用推挽输出 GPIO_Initure.Pull = GPIO_PULLDOWN; //下拉 GPIO_Initure.Speed = GPIO_SPEED_HIGH; //高速 GPIO_Initure.Alternate = GPIO_AF2_TIM5; //复用为TIM5 HAL_GPIO_Init(GPIOA, &GPIO_Initure); //初始化PA0 /* 3.中断配置 */ HAL_NVIC_SetPriority(TIM5_IRQn, 2, 0); //设置中断优先级,抢占优先级2,子优先级0 HAL_NVIC_EnableIRQ(TIM5_IRQn); //开启ITM5中断通道}
输入通道的底层配置,底层配置分成三个部分:
在底层配置中,最常见的就是GPIO的配置与中断的配置。
/** * @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){ /*********************1.设置CR1寄存器********************************/ uint32_t tmpcr1; //CR1临时值 tmpcr1 = TIMx->CR1; /* 1.1 设置计数模式 */ /* Set TIM Time Base Unit parameters ---------------------------------------*/ if (IS_TIM_COUNTER_MODE_SELECT_INSTANCE(TIMx)) //1,2,3,4,5,8:可以选择计数器的方向 { /* Select the Counter Mode */ tmpcr1 &= ~(TIM_CR1_DIR | TIM_CR1_CMS); //清零 tmpcr1 |= Structure->CounterMode; //设置模式,对齐方式以及计数器增减方向 } /* 1.2 设置死区发生器与采样时钟之间的分频比 */ if (IS_TIM_CLOCK_DIVISION_INSTANCE(TIMx)) //除了6,7:可以设置时钟分频 { /* Set the clock division */ tmpcr1 &= ~TIM_CR1_CKD; tmpcr1 |= (uint32_t)Structure->ClockDivision; //设置死区发生器与采样时钟之间的分频比 } /* 1.3 使能自动加载寄存器的影子寄存器 */ /* Set the auto-reload preload */ MODIFY_REG(tmpcr1, TIM_CR1_ARPE, Structure->AutoReloadPreload); //使能自动加载寄存器的影子寄存器 TIMx->CR1 = tmpcr1; /*********************2.设置ARR寄存器********************************/ /* Set the Autoreload value */ TIMx->ARR = (uint32_t)Structure->Period; //设置时钟的周期 /*********************3.设置PSC寄存器********************************/ /* Set the Prescaler value */ TIMx->PSC = Structure->Prescaler; //设置时钟的预分频 /*********************4.设置RCR寄存器********************************/ if (IS_TIM_REPETITION_COUNTER_INSTANCE(TIMx)) //1,8:支持重复计数器,高级时钟 { /* Set the Repetition Counter value */ TIMx->RCR = Structure->RepetitionCounter; //设置比较寄存器的更新频率 } /*********************5.设置EGR 寄存器********************************/ /* Generate an update event to reload the Prescaler and the repetition counter (only for advanced timer) value immediately */ TIMx->EGR = TIM_EGR_UG;}
该部分程序已经多次介绍,此处不再详细展开。
/** * @brief Initializes the TIM Input Capture Channels according to the specified * parameters in the TIM_IC_InitTypeDef. * @param htim TIM IC handle * @param sConfig TIM Input Capture configuration structure * @param Channel TIM Channel to configure * 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_3: TIM Channel 3 selected * @arg TIM_CHANNEL_4: TIM Channel 4 selected * @retval HAL status */HAL_StatusTypeDef HAL_TIM_IC_ConfigChannel(TIM_HandleTypeDef *htim, TIM_IC_InitTypeDef *sConfig, uint32_t Channel){ /* 1.预处理:参数检测,上锁,状态变化 */ /* Check the parameters */ assert_param(IS_TIM_CC1_INSTANCE(htim->Instance)); assert_param(IS_TIM_IC_POLARITY(sConfig->ICPolarity)); assert_param(IS_TIM_IC_SELECTION(sConfig->ICSelection)); assert_param(IS_TIM_IC_PRESCALER(sConfig->ICPrescaler)); assert_param(IS_TIM_IC_FILTER(sConfig->ICFilter)); /* Process Locked */ __HAL_LOCK(htim); htim->State = HAL_TIM_STATE_BUSY; /* 2.判断通道 */ if (Channel == TIM_CHANNEL_1) { /* 3.设置相关参数 */ /* TI1 Configuration */ TIM_TI1_SetConfig(htim->Instance, sConfig->ICPolarity, sConfig->ICSelection, sConfig->ICFilter); /* 4.设置预分频 */ /* Reset the IC1PSC Bits */ htim->Instance->CCMR1 &= ~TIM_CCMR1_IC1PSC; /* Set the IC1PSC value */ htim->Instance->CCMR1 |= sConfig->ICPrescaler; } else if (Channel == TIM_CHANNEL_2) { /* TI2 Configuration */ assert_param(IS_TIM_CC2_INSTANCE(htim->Instance)); TIM_TI2_SetConfig(htim->Instance, sConfig->ICPolarity, sConfig->ICSelection, sConfig->ICFilter); /* Reset the IC2PSC Bits */ htim->Instance->CCMR1 &= ~TIM_CCMR1_IC2PSC; /* Set the IC2PSC value */ htim->Instance->CCMR1 |= (sConfig->ICPrescaler << 8U); } else if (Channel == TIM_CHANNEL_3) { /* TI3 Configuration */ assert_param(IS_TIM_CC3_INSTANCE(htim->Instance)); TIM_TI3_SetConfig(htim->Instance, sConfig->ICPolarity, sConfig->ICSelection, sConfig->ICFilter); /* Reset the IC3PSC Bits */ htim->Instance->CCMR2 &= ~TIM_CCMR2_IC3PSC; /* Set the IC3PSC value */ htim->Instance->CCMR2 |= sConfig->ICPrescaler; } else { /* TI4 Configuration */ assert_param(IS_TIM_CC4_INSTANCE(htim->Instance)); TIM_TI4_SetConfig(htim->Instance, sConfig->ICPolarity, sConfig->ICSelection, sConfig->ICFilter); /* Reset the IC4PSC Bits */ htim->Instance->CCMR2 &= ~TIM_CCMR2_IC4PSC; /* Set the IC4PSC value */ htim->Instance->CCMR2 |= (sConfig->ICPrescaler << 8U); } htim->State = HAL_TIM_STATE_READY; __HAL_UNLOCK(htim); return HAL_OK;}
通过上面源程序,可以知道,该函数大致可以分成四个部分:
需要注意状态的变化为:
在函数的第三步骤设置参数中,该函数调用了函数TIM_TI1_SetConfig
,该函数详细定义如下:
/** * @brief Configure the TI1 as Input. * @param TIMx to select the TIM peripheral. * @param TIM_ICPolarity The Input Polarity. * This parameter can be one of the following values: * @arg TIM_ICPOLARITY_RISING * @arg TIM_ICPOLARITY_FALLING * @arg TIM_ICPOLARITY_BOTHEDGE * @param TIM_ICSelection specifies the input to be used. * This parameter can be one of the following values: * @arg TIM_ICSELECTION_DIRECTTI: TIM Input 1 is selected to be connected to IC1. * @arg TIM_ICSELECTION_INDIRECTTI: TIM Input 1 is selected to be connected to IC2. * @arg TIM_ICSELECTION_TRC: TIM Input 1 is selected to be connected to TRC. * @param TIM_ICFilter Specifies the Input Capture Filter. * This parameter must be a value between 0x00 and 0x0F. * @retval None * @note TIM_ICFilter and TIM_ICPolarity are not used in INDIRECT mode as TI2FP1 * (on channel2 path) is used as the input signal. Therefore CCMR1 must be * protected against un-initialized filter and polarity values. */void TIM_TI1_SetConfig(TIM_TypeDef *TIMx, uint32_t TIM_ICPolarity, uint32_t TIM_ICSelection, uint32_t TIM_ICFilter){ uint32_t tmpccmr1; uint32_t tmpccer; /* 1.关闭通道1捕获使能 */ /* Disable the Channel 1: Reset the CC1E Bit */ TIMx->CCER &= ~TIM_CCER_CC1E; tmpccmr1 = TIMx->CCMR1; tmpccer = TIMx->CCER; /* 2.设置通道的输入选择 */ /* Select the Input */ if (IS_TIM_CC2_INSTANCE(TIMx) != RESET) //至少包含2通道:1,2,3,4,5,8,9,12 { tmpccmr1 &= ~TIM_CCMR1_CC1S; tmpccmr1 |= TIM_ICSelection; } else { tmpccmr1 |= TIM_CCMR1_CC1S_0; } /* 3.设置滤波器 */ /* Set the filter */ tmpccmr1 &= ~TIM_CCMR1_IC1F; tmpccmr1 |= ((TIM_ICFilter << 4U) & TIM_CCMR1_IC1F); /* 4.设置极性 */ /* Select the Polarity and set the CC1E Bit */ tmpccer &= ~(TIM_CCER_CC1P | TIM_CCER_CC1NP); tmpccer |= (TIM_ICPolarity & (TIM_CCER_CC1P | TIM_CCER_CC1NP)); /* 5.将临时变量写入 */ /* Write to TIMx CCMR1 and CCER registers */ TIMx->CCMR1 = tmpccmr1; TIMx->CCER = tmpccer;}
此函数大致分成5个步骤:
此处需要注意两点:
/** * @brief Starts the TIM Input Capture measurement in interrupt mode. * @param htim TIM Input Capture 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_3: TIM Channel 3 selected * @arg TIM_CHANNEL_4: TIM Channel 4 selected * @retval HAL status */HAL_StatusTypeDef HAL_TIM_IC_Start_IT(TIM_HandleTypeDef *htim, uint32_t Channel){ uint32_t tmpsmcr; /* 1.检测参数 */ /* Check the parameters */ assert_param(IS_TIM_CCX_INSTANCE(htim->Instance, Channel)); /* 2.使能通道中断 */ switch (Channel) { case TIM_CHANNEL_1: { /* Enable the TIM Capture/Compare 1 interrupt */ __HAL_TIM_ENABLE_IT(htim, TIM_IT_CC1); break; } case TIM_CHANNEL_2: { /* Enable the TIM Capture/Compare 2 interrupt */ __HAL_TIM_ENABLE_IT(htim, TIM_IT_CC2); break; } case TIM_CHANNEL_3: { /* Enable the TIM Capture/Compare 3 interrupt */ __HAL_TIM_ENABLE_IT(htim, TIM_IT_CC3); break; } case TIM_CHANNEL_4: { /* Enable the TIM Capture/Compare 4 interrupt */ __HAL_TIM_ENABLE_IT(htim, TIM_IT_CC4); break; } default: break; } /* 3.使能通道,通过CCER寄存器 */ /* Enable the Input Capture channel */ TIM_CCxChannelCmd(htim->Instance, Channel, TIM_CCx_ENABLE); /* 4.若非触发模式,则使能时钟 */ /* Enable the Peripheral, except in trigger mode where enable is automatically done with trigger */ tmpsmcr = htim->Instance->SMCR & TIM_SMCR_SMS; if (!IS_TIM_SLAVEMODE_TRIGGER_ENABLED(tmpsmcr))//判断其为非触发模式 { __HAL_TIM_ENABLE(htim); } /* Return function status */ return HAL_OK;}
该函数用于启动捕获通道,共分成4个步骤:
其中使能通道这一步是该函数的关键,通过函数TIM_CCxChannelCmd
实现,该函数具体定义如下:
/** * @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; /* 1.参数检查 */ /* Check the parameters */ assert_param(IS_TIM_CC1_INSTANCE(TIMx)); assert_param(IS_TIM_CHANNELS(Channel)); /* 2.通过位移得到通道对应位数 */ tmp = TIM_CCER_CC1E << (Channel & 0x1FU); /* 0x1FU = 31 bits max shift */ /* 3.清除对应的位 */ /* Reset the CCxE Bit */ TIMx->CCER &= ~tmp; /* 4.使能通道对应的位 */ /* Set or reset the CCxE Bit */ TIMx->CCER |= (uint32_t)(ChannelState << (Channel & 0x1FU)); /* 0x1FU = 31 bits max shift */}
该函数在博客<>中已经介绍。
大致分成四个步骤:
在本实验中,关于捕获功能部分,共开了两个中断:
首先,看一下中断响应函数:
/** * @brief TIM5中断响应函数 * @note 该函数响应TIM5的中断 * @param {*}无 * @retval 无 */void TIM5_IRQHandler(void){ HAL_TIM_IRQHandler(&TIM5_Handler); //定时器中断处理函数}
该函数为TIM5的中断响应函数,当本实验中的两个中断发生时,都共同调用此函数。在此函数中,调用了HAL库中的定时器中断处理函数HAL_TIM_IRQHandler
。该函数的作用就是用于区分是控制器发生哪个中断。该函数的具体定义如下:
/** * @brief This function handles TIM interrupts requests. * @param htim TIM handle * @retval None */void HAL_TIM_IRQHandler(TIM_HandleTypeDef *htim){ /* Capture compare 1 event */ if (__HAL_TIM_GET_FLAG(htim, TIM_FLAG_CC1) != RESET) { if (__HAL_TIM_GET_IT_SOURCE(htim, TIM_IT_CC1) != RESET) { { __HAL_TIM_CLEAR_IT(htim, TIM_IT_CC1); htim->Channel = HAL_TIM_ACTIVE_CHANNEL_1; /* Input capture event */ if ((htim->Instance->CCMR1 & TIM_CCMR1_CC1S) != 0x00U) { #if (USE_HAL_TIM_REGISTER_CALLBACKS == 1) htim->IC_CaptureCallback(htim);#else HAL_TIM_IC_CaptureCallback(htim);#endif /* USE_HAL_TIM_REGISTER_CALLBACKS */ } /* Output compare event */ else { #if (USE_HAL_TIM_REGISTER_CALLBACKS == 1) htim->OC_DelayElapsedCallback(htim); htim->PWM_PulseFinishedCallback(htim);#else HAL_TIM_OC_DelayElapsedCallback(htim); HAL_TIM_PWM_PulseFinishedCallback(htim);#endif /* USE_HAL_TIM_REGISTER_CALLBACKS */ } htim->Channel = HAL_TIM_ACTIVE_CHANNEL_CLEARED; } } } /* Capture compare 2 event */ if (__HAL_TIM_GET_FLAG(htim, TIM_FLAG_CC2) != RESET) { if (__HAL_TIM_GET_IT_SOURCE(htim, TIM_IT_CC2) != RESET) { __HAL_TIM_CLEAR_IT(htim, TIM_IT_CC2); htim->Channel = HAL_TIM_ACTIVE_CHANNEL_2; /* Input capture event */ if ((htim->Instance->CCMR1 & TIM_CCMR1_CC2S) != 0x00U) { #if (USE_HAL_TIM_REGISTER_CALLBACKS == 1) htim->IC_CaptureCallback(htim);#else HAL_TIM_IC_CaptureCallback(htim);#endif /* USE_HAL_TIM_REGISTER_CALLBACKS */ } /* Output compare event */ else { #if (USE_HAL_TIM_REGISTER_CALLBACKS == 1) htim->OC_DelayElapsedCallback(htim); htim->PWM_PulseFinishedCallback(htim);#else HAL_TIM_OC_DelayElapsedCallback(htim); HAL_TIM_PWM_PulseFinishedCallback(htim);#endif /* USE_HAL_TIM_REGISTER_CALLBACKS */ } htim->Channel = HAL_TIM_ACTIVE_CHANNEL_CLEARED; } } /* Capture compare 3 event */ if (__HAL_TIM_GET_FLAG(htim, TIM_FLAG_CC3) != RESET) { if (__HAL_TIM_GET_IT_SOURCE(htim, TIM_IT_CC3) != RESET) { __HAL_TIM_CLEAR_IT(htim, TIM_IT_CC3); htim->Channel = HAL_TIM_ACTIVE_CHANNEL_3; /* Input capture event */ if ((htim->Instance->CCMR2 & TIM_CCMR2_CC3S) != 0x00U) { #if (USE_HAL_TIM_REGISTER_CALLBACKS == 1) htim->IC_CaptureCallback(htim);#else HAL_TIM_IC_CaptureCallback(htim);#endif /* USE_HAL_TIM_REGISTER_CALLBACKS */ } /* Output compare event */ else { #if (USE_HAL_TIM_REGISTER_CALLBACKS == 1) htim->OC_DelayElapsedCallback(htim); htim->PWM_PulseFinishedCallback(htim);#else HAL_TIM_OC_DelayElapsedCallback(htim); HAL_TIM_PWM_PulseFinishedCallback(htim);#endif /* USE_HAL_TIM_REGISTER_CALLBACKS */ } htim->Channel = HAL_TIM_ACTIVE_CHANNEL_CLEARED; } } /* Capture compare 4 event */ if (__HAL_TIM_GET_FLAG(htim, TIM_FLAG_CC4) != RESET) { if (__HAL_TIM_GET_IT_SOURCE(htim, TIM_IT_CC4) != RESET) { __HAL_TIM_CLEAR_IT(htim, TIM_IT_CC4); htim->Channel = HAL_TIM_ACTIVE_CHANNEL_4; /* Input capture event */ if ((htim->Instance->CCMR2 & TIM_CCMR2_CC4S) != 0x00U) { #if (USE_HAL_TIM_REGISTER_CALLBACKS == 1) htim->IC_CaptureCallback(htim);#else HAL_TIM_IC_CaptureCallback(htim);#endif /* USE_HAL_TIM_REGISTER_CALLBACKS */ } /* Output compare event */ else { #if (USE_HAL_TIM_REGISTER_CALLBACKS == 1) htim->OC_DelayElapsedCallback(htim); htim->PWM_PulseFinishedCallback(htim);#else HAL_TIM_OC_DelayElapsedCallback(htim); HAL_TIM_PWM_PulseFinishedCallback(htim);#endif /* USE_HAL_TIM_REGISTER_CALLBACKS */ } htim->Channel = HAL_TIM_ACTIVE_CHANNEL_CLEARED; } } /* TIM Update event */ if (__HAL_TIM_GET_FLAG(htim, TIM_FLAG_UPDATE) != RESET) { if (__HAL_TIM_GET_IT_SOURCE(htim, TIM_IT_UPDATE) != RESET) { __HAL_TIM_CLEAR_IT(htim, TIM_IT_UPDATE);#if (USE_HAL_TIM_REGISTER_CALLBACKS == 1) htim->PeriodElapsedCallback(htim);#else HAL_TIM_PeriodElapsedCallback(htim);#endif /* USE_HAL_TIM_REGISTER_CALLBACKS */ } } /* TIM Break input event */ if (__HAL_TIM_GET_FLAG(htim, TIM_FLAG_BREAK) != RESET) { if (__HAL_TIM_GET_IT_SOURCE(htim, TIM_IT_BREAK) != RESET) { __HAL_TIM_CLEAR_IT(htim, TIM_IT_BREAK);#if (USE_HAL_TIM_REGISTER_CALLBACKS == 1) htim->BreakCallback(htim);#else HAL_TIMEx_BreakCallback(htim);#endif /* USE_HAL_TIM_REGISTER_CALLBACKS */ } } /* TIM Trigger detection event */ if (__HAL_TIM_GET_FLAG(htim, TIM_FLAG_TRIGGER) != RESET) { if (__HAL_TIM_GET_IT_SOURCE(htim, TIM_IT_TRIGGER) != RESET) { __HAL_TIM_CLEAR_IT(htim, TIM_IT_TRIGGER);#if (USE_HAL_TIM_REGISTER_CALLBACKS == 1) htim->TriggerCallback(htim);#else HAL_TIM_TriggerCallback(htim);#endif /* USE_HAL_TIM_REGISTER_CALLBACKS */ } } /* TIM commutation event */ if (__HAL_TIM_GET_FLAG(htim, TIM_FLAG_COM) != RESET) { if (__HAL_TIM_GET_IT_SOURCE(htim, TIM_IT_COM) != RESET) { __HAL_TIM_CLEAR_IT(htim, TIM_FLAG_COM);#if (USE_HAL_TIM_REGISTER_CALLBACKS == 1) htim->CommutationCallback(htim);#else HAL_TIMEx_CommutCallback(htim);#endif /* USE_HAL_TIM_REGISTER_CALLBACKS */ } }}
该函数通过一系列的判断语句,来确定发生的中断。由于语句含义都基本类似,本文只是详细介绍与本文相关的更新中断与捕获/比较中断。
与更新中断相关语句如下:
/* TIM Update event */ if (__HAL_TIM_GET_FLAG(htim, TIM_FLAG_UPDATE) != RESET) //判断更新中断标志位是否置位 { if (__HAL_TIM_GET_IT_SOURCE(htim, TIM_IT_UPDATE) != RESET)//判断更新中断是否使能 { __HAL_TIM_CLEAR_IT(htim, TIM_IT_UPDATE);//清除中断标志#if (USE_HAL_TIM_REGISTER_CALLBACKS == 1) htim->PeriodElapsedCallback(htim);#else HAL_TIM_PeriodElapsedCallback(htim);//调用中断回调函数#endif /* USE_HAL_TIM_REGISTER_CALLBACKS */ } }
捕获/比较中断中断相关语句如下:
/* Capture compare 1 event */ if (__HAL_TIM_GET_FLAG(htim, TIM_FLAG_CC1) != RESET) //通道中断标志是否置位 { if (__HAL_TIM_GET_IT_SOURCE(htim, TIM_IT_CC1) != RESET) //通过中断是否使能 { { __HAL_TIM_CLEAR_IT(htim, TIM_IT_CC1); //清除中断标志位 htim->Channel = HAL_TIM_ACTIVE_CHANNEL_1; //设置句柄活跃通道 /* Input capture event */ if ((htim->Instance->CCMR1 & TIM_CCMR1_CC1S) != 0x00U) //通道若为输入通道 { #if (USE_HAL_TIM_REGISTER_CALLBACKS == 1) htim->IC_CaptureCallback(htim);#else HAL_TIM_IC_CaptureCallback(htim); //调用回调函数#endif /* USE_HAL_TIM_REGISTER_CALLBACKS */ } /* Output compare event */ else //通道为输出通道 { #if (USE_HAL_TIM_REGISTER_CALLBACKS == 1) htim->OC_DelayElapsedCallback(htim); htim->PWM_PulseFinishedCallback(htim);#else HAL_TIM_OC_DelayElapsedCallback(htim); HAL_TIM_PWM_PulseFinishedCallback(htim);#endif /* USE_HAL_TIM_REGISTER_CALLBACKS */ } htim->Channel = HAL_TIM_ACTIVE_CHANNEL_CLEARED; //清除句柄活跃通道 } } }
通过比较以上两段代码,可以发现中断处理函数基本一致:
个人认为,HAL库此处处理时有一定问题的,这是因为中断处理函数与中断回调函数加起来比较长。若程序中开的中断比较多,那么这里有点类似于递归函数,此处对于栈的开销比较大。
下面看中断回调函数:
/** * @brief 通道回调函数 * @note 在HAL_TIM_IRQHandler中自动调用,捕获中断发生时响应 * @param {TIM_HandleTypeDef} *htim 句柄 * @retval 无 */void HAL_TIM_IC_CaptureCallback(TIM_HandleTypeDef *htim){ if ((TIM5CH1_CAPTURE_STA & 0X80) == 0) //还未成功捕获 { if (TIM5CH1_CAPTURE_STA & 0X40) //捕获到一个下降沿 { TIM5CH1_CAPTURE_STA |= 0X80; //标记成功捕获到一次高电平脉宽 TIM5CH1_CAPTURE_VAL = HAL_TIM_ReadCapturedValue(&TIM5_Handler, TIM_CHANNEL_1); //获取当前的捕获值. TIM_RESET_CAPTUREPOLARITY(&TIM5_Handler, TIM_CHANNEL_1); //清除极性:将对应位清零 TIM_SET_CAPTUREPOLARITY(&TIM5_Handler, TIM_CHANNEL_1, TIM_ICPOLARITY_RISING); //配置TIM5通道1上升沿捕获 } else //还未开始,第一次捕获上升沿 { TIM5CH1_CAPTURE_STA = 0; //清空标记位 TIM5CH1_CAPTURE_VAL = 0; //清空数据位 TIM5CH1_CAPTURE_STA |= 0X40; //标记捕获到了上升沿 __HAL_TIM_DISABLE(&TIM5_Handler); //关闭定时器5 __HAL_TIM_SET_COUNTER(&TIM5_Handler, 0); //将计数器值清零 TIM_RESET_CAPTUREPOLARITY(&TIM5_Handler, TIM_CHANNEL_1); //清除极性:将对应位清零 TIM_SET_CAPTUREPOLARITY(&TIM5_Handler, TIM_CHANNEL_1, TIM_ICPOLARITY_FALLING); //定时器5通道1设置为下降沿捕获 __HAL_TIM_ENABLE(&TIM5_Handler); //使能定时器5 } }}
通道中断回调函数。当发生捕获事件时,会调用该函数。该函数主要功能为:测试PWM波的高电平时间。所以,首先需要在上升沿处重新启动定时器,再在下降沿处获得捕获事件。
一次完整的捕获过程如下所示:
TIM5CH1_CAPTURE_STA
为0,表明捕获还没有开始,置0工作在主函数中。TIM5CH1_CAPTURE_STA[6]
置1,表示捕获已经开始,且捕获到上升沿。TIM5CH1_CAPTURE_STA[7]
置1,表示已经捕获到下降沿,一次捕获结束。TIM5CH1_CAPTURE_STA
设置为0。此处需要注意几个宏的定义:
/** * @brief Disable the TIM peripheral. * @param __HANDLE__ TIM handle * @retval None */#define __HAL_TIM_DISABLE(__HANDLE__) \ do { \ if (((__HANDLE__)->Instance->CCER & TIM_CCER_CCxE_MASK) == 0UL) \ { \ if(((__HANDLE__)->Instance->CCER & TIM_CCER_CCxNE_MASK) == 0UL) \ { \ (__HANDLE__)->Instance->CR1 &= ~(TIM_CR1_CEN); \ } \ } \ } while(0)
该宏用于关闭时钟,通过直接写入CR1寄存器的CEN位。但是需要注意的是,有两个判断语句作为前提条件。换言之,必须与该时钟相关的通道都处于禁用状态,才可以关闭时钟。
此处,正点原子的代码我个人认为,该宏使用是有问题的。
/** * @brief Set the TIM Counter Register value on runtime. * @param __HANDLE__ TIM handle. * @param __COUNTER__ specifies the Counter register new value. * @retval None */#define __HAL_TIM_SET_COUNTER(__HANDLE__, __COUNTER__) ((__HANDLE__)->Instance->CNT = (__COUNTER__))
/** * @brief Enable the TIM peripheral. * @param __HANDLE__ TIM handle * @retval None */#define __HAL_TIM_ENABLE(__HANDLE__) ((__HANDLE__)->Instance->CR1|=(TIM_CR1_CEN))
以上两个宏作用分别为写入计数器值,以及使能计数器。两个宏没有任何条件,且直接操作寄存器,所以不再详细解释。
#define TIM_SET_CAPTUREPOLARITY(__HANDLE__, __CHANNEL__, __POLARITY__) \ (((__CHANNEL__) == TIM_CHANNEL_1) ? ((__HANDLE__)->Instance->CCER |= (__POLARITY__)) :\ ((__CHANNEL__) == TIM_CHANNEL_2) ? ((__HANDLE__)->Instance->CCER |= ((__POLARITY__) << 4U)) :\ ((__CHANNEL__) == TIM_CHANNEL_3) ? ((__HANDLE__)->Instance->CCER |= ((__POLARITY__) << 8U)) :\ ((__HANDLE__)->Instance->CCER |= (((__POLARITY__) << 12U))))#define TIM_RESET_CAPTUREPOLARITY(__HANDLE__, __CHANNEL__) \ (((__CHANNEL__) == TIM_CHANNEL_1) ? ((__HANDLE__)->Instance->CCER &= ~(TIM_CCER_CC1P | TIM_CCER_CC1NP)) :\ ((__CHANNEL__) == TIM_CHANNEL_2) ? ((__HANDLE__)->Instance->CCER &= ~(TIM_CCER_CC2P | TIM_CCER_CC2NP)) :\ ((__CHANNEL__) == TIM_CHANNEL_3) ? ((__HANDLE__)->Instance->CCER &= ~(TIM_CCER_CC3P | TIM_CCER_CC3NP)) :\ ((__HANDLE__)->Instance->CCER &= ~(TIM_CCER_CC4P | TIM_CCER_CC4NP)))
以上两个宏定义是关于捕获极性操作的。
首先,关于宏TIM_SET_CAPTUREPOLARITY
其定义是存在问题的。并不能实现随意改变捕获极性为__POLARITY__
。其设置方式是位或操作,也就是说,当参数为1的时候,设置结果肯定为1;若参数为0的时候,设置结果不一定为0。所以正点原子的代码强调在宏TIM_SET_CAPTUREPOLARITY
使用之前必须先使用宏TIM_RESET_CAPTUREPOLARITY
。 /** * @brief Read the captured value from Capture Compare unit * @param htim TIM 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_3: TIM Channel 3 selected * @arg TIM_CHANNEL_4: TIM Channel 4 selected * @retval Captured value */uint32_t HAL_TIM_ReadCapturedValue(TIM_HandleTypeDef *htim, uint32_t Channel){ uint32_t tmpreg = 0U; switch (Channel) { case TIM_CHANNEL_1: { /* Check the parameters */ assert_param(IS_TIM_CC1_INSTANCE(htim->Instance)); /* Return the capture 1 value */ tmpreg = htim->Instance->CCR1; break; } case TIM_CHANNEL_2: { /* Check the parameters */ assert_param(IS_TIM_CC2_INSTANCE(htim->Instance)); /* Return the capture 2 value */ tmpreg = htim->Instance->CCR2; break; } case TIM_CHANNEL_3: { /* Check the parameters */ assert_param(IS_TIM_CC3_INSTANCE(htim->Instance)); /* Return the capture 3 value */ tmpreg = htim->Instance->CCR3; break; } case TIM_CHANNEL_4: { /* Check the parameters */ assert_param(IS_TIM_CC4_INSTANCE(htim->Instance)); /* Return the capture 4 value */ tmpreg = htim->Instance->CCR4; break; } default: break; } return tmpreg;}
该函数比较简单,即通过通道辨别,直接获取计数器值,通过直接读取寄存器获得。
/** * @brief 更新中断回调函数 * @note 在HAL_TIM_IRQHandler中自动调用,更新中断发生时 * @param {TIM_HandleTypeDef} *htim 句柄 * @retval 无 */void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim){ if ((TIM5CH1_CAPTURE_STA & 0X80) == 0) //还未成功捕获 { if (TIM5CH1_CAPTURE_STA & 0X40) //已经捕获到高电平了 { if ((TIM5CH1_CAPTURE_STA & 0X3F) == 0X3F) //高电平太长了 { TIM5CH1_CAPTURE_STA |= 0X80; //标记成功捕获了一次 TIM5CH1_CAPTURE_VAL = 0XFFFFFFFF; } else TIM5CH1_CAPTURE_STA++; } }}
该中断回调函数在每次自动装载新值的时候触发。所以,若触发该中断,说明一次装载值记录的时间不够高电平的时间。为了准确记录时间,应该记录自动装载的此处,且将这部分时间计算进去。此中断回调函数的作用就是记录计数器自动装载的次数。
在此处,正点原子定义了两个全局变量:TIM5CH1_CAPTURE_STA
和TIM5CH1_CAPTURE_VAL
。其中,第二个变量比较简单,用于存储计数器的捕获数值。第一个变量可以分成三个部分:
TIM5CH1_CAPTURE_STA & 0X80
:用于标记一次捕获过程,即从上升沿开始到下降沿结束,一个完整的捕获过程完成,则将该值置位,表示一次捕获结束。TIM5CH1_CAPTURE_STA & 0X40
:用于标记捕获过程开始,即上升沿已经被捕获到。TIM5CH1_CAPTURE_STA & 0X3F
:计数器自动装载次数。转载地址:http://zdztz.baihongyu.com/