参考博客:STM32 HAL库STM32脉冲宽度和周期测量
感谢大佬!!!
前言
之前拿外部中断做了一个频率计,范围到了1MHz,但是精确度并不是很高,误差在%0.5左右(看了一下那些大佬的频率计,基本上都是25MHz的量程范围,误差也是远低于%0.5)。
再结合外部中断的知识,发现除非采用matlab进行数据拟合(应该就是每一个频率节点都进行数据的校对),不然精度是无法提高的,而且对于一块STM32F103单片机而言,将时钟频率配置到最大的72MHz,1MHz的频率也应该是外部中断的极限了,所以我决定再用输入捕获的方法尝试一下。
原理
定时器的输入捕获的功能就是检测上升沿或者下降沿之间的时间间隔(这是我自己的理解可能有点不太严谨),然后就是根据定时器的配置的具体的理解:
第一个框是预装载值,第二个是满载值
这样的话 这个定时器的频率就是 单片机的频率(我这里配置的是72MHz)/预装载值+1 就是1MHz 那每次定时的周期就是1us,言下之意就是如果如果说检测的频率高于1MHz,就靠这样的配置是远远不够的。(而且输入捕获其实也是十分的消耗 cpu的资源的,他也和外部中断一样要不断的进入中断,对于一个100khHz的信号,1s就要进入中断100k次,这样就意味着,不光单片机无法实现其他的功能,而且中断的回调函数也必须十分精简不然会有相当大的误差)。
我的想法是:先检测信号的上升沿,当上升沿到来之后再将输入捕获改变成下降沿捕获,再进行数据处理得到相关的数据。
HAL库输入捕获相关的函数
__HAL_TIM_SET_COUNTER( 定时器地址 , 设置的数值 );
我用的是定时器2,然后用这个函数执行定时器2的计数值清零的操作,所以我是这样用的:
__HAL_TIM_SET_COUNTER(&htim2, 0);
TIM2_SetCapturePolarity(捕获触发模式);
我这个函数来改变输入捕获的模式(上升沿模式和下降沿模式)
TIM2_SetCapturePolarity(TIM_INPUTCHANNELPOLARITY_RISING); // 设置为上升沿触发
TIM2_SetCapturePolarity(TIM_INPUTCHANNELPOLARITY_FALLING); // 设置为下降沿触发
HAL_TIM_ReadCapturedValue(htim, TIM_CHANNEL_1)
这个是读取当时的数值,也比较的通俗易懂我这里就不详细介绍了。
代码部分
void TIM2_Poll(void)
{
switch (TIM2_CAPTURE_STA)
{
case 0:
{
TIM2_TIMEOUT_COUNT = 0;
__HAL_TIM_SET_COUNTER(&htim2, 0); // 清除定时器2现有计数
memset(TIM2_CAPTURE_BUF, 0, sizeof(TIM2_CAPTURE_BUF)); // 清除捕获计数
TIM2_SetCapturePolarity(TIM_INPUTCHANNELPOLARITY_RISING); // 设置为上升沿触发
HAL_TIM_Base_Start_IT(&htim2); // 启动定时器更新中断
HAL_TIM_IC_Start_IT(&htim2, TIM_CHANNEL_1); // 启动捕获中断
TIM2_CAPTURE_STA++;
break;
}
case 4:
{
double high = TIM2_CAPTURE_BUF[1] - TIM2_CAPTURE_BUF[0];
double cycle = TIM2_CAPTURE_BUF[2] - TIM2_CAPTURE_BUF[0];
float frq = 1.0 / (((float)cycle) / 1000000.0);
TIM2_CAPTURE_STA++;
printf("高电平持续时间 %.3f ms\r\n", high / 1000);
//printf("周期 : %. ms\r\n", cycle / 1000.0);
printf("频率 %.3f Hz\r\n", frq);
TIM2_CAPTURE_STA=0;
break;
}
default:
break;
}
}
/// 定时器2时间溢出回调函数
void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim)
{
if (htim->Instance == htim2.Instance)
{
TIM2_TIMEOUT_COUNT++; // 溢出次数计数
}
}
///< 输入捕获回调函数
void HAL_TIM_IC_CaptureCallback(TIM_HandleTypeDef *htim)
{
if (htim->Instance == htim2.Instance)
{
switch (TIM2_CAPTURE_STA)
{
case 1:
{
TIM2_CAPTURE_BUF[0] = HAL_TIM_ReadCapturedValue(htim, TIM_CHANNEL_1) + TIM2_TIMEOUT_COUNT * 0xFFFF;
TIM2_SetCapturePolarity(TIM_INPUTCHANNELPOLARITY_FALLING); // 设置为下降沿触发
TIM2_CAPTURE_STA++;
break;
}
case 2:
{
TIM2_CAPTURE_BUF[1] = HAL_TIM_ReadCapturedValue(htim, TIM_CHANNEL_1) + TIM2_TIMEOUT_COUNT * 0xFFFF;
TIM2_SetCapturePolarity(TIM_INPUTCHANNELPOLARITY_RISING); // 设置为上升沿触发
TIM2_CAPTURE_STA++;
break;
}
case 3:
{
TIM2_CAPTURE_BUF[2] = HAL_TIM_ReadCapturedValue(htim, TIM_CHANNEL_1) + TIM2_TIMEOUT_COUNT * 0xFFFF;
HAL_TIM_IC_Stop_IT(htim, TIM_CHANNEL_1); // 停止捕获
HAL_TIM_Base_Stop_IT(&htim2); // 停止定时器更新中断
TIM2_CAPTURE_STA++;
break;
}
default:
break;
}
}
}
结合函数的说明,这个还是挺好理解的。(重点还是对定时器那预装载值和满载值的理解)
问题
现在串口的输出过于频繁了(本来可以再用一个定时器中断来实现的,但是外部中断那一章节也用的是这个方法,我这次就没用了,之后会用dma来实现,这个更加合理)。
频率精度很高,但是测量范围只有50kHz(看了一些文章,好像输入捕获也可以到1MHz,还在分析是什么原因。。。)
后来打算
昨天找到了这篇文章 我觉得很赞:
使用 STM32 测量频率和占空比的几种方法
之后还是想继续完善频率计,打算采用使用外部时钟计数器来实现。