STM32的HAL库开发-通用定时器输入捕获实验
一、通用定时器输入捕获部分框图介绍
1、捕获/比较通道的输入部分(通道1)
首先设置 TIM_CCMR1的CC1S[1:0]位,设置成01,那么IC1来自于TI1,也就是说连接到TI1FP1上边。设置成10,那个IC1来自于TI2,连接到TI2FP1上。设置成11,将IC1连接到TRC上边。
假设IC1连接到TI1FP1上,那么TIMx_CH1作为输入,首先来到一个滤波器,滤波器需要设置TIMx_CCMR1的ICF[3:0]位设置滤波方式,配合着通过设置TIMx_CR寄存器的CKD[1:0]位,设置采样频率fCKD。这个在前面通用定时器讲过。
然后输入信号来到边沿检测器,通过设置TIMx_CCER的CC1P位设置边沿检测方式。设置之后上升沿或者下降沿只有一个信号能进来,然后经过一个捕获分频器,通过设置TIMx_CCMR1的IC1PS[1:0]位,设置捕获分频系数。
最后使能TIMx_CCER的CC1E位,信号就来到了捕获/比较寄存器了。
2、捕获/比较通道1的主电路---输入部分
首先CC1S需要设置成输入模式,同时将CC1E设置为1,使能捕获。图中IC1PS信号来自于前面的分频后的捕获信号。 经过一个与门结果为1,上面的或门至少有一个为1,结果为1,与门为1那么经过或门还是1,下面的那个CC1G信号为软件产生捕获事件。这样就会产生一个捕获事件,计数器的值就会转移到捕获/比较影子寄存器里边。
捕获/比较影子寄存器不能直接读,需要将值转移到捕获/比较寄存器里边,那么需要capture_transfer条件。需要与门的两个信号都是1,CC1S信号一定是1,上面那个信号是raed操作,也就是说在读CCR1寄存器的时候不能转移数据。
例子:捕获信号一个脉冲信号
假如有一个脉冲信号,首先配置上升沿捕获,当上升沿来的时候,经过前面的滤波器分频器来到IC1PS,然后产生捕获事件,计数器的值转移到捕获/比较寄存器里边,然后程序读取CCR1的值即为CNT1,马上将输入设置为下降沿触发,然后再读取CCR1寄存器的值,设置为CNT2,利用CNT2的值减去CNT1的值,得到计数的个数,乘上一个数的事件,就得到了脉冲事件。
二、通用定时器输入捕获脉宽测量原理
首先将输入捕获通道配置成上升沿触发,对应图中就是在t1时刻产生上升沿,此实计数器的值会转移到捕获比较寄存器里,记为CCRx1。然后同是将计数器的值设置为0。之后有两种发情况,一种就是高电平时间非常短,在计时器一个周期里边下降沿就来了,那么计数个数就是t2时刻对应的CCRx2的值。第二种情况是在t1时刻与t2时刻之间,计数器发生了N次溢出事件,那么总共的计数器个数就是(ARR + 1)*N + CRRx2。计时器记一个数的时间乘上总共记得个数,就是高电平总共时间。时钟频率PCLK除分频系数PSC,就是计数器得工作频率,再取倒数,就是记一个数得时间。
三、通用定时器输入捕获实验配置步骤
1、HAL_TIM_IC_init()函数,配置定时器基础工作参数。,与base_init()函数一样
2、HAL_TIM_IC_MspInit()函数,配置NVIC、CLOCK、GPIO等。
3、HAL_TIM_IC_ConfigChannel()函数,配置输入通道映射、捕获边沿等。
4、HAL_NVIC_SetPriority()、HAL_NVIC_EnablelRQ()函数,设置优先级,使能中断。
5、__HAL_TIM_ENABLE IT()宏定义,使能定时器更新中断。
6、HAL_TIM_IC_Start_IT()函数,使能捕获、捕获中断及计数器。
7、TIMx IROHandler()->HAL TIM IROHandler(),中断服务函数。
8、HAL_TIM_PeriodElapsedCallback()函数,更新中断回调函数
HAL_TIM_IC_CaptureCallback()函数,输入捕获中断回调函数。
四、通用定时器输入捕获实验
实验:通过定时器5通道1来捕获按键高电平脉宽时间,通过串口打印出来
1MHz频率测量精度高,计数器计一个数就是1us。ARR的值随便设置,设置小的话溢出次数多。
1、使用寄存器配置输入捕获
#include "./BSP/TIMER/TIM_IC.h"
void TIM_IC_Init(void)
{
//开启定时器5时钟
RCC->APB1ENR |= (1 << 3);
//开启ARR寄存器缓冲功能
TIM5->CR1 |= (1 << 7);
//设置计数器向上计数模式
TIM5->CR1 &= ~(1 << 4);
//设置输入滤波 IC1F 0000
TIM5->CCMR1 &= ~(0X0F << 4);
//设置输入分频 IC1PSC 00 不分频
TIM5->CCMR1 &= ~(0X03 << 2);
//设置CC1S为输入模式 将IC1映射到通道一 01
TIM5->CCMR1 |= (1 << 0);
TIM5->CCMR1 &= ~(1 << 1);
//设置CC1P 上升沿捕获
TIM5->CCER &= ~(1 << 1);
//设置分频系数PSC
TIM5->PSC = 71;
//设置ARR值为999 计数器溢出一次时间为0.1s
TIM5->ARR = 999;
//开启定时器3捕获比较通道1的捕获中断请求
TIM5->DIER |= (1 << 1);
//开启定时器三更新中断
TIM5->DIER |= (1 << 0);
//开启GPIOA时钟
RCC->APB2ENR |= (1 << 2);
//设置PA0为输入模式
GPIOA ->CRL &= ~(0X03 << 0);
//设置PA0为输入浮空
GPIOA->CRL |= (1 << 2);
GPIOA->CRL &= ~(1 << 3);
//使能定时器3中断
HAL_NVIC_SetPriority(TIM5_IRQn, 2, 2);
HAL_NVIC_EnableIRQ(TIM5_IRQn);
TIM5->SR = 0;
//使能计数器 CEN位
TIM5->CR1 |= (1<< 0);
//开启输入捕获 CC1E位
TIM5->CCER |= (1 << 0);
}
uint16_t IC_Value = 0;//定义输入捕获寄存器的值
uint8_t rising = 0;//定义捕获到上升沿 1为已经捕获到上升沿
uint8_t flag = 0;//捕获高电平完成
uint16_t yichu = 0;
void TIM5_IRQHandler(void)
{
//判断计数器溢出中断
if(TIM5->SR & (1 << 0))
{
TIM5->SR &= ~(1 << 0);
yichu ++;
}
//这里注意标志位一定要进中断立马清除 不要放在if里边
//因为如果条件不能成立 那么标志位没有清除 他会一直进中断 要不然就是使用库函数 捕获出现这种情况
//清空SR输入捕获事件标志位
if(flag == 0 && TIM5->SR & (1 << 1))
{
//还没有上升沿
if(rising == 0 )
{
//清空计数器值
TIM5->CNT = 0;
//将输入捕获设置为下降沿
TIM5->CCER |= (1 << 1);
rising = 1;//捕获到上升沿
yichu = 0;
}
else
{
//读取计数器得值
IC_Value = TIM5->CCR1;
//将输入捕获设置为上降沿
TIM5->CCER &= ~(1 << 1);
rising = 0;
flag = 1;
IC_Value = 1000*yichu + IC_Value;
}
}
TIM5->SR &= ~(1 << 1);
}
在配置过程中,最开始将SR中断标志位清除写在了if判断里边,导致条件一直不满足,就一直进入中断,主函数里边的程序也一直被中断卡着。
注意:这个程序捕获的额第一次数据不准确,目前还没有找到问题在哪,怀疑是第一次上升沿触发有问题。从第二次捕获开始,数据准确。
2、使用库函数配置输入捕获
tim_inCapture.h头文件
#ifndef __TIM_INCAPTURE_H
#define __TIM_INCAPTURE_H
#include "stm32f1xx.h"
void TIM_IC_Init(uint16_t psc,uint16_t arr);
#endif
tim_inCapture.c源文件
#include "./BSP/TIMER/tim_inCapture.h"
TIM_HandleTypeDef htim;
void TIM_IC_Init(uint16_t psc,uint16_t arr)
{
htim.Instance = TIM5;
htim.Init.AutoReloadPreload = TIM_AUTORELOAD_PRELOAD_ENABLE;
htim.Init.CounterMode = TIM_COUNTERMODE_UP;
htim.Init.Period = arr;
htim.Init.Prescaler = psc;
//定时器初始化 PSC ARR 计数模式 ARR缓冲功能
HAL_TIM_IC_Init(&htim);
//这个要给初始化0 不然里边的值是随机的 会影响配置
TIM_IC_InitTypeDef sConfig = {0};
sConfig.ICFilter = 0x0;
sConfig.ICPolarity = TIM_ICPOLARITY_RISING;
sConfig.ICPrescaler = TIM_ICPSC_DIV1;
sConfig.ICSelection = TIM_ICSELECTION_DIRECTTI;
//定时器输入捕获配置 滤波器 分频器 输入极性(上升沿、下降沿) IC1来连接到通道1 还是连接到通道2
HAL_TIM_IC_ConfigChannel(&htim, &sConfig, TIM_CHANNEL_1);
//开启更新事件中断
__HAL_TIM_ENABLE_IT(&htim, TIM_IT_UPDATE);
//启动定时器 输入捕获使能 输入捕获中断
HAL_TIM_IC_Start_IT(&htim, TIM_CHANNEL_1);
}
void HAL_TIM_IC_MspInit(TIM_HandleTypeDef *htim)
{
if(htim ->Instance == TIM5)
{
//开启定时器5时钟
__HAL_RCC_TIM5_CLK_ENABLE();
//开启GPIOA时钟
__HAL_RCC_GPIOA_CLK_ENABLE();
GPIO_InitTypeDef GPIO_Init;
GPIO_Init.Mode = GPIO_MODE_INPUT;
GPIO_Init.Pin = GPIO_PIN_0;
GPIO_Init.Pull = GPIO_NOPULL;
GPIO_Init.Speed = GPIO_SPEED_FREQ_HIGH;//速度是输出用的 可以不设置
//初始化PA0为浮空输入
HAL_GPIO_Init(GPIOA, &GPIO_Init);
HAL_NVIC_SetPriority(TIM5_IRQn, 0, 0);
HAL_NVIC_EnableIRQ(TIM5_IRQn);
}
}
//定时器5中断处理函数
void TIM5_IRQHandler(void)
{
HAL_TIM_IRQHandler(&htim);
}
uint16_t IC_Value = 0;//定义输入捕获寄存器的值
uint8_t rising = 0;//定义捕获到上升沿 1为已经捕获到上升沿
uint8_t flag = 0;//捕获高电平完成
uint16_t yichu = 0;
//定时器输入捕获回调函数
void HAL_TIM_IC_CaptureCallback(TIM_HandleTypeDef *htim)
{
if(htim ->Instance ==TIM5)
{
//还没有捕获到高电平
if(flag ==0)
{
//还没有捕获到上升沿
if(rising ==0)
{
//已经捕获到了上升沿
rising = 1;
yichu = 0;
//设置计数器的值为0
__HAL_TIM_SET_COUNTER(htim, 0);
//设置为下降沿捕获
__HAL_TIM_SET_CAPTUREPOLARITY(htim, TIM_CHANNEL_1, TIM_INPUTCHANNELPOLARITY_FALLING);
}
else
{
//获取捕获寄存器的值
IC_Value = __HAL_TIM_GET_COMPARE(htim, TIM_CHANNEL_1);
//设置为上降沿捕获
__HAL_TIM_SET_CAPTUREPOLARITY(htim, TIM_CHANNEL_1, TIM_INPUTCHANNELPOLARITY_RISING);
rising = 0;
flag =1;
IC_Value = 1000*yichu + IC_Value;
}
}
}
}
void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim)
{
if(htim ->Instance ==TIM5)
{
yichu++;
}
}
这个是用库函数版本的程序,没啥问题,数据全是准确的。
main.c主函数
#include "./SYSTEM/sys/sys.h"
#include "./SYSTEM/usart/usart.h"
#include "./SYSTEM/delay/delay.h"
#include "./BSP/LED/led.h"
#include "./BSP/TIMER/tim_inCapture.h"
extern uint16_t IC_Value;//定义输入捕获寄存器的值
extern uint8_t flag;//捕获高电平完成
int main(void)
{
HAL_Init(); /* 初始化HAL库 */
sys_stm32_clock_init(RCC_PLL_MUL9); /* 设置时钟, 72Mhz */
delay_init(72); /* 延时初始化 */
led_Init(); /* LED初始化 */
usart_init(115200);
TIM_IC_Init(71 ,999);
while(1)
{
LED0(1);
LED1(0);
delay_ms(500);
LED0(0);
LED1(1);
delay_ms(500);
printf("脉冲时间为%dUS\r\n",IC_Value);
flag = 0;
}
}