STM32输入捕获之快速构建频率计

news2024/9/23 12:50:27

        简介:配置好STM32 CUBE IDE后只需要额外7行代码就可以构建一个频率计,目前只计算测频,占空比测量需要加入下降沿捕获标记(暂时没做)。

 一、原理

  1. 频率:单位时间内完成周期性变化的次数,f = 1/T。
  2. 如何测量:
    1. 脉冲数测量法,单位时间内脉冲的数量来计算频率。适用条件:高频,因为高频的脉冲间隔时间很短,测量时间不容易达到准确状态。
    2. 测量时间间隔法,测量脉冲之前的时间间隔。适用条件:低、中频
    3. 高、中低频分类:暂时没有找到依据借鉴,这里取经验算,当单片机定时器的时钟为108MHz时,取脉冲为误差为1的条件下,计算频率误差为10%时的最高测量频率为1.08MHz,即1MHz以下的频率可以用测量时间间隔法,理论频率可以测到108MHz只是高于1MHz的情况下,误差大于10%。
    4. 其他减小误差的方法,多次平均值等。

二、时间间隔法测频

  1. 计算方法,f = 1/T,如T = 1mS时,f = 1KHz,所以当我测到两个脉冲的间隔T,就能得到频率。
  2. 采用定时器输入捕获得到时间间隔。例如,输入捕获有三种触发方式,上升沿、下降沿、上/下沿,当配置为上升沿时,当引脚有一个上升沿,就会触发一次捕获事件,此时记录定时器的计数值V1,第二次上升沿触发时,记录定时器的计数值V2,所以事件间隔为T = V2 – V1,从而得到频率。
  3. 这里需要考虑定时器溢出的情况,如16位定时器计数值为0-65535,两个上升沿捕获可能发生在当前计数周期最后或下一个计数周期、或跨越多个计数周期。
  4. 有些例子使用置计数值0的方法,这种方法限制挺多的,如一个定时器多个通道等。

三、实际应用

例:使用TIM4_CH2和TIM4_CH3测频,测量范围2Hz-200Hz,频率更新速度200mS

  1. STM32 CUBE IDE配置

        2.添加代码

包含头文件:文件里面可更改采集和计算次数

#include "user_tim_measure_frequency.h"

定义测量对象:

TIM_Capture_Def TIM4_CH2, TIM4_CH3;

初始化测量状态,主要在于输入定时器主频,用于后续计算频率

// 定时器主频

    user_tim_capture_init(&TIM4_CH2, (HAL_RCC_GetPCLK1Freq() * 2) / htim4.Init.Prescaler);

    user_tim_capture_init(&TIM4_CH3, (HAL_RCC_GetPCLK1Freq() * 2) / htim4.Init.Prescaler);

开启定时器捕获中断和溢出中断

HAL_TIM_IC_Start_IT(&htim4, TIM_CHANNEL_2); // 开启捕获

HAL_TIM_IC_Start_IT(&htim4, TIM_CHANNEL_3); // 开启捕获

__HAL_TIM_ENABLE_IT(&htim4, TIM_IT_UPDATE); // 更新中断用于溢出计数

捕获中断和溢出中断中加入标记函数

/******************************************************************************
 * 功能:定时器更新中断(计数溢出)中断处理回调函数, 该函数在HAL_TIM_IRQHandler中会被调用
 * TIM4:溢出计数
 *****************************************************************************/
void user_HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim) // 更新中断(溢出)发生时执行
{
    if (htim->Instance == TIM4) // 捕获
    {
        user_tim_tick_overflow(&TIM4_CH2);
        user_tim_tick_overflow(&TIM4_CH3);
    }
}

/******************************************************************************
 * 功能:定时器捕获,测频
 *****************************************************************************/
void HAL_TIM_IC_CaptureCallback(TIM_HandleTypeDef *htim)
{
    if (htim->Instance == TIM4)
    {
        if (htim->Channel == HAL_TIM_ACTIVE_CHANNEL_2)
        {
            user_tim_capture_mark(&TIM4_CH2, HAL_TIM_ReadCapturedValue(&htim4, TIM_CHANNEL_2));
        }
        if (htim->Channel == HAL_TIM_ACTIVE_CHANNEL_3)
        {
            user_tim_capture_mark(&TIM4_CH3, HAL_TIM_ReadCapturedValue(&htim4, TIM_CHANNEL_3));
        }
    }
}

 200mS一次计算频率

printf("---> TIM4_CH2: %f Hz   TIM4_CH3: %f Hz \r\n", user_tim_calculate_frequency(&TIM4_CH2), user_tim_calculate_frequency(&TIM4_CH3));

四、测量结果

2Hz:

20Hz:

200Hz:

1KHz:

五、user_tim_measure_frequency.h文件

#ifndef __USER_TIM_MEASURE_FREQUENCY_H
#define __USER_TIM_MEASURE_FREQUENCY_H

/******************************************************************************
 *参数:设置最大采集和计算次数,
 *****************************************************************************/
#define capture_MAX_times 16 // 最大捕获数据次数

/******************************************************************************
 * 参数:测量相关的数据结构体
 *****************************************************************************/
typedef struct
{
    uint32_t basic_frequency; // 定时器的主频 = 时钟主频/分频

    _Bool capture_state;           // 输入捕获状态
    uint16_t capture_last_value;   // 当前捕获值
    uint16_t capture_now_value;    // 上一次的捕获值
    uint8_t capture_count;         // 一次计算的捕获次数
    uint8_t capture_overflow_time; // 溢出次数

    uint32_t capture_total_times; // 捕获总次数

    uint8_t data_point;                              // 数据存储位置
    uint16_t pulse_interval_time[capture_MAX_times]; // 脉冲间隔缓冲数组

    float frequency; // 计算的频率
} TIM_Capture_Def;

/******************************************************************************
 * 功能:置零计算数据
 *****************************************************************************/
void user_reset_tim_calculate_data(TIM_Capture_Def *_capture_channel)
{
    _capture_channel->capture_state = 0;
    _capture_channel->data_point = 0;
    _capture_channel->capture_overflow_time = 0;

    if (_capture_channel->capture_count >= capture_MAX_times)
    {
        memset(_capture_channel->pulse_interval_time, 0, capture_MAX_times * sizeof(uint16_t));
    }
    else
    {
        memset(_capture_channel->pulse_interval_time, 0, _capture_channel->capture_count * sizeof(uint16_t));
    }
    _capture_channel->capture_count = 0;
}

/******************************************************************************
 * 功能:测频初始化
 * 需要输入定时器的主频
 * 1、输入定时器主频,用于后续计算频率
 * 2、置零数据
 *****************************************************************************/
void user_tim_capture_init(TIM_Capture_Def *_capture_channel, uint32_t _timer_basic_frequency)
{
    _capture_channel->basic_frequency = _timer_basic_frequency;
    user_reset_tim_calculate_data(_capture_channel);
}

/******************************************************************************
 * 功能:溢出状态
 *****************************************************************************/
void user_tim_tick_overflow(TIM_Capture_Def *_capture_channel)
{
    _capture_channel->capture_overflow_time++;
}

/******************************************************************************
 * 功能:定时器产生捕获,标记时间同时计算时间间隔 T
 * _capture_now_value: 当前捕获值
 *****************************************************************************/
void user_tim_capture_mark(TIM_Capture_Def *_capture_channel, uint32_t _capture_now_value)
{
    // 0、捕获总次数记录,捕获状态更新
    if (_capture_channel->capture_state == 0)
    {
        _capture_channel->capture_state = 1;
    }
    _capture_channel->capture_total_times++;

    // 1、计算时间间隔
    if (_capture_channel->capture_overflow_time == 0) // 无溢出
    {
        _capture_channel->pulse_interval_time[_capture_channel->data_point] = _capture_now_value - _capture_channel->capture_last_value;
    }
    else // 有溢出
    {
        if (_capture_channel->capture_overflow_time == 1) // 溢出一次
        {
            _capture_channel->pulse_interval_time[_capture_channel->data_point] = _capture_now_value + 65535 - _capture_channel->capture_last_value;
        }
        else // 溢出多次,这里没做计算,最大取65535
        {
            _capture_channel->pulse_interval_time[_capture_channel->data_point] = 65535;
        }
        _capture_channel->capture_overflow_time = 0; // 溢出状态清零
    }

    // 2、保存上一次状态
    _capture_channel->capture_last_value = _capture_now_value;

    // 3、指向下一个存储地址,当前次数+1
    _capture_channel->data_point++;
    _capture_channel->capture_count++;

    // 4、位置溢出的情况下,调整为第一个位置,覆盖原有数据
    if (_capture_channel->data_point >= capture_MAX_times)
    {
        _capture_channel->data_point = 0;
    }
}

/******************************************************************************
 * 功能:计算频率
 *****************************************************************************/
float user_tim_calculate_frequency(TIM_Capture_Def *_capture_channel)
{
    if (_capture_channel->capture_state) // 捕获到数据
    {
        // 1、取平均值,先取和,再取平均
        uint32_t capture_sum_time = 0;
        float _average;

        if (_capture_channel->capture_count >= capture_MAX_times) // 一次间隔捕获时间大于 capture_MAX_times 次
        {
            for (size_t i = 0; i < capture_MAX_times; i++)
            {
                capture_sum_time += _capture_channel->pulse_interval_time[i];
            }
            _average = (float)capture_sum_time / capture_MAX_times;
        }
        else
        {
            for (size_t i = 0; i < _capture_channel->capture_count; i++)
            {
                capture_sum_time += _capture_channel->pulse_interval_time[i];
            }
            _average = (float)capture_sum_time / _capture_channel->capture_count;
        }

        // printf("---> capture_sum_time: %ld  count:%d    _average:%f\r\n", capture_sum_time, _capture_channel->capture_count, _average);

        // 2、计算频率,f = 主频/(分频*重装值)
        _capture_channel->frequency = _capture_channel->basic_frequency / _average;

        // 3、归零状态
        user_reset_tim_calculate_data(_capture_channel);

        // 4、返回计算频率
        return _capture_channel->frequency;
    }
    else // 没有捕获到数据
    {
        return 0;
    }
}

#endif /* __USER_TIM_MEASURE_FREQUENCY_H */

六、使用步骤和参数解释

 /***********************************************************************************************************************************************************/

步骤1:定义测量对象。

    如:TIM_Capture_Def TIM4_CH2, TIM4_CH3;

步骤2:初始化对象。

    针对对象,传入定时器主频(用于计算频率)、置零状态和数据。
    user_tim_capture_init(&TIM4_CH2, (HAL_RCC_GetPCLK1Freq() * 2) / htim4.Init.Prescaler);

    通过系统时钟树得到TIM4 对应的时钟为 PLCK1 x 2

    计算频率为 f = 1/T 
    定时器主频 = APBx外设时钟 / 预分频
    T = 定时器主频 / 重装值
    f = 1/T = 定时器主频 / 重装值

    预分频为根据需求自己设定,这个影响到测频范围。
    例如,tim4时钟为108M、预分频为1080、16位重装计数器的条件下,
    测量频率 fmax = 0.1M / 1 = 100 KHz
            fmin = 0.1M / 65535 = 1.53Hz
    根据以上配置,理论可测量 1.53Hz —— 100KHz

    因为捕获计时是有抖动误差的,即重装值误差,所以在保证精度的前提下,需要限值最大测量频率。
    例,同样相差10个重装值的情况下,在低频时, f = 0.1M / 65530 = 1.52601 Hz    、  f = 0.1M / 65520 = 1.52625 Hz
    而在高频时f = 0.1M / 100 = 1000 Hz    、  f = 0.1M / 90 = 1111.11 Hz

    减小误差的方法有很多,如平均值、滤波等,需要平衡计算时间等,测量时间越长,结果就越精确。

    例如,因为自己需要的测频条件为:范围2Hz —— 100Hz,200mS计算一次频率。所以可以采集到200组数据,取最后16组数据做平均值,重装值误差为1,取1Hz测量误差的条件下,最高可测得1.2KHz
    所以在这种情况下,测量范围为1.53Hz —— 1.2KHz

步骤3: 捕获中断中添加标记函数,每捕获到一个上升沿,记录重装值,同时计算上升沿的间隔时间
       溢出中断加入溢出标记函数,用于标记重装是否产生溢出

    void HAL_TIM_IC_CaptureCallback(TIM_HandleTypeDef *htim)
    {
        if (htim->Instance == TIM4)
        {
            if (htim->Channel == HAL_TIM_ACTIVE_CHANNEL_2)
            {
                user_tim_capture_mark(&TIM4_CH2, HAL_TIM_ReadCapturedValue(&htim4, TIM_CHANNEL_2));
            }
            else if (htim->Channel == HAL_TIM_ACTIVE_CHANNEL_3)
            {
                user_tim_capture_mark(&TIM4_CH3, HAL_TIM_ReadCapturedValue(&htim4, TIM_CHANNEL_3));
            }
        }
    }

    void user_HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim) // 更新中断(溢出)发生时执行
    {
        if (htim->Instance == TIM4) // 捕获
        {
            user_tim_tick_overflow(&TIM4_CH2);
            user_tim_tick_overflow(&TIM4_CH3);
        }
    }

步骤4:计算频率 f = 1/T 

    T为上升沿间隔时间的平均值,可减小误差,这里取16组数据做计算,因为我是测量100Hz内的低频,如果需要根据自己需求提高采集计算次数。

    user_tim_calculate_frequency(&TIM4_CH2);

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/505522.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

如何裁剪图片大小尺寸?

如何裁剪图片大小尺寸&#xff1f;平时我们在工作或者学习的时候&#xff0c;会经常需要将图片上传到不同的网站或者平台上&#xff0c;然而上传的时候经常会受到尺寸的限制&#xff0c;有时候尺寸太大就需要变小&#xff0c;为了确保上传成功&#xff0c;我们需要将图片进行裁…

Vue Router 最新版惊现使用 BUG

就在刚刚&#xff0c;当我在 写 Vue3 的项目的时候&#xff0c;使用到了 Vue Router 4 的最新版本&#xff08;4.1.6&#xff09;&#xff0c;然而在使用过程中&#xff0c;发现了一个使用问题&#xff0c;起初我还以为&#xff0c;是我这边的代码逻辑有问题&#xff0c;然而在…

九联UNT402A_当贝纯净桌面-卡刷固件包-内有教程

九联UNT402A_当贝纯净桌面-卡刷固件包-内有教程 特点&#xff1a; 1、适用于对应型号的电视盒子刷机&#xff1b; 2、开放原厂固件屏蔽的市场安装和u盘安装apk&#xff1b; 3、修改dns&#xff0c;三网通用&#xff1b; 4、大量精简内置的没用的软件&#xff0c;运行速度提…

【Java笔试强训 33】

&#x1f389;&#x1f389;&#x1f389;点进来你就是我的人了博主主页&#xff1a;&#x1f648;&#x1f648;&#x1f648;戳一戳,欢迎大佬指点! 欢迎志同道合的朋友一起加油喔&#x1f93a;&#x1f93a;&#x1f93a; 目录 一、选择题 二、编程题 &#x1f525;剪花布条…

虹科新闻 | HKATTO推出FastFrame™ 第四代智能以太网适配器系列

ATTO Technology, Inc. 是 35 多年来为数据密集型计算环境提供网络、存储连接和基础设施解决方案的全球领导者&#xff0c;今天宣布推出其新的FastFrame™ 第四代以太网产品系列智能网卡。 第四代FastFrame SmartNICs 产品线具有四种独特的型号&#xff0c;产品线涵盖 10/25/40…

OmniVerse + ChatGPT = 智能3D建模

全球各行各业对 3D 世界和虚拟环境的需求呈指数级增长。 3D 工作流是工业数字化的核心&#xff0c;开发实时模拟以测试和验证自动驾驶汽车和机器人&#xff0c;运行数字孪生以优化工业制造&#xff0c;并为科学发现铺平新道路。 今天&#xff0c;3D 设计和世界构建仍然是高度手…

IS200TPROH1BCB用于工业应用和电力分配等。高压型隔离开关用于变电站

​ IS200TPROH1BCB用于工业应用和电力分配等。高压型隔离开关用于变电站 什么是隔离器&#xff0c;它与断路器有何不同 什么是隔离器&#xff0c;为什么要使用隔离器 隔离器是一种开关装置&#xff0c;它可以手动或自动操作&#xff0c;隔离一部分电能。隔离器可用于在无负载情…

Python 实验四 常用数据结构(2)

6.某企业为职工发放奖金&#xff1a;如果入职超过5年&#xff0c;且销售业绩超过15000元的员工&#xff0c;奖金比例为0.2&#xff1b;销售业绩超过10000元的员工&#xff0c;奖金比例为0.15&#xff1b;销售业绩超过5000元的员工&#xff0c;奖金比例为0.1&#xff1b;其他奖金…

【25】核心易中期刊推荐——智能控制机器人

🚀🚀🚀NEW!!!核心易中期刊推荐栏目来啦 ~ 📚🍀 核心期刊在国内的应用范围非常广,核心期刊发表论文是国内很多作者晋升的硬性要求,并且在国内属于顶尖论文发表,具有很高的学术价值。在中文核心目录体系中,权威代表有CSSCI、CSCD和北大核心。其中,中文期刊的数…

【PWN · ret2libc】ret2libc2

ret2libc1的略微进阶——存在systemplt但是不存在“/bin/sh”怎么办&#xff1f; 目录 前言 python3 ELF 查看文件信息 strings 查看寻找"/bin/sh" IDA反汇编分析 思路及实现 老规矩&#xff0c;偏移量 offset EXP编写 总结 前言 经过ret2libc1的洗礼&a…

MyBatis介绍和MyBatis的增删改查xml配置--日志输出-查看 SQL

目录 MyBatis(简化数据库操作的持久层 框架) 官方文档 Maven 仓库 传统的 Java 程序操作 DB 分析 工作示意图 传统方式问题分析(如上) 引出 MyBatis MyBatis 工作原理示意图 MyBatis 快速入门 快速入门需求说明 创建 mybatis 数据库 - monster 表 MyBatis 快速入门 …

【需求响应】基于进化算法的住宅光伏电池系统需求响应研究(Matlab代码实现)

&#x1f4a5;&#x1f4a5;&#x1f49e;&#x1f49e;欢迎来到本博客❤️❤️&#x1f4a5;&#x1f4a5; &#x1f3c6;博主优势&#xff1a;&#x1f31e;&#x1f31e;&#x1f31e;博客内容尽量做到思维缜密&#xff0c;逻辑清晰&#xff0c;为了方便读者。 ⛳️座右铭&a…

VirtualBox 安装ubuntu22.04-live-server版本

文章目录 一、安装过程二、启动过程三、测试网络连通四、设置共享文件夹 一、安装过程 1、新建一个虚拟机 2、设置名称和位置 3、设置内存大小 4、设置虚拟硬盘&#xff0c;选择现在创建虚拟硬盘 5、选择虚拟硬盘文件类型&#xff0c;这里选择VDI 6、选择存储方式&#x…

甘特图控件DHTMLX Gantt入门使用教程【引入】:dhtmlxGantt 与 ASP.NET MVC(下)

DHTMLX Gantt是用于跨浏览器和跨平台应用程序的功能齐全的Gantt图表。可满足项目管理应用程序的大部分开发需求&#xff0c;具备完善的甘特图图表库&#xff0c;功能强大&#xff0c;价格便宜&#xff0c;提供丰富而灵活的JavaScript API接口&#xff0c;与各种服务器端技术&am…

2023爱分析・可观测性平台市场厂商评估报告:乘云科技

1. 研究范围定义 IT运维是企业信息化建设中不可或缺的一环&#xff0c;其作用在于确保系统稳定性、提高效率和降低成本&#xff0c;对企业的业务生产和服务质量有着至关重要的影响。自十四五规划以来&#xff0c;随着企业数字化转型的加速推进&#xff0c;以及信创转型的大…

Spring IoC 深度学习

Io回顾 IoC 是 Inversion of Control 的简写&#xff0c;译为“控制反转”&#xff0c;它不是一门技术&#xff0c;而是一种设计思想&#xff0c;是一个重要的面向对象编程法则&#xff0c;能够指导我们如何设计出松耦合、更优良的程序。 Spring 通过 IoC 容器来管理所有 Jav…

【代码随想录】刷题Day21

1.二叉搜索树的最小绝对值 530. 二叉搜索树的最小绝对差 首先对于单个节点而言&#xff0c;其最小绝对值有两个可能 一是根节点和左节点的最右节点绝对值 二是根节点和右节点的最左节点绝对值 其实这俩条件的实现就是我们代码的关键&#xff0c;不过这题和二叉搜索树的判断殊途…

Es分布式搜索引擎

目录 一、什么是ES&#xff1f; 二、什么是elk&#xff1f; 三、什么是倒排索引&#xff1f; 四、正向索引和倒排索引的优缺点对比 五、mysql数据库和es的区别&#xff1f; 六、索引库&#xff08;es中的数据库表&#xff09;操作有哪些&#xff1f; 八、ES分片存储原理 …

AutoCV第七课:ML基础

目录 ML基础前言1. 复习sqrt函数2. 线性回归预测房价2.1 问题分析2.2 代码实现2.3 总结 个人总结 ML基础 前言 手写AI推出的全新保姆级从零手写自动驾驶CV课程&#xff0c;链接。记录下个人学习笔记&#xff0c;仅供自己参考。 本次课程主要学习复习 sqrt 函数和线性回归预测房…

Java开发手册-8

Java开发手册-8 MySQL 数据库SQL语句ORM映射 工程结构应用分层二方库依赖 MySQL 数据库 SQL语句 【强制】不要使用count(列名)或count(常量)来替代count(*)&#xff0c;count(*)是SQL92定义的标准统计行数的语法&#xff0c;跟数据库无关&#xff0c;跟NULL和非NULL无关。 说…