对于PWM的捕获,我这里一共使用两种方法实现:第一种是PWM输入模式,采用一个定时器的两个通道(通道一和通道二),配置从模式为复位模式,没有进行溢出处理,所以需要考虑捕获的最低频率;第二种是普通的输入捕获模式,采用一个定时器一个通道,有进行溢出处理,所以没有最低频率的限制。
实验内容:使用高级定时器输入捕获测量PWM的周期和占空比。
一、原理图
—— —— —— —— —— —— —— ——PWM输入模式—— —— —— —— —— —— —— ——
二、 CubeMX配置
Step1.打开 STM32CubeMX,点击“New Project”,选择芯片型号,STM32F103VETx。
Step2.选择时钟源,并配置时钟树。选择Crystal/Ceramic Resonator,并配置系统时钟为72M。
Step3.配置SYS,我们这里选择的是Serial Wire。(正常情况配置不配置不影响,debug可以使用。但是你不可以把这两个引脚用于其他复用功能,如果用于其他复用功能,debug就不起作用了。)
Step4.串口配置(主要为了在串口调试助手显示测试的时间),因为没有用到中断和DMA所以我们就不过多讲解。
Step5.接下来进行最主要的定时器配置。首先我们先配置PWM信号,用于等会捕获实验。我这边选用的定时器3通道4(PB1引脚),如原理图所示也就是我们蓝灯的控制IO口,我们更直观的观察到PWM信号的情况。
(更改一下:直接模式和间接模式应该叫直连模式和非直连模式)
到这里关于相关参数配置基本已经完成,只需要根据之前文章《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输入模式工作原理如下图:
二、用于捕获的定时器时基设置,需要考虑到能捕获的最低频率。
公式:能捕获的最低频率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。
Step6.定时器输入捕获参数的配置,因为我们使用的普通的输入捕获模式,我选用的定时器8通道1。这里没有用到从模式,所以不进行配置。捕获频率最低频率设置为33.3HZ,增加使能更新中断,对溢出进行处理,解决了最小捕获频率的限制。因此我们可以捕获小于33.3HZ的频率。
到这里关于串口参数配置基本已经完成,只需要根据之前文章《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频率正常。暂时还是没法找到合理的解释。