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

news2025/4/23 20:59:21

一、输入捕获测频率

 接线图:

测信号的输入引脚为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)=100
	PWM_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;				//选择配置定时器通道1
	TIM_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);					//触发源选择TI1FP1
	TIM_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)=100
	PWM_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:00000Hz
	OLED_ShowString(2, 1, "Duty:00%");			//2行1列显示字符串Duty:00%
	
	/*使用PWM模块提供输入捕获的测试信号*/
	PWM_SetPrescaler(720 - 1);					//PWM频率Freq = 72M / (PSC + 1) / 100
	PWM_SetCompare1(50);						//PWM占空比Duty = CCR / 100
	
	while (1)
	{
		OLED_ShowNum(1, 6, IC_GetFreq(), 5);	//不断刷新显示输入捕获测得的频率
		OLED_ShowNum(2, 6, IC_GetDuty(), 2);	//不断刷新显示输入捕获测得的占空比
	}
}

 测频率性能:32:43

误差分析:34:45 

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

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

相关文章

K8S学习之基础十六:k8s中Deployment更新策略

滚动更新 滚动更新是一种自动化程度较高的发布方式、用户体验比较平滑、是目前成熟型技术组织采用的主流发布方式,一次滚动发布一般有若干发布批次组成,每批的数量一般都是可配置的,可通过发布模板定义,例如第一批10%&#xff0c…

EtherNet/IP转Modbus解析基于网关模块的罗克韦尔PLC与Modbus上位机协议转换通讯案例

在工业自动化控制系统中,常常会遇到不同品牌和通信协议的设备需要协同工作的情况。本案例中,客户现场采用了 AB PLC,但需要控制的变频器仅支持 Modbus 协议。为了实现 AB PLC 对变频器的有效控制与监控,引入了捷米特 JM-EIP-RTU 网…

Devart dbForge Studio for MySQL Enterprise 9.0.338高效数据库管理工具

Devart dbForge Studio for MySQL Enterprise 9.0.338 是一款功能强大的 MySQL 数据库管理工具,专为数据库开发人员和管理员设计。它提供了丰富的功能,帮助用户更高效地管理、开发和维护 MySQL 数据库 Devart dbForge Studio for MySQL Enterprise 9.0.…

STM32-USART串口数据包

一:HEX数据包发送 1.为了收发数据包,先定义两个缓存区的数组 ,这4个数据只存储发送或者接收的载荷数据,包头和包尾不存 uint8_t Serial_TxPacket[4]; uint8_t Serial_RxPacket[4]; uint8_t Serial_RxFlag;//接收一个数据包就置F…

轻闪PDF(Windows傲软PDF编辑软件)2.15.2中文安装版

前言 轻闪pdf是个很好用的文件编辑软件,它能让大家编辑文档变得更简单、更快。这个软件特别厉害,能从照片里直接“抓”出文字来,让你打字变得更轻松。而且,它还能把PDF文件变成其他格式的文件,反过来也行。还有啊&…

Python-07PDF转Word

2025-03-04-PDF转Word DeepSeek等大模型从来都不是简单的写一个静态博客这么肤浅(太多博主都只讲这个内容了)借助全网大神的奇思妙想,拓展我狭隘的思维边界。 文章目录 2025-03-04-PDF转Word [toc]1-参考网址2-学习要点3-核心逻辑4-核心代码 …

Arcgis中添加脚本工具箱

文章目录 准备资料1、打开arcmap2、找到目录窗口3、复制粘贴工具箱的路径4、添加或者确认python脚本路径准备资料 (1)工具箱 (2)python脚本 1、打开arcmap 2、找到目录窗口 3、复制粘贴工具箱的路径 4、添加或者确认python脚本路径 脚本上右键属性(注意:脚本内容和路径…

拥抱健康养生,开启活力生活

在快节奏的现代生活中,健康养生已成为人们关注的焦点,它不仅是对身体的呵护,更是一种积极的生活态度。 合理饮食是健康养生的基石。我们应秉持均衡膳食的理念,谷物、蔬菜、水果、蛋白质类食物一个都不能少。每天保证足够的蔬菜摄入…

字节跳动AI原生编程工具Trae和百度“三大开发神器”AgentBuilder、AppBuilder、ModelBuilder的区别是?

字节跳动AI编程工具Trae与百度"三大开发神器"(AgentBuilder、AppBuilder、ModelBuilder)在定位、功能架构和技术路线上存在显著差异,具体区别如下: 一、核心定位差异 Trae:AI原生集成开发环境(AI…

【MySQL】第十二弹---表连接详解:从内连接到外连接

✨个人主页: 熬夜学编程的小林 💗系列专栏: 【C语言详解】 【数据结构详解】【C详解】【Linux系统编程】【MySQL】 目录 1.表的内连和外连 1.1 内连接 1.2 外连接 1.2.1 左外连接 1.2.1 右外连接 1.3 实战OJ 1.表的内连和外连 表的连接…

审批流AntV框架蚂蚁数据可视化X6饼图(附注释)

大家好,这次使用的是AntV的蚂蚁数据可视化X6框架,类似于审批流的场景等,代码如下: X6框架参考网址:https://x6.antv.vision/zh/examples/showcase/practices#bpmn 可以进入该网址,直接复制下方代码进行调试…

【SpringBoot】深入解析 Maven 的操作与配置

Maven 1.什么是Maven? Maven是一个项目管理工具,通过pom.xml文件的配置获取jar包,而不用手动去添加jar包; 2. 创建一个Maven项目 IDEA本身已经集成了Maven,我们可以直接使用,无需安装 以下截图的idea版本为&#xff…

搭建一个简单的node服务,模拟后端接口

目录 一、查看是否安装了node和npm 二、创建一个文件夹,用于放你的node服务代码 三、初始化一个package.json 四、安装 Express(快速搭建服务的框架) 五、创建serve.js 六、运行服务即可 七、测试接口 法一:使用 curl 法…

【落羽的落羽 C++】C++入门基础:引用,内联,nullptr

文章目录 一、引用1. 引用的概念2. 引用的特点3. 引用的使用4. const引用5. 引用和指针 二、inline内联三、nullptr 一、引用 1. 引用的概念 引用是C中的一个较为重要的概念。它是给已存在变量取的“别名”,编译器不会为引用变量开辟内存空间,它和它引…

Python的那些事第四十一篇:简化数据库交互的利器Django ORM

Django ORM:简化数据库交互的利器 摘要 随着互联网技术的飞速发展,Web开发越来越受到重视。Django作为一款流行的Python Web框架,以其高效、安全、可扩展等特点受到了广大开发者的喜爱。其中,Django ORM(对象关系映射)是Django框架的核心组件之一,它为开发者提供了一种…

通过多线程同时获取H264和H265码流

目录 一.RV1126 VI采集摄像头数据并同时编码H264、H265的大概流程​编辑​编辑 1.1初始化VI模块: 1.2H264、H265的VENC模块初始化: 1.3VI分别绑定H264的VENC层和H265的VENC层: ​​​​​​​1.4开启H264线程采集H264的VENC数据&#xff…

DeepSeek V3 源码:从入门到放弃!

从入门到放弃 花了几天时间,看懂了DeepSeek V3 源码的逻辑。源码的逻辑是不难的,但为什么模型结构需要这样设计,为什么参数需要这样设置呢?知其然,但不知其所以然。除了模型结构以外,模型的训练数据、训练…

海量数据融合互通丨TiDB 在安徽省住房公积金监管服务平台的应用实践

导读 安徽省住房公积金监管服务平台通过整合全省 17 家公积金中心的数据,致力于实现数据共享、规范化管理与高效数据分析。为了应对海量数据处理需求,安徽省选择 TiDB 作为底层数据库,利用其分布式架构和 HTAP 能力,实现了快速的…

Linux12-UDP\TCP

一、UDP 1.特点: 尽最大努力交付,存在丢包的可能 无连接 面向数据报 机制简单,传输效率高 2.应用场景: 1.画面传输 VNC 直播:要求实时性高、允许数据丢失、 二、TCP 1.特点: 面向数据流(流式套接字) 建立连接 安全可靠的传输协议 三次握手:TCP建立连接时,…

【HeadFirst系列之HeadFirst设计模式】第14天之与设计模式相处:真实世界中的设计模式

与设计模式相处:真实世界中的设计模式 设计模式是软件开发中的经典解决方案,它们帮助我们解决常见的设计问题,并提高代码的可维护性和可扩展性。在《Head First设计模式》一书中,作者通过生动的案例和通俗的语言,深入…