【STM32】TIM定时器输入捕获

2023-12-14 19:50:42

1?输入捕获

1.1?输入捕获简介

IC(Input Capture)输入捕获

输入捕获模式下,当通道输入引脚出现指定电平跳变时(上升沿/下降沿),当前CNT的值将被锁存到CCR中(把CNT的值读出来,写入到CCR中),可用于测量PWM波形的频率、占空比、脉冲间隔、电平持续时间等参数

每个高级定时器和通用定时器都拥有4个输入捕获通道

可配置为PWMI模式(PWM的输入模式),同时测量频率和占空比

可配合主从触发模式,实现硬件全自动测量

对于同一个定时器,输入捕获和输出比较只能使用其中一个,不能同时使用(共用)

输出比较是根据CNT和CCT的大小关系来执行输出动作;

输入捕获是接收到输入信号,执行CNT锁存到CCR的动作;

1.2?频率测量

左边频率大于右边频率。STM32也只能测数字信号。

测频法:上升沿出现的次数/闸门时间。适合高频信号,测量结果更新慢

测周法:两个上升沿的持续时间。fc是标准频率/计次。适合低频信号,测量结果更新快

中界频率:测频法与测周法误差相等的频率点

当待测频率小于中界频率时,测周法误差更小;反之测频法误差更小。

本节使用的是测周法

1.3?输入捕获电路

最左边是4个通道的引脚,参考引脚定义表就可以知道这个引脚复用在哪个位置。接下来是三输入的异或门,接到了CH1、CH2、CH3,异或门的逻辑是当三个输入引脚的任何一个有电平翻转时,输出引脚就产生一次电平翻转,之后,输出通过数据选择器,到达输入捕获通道1。数据选择器如果选择上面的,就是3个引脚的异或值;如果选择下面一个,那异或门就没有用,4通道各用各的引脚。异或门是为了三相无刷电机服务的。

输入信号此时来到输入滤波器和边沿检测器,输入滤波器对信号进行滤波,避免高频的毛刺信号误触发;边沿检测器处可以选择高电平触发/低电平触发(类似中断),当出现指定电平时,边沿检测电路就会触发后续电路执行动作。有两套电路,分别输出TI1FP1(TI1 Filter Polarity 1,输出给通道1的后续电路)和TI1FP2(TI1 Filter Polarity 2,输出给?下面通道2的后续电路),下面同理。有一个交叉,即两个信号进来可以选择各走各的,也可以是让CH1引脚输入给通道2,让CH2引脚输入给通道1。目的:(1)可以灵活切换后续捕获电路的输入;(2)可以把一个引脚的输入同时映射到两个捕获单元。这也是PWMI模式的经典结构。

第一个通道使用上升沿触发,用来捕获周期;

第二个通道使用下降沿触发,用来捕获占空比;两个通道同时对一个引脚进行捕获,就可以同时测量频率和占空比,这就是PWMI模式。

TRC信号也是为了无刷电机的驱动。

经过预分频器之后的信号,就可以触发捕获电路进行工作了。每来一个触发信号,CNT的值,就会向CCR转运(写入/存储)一次,转运的同时,会发生一个捕获事件,这个事件会在状态寄存器置标志位,同时也可以产生中断,可以开启这个中断处理事情。CNT的计数器是由内部标准时钟驱动的,所以CNT的数值其实就可以记录两个上升沿之间的时间间隔,这个时间间隔就是周期,取个倒数就是频率,这就是测周法测量频率

上升沿用于触发输入捕获,CNT用于计数计时,每来一个上升沿,取一下CNT的值,自动存在CCR里,CCR捕获到的值就是计数值N,CNT的驱动时钟就是fc。每次捕获之后,需要把CNT清零(主从触发模式自动处理)。

1.4 输入捕获通道

引脚进来,还行先经过一个滤波器,滤波器的输入就是TI1,就是CH1的引脚,输出TI1F的就是滤波后的信号。fDTS是滤波器的采样时钟来源,下面的CCMR1寄存器的ICF位可以控制滤波器的参数。

这个滤波器的工作原理:以采样频率对输入信号进行采样,当连续N个值都是高电平时,输出才是高电平;连续N个值都是低电平时,输出才是低电平。采样频率越低,采样个数N越大,滤波效果就越好。

滤波之后信号通过边沿检测器,捕获上升沿/下降沿,用CCER寄存器里面的CC1P位就可以选择极性了,最终得到TI1FP1触发信号,通过数据选择器,进入通道1后续的捕获电路(还有TI1FP2触发信号;同样通道2有TI2FP1和TI2FP2信号,也就是前面的交叉)。CCMR寄存器的CC1S位可以对数据选择器进行选择,之后ICPS位可以配置这里的预分频器(不分频/2分频/4分频/8分频),最后CCER寄存器的CC1E位控制输出使能或者失能。如果使能了输出,输入端产生指定边沿信号经过层层电路,就可以让这里的CNT的值,转运到CCR里面来,硬件电路自动完成捕获之后的CNT清零工作。

这个从模式里面有电路完成CNT的自动清零

1.5?主从触发模式

主模式可以将定时器内部的信号映射到TRGO引脚,用于触发别的外设,所以这部分叫主模式。

从模式就是接收其他外设或者自身外设的一些信号,用于控制自身定时器的允运行,也就是被别的信号控制,所以这部分叫从模式。

触发源选择就是选择从模式的触发信号源(是从模式的一部分),选择一个指定的信号,得到TRGI,TRGI去触发从模式,从模式可以在列表中选择一项操作来自动执行。

TI1FP1信号自动触发CNT清零操作选择线路。

手册
主模式选择:

从模式触发源选择

从模式选择

1.6?输入捕获基本结构

只使用了一个通道,目前只能测频率。右上角是时基单元(配置好),启动定时器,CNT就会在预分频器之后的时钟驱动下不断自增(CNT就是测周法计数计时的参数),经过预分频之后的时钟频率就是驱动CNT的标准频率fc(fc = 72M /?预分频系数),

下面输入捕获通道1的GPIO口,输入一个左上角这样的方波信号,经过滤波器和边沿检测,选择TI1FP1为上升沿触发,之后输出选择直连的通道,分频器选择不分频;当TI1FP1出现上升沿后,CNT的当前计数值转运到CCR1里;同时触发源选择,选中TI1FP1为触发信号,从模式选择复位操作。有先后顺序,先转运CNT的值到CCR里去,再触发从模式给CNT清零。

信号出现一个上升沿,CCR1 = CNT,就是把CNT的值转运到CCR1里面去,这是输入捕获自动执行的,然后CNT = 0,清零计数器,这是从模式自动执行的;然后在一个周期之内,CNT在标准时钟的驱动下,不断自增,并且由于之前清零过了,所以CNT就是从上升沿开始,从0开始计数,一直自增,直到下一次上升沿来临,然后执行相同的操作。

第二次捕获的时候,CNT的值就是之前的计数值,这个计数值就自动放在CCR1里;然后下一个周期里,CNT从0开始自增,直到下一个上升沿,这是CCR1刷新为第二个周期即CNT计数值;不断重复。所以当这个电路工作时,CCR1的值始终保持为最新一个周期的计数值(就是测周法的N)

注意事项:

(1)CNT的值是有上限的(65535),如果信号频率太低,CNT可能会溢出;

(2)从模式的触发源选择,只有TI1FP1和TI2FP2,没有TI3和TI4

1.7 PWMI基本结构

PWMI模式使用了两个通道同时捕获一个引脚,可以同时测量周期和占空比。下面多了一个通道。首先TI1FP1配置上升沿触发,触发捕获和清零CNT。这是再来一个TI1FP2,将其配置为下降沿触发,通过交叉通道,器触发通道2的捕获单元,见左上角图。

最开始上升沿,CCR1捕获,同时清零CNT,之后CNT一直自增;然后在下降沿这个时刻,触发CCR2捕获,所以CCR2捕获的值就是高电平所计数的CNT的值(好巧妙),这样CCR1的值是整个周期的值,而CCR2的值是高电平的值,就可以求出占空比(CCR2/CCR1),这就是PWMI模式,使用两个通道来捕获频率和占空比。

手册

2?TIM输入捕获之测频率

2.1?接线图

目前测量信号的输入引脚是PA6,信号从PA6进来,待测的PWM信号也是STM32自己生成的,输出引脚是PA0。也就是STM32产生的信号从PA0输出,而STM32测频率的接口是PA6。

2.2?模块封装

PWM.c

#include "stm32f10x.h"                  // Device header

// PWM初始化函数
void PWM_Init(void)
{
	// 1RCC开启时钟
	RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2, ENABLE);
	TIM_InternalClockConfig(TIM2);
	
	// 2配置时基单元(预分频器、自动重装器、计数模式等)
	TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStructure;
	TIM_TimeBaseInitStructure.TIM_ClockDivision = TIM_CKD_DIV1;		// 时钟分频,影响不大
	TIM_TimeBaseInitStructure.TIM_CounterMode = TIM_CounterMode_Up;	// 计数模式,向上计数
	TIM_TimeBaseInitStructure.TIM_RepetitionCounter = 0;			// 重复计数器的值
	// 关键参数
	TIM_TimeBaseInitStructure.TIM_Period = 100 - 1;				// ARR自动重装器的值,固定ARR
	TIM_TimeBaseInitStructure.TIM_Prescaler = 720 - 1;			// PSC预分频器的值,调节PSC来实现调节频率
	TIM_TimeBaseInit(TIM2, &TIM_TimeBaseInitStructure);

	// 3配置输出比较单元(CCR的值、输出比较模式、极性选择、输出使能)
	TIM_OCInitTypeDef TIM_OCInitStructure;
	TIM_OCStructInit(&TIM_OCInitStructure);               					 // 先初始化,后面再按需修改
	TIM_OCInitStructure.TIM_OCMode = TIM_OCMode_PWM1;     					 // 输出比较模式,PWM模式1
	TIM_OCInitStructure.TIM_OCPolarity = TIM_OCPolarity_High;                // 输出比较极性,高级性,极性不反转,有效电平是高电平
	TIM_OCInitStructure.TIM_OutputState = TIM_OutputState_Enable;            // 输出状态输出使能
	TIM_OCInitStructure.TIM_Pulse = 0;            							 // CCR的值w为50,先设置为0,后面变化
	// 这个函数的选择按照GPIO口需求来,PA0口对应的是第一个输出比较通道
	TIM_OC1Init(TIM2, &TIM_OCInitStructure);
	
	// 频率1kHz,占空比50% ,分辨率为1%。 CCR = 50 ,
	/**
	PWM分辨率:	Reso = 1 / (ARR + 1) = 1%      						==> ARR= 100 - 1
	PWM占空比:	Duty = CCR / (ARR + 1) = 50%   						==> CCR = 50
	PWM频率:	Freq = CK_PSC(72M) / (PSC + 1) / (ARR + 1) = 1K  	==> PSC = 720 - 1
	*/
	
	// 4配置GPIO(初始化为复用推挽输出,参考引脚定义表)
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);
	GPIO_InitTypeDef GPIO_InitStructure;
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;				// 复用推挽输出。定时器控制引脚,输出控制权转移给片上外设
	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0;
	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
	GPIO_Init(GPIOA, &GPIO_InitStructure);
	
	// 5运行控制,启动计数器
	TIM_Cmd(TIM2, ENABLE);
}

// 更改通道1的CCR值,改变占空比
void PWM_SetCompare1(uint16_t Compare)
{
	TIM_SetCompare1(TIM2, Compare);
}

// 修改PSC,改变频率
void PWM_SetPrescaler(uint16_t prescaler)
{
	// 立刻生效
	TIM_PrescalerConfig(TIM2, prescaler, TIM_PSCReloadMode_Immediate);
}

输入捕获的初始化按照这个步骤

库函数

// TIM输入捕获的函数,4个通道公用一个函数(电路中公用模块)
void TIM_ICInit(TIM_TypeDef* TIMx, TIM_ICInitTypeDef* TIM_ICInitStruct);
// TIM输入捕获的函数,可以快速配置两个通道(PWMI模式)
void TIM_PWMIConfig(TIM_TypeDef* TIMx, TIM_ICInitTypeDef* TIM_ICInitStruct);

// 输入捕获结构体赋值一个初始值
void TIM_ICStructInit(TIM_ICInitTypeDef* TIM_ICInitStruct);

// 选择输入触发源TRGI(从模式的触发源选择)
void TIM_SelectInputTrigger(TIM_TypeDef* TIMx, uint16_t TIM_InputTriggerSource);

// 选择输出触发源TRGO(主模式输出的触发源)
void TIM_SelectOutputTrigger(TIM_TypeDef* TIMx, uint16_t TIM_TRGOSource);

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

// 分别单独配置通道1234的分频器
void TIM_SetIC1Prescaler(TIM_TypeDef* TIMx, uint16_t TIM_ICPSC);
void TIM_SetIC2Prescaler(TIM_TypeDef* TIMx, uint16_t TIM_ICPSC);
void TIM_SetIC3Prescaler(TIM_TypeDef* TIMx, uint16_t TIM_ICPSC);
void TIM_SetIC4Prescaler(TIM_TypeDef* TIMx, uint16_t TIM_ICPSC);

// 分别读取4个通道的CCR。输出比较模式下,CCR是只写的,使用set
// 输入捕获模式下CCR是只读的,使用get
uint16_t TIM_GetCapture1(TIM_TypeDef* TIMx);
uint16_t TIM_GetCapture2(TIM_TypeDef* TIMx);
uint16_t TIM_GetCapture3(TIM_TypeDef* TIMx);
uint16_t TIM_GetCapture4(TIM_TypeDef* TIMx);

选择TIM3的通道,选择引脚。使用TIM3的通道1引脚,即选择PA6

IC.c

#include "stm32f10x.h"                  // Device header


// 输入捕获初始化
void IC_Init(void)
{
	// 1RCC开启时钟
	RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM3, ENABLE);
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);
	
	// 2GPIO初始化(输入模式,上拉/浮空)
	GPIO_InitTypeDef GPIO_InitStruct;
	GPIO_InitStruct.GPIO_Mode = GPIO_Mode_IPU;         // 上拉输入
	GPIO_InitStruct.GPIO_Pin = GPIO_Pin_6;
	GPIO_InitStruct.GPIO_Speed = GPIO_Speed_50MHz;
	GPIO_Init(GPIOA, &GPIO_InitStruct);
	// 选择内部时钟
	TIM_InternalClockConfig(TIM3);
	
	// 3配置时基单元
	TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStruct;
	TIM_TimeBaseInitStruct.TIM_Prescaler = 72 - 1;                    	// PSC预分频器的值,72分频,通过PSC来调节
	TIM_TimeBaseInitStruct.TIM_Period = 65536 - 1;                     	// ARR自动重装器的值 两个合起来计数1秒,固定
	TIM_TimeBaseInitStruct.TIM_CounterMode = TIM_CounterMode_Up;      	// 向上计数
	TIM_TimeBaseInitStruct.TIM_ClockDivision = TIM_CKD_DIV1;			// 不分频
	TIM_TimeBaseInitStruct.TIM_RepetitionCounter = 0;                	// 重复计数器的值
	TIM_TimeBaseInit(TIM3, &TIM_TimeBaseInitStruct);
	
	// 4配置输入捕获单元
	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);
	
	// 5选择从模式的触发源(TI1FP1)
	TIM_SelectInputTrigger(TIM3, TIM_TS_TI1FP1);
	
	// 6选择触发之后的操作(从模式reset操作)
	TIM_SelectSlaveMode(TIM3, TIM_SlaveMode_Reset);
	
	// 7开启定时器
	TIM_Cmd(TIM3, ENABLE);
}

// 获得频率函数
uint32_t IC_GetFreq(void)
{
	// fx = fc / N
	return 1000000 / (TIM_GetCapture1(TIM3) + 1);
}

2.3?主函数

#include "stm32f10x.h"                  // Device header
#include "Delay.h"
#include "OLED.h"
#include "PWM.h"
#include "IC.h"


int main()
{
	OLED_Init();					// 初始化OLED
	PWM_Init();						// PWM初始化
	IC_Init();						// IC初始化
	
	OLED_ShowString(1, 1, "Freq:00000Hz");   	// 显示字符串
	
	PWM_SetPrescaler(720 - 1);		// Freq = 72M / (PSC + 1) / (ARR + 1 = 100) = 1000Hz
	PWM_SetCompare1(50);			// 设置CCR的值,占空比Duty = CCR / (ARR + 1 = 100) = 50%
	// 上面这里输出信号是1000Hz的PWM波
	
	while (1)
	{
		OLED_ShowNum(1, 6, IC_GetFreq(), 5);
	}
}

现象:OLED上显示1000Kz

3?TIM输入捕获之PWMI模式测频率占空比

3.1?接线图

3.2?模块封装

按照这个配置

测周法

PWM.c不变

IC.c

#include "stm32f10x.h"                  // Device header


// 输入捕获初始化
void IC_Init(void)
{
	// 1RCC开启时钟
	RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM3, ENABLE);
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);
	
	// 2GPIO初始化(输入模式,上拉/浮空)
	GPIO_InitTypeDef GPIO_InitStruct;
	GPIO_InitStruct.GPIO_Mode = GPIO_Mode_IPU;         // 上拉输入
	GPIO_InitStruct.GPIO_Pin = GPIO_Pin_6;
	GPIO_InitStruct.GPIO_Speed = GPIO_Speed_50MHz;
	GPIO_Init(GPIOA, &GPIO_InitStruct);
	// 选择内部时钟
	TIM_InternalClockConfig(TIM3);
	
	// 3配置时基单元
	TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStruct;
	TIM_TimeBaseInitStruct.TIM_Prescaler = 72 - 1;                    	// PSC预分频器的值,72分频,通过PSC来调节
	TIM_TimeBaseInitStruct.TIM_Period = 65536 - 1;                     	// ARR自动重装器的值 两个合起来计数1秒,固定
	TIM_TimeBaseInitStruct.TIM_CounterMode = TIM_CounterMode_Up;      	// 向上计数
	TIM_TimeBaseInitStruct.TIM_ClockDivision = TIM_CKD_DIV1;			// 不分频
	TIM_TimeBaseInitStruct.TIM_RepetitionCounter = 0;                	// 重复计数器的值
	TIM_TimeBaseInit(TIM3, &TIM_TimeBaseInitStruct);
	
	// 4配置输入捕获单元,修改成两个通道同时捕获同一个引脚的模式
	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;		// 选择触发信号从哪个引脚输入,直连通道/交叉通道
	// 法2,直接把另一个通道置成相反的配置,通道2/交叉/下降沿
	TIM_PWMIConfig(TIM3, &TIM_ICInitStructure);							// 配置PWMI模式
	
	// 法1
//	TIM_ICInitStructure.TIM_Channel = TIM_Channel_2;					// 选择通道1
//	TIM_ICInitStructure.TIM_ICFilter = 0xF;								// 输入捕获滤波器,数越大,滤波效果越好
//	TIM_ICInitStructure.TIM_ICPolarity = TIM_ICPolarity_Falling; 		// 下降沿触发
//	TIM_ICInitStructure.TIM_ICPrescaler = TIM_ICPSC_DIV1;				// 不分频,触发信号分频器,不分频就是每次都有效
//	TIM_ICInitStructure.TIM_ICSelection = TIM_ICSelection_IndirectTI;	// 选择触发信号从哪个引脚输入,交叉通道
//	TIM_ICInit(TIM3, &TIM_ICInitStruct);
	
	
	// 5选择从模式的触发源(TI1FP1)
	TIM_SelectInputTrigger(TIM3, TIM_TS_TI1FP1);
	
	// 6选择触发之后的操作(从模式reset操作)
	TIM_SelectSlaveMode(TIM3, TIM_SlaveMode_Reset);
	
	// 7开启定时器
	TIM_Cmd(TIM3, ENABLE);
}

// 获得频率函数
uint32_t IC_GetFreq(void)
{
	// fx = fc / N
	return 1000000 / (TIM_GetCapture1(TIM3) + 1);
}

// 获取占空比函数
uint32_t IC_GetDuty(void)
{
	// CCR2 / CCR1
	return (TIM_GetCapture2(TIM3) + 1) * 100 / (TIM_GetCapture1(TIM3) + 1);
}

3.3?主函数

#include "stm32f10x.h"                  // Device header
#include "Delay.h"
#include "OLED.h"
#include "PWM.h"
#include "IC.h"


int main()
{
	OLED_Init();					// 初始化OLED
	PWM_Init();						// PWM初始化
	IC_Init();						// IC初始化
	
	OLED_ShowString(1, 1, "Freq:00000Hz");   	// 显示字符串
	OLED_ShowString(2, 1, "Duty:00%");   		// 显示字符串
	
	PWM_SetPrescaler(720 - 1);		// Freq = 72M / (PSC + 1) / (ARR + 1 = 100) = 1000Hz
	PWM_SetCompare1(50);			// 设置CCR的值,占空比Duty = CCR / (ARR + 1 = 100) = 50%
	// 上面这里输出信号是1000Hz的PWM波
	
	while (1)
	{
		OLED_ShowNum(1, 6, IC_GetFreq(), 5);
		OLED_ShowNum(2, 6, IC_GetDuty(), 2);
	}
}

文章来源:https://blog.csdn.net/Zhouzi_heng/article/details/134865101
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。