对于PWM的捕获,我这里一共使用两种方法实现:第一种是PWM输入模式,采用一个定时器的两个通道(通道一和通道二),配置从模式为复位模式,没有进行溢出处理,所以需要考虑捕获的最低频率;第二种是普通的输入捕获模式,采用一个定时器一个通道,有进行溢出处理,所以没有最低频率的限制。

 实验内容:使用高级定时器输入捕获测量PWM的周期和占空比。

 一、原理图

mycropython 测量pwm信号_寄存器

mycropython 测量pwm信号_寄存器_02

—— —— —— —— —— —— —— ——PWM输入模式—— —— —— —— —— —— —— ——

 二、 CubeMX配置

Step1.打开 STM32CubeMX,点击“New Project”,选择芯片型号,STM32F103VETx。

mycropython 测量pwm信号_mycropython 测量pwm信号_03

 Step2.选择时钟源,并配置时钟树。选择Crystal/Ceramic Resonator,并配置系统时钟为72M。

mycropython 测量pwm信号_下降沿_04

  

mycropython 测量pwm信号_下降沿_05

Step3.配置SYS,我们这里选择的是Serial Wire。(正常情况配置不配置不影响,debug可以使用。但是你不可以把这两个引脚用于其他复用功能,如果用于其他复用功能,debug就不起作用了。)

 

mycropython 测量pwm信号_stm32_06

Step4.串口配置(主要为了在串口调试助手显示测试的时间),因为没有用到中断和DMA所以我们就不过多讲解。

mycropython 测量pwm信号_mycropython 测量pwm信号_07

 

mycropython 测量pwm信号_stm32_08

 Step5.接下来进行最主要的定时器配置。首先我们先配置PWM信号,用于等会捕获实验。我这边选用的定时器3通道4(PB1引脚),如原理图所示也就是我们蓝灯的控制IO口,我们更直观的观察到PWM信号的情况。

mycropython 测量pwm信号_下降沿_09

mycropython 测量pwm信号_mycropython 测量pwm信号_10

 

mycropython 测量pwm信号_寄存器_11

(更改一下:直接模式和间接模式应该叫直连模式和非直连模式)

mycropython 测量pwm信号_寄存器_12

mycropython 测量pwm信号_寄存器_13

mycropython 测量pwm信号_mycropython 测量pwm信号_14

mycropython 测量pwm信号_下降沿_15

  到这里关于相关参数配置基本已经完成,只需要根据之前文章《STM32Cube HAL:GPIO输入/输出(一)》Step4-Step8,设置相关工程参数和生成代码。

 三、添加功能代码

1、我们等会会向串口调试助手发送数据,进行实验结果的验证。 发送数据我们采用printf函数,所有需要重定向c库函数printf到串口。注意使用时需要在keil设置中勾选微库(use mircolib),同时需要添加头文件#include <stdio.h>。重定向代码如下(usart.c)

//重定向c库函数printf到串口DEBUG_USART,重定向后可使用printf函数
int fputc(int ch, FILE *f)
{
	/* 发送一个字节数据到串口DEBUG_USART */
	HAL_UART_Transmit(&huart1, (uint8_t *)&ch, 1, 1000);	
	
	return (ch);
}

//重定向c库函数scanf到串口DEBUG_USART,重写向后可使用scanf、getchar等函数
int fgetc(FILE *f)
{		
	int ch;
	HAL_UART_Receive(&huart1, (uint8_t *)&ch, 1, 1000);	
	return (ch);
}

 2、定义相关变量,以及使能相关定时器和计数处理代码(main.c)

宏定义、全局变量
#define cnt_clk 72000000/(71+1)//计数器频率
#define arr 65535//自定重装载值   
uint32_t CCR1,CCR2,end_flag;//存捕获寄存器获取的值的变量
float duty_cycle,frequency;//频率,占空比
//使能相关定时器以及计数结果处理代码
HAL_TIM_PWM_Start(&htim3,TIM_CHANNEL_4);//开启PWM输出,不需要中断
HAL_TIM_IC_Start_IT(&htim8,TIM_CHANNEL_1);
HAL_TIM_IC_Start_IT(&htim8,TIM_CHANNEL_2);
//处理代码
 while (1)
  {
		if(end_flag==1)
		{
			printf("\r\n频率=%.2fHZ,占空比=%.2f%%\r\n",frequency,duty_cycle);
			end_flag=0;
		}
		HAL_Delay(1000);
  }

  3、中断回调函数(捕获中断)(main.c)

void HAL_TIM_IC_CaptureCallback(TIM_HandleTypeDef *htim)
{
	  /*PWM 信号的第一个上升沿时,定时器产生中断,计数器,CCR寄存器被复位。
    当下降沿到来时,IC2 会捕获,对应的是脉冲宽度测量,但不会产生中断。当第
	  二个上升沿时,IC1会捕获,对应的是周期宽度测量,而且会再次进入中断*/
				//printf("0");
				CCR1=HAL_TIM_ReadCapturedValue(&htim8, TIM_CHANNEL_1);
				if(CCR1!=0)
				{
					CCR2=HAL_TIM_ReadCapturedValue(&htim8, TIM_CHANNEL_2);
					frequency=(float)cnt_clk/(CCR1+1);
					duty_cycle=(float)(CCR2+1)*100/(CCR1+1);	
					end_flag=1;
				}
				else
				{
					frequency=0;
					duty_cycle=0;
				}
}

 关于PWM模式主要需要的注意有两点:

一、PWM模式只能使用通道1和通道2,PWM输入模式工作原理如下图:

mycropython 测量pwm信号_下降沿_16

mycropython 测量pwm信号_mycropython 测量pwm信号_17

二、用于捕获的定时器时基设置,需要考虑到能捕获的最低频率。

公式:能捕获的最低频率f=(时钟频率/(分频系数PSC+1))/(重装载值ARR+1)

 结合这次配置的时基,可得:f=(72000000/(71+1))/(65535+1)=15.2HZ

—— —— —— —— —— —— —— 普通的输入捕获模式—— —— —— —— —— —— ——

 二、 CubeMX配置

step1-step4与上面PWM输入模式一致

step5.同样是配置PWM信号,用于等会捕获实验。我这边选用的同样是定时器3通道4(PB1引脚)。唯一不同的是,为了验证溢出处理的功能,我做了一点小改动。配置为频率20HZ,占空比为60%的PWM。

mycropython 测量pwm信号_mycropython 测量pwm信号_18

 

mycropython 测量pwm信号_下降沿_19

 

mycropython 测量pwm信号_stm32_20

 Step6.定时器输入捕获参数的配置,因为我们使用的普通的输入捕获模式,我选用的定时器8通道1。这里没有用到从模式,所以不进行配置。捕获频率最低频率设置为33.3HZ,增加使能更新中断,对溢出进行处理,解决了最小捕获频率的限制。因此我们可以捕获小于33.3HZ的频率。

mycropython 测量pwm信号_stm32_21

mycropython 测量pwm信号_上升沿_22

mycropython 测量pwm信号_mycropython 测量pwm信号_23

mycropython 测量pwm信号_下降沿_24

到这里关于串口参数配置基本已经完成,只需要根据之前文章《STM32Cube HAL:GPIO输入/输出(一)》Step4-Step8,设置相关工程参数和生成代码。

 三、添加功能代码

1、我们等会会向串口调试助手发送数据,进行实验结果的验证。 发送数据我们采用printf函数,所有需要重定向c库函数printf到串口。注意使用时需要在keil设置中勾选微库(use mircolib),同时需要添加头文件#include <stdio.h>。(代码同上)

2、定义相关变量,以及使能相关定时器和处理代码(main.c)

//宏定义,及全局变量
#define cnt_clk 72000000/(71+1)//计时器时钟
#define arr 30000  //重装载寄存器的值,根据实际情况设置。
uint32_t ccr_cnt1,ccr_cnt2;//存捕获寄存器获取的值的变量
uint32_t Period_cnt,Period_cnt1,Period_cnt2;//更新中断次数以及存放更新中断次数的变量
uint32_t ic_flag,end_flag;//触发标志位,捕获完成标志
float duty_cycle,frequency;//频率,占空比
//使能相关定时器功能
HAL_TIM_PWM_Start(&htim3,TIM_CHANNEL_4);//开启PWM输出,不需要中断
	
__HAL_TIM_CLEAR_IT(&htim8,TIM_CHANNEL_1);//清除更新中断标志位,防止一使能就进入更新中断
HAL_TIM_Base_Start_IT(&htim8);//使能定时器,更新中断
HAL_TIM_IC_Start_IT(&htim8,TIM_CHANNEL_1);//使能定时器,使能捕获输入以及捕获中断
//捕获数据处理
while (1)
  {
   
		if(end_flag==1)//捕获完成标志位
		{
     	duty_cycle=(float)(Period_cnt1*(arr+1)+ccr_cnt1+1)*100/(Period_cnt2*(arr+1)+ccr_cnt2+1);
  		frequency=(float)cnt_clk/(Period_cnt2*(arr+1)+ccr_cnt2+1);
			printf("\r\n频率=%.2fHZ,占空比=%.2f%%\r\n",frequency,duty_cycle);
			end_flag=0;//复位捕获完成标志
			
		}
  }

 3、中断回调函数(更新中断、捕获中断)(main.c)

void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim)
{
	Period_cnt++;
}
void HAL_TIM_IC_CaptureCallback(TIM_HandleTypeDef *htim)
{
				switch(ic_flag)//触发标志位判断
				{
					case 0://第一个上升沿捕获
					{
						__HAL_TIM_SET_COUNTER(&htim8,0);//清除计数器的计数
						ccr_cnt1=0;//初始化相关变量
						ccr_cnt2=0;
						Period_cnt=0;
						Period_cnt1=0;
						Period_cnt2=0;
						__HAL_TIM_SET_CAPTUREPOLARITY(&htim8,TIM_CHANNEL_1,TIM_INPUTCHANNELPOLARITY_FALLING); //设置成下降沿触发	
						ic_flag=1;//更改捕获标志位,进入case1进行相关变量的处理。
						break;
					}
					case 1://第一个下降沿捕获
					{
						ccr_cnt1=__HAL_TIM_GET_COMPARE(&htim8,TIM_CHANNEL_1);//获取存放在CCR寄存器的值(捕获值)
						Period_cnt1=Period_cnt;//获取第一个下降沿到来时的进入更新中断的次数
						__HAL_TIM_SET_CAPTUREPOLARITY(&htim8,TIM_CHANNEL_1,TIM_INPUTCHANNELPOLARITY_RISING);	//设置成上升沿触发	
						ic_flag=2;	//更改捕获标志位,进入case2进行相关变量的处理
						break;
					}
					case 2://第二个上沿捕获	
					{
						ccr_cnt2=__HAL_TIM_GET_COMPARE(&htim8,TIM_CHANNEL_1);//获取存放在CCR寄存器的值(捕获值)
						Period_cnt2=Period_cnt;//获取第二个上升沿到来时的进入更新中断的次数
						ic_flag=0;	//更改捕获标志位,进入case0进行相关变量的处理。
						end_flag=1;//捕获完成标志
						break;
					}	
					default:
					break;
				}
}

 关于普通的输入模式捕获PWM。主要工作流程:

1、第一次捕获到上升沿,计数器清零,存捕获寄存器值的变量清零,存中断次数的变量清零。改变触发边沿为下降沿触发,并将触发标志位为设置为1,使下次下降沿触发时,能进行相对应的处理。

2、第一次捕获到下降沿,将捕获寄存器(CCR寄存器)的值,中断次数存入相应的变量。同时更改触发边沿为上升沿触发,并将触发标志位为设置为2,使下次上升沿触发时,能进行相对应的处理。

3、第二次捕获到上升沿,再次将捕获寄存器(CCR寄存器)的值,中断次数存入相应的变量。将触发标志设置为0,目的为能够循环捕获。同时捕获完成标志为置1。

 4、在main函数中,使用在while循环,使用if语句进行捕获标志的轮询,从而在捕获完成后进行相应的数据处理。

虽然目前代码的配置以及实验结果是没有问题的。但是在调试时候,碰到一个比较疑惑的问题,ARR的设置影响了捕获的准确性,比如在设置为999和1000的时候,同样是存在溢出的,也进行了溢出处理。但是AAR=999时候频率不稳定,AAR=1000频率正常。暂时还是没法找到合理的解释。