文章目录
定时器编程实战3:通用定时器输入捕获
硬件设计
本实验用到的硬件资源有:
1) KEY_UP 按键
2) 串口
3) 定时器 TIM2
前面 2 个,在之前的实验均有介绍。本实验,我们将捕获 TIM2_CH1(PA0)上的高电平脉
宽,通过 KEY_UP 按键输入高电平,并从串口打印高电平脉宽。
本实验要用到串口,所以在串口实验的工程文件上进行更改。
通用定时器输入捕获脉宽测量原理
以捕获测量高电平脉宽为例
假设:递增计数模式
ARR:自动重装载寄存器的值
CCRx1:t1时间点CCRx的值
CCRx2:t2时间点CCRx的值
CubeMX配置
在 TIMERS->TIM2 配置项中,配置 Channel1 的值为 Input Capture direct mode,然后选中Internal Clock。如下图
进入 Configuration->Parameter Setting 配置项,Counter Settings 配置栏下面的五个选项就是用来配置定时器的预分频系数,自动装载值,计数模式,时钟分频因子。在界面的 Input Capture Channel 1 配置栏配置输入捕获通道 1 的捕获极性,分频系数,映射,滤波器等参数,如下图
进入 System Core->NVIC 配置页中,在弹出的界面中点击 NVIC 选项卡,配置 Intertupe Table 中的 TIM2 global interrupt,使能中断,配置抢占优先级和相应优先级。如下图
同时右边的芯片引脚图会自动配置好PA0,默认情况下不需要用户更改配置。
进行完上面的操作之后,接下来我们便是生成工程代码。
通用定时器输入捕获实验配置步骤
1.配置定时器基础工作参数
这里的代码为CubeMX配置。
//tim.c文件
TIM_HandleTypeDef htim2;
void MX_TIM2_Init(void)
{
TIM_ClockConfigTypeDef sClockSourceConfig = {0}; //时钟源结构体
TIM_MasterConfigTypeDef sMasterConfig = {0}; //主模式结构体
TIM_IC_InitTypeDef sConfigIC = {0}; //输出捕获结构体
htim2.Instance = TIM2; //通用定时器2
htim2.Init.Prescaler = 95; //分频系数
htim2.Init.CounterMode = TIM_COUNTERMODE_UP; //向上计数器
htim2.Init.Period = 0XFFFFFFFF; //自动装载值
htim2.Init.ClockDivision = TIM_CLOCKDIVISION_DIV1;//时钟分频因子
htim2.Init.AutoReloadPreload = TIM_AUTORELOAD_PRELOAD_DISABLE;//失能自动装载值预装载
if (HAL_TIM_Base_Init(&htim2) != HAL_OK)
{
Error_Handler();
}
sClockSourceConfig.ClockSource = TIM_CLOCKSOURCE_INTERNAL; //内部时钟
if (HAL_TIM_ConfigClockSource(&htim2, &sClockSourceConfig) != HAL_OK)
{
Error_Handler();
}
if (HAL_TIM_IC_Init(&htim2) != HAL_OK) //初始化输入捕获时基参数
{
Error_Handler();
}
sMasterConfig.MasterOutputTrigger = TIM_TRGO_RESET;
sMasterConfig.MasterSlaveMode = TIM_MASTERSLAVEMODE_DISABLE;
if (HAL_TIMEx_MasterConfigSynchronization(&htim2, &sMasterConfig) != HAL_OK)
{
Error_Handler();
}
sConfigIC.ICPolarity = TIM_INPUTCHANNELPOLARITY_RISING; //上升沿捕获
sConfigIC.ICSelection = TIM_ICSELECTION_DIRECTTI; //映射到TI1上
sConfigIC.ICPrescaler = TIM_ICPSC_DIV1; //配置输入分频,不分频
sConfigIC.ICFilter = 0; //配置输入滤波器,不滤波
if (HAL_TIM_IC_ConfigChannel(&htim2, &sConfigIC, TIM_CHANNEL_1) != HAL_OK)//配置TIM2通道1
{
Error_Handler();
}
}
2.定时器输入捕获MSP初始化
这里的代码为CubeMX配置。
//tim.c
void HAL_TIM_Base_MspInit(TIM_HandleTypeDef* tim_baseHandle)
{
GPIO_InitTypeDef GPIO_InitStruct = {0};
if(tim_baseHandle->Instance==TIM2)
{
/* TIM2 clock enable */
__HAL_RCC_TIM2_CLK_ENABLE();
__HAL_RCC_GPIOA_CLK_ENABLE();
/**TIM2 GPIO Configuration
PA0-WKUP ------> TIM2_CH1
*/
GPIO_InitStruct.Pin = GPIO_PIN_0;
GPIO_InitStruct.Mode = GPIO_MODE_AF_PP;
GPIO_InitStruct.Pull = GPIO_PULLDOWN;
GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_LOW;
GPIO_InitStruct.Alternate = GPIO_AF1_TIM2;
HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);
/* TIM2 interrupt Init */
HAL_NVIC_SetPriority(TIM2_IRQn, 2, 0);
HAL_NVIC_EnableIRQ(TIM2_IRQn);
}
}
3.使能定时器更新中断及捕获、捕获中断及计数器
//tim.c
void MX_TIM2_Init(void)
{
/* MX CODE BEGIN */
//。。。。。。。。
/* MX CODE END */
/* USER CODE BEGIN */
HAL_TIM_IC_Start_IT(&htim2,TIM_CHANNEL_1);
__HAL_TIM_ENABLE_IT(&htim2,TIM_IT_UPDATE);
/* USER CODE END */
}
4.编写中断服务函数
这里的TIM2_IRQHandler函数代码用CubeMX配置,会在stm32f4xx_it.c文件中生成。
若后续不用cubemx配置工程,可将这段代码剪切至tim.c文件中。若后续还用cubemx配置工程,则要将部分变量extern表明在其他stm32f4xx_it.c文件中。
//tim.c
void TIM2_IRQHandler(void)
{
HAL_TIM_IRQHandler(&TIM2_Handler); //定时器共用处理函数
}
5.编写更新中断和捕获回调函数
//tim.c
/* USER CODE BEGIN */
uint8_t g_timxchy_cap_sta = 0; /* 输入捕获状态 */
uint16_t g_timxchy_cap_val = 0; /* 输入捕获值 */
/* 定时器输入捕获中断处理回调函数 */
void HAL_TIM_IC_CaptureCallback(TIM_HandleTypeDef *htim)
{
if (htim->Instance == TIM2)
{
if ((g_timxchy_cap_sta & 0X80) == 0) /* 还没有成功捕获 */
{
if (g_timxchy_cap_sta & 0X40) /* 捕获到一个下降沿 */
{
g_timxchy_cap_sta |= 0X80; /* 标记成功捕获到一次高电平脉宽 */
g_timxchy_cap_val = HAL_TIM_ReadCapturedValue(&htim2, TIM_CHANNEL_1); /* 获取当前的捕获值 */
TIM_RESET_CAPTUREPOLARITY(&htim2, TIM_CHANNEL_1); /* 一定要先清除原来的设置 */
TIM_SET_CAPTUREPOLARITY(&htim2, TIM_CHANNEL_1, TIM_ICPOLARITY_RISING); /* 配置TIM5通道1上升沿捕获 */
}
else /* 还未开始,第一次捕获上升沿 */
{
g_timxchy_cap_sta = 0; /* 清空 */
g_timxchy_cap_val = 0;
g_timxchy_cap_sta |= 0X40; /* 标记捕获到了上升沿 */
__HAL_TIM_DISABLE(&htim2); /* 关闭定时器5 */
__HAL_TIM_SET_COUNTER(&htim2, 0); /* 定时器5计数器清零 */
TIM_RESET_CAPTUREPOLARITY(&htim2, TIM_CHANNEL_1); /* 一定要先清除原来的设置!! */
TIM_SET_CAPTUREPOLARITY(&htim2, TIM_CHANNEL_1, TIM_ICPOLARITY_FALLING); /* 定时器5通道1设置为下降沿捕获 */
__HAL_TIM_ENABLE(&htim2); /* 使能定时器5 */
}
}
}
}
/* 定时器更新中断回调函数 */
void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim)
{
if (htim->Instance == TIM2)
{
if ((g_timxchy_cap_sta & 0X80) == 0) /* 还未成功捕获 */
{
if (g_timxchy_cap_sta & 0X40) /* 已经捕获到高电平了 */
{
if ((g_timxchy_cap_sta & 0X3F) == 0X3F) /* 高电平太长了 */
{
TIM_RESET_CAPTUREPOLARITY(&htim2, TIM_CHANNEL_1); /* 一定要先清除原来的设置 */
TIM_SET_CAPTUREPOLARITY(&htim2, TIM_CHANNEL_1, TIM_ICPOLARITY_RISING);/* 配置TIM5通道1上升沿捕获 */
g_timxchy_cap_sta |= 0X80; /* 标记成功捕获了一次 */
g_timxchy_cap_val = 0XFFFF;
}
else /* 累计定时器溢出次数 */
{
g_timxchy_cap_sta++;
}
}
}
}
}
/* USER CODE END */
6.串口数据发送
使用printf函数需要在工程中添加官方给的SYSTEM文件加中的sys,delay,usart文件才能正常使用。
//main.c
/* USER CODE BEGIN */
extern uint8_t g_timxchy_cap_sta; /* 输入捕获状态 */
extern uint16_t g_timxchy_cap_val; /* 输入捕获值 */
/* USER CODE END */
int main(void)
{
/* USER CODE BEGIN */
uint32_t temp = 0;
/* USER CODE END */
HAL_Init();
SystemClock_Config();
MX_GPIO_Init();
MX_USART1_UART_Init();
MX_TIM2_Init();
while (1)
{
/* USER CODE BEGIN */
if (g_timxchy_cap_sta & 0X80) /* 成功捕获到了一次高电平 */
{
temp = g_timxchy_cap_sta & 0X3F;
temp *= 0XFFFFFFFF; /* 溢出时间总和 */
temp += g_timxchy_cap_val; /* 得到总的高电平时间 */
printf("HIGH:%lld us\r\n",temp);//打印总的高点平时间
g_timxchy_cap_sta = 0; /* 开启下一次捕获*/
}
HAL_GPIO_TogglePin(GPIOC, GPIO_PIN_0);
HAL_Delay(200);
/* USER CODE END */
}
}