嵌入式|蓝桥杯STM32G431(HAL库开发)——CT117E学习笔记14:PWM捕获

系列文章目录

嵌入式|蓝桥杯STM32G431(HAL库开发)——CT117E学习笔记01:赛事介绍与硬件平台

嵌入式|蓝桥杯STM32G431(HAL库开发)——CT117E学习笔记02:开发环境安装

嵌入式|蓝桥杯STM32G431(HAL库开发)——CT117E学习笔记03:G4时钟结构

嵌入式|蓝桥杯STM32G431(HAL库开发)——CT117E学习笔记04:从零开始创建工程模板并开始点灯

嵌入式|蓝桥杯STM32G431(HAL库开发)——CT117E学习笔记05:Systick滴答定时器

嵌入式|蓝桥杯STM32G431(HAL库开发)——CT117E学习笔记06:按键输入

嵌入式|蓝桥杯STM32G431(HAL库开发)——CT117E学习笔记07:ADC模数转换

嵌入式|蓝桥杯STM32G431(HAL库开发)——CT117E学习笔记08:LCD液晶屏

嵌入式|蓝桥杯STM32G431(HAL库开发)——CT117E学习笔记09:EEPROM

嵌入式|蓝桥杯STM32G431(HAL库开发)——CT117E学习笔记10:USART串口通讯

嵌入式|蓝桥杯STM32G431(HAL库开发)——CT117E学习笔记11:数字电位器MCP4017

嵌入式|蓝桥杯STM32G431(HAL库开发)——CT117E学习笔记12:DAC数模转换

嵌入式|蓝桥杯STM32G431(HAL库开发)——CT117E学习笔记13:RTC实时时钟

嵌入式|蓝桥杯STM32G431(HAL库开发)——CT117E学习笔记14:PWM捕获

嵌入式|蓝桥杯STM32G431(HAL库开发)——CT117E学习笔记15:PWM输出

嵌入式|蓝桥杯STM32G431(HAL库开发)——CT117E学习笔记16:蓝桥杯编程手册

嵌入式|蓝桥杯STM32G431(HAL库开发)——CT117E学习笔记17:第十四届省赛真题


目录

系列文章目录

前言

一、基础知识

二、测量单路频率

三、测量双路频率

四、测量单路占空比

五、测量双路占空比


前言

这节讲解一下PWM捕获的基础知识。PWM捕获是指我们的单片机可以测量输入到它管脚上的方波的频率或者占空比,捕获就是说我们可以捕捉外部方波的信息。这就是我们这节要学习的内容。

一、基础知识

首先我们看一下电路图,这里有两个555芯片构成了一个方波发生器。

我们可以通过R40和R39两个电位器,就可以在555芯片的3号管脚输出一个方波,方波的频率就是通过R40和R39进行调节。3号管脚通过跳帽J9、J10接入PA15和PB4两个管脚。PA15和PB4分别可以配置成TIM2_CH1和TIM3_CH1,也就是定时器2的1通道和定时器3的1通道,用他们来捕获外部方波的频率和占空比。

捕获频率的作用有很多,比如说无人机上很多接收机的信号都是方波信号,也可以用来测量霍尔传感器的输出等等。但是我们这里只需要测量555的输出就行了。我们不需要知道555的具体接法是什么,只需要知道他可以通过调节这两个电阻就能产生不同频率的方波就行了,而我们的任务就是要用单片机测量他们的频率。另外还要注意好每个管脚分别对应着定时器的哪个通道。

此外呢,我们还要知道如何用示波器来观察方波的频率和占空比,只需要把示波器接在J9、J10两个跳线上就行了,我们调节R39、R40就可以看到方波的频率是在变化的。大致范围是在1-23kHz,并且频率太快(20k以上)的话波形是会失真的,波形正常时的频率范围应该在5k左右。占空比大概就是在50%。

二、测量单路频率

我们先研究U8芯片,通过R40可以产生频率可变的方波,然后通过跳帽J10接到了PA15管脚,对应TIM2_CH1通道来捕获。

首先看一下编程思路:每次上升沿中断,获取CNT值(定时器的计数值),并重新清零CNT值,重新计时。CNT的值就是一个PWM的周期,通过周期可以获得其频率(周期的倒数)。

我们可以把定时器配置成每us加1,那么如果CNT=1000,就相当于周期就是1ms,对应的频率就是1kHz。

程序设计步骤:

1.用“模板”生成代码

2.配置TIM2的PA15作为TIM2_CH1的输入捕获,注意要配置输入捕获模式,并勾选中断NVIC(一定不能忘)

3.根据需求配置TIM2_CH1的分频值,推荐配置成1us计数一次(因为系统时钟80MHz,我们想要1us就要1MHz,也就是分频80,因为从0开始算所以设置成79。up是上升计数模式,默认,不用更改。Counter Period是计时周期,它是32位的,也就是0—0xffffffff,如果超过这个这个周期就会溢出,所以我们把它设置成最大。)

4.移植文件到编程工程

  • main.c包含tim.h头文件
  • 添加tim.c的源文件和TIM相关的HAL库驱动函到工程中
  • 在stm32g4xx_hal_conf.h中启动TIM模块
  • 在stm32g4xx_it.c中移植TIM2_IRQHandler中断服务函数
  • 在主函数中调用MX_TIM2_Init()定时器初始化函数和HAL_TIM_IC_Start_IT(&htim2,TIM_CHANNEL_1)启动定时器2捕获功能
  • HAL_TIM_IC_CaptureCallback回调函数里,获取CNT值,计算PWM的频率

前四步都是常规的移植步骤,我们前面已经做过很多次,下面重点讲解一下后面两步:定时器捕获启动函数和中断回调函数。

MX_TIM2_Init()初始化之后,我们在后面加上HAL_TIM_IC_Start_IT(&htim2,TIM_CHANNEL_1)启动定时器2捕获。这样只要有up输入进到PA15,我们的定时器就会产生一个中断,然后就可以在中断处理函数中处理了。

下面我们就可以用HAL_TIM_IC_CaptureCallback(TIM_HandleTypeDef *htim)回调函数了,这个回调函数我们可以在stm32g4xx_it.c中找到HAL_TIM_IRQHandler(),即TIM的中断处理函数,然后在里面就能找到了。

这是一个弱定义的函数,我们把它复制到main.c中编辑它的功能。编程思路之前已经讲过了,中断后开始计时,每1us计时一次作为CNT,回调函数中先要__HAL_TIM_GetCounter(&htim2)获取之前计时的CNT,再__HAL_TIM_SetCounter(&htim2,0)设置重新计时,然后计算得到频率,最后重新使能中断启动函数(否则只会中断一次)。

//pwm capture
u32 tim2_cnt1=0;
u32 f1=0;
void HAL_TIM_IC_CaptureCallback(TIM_HandleTypeDef *htim)
{
	tim2_cnt1 = __HAL_TIM_GetCounter(&htim2); //获取CNT
	__HAL_TIM_SetCounter(&htim2,0);//CNT置0,重新计数
	f1=1000000/tim2_cnt1;//计算频率
	HAL_TIM_IC_Start_IT(&htim2,TIM_CHANNEL_1);//重新启动中断
}

这样我们就能得到R40输出的方波频率f1了。我们可以把它显示在LCD上,也可以接在示波器上看一下波形,频率对不对。接示波器的时候一头接地,一头接J10。

三、测量双路频率

除了PA15可以测量R40调节输出的PWM频率之外,PB4也可以测量R39调节输出的PWM频率。我们可以同时测量这两路的频率。

PB4可以配置为TIM3的CH1通道,经过上一节我们知道,PA15可以配置为TIM2的CH1通道,我们打开CubeMX查看如何初始化,这里我们就直接打开刚刚的工程即可,就不新建新的工程了。

如图,选择PB4和PA15引脚。

分别选择Channel1的输入捕获直连模式

然后使能TIM2和TIM3的中断。使能好后我们设置一下Configuration里面的参数。依然是预分频79,让定时器1us计数一次到CNT中。Counter Period继续选择最大值0xFFFF或者65535(因为这里是16位的,鼠标点击它会提示可以设置的最大数值)。其他的我们就保持默认。

然后生成代码,CubeMX会自动帮我们做好分频、周期的配置、使能中断。这里我们就不需要移植了。

我们可以在stm32g4xx_it.c文件中看到两个TIM的中断处理函数了。

查看它的定义,在里面也可以看到中断回调函数HAL_TIM_IC_CaptureCallback()了。

继续查看它的定义,可以看到HAL_TIM_IC_CaptureCallback(TIM_HandleTypeDef *htim)。

这样以后我们要编写TIM2的回调函数就可以直接定义它了(因为是弱定义的函数),但是有一个问题是,我们可以注意TIM2和TIM3的回调函数都是同一个,所以函数本身无法区分是哪个TIM。就需要我们人为地编写程序判断,要写一个if语句,判断这里的结构体htim是哪一个。比如if(htim == &htim2),就说明这里是TIM2,后面就写TIM2要做的工作。

不过我们这里还没有启动中断,同样,与上节一样,TIM2和TIM3的中断启动函数分别为:

HAL_TIM_IC_Start_IT(&htim2,TIM_CHANNEL_1)

HAL_TIM_IC_Start_IT(&htim3,TIM_CHANNEL_1)

这个函数名和格式是需要记忆的。我们直接把他们放在各自的TIM初始化函数后面即可开启中断。

启动中断之后我们继续编写中断回调函数,实现PWM捕获的功能,逻辑依然是:先获取CNT值,再清空CNT值,重新计数,再用CNT值计算此时的频率,再重新开启中断。注意不要忘记__HAL_TIM_GetCounter()__HAL_TIM_SetCounter()这两个函数的用法。

//PWM Capture
u32 tim2_cnt = 0;
u32 tim3_cnt = 0;
u32 f40 = 0;
u32 f39 = 0;
void HAL_TIM_IC_CaptureCallback(TIM_HandleTypeDef *htim)
{
	if(htim == &htim2)
	{
		tim2_cnt = __HAL_TIM_GetCounter(&htim2);
		__HAL_TIM_SetCounter(&htim2,0);
		f40 = 1000000/tim2_cnt;//得到频率
		HAL_TIM_IC_Start_IT(&htim2,TIM_CHANNEL_1);
	}
	else if(htim == &htim3)
	{
		tim3_cnt = __HAL_TIM_GetCounter(&htim3);
		__HAL_TIM_SetCounter(&htim3,0);
		f39 = 1000000/tim3_cnt;//得到频率
		HAL_TIM_IC_Start_IT(&htim3,TIM_CHANNEL_1);
	}
}

我们可以在LCD上显示R39和R40的频率,这里就不演示了。

四、测量单路占空比

测完频率,我们接下来看一下如何测量频率和占空比。我们知道,测量频率本质就是测量PWM输入的周期,用CNT计数器来实现,那么同样,占空比的本质就是高电平占整个周期的比值。所以测量占空比除了要测量周期之外,还要再测量一下高电平时间。

那么大概的编程思路就是:

  • 第一次上升沿中断,开始计时(清零计数器),并改成下降沿中断。
  • 下降沿中断,获取计数器的CNT值T1,并改成上升沿中断。
  • 第二次上升沿中断,获取计数器的CNT值T2。通过T2可以获得PWM的频率,T1/T2可以获得PWM的占空比。

其实这里面大部分的操作,比如如何开启中断,如何获取计数器的CNT的值等等,我们都是已经掌握的,唯一没有掌握的就是如何改变上升沿下降沿的极性。所以我们初始化的时候要怎么改变极性呢?我们只需要操作一个寄存器:CCER,就可以改变极性了。此处如果我们把CCER设置成TIMx->CCER |= 0x02就是改成下降沿中断,TIMx->CCER &= ~0x02就是改成上升沿中断。

具体的编程实践中,我们需要定义一个变量用于记录计数状态,把开始计数定义成0,把获取t1定义成1,把获取t2定义成2。像这样:

u8 tim2_state = 0;

这样的话我们中断回调函数中就可以通过判断tim2_state的值来选择不同的操作了。比如我们开始计时后第一次上升沿中断后(tim2_state=0),清零计数器,然后改成下降沿中断,然后让tim_state=1,这样下次一次下降沿中断时,我们判断tim_state=1,就进入第二段程序,获取此时的CNT值,改成上升沿中断,再让tim2_state=2。最后上升沿中断的时候,获取CNT,再把time2_state改为0。

写成代码:

//PWM Capture
u32 tim2_cnt1 = 0;
u32 tim2_cnt2 = 0;
u32 f40 = 0;
float d40 =0;
u8 tim2_state = 0;
void HAL_TIM_IC_CaptureCallback(TIM_HandleTypeDef *htim)
{
	if(tim2_state == 0)
	{
        __HAL_TIM_SetCounter(&htim2,0);  //开始计时
        TIM2->CCER |= 0x02;  //开始下降沿中断
        tim2_state == 1;
	}
	else if(tim2_state == 1)
	{
		tim2_cnt1 = __HAL_TIM_GetCounter(&htim2); //获取t1
		TIM2->CCER &= ~0x02;  //开始上升沿中断
        tim2_state == 2;
	}
    else if(tim2_state == 2)
	{
		tim2_cnt2 = __HAL_TIM_GetCounter(&htim2); //获取t2
        tim2_state == 0;
		f40 = 1000000/tim2_cnt2;//得到频率
		d40 = tim2_cnt1*100.0f/tim2_cnt2;//得到占空比
	}
    HAL_TIM_IC_Start_IT(&htim2,TIM_CHANNEL_1);
}

五、测量双路占空比

测量双路占空比与单路占空比是一样的,唯一不同的是要在回调函数里加一个判断htim。CubeMX的初始化与第三章测量双路频率是一样的,生成代码后要注意两个tim都要初始化,并且开启中断。

代码:

//PWM Capture
u32 tim2_cnt1 = 0;
u32 tim2_cnt2 = 0;
u32 tim3_cnt1 = 0;
u32 tim3_cnt2 = 0;
u32 f40 = 0;
u32 f39 = 0;
float d40 =0;
float d39 =0;
u8 tim2_state = 0;
u8 tim3_state = 0;
void HAL_TIM_IC_CaptureCallback(TIM_HandleTypeDef *htim)
{
    if(htim == &htim2)
    {
	    if(tim2_state == 0)
	    {
            __HAL_TIM_SetCounter(&htim2,0);  //开始计时
            TIM2->CCER |= 0x02;  //开始下降沿中断
            tim2_state == 1;
	    }
	    else if(tim2_state == 1)
	    {
	    	tim2_cnt1 = __HAL_TIM_GetCounter(&htim2); //获取t1
	    	TIM2->CCER &= ~0x02;  //开始上升沿中断
            tim2_state == 2;
    	}
        else if(tim2_state == 2)
	    {
	    	tim2_cnt2 = __HAL_TIM_GetCounter(&htim2); //获取t2
            tim2_state == 0;
	    	f40 = 1000000/tim2_cnt2;//得到频率
	    	d40 = tim2_cnt1*100.0f/tim2_cnt2;//得到占空比
    	}
        HAL_TIM_IC_Start_IT(&htim2,TIM_CHANNEL_1);
    }

    if(htim == &htim3)
    {
	    if(tim3_state == 0)
	    {
            __HAL_TIM_SetCounter(&htim3,0);  //开始计时
            TIM3->CCER |= 0x02;  //开始下降沿中断
            tim3_state == 1;
	    }
	    else if(tim3_state == 1)
	    {
	    	tim3_cnt1 = __HAL_TIM_GetCounter(&htim3); //获取t1
	    	TIM3->CCER &= ~0x02;  //开始上升沿中断
            tim3_state == 2;
    	}
        else if(tim3_state == 2)
	    {
	    	tim3_cnt2 = __HAL_TIM_GetCounter(&htim3); //获取t2
            tim3_state == 0;
	    	f39 = 1000000/tim3_cnt2;//得到频率
	    	d39 = tim3_cnt1*100.0f/tim3_cnt2;//得到占空比
    	}
        HAL_TIM_IC_Start_IT(&htim3,TIM_CHANNEL_1);
    }
}
  • 33
    点赞
  • 14
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值