本博客主要介绍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 */
/**
* @}
*/
注意:
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
其中,第一个结构体在之前博客中已经详细介绍,本文不再详细介绍,更多详细内容参考
第二个结构体可以参考本文结构体部分: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
:计数器自动装载次数。