STM32F4XX 学习日志:定时器输入捕获

STM32F4XX 学习日志:定时器输入捕获

  • 前言
    • 任务目标
    • 设计过程
      • TIM1初始化代码
      • TIM5初始化代码
      • 中断服务函数
      • 薛定谔的代码
    • ·以上代码亲测有效

前言

使用反客科技STM32F407VET6 M1的核心板,板载8M主时钟晶振(HSE),32.768kHz低速外部晶振(LSE)。含有一个用户LED以及一个用户按键。

任务目标

使用定时器输入捕获测量方波高电平时间。
学生党放假在家没有信号发生器,只能用定时器产生一个方波用于测量。
没错,我测我自己。

设计过程

首先生成一个PWM波,这里我选用高级定时器1也就是TIM1进行配置。
TIM1挂载在APB2高速总线上。我使用标准库将系统时钟配置为168MHZ,则该总线频率也为168MHZ。
(标准库默认的是配置HSE为时钟源,而在我这块板子上不兼容标准库的默认配置,所以我就自己配置了HSI为系统时钟,具体代码在我上一篇博客中,这里是传送门:链接: STM32F4XX 学习日志:定时器中断模拟PWM波实现呼吸灯.)
STM32F4XX 学习日志:定时器输入捕获_第1张图片

TIM1初始化代码

复用引脚PA8为通道1引脚
在这里插入图片描述
映射引脚代码。

GPIO_PinAFConfig(GPIOA, GPIO_PinSource8, GPIO_AF_TIM1);

这里注意一下stm32f4的引脚时钟是AHB1总线的

RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOA, ENABLE);

我看网上的其他代码好像F1的芯片引脚时钟不是在这条总线上的,敲代码的时候如果不注意很容易忽略(别问我怎么知道的,我之前就是只改了参数,系统也不会报错)

定时器初始化为输出比较,PWM波模式1,该模式效果可以由数据手册查到。
·
·
·
·
·
算了我觉得你可能不会去查,我还是贴出来吧【狗头】
STM32F4XX 学习日志:定时器输入捕获_第2张图片
定时器周期设置为100ms也就是10HZ
计算 168 000 000 / 16800 / 1000 = 10HZ。
168M先经过16800倍数分频率得到1 000 0HZ的频率
然后计数器每1000次计数会产生溢出。

以上

是我之前初学的时候所了解的知识,但是对于怎么计算出周期还是有点懵懵的。后来经过多次练习才了解了。

定时器需要工作肯定需要一个时钟,而这个时钟则是由系统时钟分出来的。
那么定时器的时钟有什么用呢。这可能需要一点数电的知识。


那么最简单的理解方法就是,既然定时器计数值会自动变化,那么每一次变化期间的间隔时间是多少呢????????????

那自然就是定时器的时钟周期,也就是由系统时钟分频得出的频率。
这样理解就显得容易计算了。


主时钟分过来来的频率是1 000 0HZ换算成周期也就是100us,换句话说也就是每隔100us,定时器的值变化1。
而后定时器是每计数1000次产生一个溢出。
也就有1000个100us 1000 00us=100ms=0.1s


这样理解就方便多了。
而后就是正常的配置过程,这里不做赘述。

   TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure;
   TIM_OCInitTypeDef  TIM_OCInitStructure;
   NVIC_InitTypeDef NVIC_InitStructure;
   GPIO_InitTypeDef  GPIO_InitStructure;
   
  /* 1. 使能时钟 */
   RCC_APB2PeriphClockCmd(RCC_APB2Periph_TIM1, ENABLE);
  RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOA, ENABLE);
  GPIO_PinAFConfig(GPIOA, GPIO_PinSource8, GPIO_AF_TIM1);
   
   GPIO_InitStructure.GPIO_Pin = GPIO_Pin_8;				 
   GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF;
   GPIO_InitStructure.GPIO_OType= GPIO_OType_PP;
   GPIO_InitStructure.GPIO_Speed = GPIO_Speed_100MHz;		
   GPIO_Init(GPIOA,&GPIO_InitStructure);
   
   /* 2. 配置定时器参数 */
   TIM_TimeBaseStructure.TIM_Prescaler         = 16800 - 1;              /* 定时器时钟分频系数 */   
   TIM_TimeBaseStructure.TIM_Period            = 1000 - 1;               /* 定时器重装载值 */              
   TIM_TimeBaseStructure.TIM_ClockDivision     = TIM_CKD_DIV1;     
   TIM_TimeBaseStructure.TIM_CounterMode       = TIM_CounterMode_Up;     /* 计数器模式 */
   TIM_TimeBaseStructure.TIM_RepetitionCounter = 0;                      /* 重复计数值 */
   TIM_TimeBaseInit(TIM1, &TIM_TimeBaseStructure); 

   TIM_OCInitStructure.TIM_OCMode = TIM_OCMode_PWM1;					//设置每次进入中断为电平翻转模式
   TIM_OCInitStructure.TIM_OutputState = TIM_OutputState_Enable;		//输出开
   TIM_OCInitStructure.TIM_Pulse = 550;											//设置最初CCR为0,这样一配置完就进去中断服务程序
   TIM_OCInitStructure.TIM_OCPolarity = TIM_OCPolarity_High;			//设置最开始的电平为高电平
   TIM_OC1Init(TIM1, &TIM_OCInitStructure);									//载入寄存器	
   
   


   TIM_OC1PreloadConfig(TIM1, TIM_OCPreload_Enable);  //CH1预装载使能	 
   
   TIM_ARRPreloadConfig(TIM1, ENABLE); //使能TIMx在ARR上的预装载寄存器
   
   TIM_Cmd(TIM1, ENABLE);  //使能TIM1

TIM5初始化代码

接下来我是用通用定时器TIM5来进行输入捕获
根据数据手册TIM5挂载在APB1总线上,当系统频率配置为168MHZ时,该总线频率为84MHZ。
也就是该定时器,可以从系统分得84MHZ的时钟。但是定时器肯定跑不了这么高的时钟,所以要先进行分频。为了方便计算,我选择84分频。所以定时器计数频率为1 000 000MHZ。
同时再给计数器设置1000的计数值。也就是计数值变化1000次产生一次上溢事件。
所以上溢时间发生的频率为1 000 HZ 也就是1ms一次。


接下来就是输入捕获的模式配置。这里我选择通道1为注入通道。
重映射引脚PA0为定时器5通道1。

  • 这里注意一下,我看过许多STM32F1的代码对引脚的初始化都是配置为输入模式。而F4的芯片应该是要配置为复用模式。这里是一点不同的地方。如果配置不对有的地方可能会不能用。我之前就遇到过,但凡是重映射引脚,我现在一律配置为复用模式(F4代码中没有复用时钟开启好像)。
  • 然后这一段代码是配置数字滤波器时钟的的
    TIM5_ICInitStructure.TIM_ICPrescaler = TIM_ICPSC_DIV1;
    TIM5_ICInitStructure.TIM_ICFilter = 0x02;
    

这里TIM_ICPSC_DIV1意思是:配置捕获1预分频器。每次在捕获输入上检测到边缘时执行的捕获。换句话说,检测到上升沿就判断是上升沿。这句话可能有点奇怪。如果换一种说法

检测到1次上升沿就捕获。也就说还能设置检测到两次上升沿,四次,八次才捕获配置。

接下来一个成员变量就是给滤波器配置时钟周期,滤波器的时钟周期是由定时器直接分得的,这直接决定了滤波器的采样周期,这里贴出数据手册上的介绍。
STM32F4XX 学习日志:定时器输入捕获_第3张图片

  • 从图中可以得知定时器将一次边沿信号变化视为真实变化(而不是噪声)的真正因素是滤波器在周期内连续捕获了N次都为上升沿才认定为是上升沿。
    这里的捕获和前一个预分频的捕获可能有点混淆。这里做这样的理解

1.滤波器的捕获是用于判断信号是否是变化而不是由于噪声造成抖动,当在周期内连续检测到N次的事件,才认为该事件是一个有效边沿,反之则为噪声杂波。
2.预分频的配置是用于检测何时该触发中断事件。也就是当来N次上升沿时候才触发中断。

以下贴出初始化代码。 使能更新中断与捕获中断。

TIM_ICInitTypeDef  TIM5_ICInitStructure;
 
void TIM5_Cap_Init(void)
{
     	 
	GPIO_InitTypeDef GPIO_InitStructure;
	TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure;
    NVIC_InitTypeDef NVIC_InitStructure;

	RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM5, ENABLE);	//使能TIM5时钟
 	RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOA, ENABLE);
	GPIO_PinAFConfig(GPIOA, GPIO_PinSource0, GPIO_AF_TIM5);
	
	GPIO_InitStructure.GPIO_Pin  = GPIO_Pin_0;  //PA0 清除之前设置  
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF; //PA0 输入
	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_100MHz;		
	GPIO_Init(GPIOA, &GPIO_InitStructure);
	GPIO_ResetBits(GPIOA,GPIO_Pin_0);						 //PA0 下拉
	
	//初始化定时器5 TIM5	 
	TIM_TimeBaseStructure.TIM_Period = 84-1; //设定计数器自动重装值 
	TIM_TimeBaseStructure.TIM_Prescaler =1000-1; 	//预分频器   
	TIM_TimeBaseStructure.TIM_ClockDivision = TIM_CKD_DIV1; //设置时钟分割:TDTS = Tck_tim
	TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up;  //TIM向上计数模式
	TIM_TimeBaseInit(TIM5, &TIM_TimeBaseStructure); //根据TIM_TimeBaseInitStruct中指定的参数初始化TIMx的时间基数单位
  
	//初始化TIM5输入捕获参数
	TIM5_ICInitStructure.TIM_Channel = TIM_Channel_1; //CC1S=01 	选择输入端 IC1映射到TI1上
  	TIM5_ICInitStructure.TIM_ICPolarity = TIM_ICPolarity_Rising;	//上升沿捕获
  	TIM5_ICInitStructure.TIM_ICSelection = TIM_ICSelection_DirectTI; //映射到TI1上
  	TIM5_ICInitStructure.TIM_ICPrescaler = TIM_ICPSC_DIV1;	 //配置输入分频,不分频 
  	TIM5_ICInitStructure.TIM_ICFilter = 0x02;//IC1F=0000 配置输入滤波器 不滤波
  	TIM_ICInit(TIM5, &TIM5_ICInitStructure);
	
	//中断分组初始化
	NVIC_InitStructure.NVIC_IRQChannel = TIM5_IRQn;  //TIM3中断
	NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0;  //先占优先级2级
	NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0;  //从优先级0级
	NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; //IRQ通道被使能
	NVIC_Init(&NVIC_InitStructure);  //根据NVIC_InitStruct中指定的参数初始化外设NVIC寄存器 
	
	TIM_ITConfig(TIM5,TIM_IT_Update|TIM_IT_CC1,ENABLE);//允许更新中断 ,允许CC1IE捕获中断	
	
    TIM_Cmd(TIM5,ENABLE ); 	//使能定时器5
 
}

中断服务函数

这里遇到了问题
首先讲一下我的思路。希望有大佬看出来哪里出问题了帮我做个解答。

  • 这里我原本是打算,触发捕获事件中断的时候,记录当前定时器的计数值,也就是CNT中的值。然后通过计算得出更精确的数值。
TIM5->CNT  //该寄存器中即为定时器当前计数值

这里先讲方法:在检测当上升沿之后,先将捕获设置为下降沿捕获,记录进入定时器更新事件中断的次数,按照刚刚的配置方法也就是记录一次中断为1ms。当捕获到下降沿的时候再记录当前的计数器的值,而后通过计算得出该信号处于上升沿的时间。

以下贴出代码(问题往下翻)

u8  TIM5CH1_CAPTURE_STA=0;	//输入捕获状态		    				
float TIM5CH1_CAPTURE_VAL;	//输入捕获值
float TIM5CH1_CAPTURE_VAL_1,TIM5CH1_CAPTURE_VAL_2;
float TIM5CH1_CAPTURE_VAL_N=0;
void TIM5_IRQHandler(void)
{
     
	if(TIM_GetITStatus(TIM5, TIM_IT_Update) != RESET)
	{
     
//		if( TIM5CH1_CAPTURE_STA == 0 )//未捕获到上升沿
//		{
     
//			TIM5CH1_CAPTURE_VAL_N = 0;
//		}
		if(TIM5CH1_CAPTURE_STA==1)//未捕获到上升沿
		{
     
			TIM5CH1_CAPTURE_VAL_N++;
//			TIM5->CNT = 0 ;
		}
	}
	if (TIM_GetITStatus(TIM5, TIM_IT_CC1) != RESET)//捕获1发生捕获事件
	{
     
		if(TIM5CH1_CAPTURE_STA==0)
		{
     
//			TIM5CH1_CAPTURE_VAL_1=TIM5->CNT;
			TIM_OC1PolarityConfig(TIM5,TIM_ICPolarity_Falling);
			TIM5->CNT = 0 ;
			TIM5CH1_CAPTURE_STA=1;
		}
		else
		{
     
//			TIM5CH1_CAPTURE_VAL_2=TIM5->CNT;
			TIM_OC1PolarityConfig(TIM5,TIM_ICPolarity_Rising);
			TIM5->CNT = 0 ;
			TIM5CH1_CAPTURE_VAL=TIM5CH1_CAPTURE_VAL_N;
			/*+(((float)TIM5CH1_CAPTURE_VAL_2+1000-(float)TIM5CH1_CAPTURE_VAL_1)/1000)*/
			TIM5CH1_CAPTURE_VAL_N=0;
			TIM5CH1_CAPTURE_STA=0;
		}
	}
    TIM_ClearITPendingBit(TIM5, TIM_IT_CC1|TIM_IT_Update); //清除中断标志位
}

薛定谔的代码

好吧,这也是我自己学艺不精。讲一下问题。

可以看到,我在以上代码中并没有将我自己所讲的思路完全实现,而是只保留了用更新中断计算时间的部分。

关于记录定时器计数值以及相关的计算代码以及被我注释掉了。

原因是实际运行时,这部分出现奇怪的问题。

当我在代码中赋值部分打上断点时,通过调试可以看到其变量的值在不断的变化。

/*就是这俩行代码,打断点和不打断点是两种状态。*/
		TIM5CH1_CAPTURE_VAL_1=TIM5->CNT;
		TIM5CH1_CAPTURE_VAL_2=TIM5->CNT;

但是!当我不在这一句上打断点的时候,调试时直接运行到中断服务函数最后一句,虽然该计数器的值不断在变化,但是记录计数器的值的变量值却不会改变一直为42(十进制)。
当我重新打上断点,发现,这个变量又开始愉快的变化了(微笑)。

希望有大佬可以看出是什么问题。
需要工程文件的可以评论。

·以上代码亲测有效

你可能感兴趣的:(学习日志,stm32,单片机,嵌入式)