为实现测量频率这个功能,采用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秒呢? 这似乎是不可能的事情。..得和标准的原子时钟做比较。才行了。