基于STM32的频率计(采用输入捕获的方式)

2024-03-25 08:30

本文主要是介绍基于STM32的频率计(采用输入捕获的方式),希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

参考博客: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 测量频率和占空比的几种方法
之后还是想继续完善频率计,打算采用使用外部时钟计数器来实现。

这篇关于基于STM32的频率计(采用输入捕获的方式)的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/844422

相关文章

在Java中实现线程之间的数据共享的几种方式总结

《在Java中实现线程之间的数据共享的几种方式总结》在Java中实现线程间数据共享是并发编程的核心需求,但需要谨慎处理同步问题以避免竞态条件,本文通过代码示例给大家介绍了几种主要实现方式及其最佳实践,... 目录1. 共享变量与同步机制2. 轻量级通信机制3. 线程安全容器4. 线程局部变量(ThreadL

Django中的函数视图和类视图以及路由的定义方式

《Django中的函数视图和类视图以及路由的定义方式》Django视图分函数视图和类视图,前者用函数处理请求,后者继承View类定义方法,路由使用path()、re_path()或url(),通过in... 目录函数视图类视图路由总路由函数视图的路由类视图定义路由总结Django允许接收的请求方法http

Python yield与yield from的简单使用方式

《Pythonyield与yieldfrom的简单使用方式》生成器通过yield定义,可在处理I/O时暂停执行并返回部分结果,待其他任务完成后继续,yieldfrom用于将一个生成器的值传递给另一... 目录python yield与yield from的使用代码结构总结Python yield与yield

pandas数据的合并concat()和merge()方式

《pandas数据的合并concat()和merge()方式》Pandas中concat沿轴合并数据框(行或列),merge基于键连接(内/外/左/右),concat用于纵向或横向拼接,merge用于... 目录concat() 轴向连接合并(1) join='outer',axis=0(2)join='o

shell脚本批量导出redis key-value方式

《shell脚本批量导出rediskey-value方式》为避免keys全量扫描导致Redis卡顿,可先通过dump.rdb备份文件在本地恢复,再使用scan命令渐进导出key-value,通过CN... 目录1 背景2 详细步骤2.1 本地docker启动Redis2.2 shell批量导出脚本3 附录总

Oracle查询表结构建表语句索引等方式

《Oracle查询表结构建表语句索引等方式》使用USER_TAB_COLUMNS查询表结构可避免系统隐藏字段(如LISTUSER的CLOB与VARCHAR2同名字段),这些字段可能为dbms_lob.... 目录oracle查询表结构建表语句索引1.用“USER_TAB_COLUMNS”查询表结构2.用“a

SpringBoot多环境配置数据读取方式

《SpringBoot多环境配置数据读取方式》SpringBoot通过环境隔离机制,支持properties/yaml/yml多格式配置,结合@Value、Environment和@Configura... 目录一、多环境配置的核心思路二、3种配置文件格式详解2.1 properties格式(传统格式)1.

Oracle数据库定时备份脚本方式(Linux)

《Oracle数据库定时备份脚本方式(Linux)》文章介绍Oracle数据库自动备份方案,包含主机备份传输与备机解压导入流程,强调需提前全量删除原库数据避免报错,并需配置无密传输、定时任务及验证脚本... 目录说明主机脚本备机上自动导库脚本整个自动备份oracle数据库的过程(建议全程用root用户)总结

Debian系和Redhat系防火墙配置方式

《Debian系和Redhat系防火墙配置方式》文章对比了Debian系UFW和Redhat系Firewalld防火墙的安装、启用禁用、端口管理、规则查看及注意事项,强调SSH端口需开放、规则持久化,... 目录Debian系UFW防火墙1. 安装2. 启用与禁用3. 基本命令4. 注意事项5. 示例配置R

最新Spring Security的基于内存用户认证方式

《最新SpringSecurity的基于内存用户认证方式》本文讲解SpringSecurity内存认证配置,适用于开发、测试等场景,通过代码创建用户及权限管理,支持密码加密,虽简单但不持久化,生产环... 目录1. 前言2. 因何选择内存认证?3. 基础配置实战❶ 创建Spring Security配置文件