GD32_定时器输入捕获波形频率
GD32_定时器输入捕获波形频率(多通道轮询)
之前项目上用到一个使用定时器捕获输入采集风扇波形频率得到风扇转速的模块,作为笔记简单记录以下当时的逻辑结构和遇到的问题,有需要参考源码、有疑问或需要提供帮助的可以留言告知 。
前言
提示: 测试基于GD32F103CBT6硬件平台,标准的108MHz系统时钟, 使用标准库GD32F10x_Firmware_Library_V1.0.0提示:(提示:此库坑多,外设编号从1开始,与用户手册略有出入、慎用!)
一、定时器输入捕获原理
定时器输入捕获模式可以用来测量脉冲宽度或者测量频率,我们以测量频率为例,用一个简图来说明输入捕获的原理:
如图示,斜线表示向上计数的定时器的计数值,ARR表示定时器的自动重装载值,定时计数器由0递加到这里就会发生溢出,并重0重新开始计数。将输入捕获配置为上升沿捕获,当检测到一个波形的上升沿时候,触发第一次捕获中断,T1时刻会采集计数器当前CNT值并保存记为CCRx1,当再次出现上升沿时触发第二次捕获,T2时刻会再次采集计数器当前CNT值并保存记为CCRx2,理想状态波形周期就是T2 -T1。但是如果波形较长可能产生 N 次定时器溢出,这就要求我们对定时器溢出,做处理,防止高电平太长,导致数据不准确。 T1~T2之间CNT计数的次数等于: (ARR - CCRx1)+( N * ARR)+ CCRx2,有了这个计数次数,再乘以 CNT 的计数周期,即可得到 T1 -T2的时间长度,即波形周期以及频率。
二、外设配置
GD32的定时器,我们使用通用定时器Timer1的,使用的GPIO为PB10和PB11,根据用户手册,需要进行功能重映射才能使其分别对应Timer1的通道2和通道3。在定时器初始化中使用函数GPIO_PinRemapConfig()即可;
使用的主时钟频率为108MHz,定时器的重装载值寄存器为16位,最大为65535,当定时器时钟分频系数为108分频时候,相当于定时器每65.535ms会溢出一次,如下是整个定时器PWM输入捕获的配置方式,目的是分别对通道3和通道4上的两个风扇进行脉冲周期数据采集和计算,详细内容参见代码注释:
void FanPwm_Input_Init(void)
{TIMER_BaseInitPara sTIM_TimeBaseStructure;NVIC_InitPara NVIC_InitStructure;TIMER_ICInitPara sTIM_ICInitCaptureStructure;/*初始化Timer2输入捕获*/RCC_APB1PeriphClock_Enable(RCC_APB1PERIPH_TIMER2, ENABLE); //实际是复位Timer1(手册与固件库有误)TIMER_DeInit(TIMER2); //复位GPIO_PinRemapConfig(GPIO_FULL_REMAP_TIMER2,ENABLE); //PB10和PB11是全映射复用sTIM_TimeBaseStructure.TIMER_Period = 65535; //计数器自动重装值sTIM_TimeBaseStructure.TIMER_Prescaler = 107; //计数器时钟预分频值,计数器时钟等于 PSC 时钟除以 (PSC+1)sTIM_TimeBaseStructure.TIMER_ClockDivision = TIMER_CDIV_DIV1; //设置时钟分割:fDTS = fTIMER_CKsTIM_TimeBaseStructure.TIMER_CounterMode = TIMER_COUNTER_UP; //TIM向上计数模式 TIMER_BaseInit(TIMER2, &sTIM_TimeBaseStructure); //根据TIM_TimeBaseInitStruct中指定的参数初始化TIMx的时间基数单位sTIM_ICInitCaptureStructure.TIMER_ICSelection = TIMER_IC_SELECTION_DIRECTTI; //通道x配置为输入, ISx 映射在 CIxFEx 上sTIM_ICInitCaptureStructure.TIMER_ICPrescaler = TIMER_IC_PSC_DIV1; //时钟分频sTIM_ICInitCaptureStructure.TIMER_ICPolarity = TIMER_IC_POLARITY_RISING; //上升沿捕获sTIM_ICInitCaptureStructure.TIMER_ICFilter = 0x00; //不滤波sTIM_ICInitCaptureStructure.TIMER_CH = TIMER_CH_3; //PB10对应Timer2的通道3(手册是通道2,标准库有错位)TIMER_ICInit(TIMER2,&sTIM_ICInitCaptureStructure); sTIM_ICInitCaptureStructure.TIMER_CH = TIMER_CH_4; //PB11对应Timer2的通道4(手册是通道3,标准库有错位)TIMER_ICInit(TIMER2,&sTIM_ICInitCaptureStructure); NVIC_InitStructure.NVIC_IRQ = TIMER2_IRQn; //TIM2中断NVIC_InitStructure.NVIC_IRQPreemptPriority = 0; //Q抢占优先级优先级0级NVIC_InitStructure.NVIC_IRQSubPriority = 5; //副优先级2级NVIC_InitStructure.NVIC_IRQEnable = ENABLE; //IRQ通道被使能NVIC_Init(&NVIC_InitStructure); TIMER_INTConfig(TIMER2,TIMER_INT_UPDATE,ENABLE); //使能定时器溢出中断,暂不使能通道捕获中断TIMER_Enable(TIMER2, ENABLE); //使能定时器外设,暂不使能中断
}
三、逻辑结构
配置完成后,会分别轮询打开两个通道的输入捕获中断,当脉冲的第一个上升沿触发对应通道的输入捕获中断时,会捕获第一个定时器计数值并将变量WaveEgde 置为1 ,说明此时已经采集到第一个上升沿。当WaveEgde = 1的情况下,当再次触发中断时候会判断是定时器溢出中断还是上升沿触发的输入捕获中断,如果是定时器溢出,说明定时器重新计数了,会将录有效溢出次数+1;如果发生上升沿捕获中断,则说明第二个上升沿到来,会捕获第一个定时器计数值且已采集到一个完整波形周期。数据采集结束,关闭该通道的输入捕获中断,并将捕获完成标记位置位,变量WaveEgde 置为0(无采集状态)。然后根据是否有有效溢出次数,选择计算方式计算两次捕获总的时间差。整个采集流程如图所示:
以上是一个通道的采集流程,使用多个通道采集时采集流程基本原理一致,但是如果同时打开多个通道的捕获中断,会导致某些通道的数据差异非常大,这个差异随着增加的通道数量越多而变得明显。因为如果通道数量太多,会在集中一段时间内频繁进中断导致数据采集不准确,所以我们使用轮询方式打开通道的捕获中断,轮询间隔时间设置为200ms,这段逻辑代码如下所示:
定义了捕获数据结构体,所有的数据都保存在此结构体中
typedef struct
{uint8_t WaveEgde; //Eegd = 0,表示当前处于低电平,Egde = 1,表示当前处于高电平uint8_t ucFinishFlag; //捕获结束标记位uint16_t ucCaptureRisingVal[2]; //输入捕获值,[0]:第1次触发捕获值,[1]:第2次触发捕获值uint32_t ucUpdateCnt; //记录溢出次数uint32_t ulFanSpeed; //风扇转速uint32_t ulFrequency; //输入波形频率
}Capture_DateType;Capture_DateType WaveCap[FAN_Count_Num]; //定义两个风扇的捕获数据结构体
这里只给出了Timer2的中断回调函数中的逻辑结构
/**********************************************************************1-函数名:Fan_PwmI_IRQFunction*2-函数功能:Timer2的中断回调函数*3-输入参数:无*4-返回值:无*5-输入全局变量:无*6-输出全局变量:无*7-创建者与创建日期: Awen_ 2023-9-24**********************************************************************/
void Fan_PwmI_IRQFunction(void)
{if(TIMER_GetIntBitState(TIMER2,TIMER_INT_UPDATE) != RESET){ /*定时器溢出中断*/TIMER_ClearIntBitState(TIMER2,TIMER_INT_UPDATE);PwmInput_Timer_Update_Handler();}if(TIMER_GetIntBitState(TIMER2,TIMER_INT_CH3) != RESET){/*风扇1通道输入捕获中断*/TIMER_ClearIntBitState(TIMER2,TIMER_INT_CH3);PwmInput_Capture(FAN_NO_0);}if(TIMER_GetIntBitState(TIMER2,TIMER_INT_CH4) != RESET){/*风扇2通道输入捕获中断*/TIMER_ClearIntBitState(TIMER2,TIMER_INT_CH4);PwmInput_Capture(FAN_NO_1);}else{}
}
/**********************************************************************1-函数名:PwmInput_Timer_Update_Handler*2-函数功能:判断是否是有效溢出*3-输入参数:无*4-返回值:无*5-输入全局变量:无*6-输出全局变量:无*7-创建者与创建日期:Awen_ 2023-9-24**********************************************************************/
static void PwmInput_Timer_Update_Handler(void)
{uint8_t Index = 0;for(Index = 0;Index < FAN_Count_Num;Index++) //FAN_Count_Num:总风扇个数{if(WaveCap[Index].WaveEgde == HIGH_LEVEL) //是否是高电平状态{WaveCap[Index].ucUpdateCnt++; //有效溢出 }else{} }
}
/**********************************************************************1-函数名:PwmInput_Capture*2-函数功能:输入捕获中断中的处理*3-输入参数:无*4-返回值:无*5-输入全局变量:无*6-输出全局变量:无*7-创建者与创建日期:Awen_ 2023-9-24**********************************************************************/
static void PwmInput_Capture(uint8_t Channel)
{ /*是否是第一次捕获*/if(WaveCap[Channel].WaveEgde == LOW_LEVEL){/*第一次捕获*/WaveCap[Channel].ucCaptureRisingVal[0] = FanPwm_Input_GetCapture(Channel); //第一次捕获CNTWaveCap[Channel].WaveEgde = HIGH_LEVEL; //高电平状态 }else if(WaveCap[Channel].WaveEgde == HIGH_LEVEL){/*第二次捕获*/WaveCap[Channel].ucCaptureRisingVal[1] = FanPwm_Input_GetCapture(Channel); //第二次捕获CNTFanPwm_Input_EnableINT(Channel, DISABLE); //关闭中断WaveCap[Channel].WaveEgde = LOW_LEVEL; //恢复到低电平状态 WaveCap[Channel].ucFinishFlag = TRUE; ///捕获完成标记位}else{WaveCap[Channel].WaveEgde = LOW_LEVEL;FanPwm_Input_EnableINT(Channel, DISABLE);}
}
最后频率的计算方式
static void FanPwmI_SpeedCalcul()
{uint8_t Index = 0;uint32_t Freq_vallue;for(Index = 0;Index < FAN_Count_Num;Index++){if(WaveCap[Index].ucFinishFlag == TRUE) //判断采集完成标记位{if(WaveCap[Index].ucUpdateCnt > 0) //是否有有效溢出{/*算式1*/Freq_vallue =(0xFFFF - WaveCap[Index].ucCaptureRisingVal[0]) + ((WaveCap[Index].ucUpdateCnt - 1) * 0xFFFF) + WaveCap[Index].ucCaptureRisingVal[1];}else{/*算式2*/Freq_vallue =WaveCap[Index].ucCaptureRisingVal[1] - WaveCap[Index].ucCaptureRisingVal[0];}WaveCap[Index].ulFrequency = 1000000 / (Freq_vallue + 1); //频率计算WaveCap[Index].ulFanSpeed = WaveCap[Index].ulFrequency * 30; //根据风扇手册计算转速 }else{}WaveCap[Index].ucFinishFlag = FALSE; //采集完成标记位复位WaveCap[Index].ucUpdateCnt = 0; //有效溢出计数复位}
}
总结
输入捕获还可采集波形占空比,其原理相同,只需要在第一次捕获之后改为下降沿触发,采集到第一个上升沿到第一个下降沿的CNT值,然后再设置为上升沿触发,采集一个完整波形周期CNT值,然后计算得到占空比。比较高效的做法是:硬件上使用Timer的两个通道的GPIO同时连到需要采集的波形管脚上,一个通道采集上升沿得到整个波形的周期,另一个通道采集下降沿得到波形高电平时长,再计算占空比。
相关文章:

GD32_定时器输入捕获波形频率
GD32_定时器输入捕获波形频率(多通道轮询) 之前项目上用到一个使用定时器捕获输入采集风扇波形频率得到风扇转速的模块,作为笔记简单记录以下当时的逻辑结构和遇到的问题,有需要参考源码、有疑问或需要提供帮助的可以留言告知 。…...

单窗口单IP适合炉石传说游戏么?
游戏道具制作在炉石传说中是一个很有挑战的任务,但与此同时,它也是一个充满机遇的领域。在这篇文章中,我们将向您展示如何在炉石传说游戏中使用动态包机、多窗口IP工具和动态IP进行游戏道具制作。 作者与主题的关系:作为一名热爱炉…...

win11安装docekr、docker-compose
1.docker安装 下载地址:Install Docker Desktop on Windows | Docker Docs 出问题别慌,看清楚提示信息,cmd更新wsl,什么是wsl,百度好好理解一下哦 2.docker-compose安装 还是去官方看看怎么说的,然后跟着处…...

Postman的简单使用
Postman简介 官网 Postman是Google公司开发的一款功能强大的网页调试与发送HTTP请求,并能运行测试用例的Chrome插件 使用Postman进行简单接口测试 新建测试 → 选择请求方式 → 请求URL,下面用百度作为例子: 参考文档 [1] Postman使用教程…...

信号继电器驱动芯片(led驱动芯片)
驱动继电器需要配合BAV99(防止反向脉冲)使用 具体应用参考开源项目 电阻箱 sbstnh/programmable_precision_resistor: A SCPI programmable precision resistor (github.com) 这个是芯片的输出电流设置 对应到上面的实际开源项目其设置电阻为1.5K&…...

IDEA配置HTML和Thymeleaf热部署开发
IDEA配置HTML和Thymeleaf热部署开发 1.项目配置2. IDEA配置3. 使用 需求:现在我们在开发不分离项目的时候(SpringBootThmeleaf)经常会改动了类或者静态html文件就需要重启一下服务器, 这样不仅时间开销很大,而且经常重…...

Nginx动静分离
为了加快网站的解析速度,可以把动态页面和静态页面由不同的服务器来解析,加快解析速度。降低原来单个服务器的压力。 在动静分离的tomcat的时候比较明显,因为tomcat解析静态很慢,其实这些原理的话都很好理解,简单来说&…...

Spring中AOP详解
目录 一、AOP的概念 二、AOP的底层实现原理 2.1 JDK的动态代理 2.1.1 invocationhandler接口 2.1.2 代理对象和原始类实现相同的接口 interfaces 2.1.3 类加载器ClassLoador 2.1.4 编码实现 2.2 Cglib动态代理 2.2.1 Cglib动态代理编码实现 三、AOP如何通过原始对象的id获取到代…...

Unity DOTS系列之Filter Baking Output与Prefab In Baking核心分析
最近DOTS发布了正式的版本, 我们来分享一下DOTS里面Baking核心机制,方便大家上手学习掌握Unity DOTS开发。今天给大家分享的Baking机制中的Filter Baking Output与Prefab In Baking。 对啦!这里有个游戏开发交流小组里面聚集了一帮热爱学习游戏的零基础…...

Matlab读写操作
随机生成一个3*3矩阵,对矩阵进行按列升序排列 >> Arand(3,3); >> [B, ~] sort(A, 2); >> B B 0.4898 0.6797 0.70940.4456 0.6551 0.75470.1626 0.2760 0.6463在不同数值类型下显示π的值 1、默认数值类型 >> p_defa…...

Android 开发技巧:音乐播放器的后台处理【Service、Handler、MediaPlayer】
给定部分完成的MusicPlayer项目,实现其中未完成的service部分: 1、创建MusicService类,通过service组件实现后台播放音乐的功能; 2、在MainActivity中通过ServiceConnection连接MusicService,实现对音乐播放的控制&…...

使用Windows平台的Hyper-V虚拟机安装CentOS7的详细过程
Hyper-V虚拟机安装CentOS7 前言常见Linux系统CentOSUbuntuDebianKaliFedoraArch LinuxMintManjaroopenSUSE Hyper-V开启Hyper-V打开Hyper-V Hyper-V的使用新建虚拟机开始安装分区配置开始安装 修改yum源为阿里源 前言 作为一名开发者,就服务器而言,接触最…...

某马机房预约系统 C++项目(二) 完结
8.4、查看机房 8.4.1、添加机房信息 根据案例,我们还是先在computerRoom.txt中直接添加点数据 //几机房 机器数量 1 20 2 50 3 1008.4.2、机房类创建 同样我们在头文件下新建一个computerRoom.h文件 添加如下代码: #pragma once #include<i…...

npm 安装到指定文件夹
创建一个文件夹,用vscode或者cmd打开, 执行 npm install --prefix ./ 路径 包名, npm install --prefix ./ 包名 , 就会将包安装在当前文件夹, 例如: npm install --prefix ./ -g oppo-minigame…...

自建的离散傅里叶变换matlab程序实现及其与matlab自带函数比较举例
自建的离散傅里叶变换matlab程序实现及其与matlab自带函数比较举例 在matlab中有自带的离散傅里叶变换程序,即fft程序,但该程序是封装的,无法看到源码。为了比较清楚的了解matlab自带的实现过程,本文通过自建程序实现matlab程序&…...

Vue图片路径问题(动态引入)
vue项目中我们经常会遇到动态路径的图片无法显示的问题,以下是静态路径和动态路径的常见使用方法。 1.静态路径 在日常的开发中,图片的静态路径通过相对路径和绝对路径的方式引入。 相对路径:以.开头的,例如./、../之类的。就是…...

项目部署Linux步骤
1、最小化安装centos7-环境准备 安装epel-release 安装epel-release,因为有些rpm包在官方库中找不到。前提是保证可以联网 yum install -y epel-release 修改IP net-tools net-tool:工具包集合,包含ifconfig等命令 yum install -y net-…...

UG\NX二次开发 在资源栏(左侧面板)中添加按钮
文章作者:里海 来源网站:王牌飞行员_里海_里海NX二次开发3000例,里海BlockUI专栏,C\C++-CSDN博客 感谢粉丝订阅 感谢 apolloryd 订阅本专栏,非常感谢。 简介 UG\NX二次开发 在资源栏(左侧面板)中添加按钮,下面提供了帮助说明,在 UGOPEN 文件夹下有示例。 C++语言在UG二次…...

Proteus仿真--量程自动切换数字电压表(仿真+程序)
本文主要介绍基于51单片机的量程自动切换数字电压表Proteus仿真设计(完整仿真源文件及代码见文末链接) 简介 硬件电路主要分为单片机主控模块、AD转换模块、量程选择模块以及数码管显示模块 (1)单片机主控模块:单片…...

如何使用ArcGIS Pro制作一张地形图
01数据来源 本教程所使用的数据是从水经微图中下载的DEM数据,除了DEM数据,常见的GIS数据都可以从水经微图中下载,你可以通过关注“水经注GIS”,然后在后台回复“微图”即可获取软件下载地址,当然也可以直接在水经注…...

人工智能三要数之算法Transformer
1. 人工智能三要数之算法Transformer 人工智能的三个要素是算法、数据和计算资源。Transformer 模型作为一种机器学习算法,可以应用于人工智能系统中的数据处理和建模任务。 算法: Transformer 是一种基于自注意力机制的神经网络模型,用于处理序列数据的…...

Java ThreadPoolExecutor 线程池
import java.util.concurrent.ThreadPoolExecutor; import java.util.concurrent.TimeUnit; import java.util.concurrent.ArrayBlockingQueue;public class ThreadPoolExample {public static void main(String[] args) {// 创建线程池对象ThreadPoolExecutor threadPool new…...

网络协议--IP选路
9.1 引言 选路是IP最重要的功能之一。图9-1是IP层处理过程的简单流程。需要进行选路的数据报可以由本地主机产生,也可以由其他主机产生。在后一种情况下,主机必须配置成一个路由器,否则通过网络接口接收到的数据报,如果目的地址不…...

使用udevil自动挂载U盘或者USB移动硬盘
最近在折腾用树莓派(实际上是平替香橙派orangepi zero3)搭建共享文件服务器,有一个问题很重要,如何在系统启动时自动挂载USB移动硬盘。 1 使用/etc/fstab 最开始尝试了用/etc/fstab文件下增加:"/dev/sda1 /home/orangepi/s…...

学习笔记二十二:K8s控制器Replicaset
K8s控制器Replicaset Replicaset控制器:概念、原理解读Replicaset概述Replicaset工作原理:如何管理PodReplicaset控制器三个组成部分 Replicaset资源清单文件编写技巧Replicaset使用案例:部署Guestbook留言板编写一个ReplicaSet资源清单资源清…...

2023-10-25 精神分析-领悟新技术的错误做法-持续数年的错误做法-记录与分析
摘要: 过去数年对于领悟技术, 采取的做法不能说是对达到目的毫无裨益,但是对突破技术和将技术融为自身这个目的来说, 没有达到。 而且随着时间的流逝, 过去已经熟悉的技术, 竟然会被忘掉!就像是没有涉猎过一样! 根本原因出在对技术的领悟的…...

Arrays 中的 asList()方法
public static <T> List<T> asList( T . . . a ){ return new ArrayList<>(a); } 返回由指定数组支持的固定大小的 list集合。对数组所做的更改将在返回的 l…...

基于自动化工具autox.js的抢票(猫眼)
1.看到朋友圈抢周杰伦、林俊杰演唱会票贼难信息,特研究了一段时间,用autox.js写了自动化抢票脚本,购票页面自动点击下单(仅限安卓手机)。 2.脚本运行图 3.前期准备工作 (1)autox.js社区官网:AutoX.js (2)b站上学习资料:10分钟学会AutoX.js hello world_哔哩哔哩_bi…...

Java架构师内功计算机网络
目录 1 导学2 网络功能和分类3 OSI七层模型3.1 局域网和广域网协议4 TCP/IP协议5 通信技术和交换技术5.1 通信技术5.2 交换技术5.2.1 路由技术5.2.2 传输介质6 通信方式和交换方式7 IP地址7.1 IP地址表示7.2 子网划分8 IPv69 网络规划与设计10 网络存储技术10.1 廉价磁盘几余阵…...

vue 中 mixin 和 mixins 区别
目录 前言 用法 全局Mixin 局部Mixin 代码 理解 高质量的Mixin使用 在Vue.js框架中,Mixin是一种非常重要和强大的功能,它允许开发者创建可复用的代码片段,并将其应用到一个或多个组件中。Vue提供了两种方式来使用Mixin,分别…...