STM32 定时器1秒执行一次 计算频率 STM32CubemxIDE

为实现测量频率这个功能,采用STM32的定时器功能,大体方案是用2个定时器来实现,
TIM4 定时器负责计数.
TIM2 定时器负责1秒产生一次中断,执行一次脉冲计数采集工作.记录下TIM4的计数值.

实现过程如下
先看下时钟频率
在这里插入图片描述
配置定时器TIM2
在这里插入图片描述
TIM2开中断
在这里插入图片描述
关于定时器4是如何计数的,外部时钟模式走的是TI1_ED 时钟线.这里为啥是65536呢? 是因为这个计数器是16位的. 最大只能到65536-1,溢出以后自动到中断值里面.count_over++ 这样就可以计数很大的次数了.当然不中断也可以,还可以定时器级联.这样就要多占用一个定时器了.
在这里插入图片描述

后来发现上面这个是错误的,

不能配置Time4的CH通道,时钟源改成ETR2就可以了。正确的是下面的效果,已验证。
在这里插入图片描述

实现代码如下

void HAL_TIM_PeriodElapsedCallback (TIM_HandleTypeDef * htim)
{
	///每秒定时器
	///定时器2.每1秒触发一次中断, 在中断中读取Time4 的脉冲数 
	if (htim == &htim2 ) //检查TIM2更新中断发生与否
	{ 
	        //1.读取Count
		    count_i = __HAL_TIM_GET_COUNTER(&htim4);
		    __HAL_TIM_SET_COUNTER(&htim4, 0); //重置定时计数器
	        //2.加上溢出数.* 36mhz
		    count_i = count_over * 65536 + count_i;
	        count_over = 0;
	}

	//TIM4 外部脉冲信号计数溢出,产生中断的时候就会进来.
	//脉冲信号经过TIMx_CH1 输入滤波,和边沿检测后,转成TI1F_ED->TRC时钟信号.然后这个Conte 记录的实际上是霍尔传感器的脉冲数.
	///定时器2.每次触发都是定时器计数超出了最大值,为了保证不丢失计数信息,只需要给溢出标志 count_over++;
	//if (htim-> Instance ==TIM4 && __HAL_TIM_GET_FLAG(htim, TIM_FLAG_UPDATE)   != RESET)  //检查TIM2的中断是否更新中断,因为很多中断都会调用HAL_TIM_IRQHandler ,所以要区分一下
	if (htim  == &htim4  )
	{ 
		count_over++;
	}
}

今天发现定时器中断总是进不去.发现了2个问题

一.生成的配置文件有问题
需要点好几次NVIC中断,才能生成合格的代码. 生成的tim.c文件中应该有 HAL_NVIC_EnableIRQ(TIM2_IRQn);才能开启中断. 先确认下生成的代码有没有问题, 如果没有那么需要去勾上Nvic中断 再去掉.生成代码来回折腾几次就出来了.

二 . 定时器开中断没有开启
,定时器开中断默认是没有开启的, 而且要在初始化代码里面手动初始化中断.

void MyInit(void)
{  
	HAL_TIM_Base_Start_IT(&htim2);//开中断才会触发中断函数 HAL_TIM_PeriodElapsedCallback
	HAL_TIM_Base_Start_IT(&htim4);//开中断才会触发中断函数 HAL_TIM_PeriodElapsedCallback
}
 

TIM Update event 计数更新事件的处理函数在HAL库的写法里面变了.
在标准库时代是TIM_IRQHandler 里面写,
后来HAL库发现已经实现了 HAL_TIM_IRQHandler .

经过查看源代码发现, 在 void HAL_TIM_IRQHandler(TIM_HandleTypeDef *htim)中调用了HAL_TIM_PeriodElapsedCallback.
代码如下,那么只要实现 HAL_TIM_PeriodElapsedCallback方法就可以实现对 TIM Update event 的处理.也就是上面的代码.

void HAL_TIM_IRQHandler(TIM_HandleTypeDef *htim){
  ....
  ....
  /* 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 */
    }
  }
  ....
  ....
}

在后来的调试过程中,发现.了另外两个问题.
第一个是传感器的信号始终得不到, 因为我采用的是3144霍尔传感器, 这种传感器的信号输出引脚是可以直接挂在STM32芯片上Time2的外部始终引脚上的 . 后来在小火箭兄弟的帮助下,找到了问题的原因, 这种传感器要把GPIO mode引脚改成上拉Pull-up. 这样传感器的输出电压才能上来, 因为这个3144霍尔传感器在检测到磁性物体时发出的信号是低电压… 所以在没设置上拉的时候电压达不到,所以就检测不到信号了… (这应该属于低级问题了,我电子和单片机刚入门新手,请谅解),

另外下面这个图里面的Time2 对于的应该是上文的Time4, 但是配置都是一样的,因为我的程序后来Time4的脉冲采集功能放在了Time2 Time2的定时1秒功能放在了Time1, 所以如下所见了.
在这里插入图片描述
第二个问题,测得的频率不及时.
这个问题是因为我上面的Time1 配置是按照1秒更新一次频率数据, 这1秒一次的速度对于人来讲还行,但是对于某些机器来讲是不行的, 例如四轴飞行器,1S更新一次数据估计都要坠机了. 所以为了更快的得到实时数据,我改成了100ms更新一次数据.
频率数据的实时性提高了,但是这样又引入了另外一个问题, 如果频率小于10HZ的时候,将无法测量准确. 因为在每100ms采样一次的时候有可能信号还没触发会在下一个100ms采样的时候触发. … 这样会导致10hz以下的频率无法准确测得.为了解决这个问题,我引入了 循环数组,将最近10次的计数值.存储在一个数组中. 100ms采样1次,1秒钟正好采样10次, 所以我创建了一个长度为10的数组,用来存储每次采样获得的霍尔传感器的脉冲次数. 在每次取数据的时候在求和.就得到了1秒钟的脉冲总数…
这里要说明的是我并没有在100ms一次的中断里面立即计算结果.这样会导致性能方面的一些影响. 所以只是在中断中循环存储数据然后清零而已,取数据的时候再对数组求和.毕竟取数据的速度要低的多次数也少的多.
引入循环池后的代码如下

//频率相关的变量
//int32_t pulse_count=0;
#define SAMPLECOUNT 10
//uint32_t count_over=0;
uint32_t count_Array[SAMPLECOUNT];
uint8_t count_ArrayIndex=0;
//频率相关的变量 

//读取转速数据,
void HAL_TIM_PeriodElapsedCallback (TIM_HandleTypeDef * htim)
{
	if (htim == &htim2 )
	{	    ///每0.1秒定时器 
	        //1.读取霍尔传感器在0.1秒内收到的脉冲计数Count
		    uint32_t count_i = __HAL_TIM_GET_COUNTER(&htim4);
		    __HAL_TIM_SET_COUNTER(&htim4, 0); //把这个数值清零重新计数 
	        if(count_ArrayIndex >= SAMPLECOUNT) {count_ArrayIndex = 0 ;}
	        count_Array[count_ArrayIndex] = count_i;//pulse_count
	        count_ArrayIndex ++;
	} 
}
//在主函数中读取频率函数,将1秒内的10次计数求和即可
int32_t ReadFrequency(void)
{
	int32_t Frequency =0;
	for(int i=0;i<SAMPLECOUNT;i++){
		Frequency += count_Array[i] ;
	}
	return Frequency;
}

经过测试,此方法非常有效, 可以测得1-10HZ以下的频率, 最高目前测到150KHZ没啥大问题. 而且省去了一个溢出变量和一个判断, 再高的频率我还没有方法测试,因为没有合适的信号发生器…有条件了再测一下.

后来在经过实际比对的时候又发现一个致命的问题, 频率有点误差, 记录如下表:
1khz 误差8hz
2khz 误差15hz
3khz 误差22hz
4khz 误差30hz
5khz 误差38hz
6khz 误差45hz
7khz 误差51hz
8khz 误差60hz
基本上呈线性误差.
经过推测,在150khz的时候应该误差在1150左右, 但是实际上在150khz却只有200左右. 说明, 这个误差跟系统代码或逻辑没有什么关系. 因为如果是系统或代码引起的问题一般呈线性或固定误差. 那么这个误差是哪里来的呢?
经过2个小时的苦思冥想, 终于明白了. 应该是时间问题.这个时间应该是我们的0.1秒定时器的时间不准确导致的. 我们的0.1秒可能就不是标准的0.1秒,有可能比0.1秒多了0.001秒, 10次累积下来就差不多了.所以我就开始调周期定时器的配置,最终改成了下面这个样子
在这里插入图片描述
这个小小的修改就把误差从200多降到了非常低的水平. 当然原先的-1是完全不对的.
这里有个小技巧, 64001000和64010000理论上应该是一样的吧.但实际上不一样.
这个地方640个时钟周期计数一次和6400个时钟周期计数一次,差了10倍. 在这段时间里面就有可能越过了好多信号. 所以要想时间精准就要减少预分频数.两者的成绩最后任然是0.1秒即可.
相当于最小的时间片. 时间切得小,就越是精准. 但是下面的计数上限是65536. 不能超过这个数值.
当然还有最佳的数值应该是100个时钟周期计数一次,计数超过64000就是0.1秒. 这样的时间片最小, 定时也更接近真正的0.1秒. 最终就是下面的配置了.
在这里插入图片描述

   我又产生了另外一个疑问. 如何才能准确的产生绝对的0.1秒呢? 这似乎是不可能的事情...得和标准的原子时钟做比较.才行了.

后来经过很多种方法的比较发现, 最好的测量频率的方法是采用占空比的方法测量 , 必须用硬件进行测量. 要使用定时器的双通道测量法.

步骤参考下面的文章
https://blog.csdn.net/qq153471503/article/details/130363906

  • 8
    点赞
  • 24
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
假设使用的是STM32F4系列的定时器TIM2,时钟频率为84MHz,预分频系数为8399,则定时器的计数频率为10 kHz。 如果要计算1到10的时间,可以设置定时器的自动重载值为10000,每次计数满10000,即表示经过了1的时间。因此,需要让定时器计数满10000次,才能表示经过了10的时间。 代码如下: ```c #include "stm32f4xx.h" void TIM2_IRQHandler(void) { if(TIM_GetITStatus(TIM2, TIM_IT_Update) != RESET) { static uint32_t count = 0; count++; if(count == 10000) // 计数满10000次 { // do something count = 0; } TIM_ClearITPendingBit(TIM2, TIM_IT_Update); } } int main(void) { TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure; NVIC_InitTypeDef NVIC_InitStructure; RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2, ENABLE); TIM_TimeBaseStructure.TIM_Period = 9999; // 自动重载值为9999 TIM_TimeBaseStructure.TIM_Prescaler = 8399; // 预分频系数为8399 TIM_TimeBaseStructure.TIM_ClockDivision = 0; TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up; TIM_TimeBaseInit(TIM2, &TIM_TimeBaseStructure); NVIC_InitStructure.NVIC_IRQChannel = TIM2_IRQn; NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0; NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0; NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; NVIC_Init(&NVIC_InitStructure); TIM_ClearITPendingBit(TIM2, TIM_IT_Update); TIM_ITConfig(TIM2, TIM_IT_Update, ENABLE); TIM_Cmd(TIM2, ENABLE); while(1); return 0; } ``` 在TIM2的中断服务程序中,用一个静态变量count记录定时器计数器的值,每次计数满10000,即表示经过了1的时间,此时可以执行相应的操作。最后将count重置为0,开始下一轮计数。 在main函数中,需要先初始化定时器TIM2,并开启定时器中断。然后程序进入一个死循环,等待定时器中断触发。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值