GD32_定时器输入捕获波形频率

news2024/12/25 2:19:42

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_CK
    sTIM_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); //第一次捕获CNT
       WaveCap[Channel].WaveEgde = HIGH_LEVEL;		//高电平状态  
    }
    else if(WaveCap[Channel].WaveEgde == HIGH_LEVEL)
    {
    	/*第二次捕获*/
       WaveCap[Channel].ucCaptureRisingVal[1] = FanPwm_Input_GetCapture(Channel); //第二次捕获CNT
       FanPwm_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同时连到需要采集的波形管脚上,一个通道采集上升沿得到整个波形的周期,另一个通道采集下降沿得到波形高电平时长,再计算占空比。

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

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

相关文章

怎么防止公司终端电脑文件资料、设计图纸外泄?

PC营销页&#xff1a; https://isite.baidu.com/site/wjz012xr/2eae091d-1b97-4276-90bc-6757c5dfedee 天锐绿盾防泄密软件主要通过以下几种方式来防止公司设计图纸泄露&#xff1a; 透明加密&#xff1a;天锐绿盾采用透明加密技术&#xff0c;对设计图纸进行加密。这种加密方…

护眼灯台灯哪个牌子好?五款公认最专业护眼台灯推荐

今天跟大家聊聊现在热门的灯具&#xff0c;护眼台灯。目前的护眼台灯市场水很深&#xff0c;有很多不符标准、不适宜学习用光的护眼台灯&#xff0c;以低价吸引大家&#xff0c;在市面上热销&#xff0c;甚至销量非常高。其实这类产品在照度、光衰弱等多项核心参数上根本没有进…

p11 第63题 请设计一个判断字母序列是否对称的算法 桂林电子科技大学015年 数据结构(c语言代码实现)

本题代码如下 int symmetry(linklist* L)//判断循环双链表是否对称 {lnode* p (*L)->next, * q (*L)->prior;while (p ! q && q->next ! p){if (p->data ! q->data)return 0;else{p p->next;q q->prior;}}return 1; } 完整测试代码 #inclu…

快速排序(c语言代码实现)

交换排序&#xff1a;快速排序&#xff08;不稳定的排序&#xff09; 快速排序&#xff08;Quick Sort&#xff09;是一种常见的排序算法&#xff0c;它采用分治法的思想&#xff0c;对待排序序列进行划分&#xff0c;使得划分出的子序列可以分别进行排序&#xff0c;最终使整…

Python OpenCV剪裁图片并修改对应的Labelme标注文件

Python OpenCV剪裁图片并修改对应的Labelme标注文件 前言前提条件相关介绍实验环境剪裁图片并修改对应的Labelme标注文件代码实现 前言 由于本人水平有限&#xff0c;难免出现错漏&#xff0c;敬请批评改正。更多精彩内容&#xff0c;可点击进入Python日常小操作专栏、OpenCV-P…

RAG之微调垂域BGE的经验之谈

文章目录 前言数据格式部分代码训练参数接下来的尝试总结 前言 随着大模型的爆火&#xff0c;很多垂域行业都开始使用大模型来优化自己的业务&#xff0c;最典型的方法就是RAG&#xff08;检索增强生成&#xff09;了。简单来说就是利用检索技术&#xff0c;找出与用户问题相关…

一、PHP环境搭建[phpstorm]

一、安装 1.php编写工具 地址&#xff1a;https://www.jetbrains.com/phpstorm/download/#sectionwindows 图示&#xff1a; 2.php环境 解释&#xff1a;建议使用phpstudy进行安装&#xff0c;安装较为简单 链接&#xff1a;https://www.xp.cn/ 图示&#xff1a; 二、第…

四、W5100S/W5500+RP2040树莓派Pico<TCP Server数据回环测试>

文章目录 1. 前言2. 协议简介2.1 简述2.2 优点2.3 应用 3. WIZnet以太网芯片4. TCP Server数据回环测试4.1 程序流程图4.2 测试准备4.3 连接方式4.4 相关代码4.5 测试现象 5. 注意事项6. 相关链接 1. 前言 在计算机网络中&#xff0c;TCP Server是不可或缺的角色&#xff0c;它…

APP逆向基础(APK流程)

APK的基本结构 Android体系结构和APK基本结构-CSDN博客 APK 打包流程 【Android 安装包优化】APK 打包流程 ( 文件结构 | 打包流程 | 安装流程 | 安卓虚拟机 )_adnroid 安装包优化,打指定资源_韩曙亮的博客-CSDN博客 APK安装流程

Linux下根目录都包含什么? 每个文件什么作用?

bin: binary, 二进制文件目录, 存储了可执行程序, 系统的命令对应的可执行程序都在这个目录中 sbin: super binary, root用户使用的一些二进制可执行程序 home: 存储了普通用户的家目录&#xff0c;家目录名和用户名相同 opt: 第三方软件的安装目录 &#xff08;交叉编译等…

【tio-websocket】9、服务配置与维护—TioConfig

场景 我们在写 TCP Server 时,都会先选好一个端口以监听客户端连接,再创建N组线程池来执行相关的任务,譬如发送消息、解码数据包、处理数据包等任务,还要维护客户端连接的各种数据,为了和业务互动,还要把这些客户端连接和各种业务数据绑定起来,譬如把某个客户端绑定到一…

2001-2021年省、上市公司五年规划产业政策整理代码+匹配结果

2001-2021年省、上市公司五年规划产业政策整理代码匹配结果 1、时间&#xff1a;2001-2021年 2、来源&#xff1a;整理自wind、国民经济和社会发展五年规划纲要 3、指标&#xff1a; 上市公司数据指标&#xff1a; 国家代码、证券代码、证券简称、公司全称、公司英文全称、…

运行 Python 脚本/代码的几种方式

哈喽大家好&#xff0c;我是咸鱼 我们知道&#xff0c;python 脚本或者说 python 程序其实是一个包含了 python 代码的文件。要让它们实现特定功能&#xff0c;我们需要知道该如何运行&#xff08;run&#xff09;它 通过运行 python 代码&#xff0c;我们可以验证脚本/程序是…

Elasticsearch:使用 Open AI 和 Langchain 的 RAG - Retrieval Augmented Generation (三)

这是继之前文章&#xff1a; Elasticsearch&#xff1a;使用 Open AI 和 Langchain 的 RAG - Retrieval Augmented Generation &#xff08;一&#xff09; Elasticsearch&#xff1a;使用 Open AI 和 Langchain 的 RAG - Retrieval Augmented Generation &#xff08;二&…

JAVA设计模式详解(独家AI解析)

JAVA设计模式详解&#xff08;独家AI解析&#xff09; 一、JAVA介绍二、JAVA设计模式六大原则三、JAVA设计模式介绍四、JAVA设计模式详解4.1 单例模式4.1.1 懒汉式&#xff08;Lazy Initialization&#xff09;4.1.2 饿汉式&#xff08;Lazy Initialization&#xff09; 4.2 代…

本地化ddddocr库,完成验证码图片识别,完整流程

1.pycharm-3.8环境&#xff0c;代码&#xff0c;ddddocr库&#xff0c;以及测试图片 2.代码&#xff1a; import ddddocr ocr ddddocr.DdddOcr(oldTrue) with open("1.jpg", rb) as f:image f.read() res ocr.classification(image) print(res)3.完整打包&#…

sheng的学习笔记-【中】【吴恩达课后测验】Course 3 - 结构化机器学习项目 - 第二周测验

课程3_第2周_测验题 目录&#xff1a;目录 要解决的问题 ① 为了帮助你练习机器学习的策略&#xff0c;本周我们将介绍另一个场景&#xff0c;并询问你将如何行动。 ② 我们认为这个在机器学习项目中工作的“模拟器”将给出一个任务&#xff0c;即领导一个机器学习项目可能…

教师必备宝藏,强烈推荐

亲爱的教师朋友们&#xff0c;你们是不是在为学期末成绩查询而头疼呢&#xff1f;一学期下来&#xff0c;成堆的试卷和成绩单&#xff0c;还有学生家长的各种咨询&#xff0c;让人应接不暇。现在&#xff0c;我给你们分享一个教师必备的宝藏&#xff0c;让你们的成绩查询工作变…

Mac虚拟机哪个好用,CrossOver23.6虚拟机激活许可证激活码2023最新分享

刚买了苹果电脑的用户&#xff0c;经常会因为用不惯苹果系统而想换Windows系统&#xff0c;实际上也的确是&#xff0c;许多流行游戏或软件都暂不支持Mac系统&#xff0c;可行的办法是安装Mac虚拟机或是双系统&#xff0c;以供支持在苹果电脑上使用Windows应用。下面本文就来讲…

小程序如何设置自取规则

​在小程序中&#xff0c;自取规则是指当客户下单时选择无需配送的情况下&#xff0c;如何设置相关的计费方式、指定时段费用、免费金额、预定时间和起取金额。下面将详细介绍如何设置这些规则&#xff0c;以便更好地满足客户的需求。 在小程序管理员后台->配送设置->自…