【江科大STM32】TIM输入捕获模式PWMI模式测频率

一、输入捕获测频率

 接线图:

测信号的输入引脚为PA6,信号从PA6进来,待测的PWM信号也是STM32自己生成的,输出引脚是PA0,所以接线这里直接用一根线将PA0引到PA6就可以了。 如果有信号发生器的话,也可以设置成方波信号输出,高电平设置成3.3v,低电平0v,然后直接接到PA6,另一个就是共地。

PWM初始化模块:

目前这里我们要借用一下之前写过的PWM模块代码,以便生成待测信号,所以这里程序直接复制,之前的PWM驱动LED呼吸灯工程,在这个工程基础上写。然后在这个PWM模块代码进行改进,目前这个代码逻辑是,初始化TIM2的通道1,产生一个PWM的波形,输出引脚是PA0,然后通过Set_Compare函数,可以调节CCR1的寄存器的值,从而控制PWM的占空比,但是目前这个PWM频率是在初始化已经写好的了,是固定的,操作起来不太方便。所以我们要在最后再加一个函数,用来便捷地调节PWM频率。

那这里如何调节PWM频率? 

通过公式,我们知道PWM频率=更新频率=72M/(PSC+1/(ARR+1),所以PSC和ARR都可以调节频率,但是占空比=CCR/(ARR+1),所以通过ARR调节频率,同时还会影响到占空比,而通过PSC调节频率,不会影响占空比,显然比较方便。所以这里我们直接固定ARR为100-1,通过调节PSC来改变PWM频率,这里的ARR为100-1,CCR的数值直接就是占空比。

这里实际使用也是有技巧的,一般可以根据分辨率的要求,先确定好ARR值,比如分辨率[1/(ARR+1)],1%就足够了。那ARR就是给100-1,这样PSC决定频率,CCR决定占空比。如果想要更高的分辨率,比如0.1%,那ARR就先固定1000-1,这样频率就是72M/预分频/1000,占空比就是CCR/1000,这样也好算。

然后ARR我们固定给100-1,初始化操作的PSC就先不管,后面再写一个函数,在初始化之后单独修改PSC。 

void PWM_SetPrescaler(uint16_t Prescaler) //配置TIMx预调度器。
{TIM_PrescalerConfig(TIM2,Prescaler,TIM_PSCReloadMode_Immediate);//Prescaler立即被加载
}

代码详解: 

 void TIM_PrescalerConfig(TIM_TypeDef* TIMx, uint16_t Prescaler, uint16_t TIM_PSCReloadMode)//配置TIMx预调度器

参数说明
TIMx其中x为1 ~ 17,选择TIM外设
Prescaler指定预Prescaler寄存器值
TIM_PSCReloadMode指定TIM预分频器的重装模式。该参数可以是以下值之一:TIM_PSCReloadMode_Update:在更新事件中加载precaler;TIM_PSCReloadMode_Immediate:立即加载precaler

TIM_PSCReloadMode的两个选择参数说白了还是影子寄存器的预装载问题,就是你写入的值是立刻生效还是在更新事件中生效。

  • 立刻生效:可能在值发生改变时产生切断波形的现象,比如PWM一个周期刚过去一半,立刻生效了,那就立刻切断当前波形,开始新的一个周期。在频率变化时,这里会出现一个不完整的周期。
  • 更新事件生效:就是会有一个缓存器,延迟参数的写入时间,等一个周期结束了,在更新事件时,再统一改变参数, 保证每个周期的完整。

在这里我们选择哪个参数都无所谓,我们要求不高,哪个都可以,那这里就选择立刻生效吧。 

主函数这里调用之后就生成一个频率为 1KHz,占空比为50%的信号了。

int main(void)
{OLED_Init();PWM_Init();PWM_SetPrescaler(720 - 1);  //频率=72M/(PSC - 1)/(ARR - 1) 这里(ARR - 1)=100PWM_SetCompare1(50);		//Duty= CCR/(ARR - 1)while(1){}
}

输入捕获初始化:

这里因为是自己测自己,所以还要建一个输入捕获模块代码,测一下PA0口的频率和占空比。

 

步骤: 

①RCC开启时钟(把要用的TIM外设和GPIO外设时钟都打开) 

②GPIO初始化,把GPIO配置为输入模式 ,一般选择上拉或者浮空输入模式

③配置时基单元,让CNT计数器在内部时钟驱动下自增运行 

④配置输入捕获单元,包括滤波器、极性选择、直连通道还是交叉通道、分频器这些参数,用一个结构体就可以配置

⑤选择从模式的触发源触发源为TF1FP1,这里调用一个库函数给一个参数即可

⑥选择触发之后执行的操作

⑦开启定时器 

 代码:

 这里的时钟选择,要选择TIM3,也是APB1外设时钟,因为PWM模块还需要TIM2输出PWM,所以要开启TIM3时钟。GPIO初始化需要查看引脚定义表,TIM3通道1对应的是PA6引脚,如果选择其它定时器或者其它通道,那这个引脚就需要根据引脚定义表改变

 

这里的TIM_Prescaler值决定了测周法的标准频率Fc,72M/预分频就是就计数器自增的频率,就是计数器标准频率,这个需要根据你信号频率的分布范围来调整,这里暂时先给72-1,这样标准频率就是72M/72=1MHz,这样方便计算。 

 void TIM_ICInit(TIM_TypeDef* TIMx, TIM_ICInitTypeDef* TIM_ICInitStruct)//根据指定初始化TIM外设TIM_ICInitStruct中的参数

 输入捕获初始化结构定义
参数说明
TIM_Channel指定TIM通道(1~4通道)
TIM_ICPolarity极性选择,指定输入信号的活动边缘(上升、下降、双边沿触发)
TIM_ICSelection指定输入(直连或者交叉输入)
TIM_ICPrescaler指定输入捕获预分频(1、2、4、8分频,1分频就是不分频)
TIM_ICFilter指定输入捕获过滤器,取值范围为0x0 ~ 0xF之间的数字,数值越大,滤波效果越好
滤波器和分频器的区别: 

虽然两个都是计次的东西,但是滤波器计次,并不会改变信号的原有频率,一般滤波器的采样频率都会远高于信号频率,所以它只会滤除高频噪声,使信号更加平滑,1KHz滤波之后仍然是1KHz,信号频率不会发生变化。而分频器就是对信号本身进行计次,会改变频率,1KHz,二分频之后就是500Hz,四分频之后就是250Hz。

对应步骤⑤ :

void TIM_SelectInputTrigger(TIM_TypeDef* TIMx, uint16_t TIM_InputTriggerSource)//选择输入触发器源 

参数说明
TIMx其中x可以是1、2、3、4、5、8、9、12或15来选择TIM外设。
TIM_InputTriggerSource输入触发器源。

 TIM_InputTriggerSource参数选择:下面两图对应

对应步骤⑥ 

 void TIM_SelectSlaveMode(TIM_TypeDef* TIMx, uint16_t TIM_SlaveMode)//选择TIMx从模式

参数说明
TIMx其中x可以是1、2、3、4、5、8、9、12或15来选择TIM外设。
TIM_SlaveMode指定定时器从模式。

TIM_SlaveMode参数选择: 

最后,启动定时器后,CNT就会在内部时钟的驱动下不断自增,即使信号没有过来它也会自增。一直自增也没有关系,因为在有信号过来的时候,它就会在从模式下自动清零,不会影响测量。初始化完成后,整个电路就能实现全自动测量了。 

void IC_Init(void)
{/*开启时钟*/RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM3, ENABLE);			//开启TIM3的时钟RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);			//开启GPIOA的时钟/*GPIO初始化*/GPIO_InitTypeDef GPIO_InitStructure;GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU;GPIO_InitStructure.GPIO_Pin = GPIO_Pin_6;GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;GPIO_Init(GPIOA, &GPIO_InitStructure);							//将PA6引脚初始化为上拉输入/*配置时钟源*/TIM_InternalClockConfig(TIM3);		//选择TIM3为内部时钟,若不调用此函数,TIM默认也为内部时钟/*时基单元初始化*/TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStructure;				//定义结构体变量TIM_TimeBaseInitStructure.TIM_ClockDivision = TIM_CKD_DIV1;     //时钟分频,选择不分频,此参数用于配置滤波器时钟,不影响时基单元功能TIM_TimeBaseInitStructure.TIM_CounterMode = TIM_CounterMode_Up; //计数器模式,选择向上计数TIM_TimeBaseInitStructure.TIM_Period = 65536 - 1;               //计数周期,即ARR的值TIM_TimeBaseInitStructure.TIM_Prescaler = 72 - 1;               //预分频器,即PSC的值TIM_TimeBaseInitStructure.TIM_RepetitionCounter = 0;            //重复计数器,高级定时器才会用到TIM_TimeBaseInit(TIM3, &TIM_TimeBaseInitStructure);             //将结构体变量交给TIM_TimeBaseInit,配置TIM3的时基单元/*输入捕获初始化*/TIM_ICInitTypeDef TIM_ICInitStructure;							//定义结构体变量TIM_ICInitStructure.TIM_Channel = TIM_Channel_1;				//选择配置定时器通道1TIM_ICInitStructure.TIM_ICFilter = 0xF;							//输入滤波器参数,可以过滤信号抖动TIM_ICInitStructure.TIM_ICPolarity = TIM_ICPolarity_Rising;		//极性,选择为上升沿触发捕获TIM_ICInitStructure.TIM_ICPrescaler = TIM_ICPSC_DIV1;			//捕获预分频,选择不分频,每次信号都触发捕获TIM_ICInitStructure.TIM_ICSelection = TIM_ICSelection_DirectTI;	//输入信号交叉,选择直通,不交叉TIM_ICInit(TIM3, &TIM_ICInitStructure);							//将结构体变量交给TIM_ICInit,配置TIM3的输入捕获通道/*选择触发源及从模式*/TIM_SelectInputTrigger(TIM3, TIM_TS_TI1FP1);					//触发源选择TI1FP1TIM_SelectSlaveMode(TIM3, TIM_SlaveMode_Reset);					//从模式选择复位//即TI1产生上升沿时,会触发CNT归零/*TIM使能*/TIM_Cmd(TIM3, ENABLE);			//使能TIM3,定时器开始运行
}

注意:    

void TIM_OC1Init(TIM_TypeDef* TIMx, TIM_OCInitTypeDef* TIM_OCInitStruct);
void TIM_OC2Init(TIM_TypeDef* TIMx, TIM_OCInitTypeDef* TIM_OCInitStruct);
void TIM_OC3Init(TIM_TypeDef* TIMx, TIM_OCInitTypeDef* TIM_OCInitStruct);
void TIM_OC4Init(TIM_TypeDef* TIMx, TIM_OCInitTypeDef* TIM_OCInitStruct);
void TIM_ICInit(TIM_TypeDef* TIMx, TIM_ICInitTypeDef* TIM_ICInitStruct); 

这里输出比较和输入捕获都有四个通道,OCInit四个通道,每个通道占用一个函数,而ICInit是四个通道共用一个函数的,在结构体会有额外一个参数选择哪个通道。

  1. void TIM_SetCompare1(TIM_TypeDef* TIMx, uint16_t Compare1);
  2. void TIM_SetCompare2(TIM_TypeDef* TIMx, uint16_t Compare2);
  3. void TIM_SetCompare3(TIM_TypeDef* TIMx, uint16_t Compare3);
  4. void TIM_SetCompare4(TIM_TypeDef* TIMx, uint16_t Compare4); 
  5. uint16_t TIM_GetCapture1(TIM_TypeDef* TIMx);//获取TIMx输入捕获1值。
  6. uint16_t TIM_GetCapture2(TIM_TypeDef* TIMx);//获取TIMx输入捕获2值。
  7. uint16_t TIM_GetCapture3(TIM_TypeDef* TIMx);//获取TIMx输入捕获3值。
  8. uint16_t TIM_GetCapture4(TIM_TypeDef* TIMx); //获取TIMx输入捕获4值。

 这8个函数都是读取各个通道的CCR值,是相对应的,输出比较模式下,CCR是只写的,用TIM_SetCompare写入;输入捕获模式下,CCR是只读的,用TIM_GetCapture读出。

返回值:各Capture Compare 寄存器值。

查看频率读取CCR(频率)

 这里需要执行公式Fx=Fc/N,之前说Fc=72M/(PSC+1),PSC=72-1,所以72M/72=1MHz,然后除以N,N就是读取CCR的值,用uint16_t TIM_GetCapture1(TIM_TypeDef* TIMx)函数读取

uint32_t IC_GetFreq(void)
{return 1000000 / (TIM_GetCapture1(TIM3) + 1);		//测周法得到频率fx = fc / N,这里不执行+1的操作也可
}

主函数: 

int main(void)
{OLED_Init();PWM_Init();IC_Init();OLED_ShowString(1,1,"Freq:00000Hz");/*使用PWM模块提供输入捕获的测试信号:将待测信号传给PA0,PA0又通过导线输入到PA6,PA6是TIM3的通道1,通道1通过输入捕获模块,测得频率*/PWM_SetPrescaler(720 - 1);  //频率=72M/(PSC - 1)/(ARR - 1) 这里(ARR - 1)=100PWM_SetCompare1(50);		//Duty= CCR/(ARR - 1)while(1){OLED_ShowNum(1,6,IC_GetFreq(),5);//不断刷新显示输入捕获测得的频率}
}

二、PWMI模式测频率&占空比

这里输入捕获代码部分直接对上一个代码进行升级就可以了。配置成两个通道同时捕获一个引脚

 

这里输入捕获有两种配置方法:效果一样 

1、直接复制多一份通道初始化

2、 使用ST公司封装好的函数

void TIM_PWMIConfig(TIM_TypeDef* TIMx, TIM_ICInitTypeDef* TIM_ICInitStruct)//根据指定配置TIM外设TIM_ICInitStruct中的参数来测量外部PWM信号

 作用:这个函数你只要传入一个通道的参数,在函数里它会自动把剩下一个通道初始化成相反的配置,比如这里传入通道1,直连,上升沿,函数就会顺带配置通道2,交叉,下降;传入通道2,直连,上升沿,函数就会顺带配置通道1,交叉,下降沿。这个函数只能传入通道1和通道2,不能传通道3和通道4。

 获取占空比函数:

 根据上节课PWMI分析【STM32】TIM输入捕获-学习笔记-CSDN博客,高电平的计数值存在CCR2里,整个周期的计数值存在CCR1里,用CCR2/CCR1就可以得到占空比了。

/*** 函    数:获取输入捕获的占空比* 参    数:无* 返 回 值:捕获得到的占空比*/
uint32_t IC_GetDuty(void)
{return (TIM_GetCapture2(TIM3) + 1) * 100 / (TIM_GetCapture1(TIM3) + 1);	//占空比Duty = CCR2 / CCR1 * 100,这里不执行+1的操作也可
}

主函数:

int main(void)
{/*模块初始化*/OLED_Init();		//OLED初始化PWM_Init();			//PWM初始化IC_Init();			//输入捕获初始化/*显示静态字符串*/OLED_ShowString(1, 1, "Freq:00000Hz");		//1行1列显示字符串Freq:00000HzOLED_ShowString(2, 1, "Duty:00%");			//2行1列显示字符串Duty:00%/*使用PWM模块提供输入捕获的测试信号*/PWM_SetPrescaler(720 - 1);					//PWM频率Freq = 72M / (PSC + 1) / 100PWM_SetCompare1(50);						//PWM占空比Duty = CCR / 100while (1){OLED_ShowNum(1, 6, IC_GetFreq(), 5);	//不断刷新显示输入捕获测得的频率OLED_ShowNum(2, 6, IC_GetDuty(), 2);	//不断刷新显示输入捕获测得的占空比}
}

 测频率性能:32:43

误差分析:34:45 

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

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

相关文章

湖仓一体化及冷、热、实时三级存储

一、湖仓一体化(Lakehouse) 湖仓一体化(Lakehouse)是数据湖(Data Lake)与数据仓库(Data Warehouse)的结合,旨在解决传统数据架构中数据孤岛、存储冗余、计算性能不足等问…

go切片定义和初始化

1.简介 切片是数组的一个引用,因此切片是引用类型,在进行传递时,遵守引用传递的机制。切片的使用和数组类似,遍历切片、访问切片的元素和切片的长度都一样。。切片的长度是可以变化的,因此切片是一个可以动态变化的数…

游戏引擎学习第138天

仓库:https://gitee.com/mrxiao_com/2d_game_3 资产:game_hero_test_assets_003.zip 发布 我们的目标是展示游戏运行时的完整过程,从像素渲染到不使用GPU的方式,我们自己编写了渲染器并完成了所有的工作。今天我们开始了一些新的内容&#…

毕业项目推荐:基于yolov8/yolov5/yolo11的暴力行为检测识别系统(python+卷积神经网络)

文章目录 概要一、整体资源介绍技术要点功能展示:功能1 支持单张图片识别功能2 支持遍历文件夹识别功能3 支持识别视频文件功能4 支持摄像头识别功能5 支持结果文件导出(xls格式)功能6 支持切换检测到的目标查看 二、数据集三、算法介绍1. YO…

docker中kibana启动后,通过浏览器访问,出现server is not ready yet

问题:当我在浏览器访问kibana时,浏览器给我报了server is not ready yet. 在网上试了很多方法,都未能解决,下面是我的方法: 查看kibana日志: docker logs -f kibana从控制台打印的日志可以发现&#xff…

在 Docker 中,无法直接将外部多个端口映射到容器内部的同一个端口

Docker 的端口映射是一对一的,即一个外部端口只能映射到容器内部的一个端口。 1. 为什么不能多对一映射? 端口冲突: 如果外部多个端口映射到容器内部的同一个端口,Docker 无法区分外部请求应该转发到哪个内部端口,会…

游戏引擎学习第120天

仓库:https://gitee.com/mrxiao_com/2d_game_3 上次回顾:周期计数代码 我们正在进行一个项目的代码优化工作,目标是提高性能。当前正在优化某个特定的代码片段,已经将其执行周期减少到48个周期。为了实现这一目标,我们设计了一个…

C++中的.h文件一般是干什么的?

在C中,.h 文件通常是 头文件(Header File),它们的主要作用是声明类、函数、常量、宏以及其他在多个源文件(.cpp文件)之间共享的元素。头文件提供了一个接口,使得不同的源文件能够访问这些共享的…

基础算法总结

基础算法总结 1、模拟1.1 什么是模拟算法1.2 算法题1.2.1 多项式输出1.2.2 蛇形方阵 2 高精度算法2.1 什么是高精度算法2.2 算法题2.2.1 高精度加法 2.2.2 高精度乘法 3 普通枚举3.1 算法题3.1.1 铺地毯 3.1.2 回文日期 4 前缀和算法4.1 什么是前缀和4.2 算法题4.2.1 最大子段和…

密码学(哈希函数)

4.1 Hash函数与数据完整性 数据完整性: 检测传输消息(加密或未加密)的修改。 密码学Hash函数: 构建某些数据的简短“指纹”;如果数据被篡改,则该指纹(以高概率)不再有效。Hash函数…

游戏引擎学习第135天

仓库:https://gitee.com/mrxiao_com/2d_game_3 回顾 game_asset.cpp 的创建 在开发过程中,不使用任何现成的游戏引擎或第三方库,而是直接基于 Windows 进行开发,因为 Windows 目前仍然是游戏的标准平台,因此首先在这个环境中进行…

Linux:文件描述符与重定向

目录 一、文件描述符 1.文件内核对象 2.文件描述符分配原则 二、文件重定向 1.重定向的现象 输出重定向 输入重定向 dup2 2.重定向的使用 三、标准输出和标准错误 继上篇文章中,我们了解了fd打印的值为文件描述符,那么它还有什么作用呢&…

白盒测试(3):PCB阻抗测试方法

PCB阻抗测试是确保信号完整性的关键,通过测量走线的特性阻抗,验证其是否符合设计目标。常用方法包括时域反射法(TDR)、网络分析仪法和仿真软件法。TDR通过分析反射信号定位阻抗异常,网络分析仪通过S参数计算阻抗&#…

CentOS 7 安装Nginx-1.26.3

无论安装啥工具、首先认准了就是官网。Nginx Nginx官网下载安装包 Windows下载: http://nginx.org/download/nginx-1.26.3.zipLinxu下载 wget http://nginx.org/download/nginx-1.26.3.tar.gzLinux安装Nginx-1.26.3 安装之前先安装Nginx依赖包、自行选择 yum -y i…

笔记:如何使用XAML Styler以及在不同的开发环境中使用一致

一、目的:分享如何使用XAML Styler以及在不同的开发环境中使用一致 XAML Styler 是一个 Visual Studio 扩展,用于自动格式化和整理 XAML 文件。它可以帮助开发者保持一致的代码风格,提高代码的可读性和可维护性。以下是如何在 Visual Studio …

分布式存储学习——HBase概述

1.1 HBase概述 1.1.1 理解大数据背景 1.1.2 HBase是什么 1.1.3 HBase与Hadoop的关系 1.1.4 HBase的核心功能模块 1.1.5 HBase的应用场景和经典案例 1.1.6 小结 本文参考于学校《HBase应用于开发》教材 1.1 HBase概述 本节将介绍大数据背景和HBase的基本概念&#xff0c…

交叉编译openssl及curl

操作环境:Ubuntu20.04 IDE工具:Clion2020.2 curl下载地址:https://curl.se/download/ openssl下载地址:https://openssl-library.org/source/old/index.html 直接交叉编译curl会报错找不到openssl,所以需要先交叉编…

MDM 如何彻底改变医疗设备的远程管理

在现代医疗行业迅速发展的格局中,医院和诊所越来越依赖诸如医疗平板和移动工作站等移动设备。这些设备在提高工作效率和提供卓越的患者护理方面发挥着关键作用。然而,随着它们的广泛使用,也带来了一系列挑战,例如在不同地点确保数…

零基础C语言学习日志22(自定义类型:联合和枚举)

目录 联合体 联合体类型的声明 联合体的特点 相同成员联合体和结构体的对比 联合体大小的计算 例子 枚举类型 枚举类型的声明 枚举类型的优点 枚举类型的使用 联合体 联合体类型的声明 像结构体一样,联合体也是由一个或者多个成员构成,这些成…

天津大学02-深度解读DeepSeek:部署、使用、安全【文末附下载链接】

大模型风险与不当用例——价值观错位 大模型与人类价值观、期望之间的不一致而导致的安全问题,包含:• 社会偏见(Social Bias)LLM在生成文本时强化对特定社会群体的刻板印象,例如将穆斯林与恐怖主义关联,或…