STM32 多路PWM信号频率检测

这星期老师让给项目中添加一个检测输入信号频率的功能,用于矿下煤气浓度检测,于是搞了几天做成了一个样例,由于电路板的限制,用的是TIM3和TIM4。
这个程序最多支持8路不通频率信号的测量,由于有实际要求,我把测量的频率设定在1000~200Hz之间,当然测更高的频率也行,只是我没有测试过2000Hz以上的信号。

频率检测的原理

怎么编程需要看懂下面这张定时器输入捕获结构图,我只进行了最低限度的解释,要了解更多推荐看看野火的《零死角玩转STM32F103指南者》高级定时器那一章。
输入捕获部分结构
使用 STM32 一定要找到参考手册、数据手册或者其他参考书,不然寸步难行。
这里先说一下怎么进行信号捕获
0.TIMx一直在计数
1.信号通过 TIMx_CH1/2/3/4 流入
2.经过滤波器和边沿检测
3.信号进入捕获通道
4.经过预分频器
5.捕获寄存器在发生捕获时存储 CNT 的值,产生 CCxI 中断

首先借助于定时器的输入捕获的4个通道的边沿捕获功能,检测到上升沿后出发 CI 中断。
以 TIM3 的 CH1 为例,它检测到第一个上升沿后,我们用变量 cnt_val 记录一下 CNT(计数器) 的值。
当 CH1 遇到第二次遇到上升沿时,TIM3->CCR1 寄存器记录下此时 CNT 的值,我们用一个变量 ccr_val 记录一下。
然后退出中断, ( ccr_val - cnt_val ) 的值就是一个周期内 TIM3 计数的次数(忽略计数器更新),用变量 c_num 记录。接着用 TIM3 的计时频率 / c_num 可以得出频率的值。
检测原理描述
从上面的图中可以看出,假如捕获信号的一个周期内定时器发生了更新,那这次采集就算失败,因为更新后CNT重新计数了。
还有一种情况是信号源突然掉线,所以需要定时进行在线检测。
目前我没想到其它的bug。

GPIO初始化

我用到了两个计时器,这里我只拿一个TIM3举例子,下面用到了很多宏定义,是为了方便我开关某些功能。

#define USE_TIM3			  1
#define USE_TIM3_CH1          1
#define USE_TIM3_CH2          1
#define USE_TIM3_CH3          1
#define USE_TIM3_CH4          1
#define TIM3_IT_CCx						/**/TIM_IT_CC1|TIM_IT_CC2|TIM_IT_CC3|TIM_IT_CC4/*注意要与上面的通道向对应*/
#define TIM3_GPIO_CLK 	RCC_APB2Periph_GPIOA|RCC_APB2Periph_GPIOB 

static void TIM3_GPIO_Config(void)
{
	GPIO_InitTypeDef GPIO_InitStructure;
  //GPIO_PinRemapConfig(GPIO_FullRemap_TIM3, ENABLE);//TIM3 端口重映射,我用不上
  //GPIO_PinRemapConfig(GPIO_Remap_SWJ_JTAGDisable , ENABLE);	//禁止JTAG功能,把PB3,PB4作为普通IO口使用,我也用不上
	RCC_APB2PeriphClockCmd(TIM3_GPIO_CLK,ENABLE);//开启对应GPIO的时钟,TIM3_GPIO_CLK是宏定义
#if USE_TIM3_CH1
	//配置CH1 
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;//浮空输入
	GPIO_InitStructure.GPIO_Pin = TIM3_CH1_PIN;
	GPIO_Init(TIM3_CH1_PORT,&GPIO_InitStructure);
#endif
	
#if USE_TIM3_CH2
	//配置CH2 
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;
	GPIO_InitStructure.GPIO_Pin = TIM3_CH2_PIN;
	GPIO_Init(TIM3_CH2_PORT,&GPIO_InitStructure);
#endif
	
#if USE_TIM3_CH3
	//配置CH3 
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;
	GPIO_InitStructure.GPIO_Pin = TIM3_CH3_PIN;
	GPIO_Init(TIM3_CH3_PORT,&GPIO_InitStructure);
#endif

#if USE_TIM3_CH4
	//配置CH4 
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;
	GPIO_InitStructure.GPIO_Pin = TIM3_CH4_PIN;
	GPIO_Init(TIM3_CH4_PORT,&GPIO_InitStructure);
#endif
}

定时器初始化

同样,只拿一个TIM3举例

static void GENERAL_TIM3_Config(void)
{
	//配置引脚,就是上面的那个函数
	TIM3_GPIO_Config();
	//配置时基
	TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStructure;
	//初始化TIM3时钟72MHz
	RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM3, ENABLE);
	
	TIM_TimeBaseInitStructure.TIM_Prescaler = 72-1;	//分频 72M/72 = 1MHz
	TIM_TimeBaseInitStructure.TIM_Period = 0xffff-1;  //计时周期65535  计时器的一个周期是(1s/1M)*65535 =  0.0656s即65ms
	TIM_TimeBaseInitStructure.TIM_ClockDivision = TIM_CKD_DIV1;	//配置为1分频
	TIM_TimeBaseInitStructure.TIM_CounterMode = TIM_CounterMode_Up;//配置为向上计数模式
	TIM_TimeBaseInitStructure.TIM_RepetitionCounter = 0; //重复计数器,TIM3没有
	
	TIM_TimeBaseInit(TIM3,&TIM_TimeBaseInitStructure);  //配置TIM3
	TIM_ClearFlag(TIM3, TIM_FLAG_Update);               //清除中断标志位
}

配置输入捕获

这里需要注意 TIM_ICInitStructure.TIM_Channel 不能像配置GPIO一样多个值相或,如 GPIO_Pin_6|GPIO_Pin_7|GPIO_Pin_8,TIM_Channel只能一次配置一个,我踩过的坑希望其他人别去踩。

// 测量的起始边沿
#define GENERAL_TIM_STRAT_ICPolarity 	TIM_ICPolarity_Rising

static void TIM3_IC_Config(void)
{
	TIM_ICInitTypeDef TIM_ICInitStructure;
#if USE_TIM3_CH1
	TIM_ICInitStructure.TIM_Channel = TIM_Channel_1;				//配置捕获通道,!!!!"这个参数不支持多参数相或"!!!!!
	TIM_ICInitStructure.TIM_ICFilter = 0x0f;								//设置滤波,参考手册p215
	TIM_ICInitStructure.TIM_ICPolarity = GENERAL_TIM_STRAT_ICPolarity;//选择IC触发边沿,上升沿还是下降沿,这里用的宏定义
	TIM_ICInitStructure.TIM_ICPrescaler = TIM_ICPSC_DIV1;			//预分频数1
	TIM_ICInitStructure.TIM_ICSelection = TIM_ICSelection_DirectTI;//直接输入
	TIM_ICInit(TIM3, &TIM_ICInitStructure);									//配置TIM3的输入捕获功能
#endif
#if USE_TIM3_CH2
	TIM_ICInitStructure.TIM_Channel = TIM_Channel_2;	
	TIM_ICInitStructure.TIM_ICFilter = 0x0f;
	TIM_ICInitStructure.TIM_ICPolarity = GENERAL_TIM_STRAT_ICPolarity;
	TIM_ICInitStructure.TIM_ICPrescaler = TIM_ICPSC_DIV1;
	TIM_ICInitStructure.TIM_ICSelection = TIM_ICSelection_DirectTI;
	TIM_ICInit(TIM3, &TIM_ICInitStructure);
#endif
#if USE_TIM3_CH3
	TIM_ICInitStructure.TIM_Channel = TIM_Channel_3;
	TIM_ICInitStructure.TIM_ICFilter = 0x0f;
	TIM_ICInitStructure.TIM_ICPolarity = GENERAL_TIM_STRAT_ICPolarity;
	TIM_ICInitStructure.TIM_ICPrescaler = TIM_ICPSC_DIV1;
	TIM_ICInitStructure.TIM_ICSelection = TIM_ICSelection_DirectTI;
	TIM_ICInit(TIM3, &TIM_ICInitStructure);
#endif
#if USE_TIM3_CH4
	TIM_ICInitStructure.TIM_Channel = TIM_Channel_4;
	TIM_ICInitStructure.TIM_ICFilter = 0x0f;
	TIM_ICInitStructure.TIM_ICPolarity = GENERAL_TIM_STRAT_ICPolarity;
	TIM_ICInitStructure.TIM_ICPrescaler = TIM_ICPSC_DIV1;
	TIM_ICInitStructure.TIM_ICSelection = TIM_ICSelection_DirectTI;
	TIM_ICInit(TIM3, &TIM_ICInitStructure);
#endif
// 开启更新和捕获中断
	TIM_ITConfig (TIM3, TIM_IT_Update | TIM3_IT_CCx, ENABLE );
	TIM_ClearFlag(TIM3, TIM_FLAG_Update|TIM3_IT_CCx);//清中断标志
// 使能计数器
	TIM_Cmd(TIM3, ENABLE);
}

配置 NVIC

这里还是只贴出了TIM3的

static void GENERAL_TIM_NVIC_Config(void)
{
	NVIC_InitTypeDef NVIC_InitStructure;
#if USE_TIM3
	// 设置中断组为 1
	NVIC_PriorityGroupConfig(NVIC_PriorityGroup_1);
	// 设置中断来源
	NVIC_InitStructure.NVIC_IRQChannel = TIM3_IRQn ;
	// 设置主优先级为 1
	NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 1;
	// 设置抢占优先级为 3
	NVIC_InitStructure.NVIC_IRQChannelSubPriority = 3;
	NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;		//中断通道使能
	NVIC_Init(&NVIC_InitStructure);
#endif
}

自定义一个保存信息的结构体

typedef struct {
	uint8_t Time_OutFlag;				//捕获超时标志
	uint8_t Capture_FinishFlag; // 捕获结束标志位
	uint8_t Capture_StartFlag; // 捕获开始标志位
	uint16_t Capture_CNTValue; // 第一次捕获边沿时计数器的值
	uint16_t Capture_CcrValue; // 捕获寄存器的值
}TIM_ICUserValueType;

中断处理函数

extern TIM_ICUserValueType TIM_ICUserValue[8];
void TIM3_IRQHandler(void)
{
	 if ( TIM_GetITStatus ( TIM3, TIM_IT_Update) != RESET )
	{//假定超时
		TIM_ICUserValue[0].Time_OutFlag = 1;
		TIM_ICUserValue[1].Time_OutFlag = 1;
		TIM_ICUserValue[2].Time_OutFlag = 1;
		TIM_ICUserValue[3].Time_OutFlag = 1;
		// 采集波形的一个周期内发生了寄存器溢出,放弃本次采集
		TIM_ICUserValue[0].Capture_StartFlag = 0;
		TIM_ICUserValue[0].Capture_FinishFlag = 0;
		TIM_ICUserValue[1].Capture_StartFlag = 0;
		TIM_ICUserValue[1].Capture_FinishFlag = 0;
		TIM_ICUserValue[2].Capture_StartFlag = 0;
		TIM_ICUserValue[2].Capture_FinishFlag = 0;
		TIM_ICUserValue[3].Capture_StartFlag = 0;
		TIM_ICUserValue[3].Capture_FinishFlag = 0;
		//清除中断标志
		TIM_ClearITPendingBit ( TIM3, TIM_FLAG_Update );
	}
#if USE_TIM3_CH1
	// TIM3_CH1上升沿捕获中断
	if ( TIM_GetITStatus (TIM3, TIM_IT_CC1 ) != RESET) 
		{
			// 第一次捕获
			if ( TIM_ICUserValue[0].Capture_StartFlag == 0 )
				{
					//记录首次捕获时计数器的值
					TIM_ICUserValue[0].Capture_CNTValue = TIM3->CNT;							
					// 存捕获比较寄存器的值的变量的值清 0
					TIM_ICUserValue[0].Capture_CcrValue = 0;
					// 开始捕获标准置 1
					TIM_ICUserValue[0].Capture_StartFlag = 1;
				}
			// 下降沿捕获中断
			else { // 第二次捕获
					// 获取捕获比较寄存器的值减去上一次计数器的值,这个值就是捕获到的高电平的时间的值
					TIM_ICUserValue[0].Capture_CcrValue =	TIM_GetCapture1(TIM3)-TIM_ICUserValue[0].Capture_CNTValue;
					// 开始捕获标志清 0
					TIM_ICUserValue[0].Capture_StartFlag = 0;
					// 捕获完成标志置 1
					TIM_ICUserValue[0].Capture_FinishFlag = 1;
					//超时标志清除
					TIM_ICUserValue[0].Time_OutFlag = 0;
					}
			 TIM_ClearITPendingBit (TIM3,TIM_IT_CC1);
		}
#endif

#if USE_TIM3_CH2
	// TIM3_CH2上升沿捕获中断
	if ( TIM_GetITStatus (TIM3, TIM_IT_CC2 ) != RESET) 
		{
			if ( TIM_ICUserValue[1].Capture_StartFlag == 0 ) 
				{
					TIM_ICUserValue[1].Capture_CNTValue = TIM3->CNT;
					TIM_ICUserValue[1].Capture_CcrValue = 0;
					TIM_ICUserValue[1].Capture_StartFlag = 1;
				}
			else {
					TIM_ICUserValue[1].Capture_CcrValue =	TIM_GetCapture2(TIM3)-TIM_ICUserValue[1].Capture_CNTValue;		
					TIM_ICUserValue[1].Capture_StartFlag = 0;
					TIM_ICUserValue[1].Capture_FinishFlag = 1;
					TIM_ICUserValue[1].Time_OutFlag = 0;
				}
			 TIM_ClearITPendingBit (TIM3,TIM_IT_CC2);
		}
#endif

#if USE_TIM3_CH3
	// TIM3_CH3上升沿捕获中断
	  if ( TIM_GetITStatus (TIM3, TIM_IT_CC3 ) != RESET)
		{
			if ( TIM_ICUserValue[2].Capture_StartFlag == 0 ) 
				{
					TIM_ICUserValue[2].Capture_CNTValue = TIM3->CNT;
					TIM_ICUserValue[2].Capture_CcrValue = 0;
					TIM_ICUserValue[2].Capture_StartFlag = 1;
				}
			else {
					TIM_ICUserValue[2].Capture_CcrValue =	TIM3->CCR3-TIM_ICUserValue[2].Capture_CNTValue;		
					TIM_ICUserValue[2].Capture_StartFlag = 0;
					TIM_ICUserValue[2].Capture_FinishFlag = 1;
					TIM_ICUserValue[2].Time_OutFlag = 0;
			}
			 TIM_ClearITPendingBit (TIM3,TIM_IT_CC3);
		}
#endif
		
#if USE_TIM3_CH4
	// TIM3_CH4上升沿捕获中断
	if ( TIM_GetITStatus (TIM3, TIM_IT_CC4 ) != RESET) 
		{
			if ( TIM_ICUserValue[3].Capture_StartFlag == 0 ) 
				{
					TIM_ICUserValue[3].Capture_CNTValue = TIM3->CNT;
					TIM_ICUserValue[3].Capture_CcrValue = 0;
					TIM_ICUserValue[3].Capture_StartFlag = 1;
				}
			else {
					TIM_ICUserValue[3].Capture_CcrValue =	TIM_GetCapture4(TIM3)-TIM_ICUserValue[3].Capture_CNTValue;		
					TIM_ICUserValue[3].Capture_StartFlag = 0;
					TIM_ICUserValue[3].Capture_FinishFlag = 1;
					TIM_ICUserValue[3].Time_OutFlag = 0;
			}
			 TIM_ClearITPendingBit (TIM3,TIM_IT_CC4);
		}
#endif
}

结果处理

这个函数你可以放到 main 函数里,我只是把它的结果用串口打印出来(串口部分请自行解决,网上的例子一大堆)

void PWM_IC_Print(void)
{
	double fre;
	// TIM 计数器的驱动时钟
	uint32_t TIM_PscCLK = 72000000 / (71+1);
	int i = 0;
	for(i = 0;i<8;i++)
	{
		//超时判断
		if(TIM_ICUserValue[i].Time_OutFlag == 1)
			continue;
		//判断是否捕获完成
		if(TIM_ICUserValue[i].Capture_FinishFlag == 1) 
		{		
			// 计算一个周期的计数器的值
			fre = (TIM_ICUserValue[i].Capture_CcrValue+1);
			//滤波器,我这里只留下了我需要的频段,具体值可以任意修改
			//下面的值的计算方法是 (f(TIM3)/目标频率)
			if(fre>10000||fre<500)
				continue;
			// 打印频率,你想怎么处理,请自便
			printf ( "\r\n %d号输入测得频率: %f hz\r\n",i,1/(fre/TIM_PscCLK));
			//清除标志
			TIM_ICUserValue[i].Capture_FinishFlag = 0;
		}
	}
}

下面是实际的测试结果
我是用TIM2产生了一个200hz的方波,测了一下,还可以
输入信号
然后连起来,PA6(TIM3 CH1) 接 PA2(待测信号),棕色那根是GND
接线
打开串口调试助手
串口输入测试一下多路输入,1个200hz,2个1000hz

多路输入下面有我的工程
https://github.com/make-better/FrequencyCheck

  • 4
    点赞
  • 46
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值