新手STM32:基于HAL库的定时器和PWM输出
新手STM32:基于HAL库的定时器和PWM输出
本文是一篇面向STM32新手的详细教程,主要介绍了基于HAL库的定时器控制和PWM输出实现。文章从理论到实践,详细讲解了定时器和PWM的基本概念、配置步骤和代码实现,适合嵌入式开发爱好者和工程师参考学习。
一、定时器与PWM概述
(1)定时器概述
在STM32微控制器的丰富功能模块中,定时器起着不可或缺的关键作用。以STM32F103C8T6这款广泛应用的芯片为例,它配备了总计8个定时器,依据功能特性和复杂程度可细分为高级定时器(TIM1和TIM8)、通用定时器(TIM2-5)以及基本定时器(TIM6和TIM7)。通用定时器凭借其全面且灵活的功能特性,在众多应用场景中脱颖而出,成为开发者频繁选用的得力工具。
- 高级定时器:TIM1和TIM8
- 通用定时器:TIM2-5
- 基本定时器:TIM6和TIM7
通用定时器不仅能够精准地实现定时功能,还可对输入信号的脉冲长度进行精确测量(即输入捕获功能),以及依据实际需求输出各种特定波形,涵盖输出比较、产生PWM(脉冲宽度调制)波形以及单脉冲输出等多样化操作。这种多功能性使其在诸如工业自动化控制、智能家电控制、通信设备定时同步以及电机控制等众多领域得以大展身手。例如,在工业自动化生产线中,可借通用定时器精准把控各工序执行时间间隔与顺序;于电机控制里,借PWM功能灵活调节电机转速与转矩,实现高效稳定运行。
(2)PWM概述
PWM(脉冲宽度调制)技术的核心要素在于占空比。占空比,作为衡量脉冲信号特征的关键指标,被定义为在一个完整信号周期内,高电平持续时间与整个信号周期时长的百分比比值,即占空比 = Tp/T(Tp为高电平时间,T为信号周期)。通过细腻且精准地调整占空比数值,能够巧妙地改变信号中高电平与低电平的持续时长比例关系,进而实现对外部设备的精准功率控制或模拟信号等效输出效果。
占空比(Duty Cycle),是指在一个周期内,高电平时间占整个信号周期的百分比,即高电平时间与周期的比值:占空比=Tp/T
调节占空比可以得到不同的电平持续时间。
二、HAL库实现定时器控制
基于前文的讲述,我们可以开始对定时器进行一些基本的操作了。本次实验,我们将设置一个5秒的定时器,每隔5秒从串口发送“hello windows!”;同时设置一个2秒的定时器,让LED等周期性地闪烁,实现一个多任务并发运行的功能。
(1)CUBEMX配置
首先新建一个芯片为STM32F103C8T6的项目,然后进行一些基本的配置。
SYS:配置为Serial Wire
RCC:配置为高速外部晶振
因为我们需要配置一个定时闪烁的LED灯,所以需要配置一个GPIO口进行输出,我选择的PA5进行电平输出。
然后配置我们的GPIO口,我们采用低电平点亮的方式,也就是当我们的GPIO口输出低电平时,LED灯亮起,所以我们的初始GPIO输出电平设为高电平,其他的配置保持在默认选项就行。
接下来配置我们的定时器,选择通用定时器TIM2,配置我们的时钟源为内部时钟;预分频系数配置为71(因为系统处理时会自动为我们的预分频系数加1,所以实际上的预分频系数为72),因为系统时钟树我们会设置为72MHz,分频下来就得到1MHz;计数次数设置为5000次,那么每完成一次计数,经过的时间为 5000/1MHz=0.005秒也就是5ms,换言之,每过5ms定时器TIM2就会进行一次定时中断。
配置步骤如下:
因为我们还需要定时向串口发送数据,所以需要再配置一个定时器来控制这一部分的功能。 我们同样选择一个通用定时器,这里我选择了TIM3,配置步骤与数据和TIM2相同。
接下来开启我们的定时器中断并生成中断配置代码。
接下来配置我们的系统时钟树,按照下图步骤进行配置。
因为我们还存在串口数据的发送,所以还需要配置我们的USART。我们选择配置USART1,选择Asynchronous(异步通信)并开启中断。
接下来就可以生成我们的代码文件了,按照下图的标注进行配置。
点击右上角的GENERATE CODE生成我们的代码,在加载结束后弹出的窗口点击Open Project打开我们的keil项目。
(2)代码编写
首先,我们需要在主函数内打开我们的定时器,所使用的函数为HAL_TIM_Base_Start_IT。
对于参数,“h”指的是HAL库,“tim2”指的就是TIM2,TIM3也是一样的操作。这样子就打开了我们的定时器。
因为这次代码的功能完全由中断实现,因此我们不需要再在while循环内书写代码,只需要在主函数外用一个中断回调函数实现我们的功能。
在主函数内随便找个位置书写我们的中断回调函数,为了方便起见,大家可以在注释标注USER CODE的地方书写我们自己的代码,也就是在main函数下面的
在这个函数内,首先定义我们函数内部的变量,这是我们用来保存定时器当前定时值的参数。前文所说,每当计数器结束一个周期,时间就经过0.005秒,同时我们的计数器的值也会增加1,将我们需要的时间与定时器一个周期的时间也就是0.005秒进行相除就能得到我们的计数器周期的值。然后分别配置两个定时器在各自的周期内完成自己的工作就行了。
代码的部分就这样,接下来我们就可以进行代码的烧录了。
(3)结果展示
因为这次用到了串口的缘故,为了使烧录与接收串口通信不那么麻烦,我选择了用FLYMCU烧录工具直接使用USB to TTL进行烧录。
有需要的伙伴和不了解怎样用TTL烧录的小伙伴可以看我的另一篇文章新手STM32:利用HAL库实现USART串口通信-CSDN博客文章浏览阅读727次,点赞10次,收藏14次。在本次的学习过程中,我们系统且深入地探索了串口通信领域的相关知识。首先,着重对串口通信的电平协议展开了详细的学习与研究。串口通信的电平协议作为数据传输的基础规范,有着至关重要的意义。其中涵盖了如 TTL 电平,其以较为简洁的电平逻辑,通常运用 +5V 表示逻辑 1,0V 表示逻辑 0,这种电平标准在芯片内部或短距离通信场景中发挥着关键作用,因其成本低廉且电路设计相对简易;
因为我们开启的是USART1,所以我们需要找到USART1的TX与RX,它们分别是PA9和PA10,不清楚的小伙伴可以对照下图来看,我们待会儿的PWM也会对照下图。
进行烧录后,运行就能得到下面的情况。
LED灯每两秒翻转一次状态
每五秒向上位机发送一次串口数据
三、PWM实现呼吸灯控制
(1)CUBEMX配置
对于SYS和RCC的配置与上述相同,这里不再赘述。我们直接开始配置定时器。
其实配置定时器的步骤也和上面一致,唯一不同的是打开PWM通道,同时计数次数也要做出相应的更改,这次我们配置TIM3和TIM4这两个通用定时器。我们选择打开其PWM的通道2,如下图所示。
由于我们采用的是PWM输出,他有固定的IO口,所以我们不需要再额外配置GPIO口。
接下来的配置都和上述一致,我只是把不同的点和值得注意的点写了出来,但是大家在配置的时候还是需要按部就班地配置。
(2)代码编写
因为我这次配置了两个外接LED灯,我想配置成两个不同呼吸频率的灯,通过对比来直观展现出占空比变化的影响。
首先还是打开我们的定时器,因为这次打开了我们的PWM,所以我们需要的函数也发生了相应的更改。
我们使用HAL_TIM_PWM_Start来实现开启定时器和PWM通道的功能。
第一个参数就是我们要打开的定时器,前文已经介绍过,第二个参数是我们打开的PWM通道,因为我们之前再CUBE里面配置的是通道二,所以我们这里就需要输入TIM_CHANNEL_2来进行通道打开。
现在来实现我们的呼吸功能,所谓呼吸灯其实是不断地修改PWM的占空比来达到我们的LED灯逐渐变亮和逐渐变暗的功能。为了实现呼吸灯先逐渐变亮后逐渐变暗的功能,首先我们得设定一个阈值,在达到阈值前,我们的占空比逐渐增大;在达到阈值后,我们的占空比逐渐变小直到为0或者其他特定值后再反过来增加。
而实现点灯的IO口,我们可以按照刚才给出的开发板的接口图对照,其中T3C2指的就是TIM3_CHANNEL2,而所对应的接口为PA7,同理可以找到T4C2的接口为PB7,将我们的两个灯以低电平点亮的方式外接在开发板上。
而实现将PWM输出的函数为:__HAL_TIM_SetCompare
从左至右的三和参数分别为:定时器、通道、占空比。其实现的功能就是在定时器指定的通道输出指定占空比的PWM脉冲。了解这些后,就可以开始我们的代码书写了。
while (1)
{
/* USER CODE END WHILE */
HAL_Delay(50); //控制呼吸灯呼吸速率
//控制占空比大小,即修改CCRx的大小
if(TIM3_direction){
TIM3_pwmVal=TIM3_pwmVal+10;
}else{
TIM3_pwmVal=TIM3_pwmVal-10;
}
//设置了ARR为499,因此每计数500为一个周期
if(TIM3_pwmVal > 500){
TIM3_direction = 0; //改变呼吸灯方向
}else if(TIM3_pwmVal == 0){
TIM3_direction = 1; //改变呼吸灯方向
}
//修改定时器3通道2的PWM的占空比
__HAL_TIM_SetCompare(&htim3,TIM_CHANNEL_2,TIM3_pwmVal);
if(TIM4_direction){
TIM4_pwmVal=TIM4_pwmVal+20;
}else{
TIM4_pwmVal=TIM4_pwmVal-20;
}
if(TIM4_pwmVal > 500){
TIM4_direction = 0; //改变呼吸灯方向
}else if(TIM4_pwmVal == 0){
TIM4_direction = 1; //改变呼吸灯方向
}
//修改定时器4通道2的PWM的占空比
__HAL_TIM_SetCompare(&htim4,TIM_CHANNEL_2,TIM4_pwmVal);
/* USER CODE BEGIN 3 */
}
当然,别忘了定义我们的全局变量,这是完成我们呼吸灯的重要参数。
uint16_t TIM3_pwmVal=0; //TIM3的占空比
uint16_t TIM4_pwmVal=0; //TIM3的占空比
uint16_t TIM3_direction=1; //控制TIM3的呼吸;1是越来越亮,0是越来越暗
uint16_t TIM4_direction=1; //控制TIM4的呼吸;1是越来越亮,0是越来越暗
代码的具体位置无所谓,但是要在我们的int main前面。接下来就可以进行代码的烧录了。
(3)结果展示
编译完成无错误后,就可以将我们的代码烧录进去。结果应该和下图一致。
(4)Keil仿真
因为身边没有真实的滤波器,所以我们选择用Keil仿真来看一下PWM的输出波形。
首先点击Keil工具栏中的魔术棒,点击Debug选项卡,按照下图方式配置。
配置完成后点击OK,再点击
开始Debug调试,然后通过下面两种方式找到我们的逻辑分析仪。
方法一
方法二
打开逻辑分析仪后,点击左上角的Set Up添加端口。
按照下面的方式添加我们的端口。
添加定时器3的通道2
添加定时器4的通道2
记得要将输出模式改为bit方便观察。
点击close后,我们的两个端口就保存好了。接下来找到
,先点击左边按钮复位,复位后点击右边按钮运行。
这就是我们得到的波形,因为PWM的占空比是逐步变化的,我们将时间轴拉长一点观察的更加清晰。
四、总结
本文聚焦于STM32定时器与PWM输出,利用HAL库实现多任务与呼吸灯控制,涵盖理论阐释、实践步骤及结果验证,为嵌入式开发人员掌握定时器和PWM技术提供全面指引。
(1)定时器与PWM核心要点
定时器分类及特性:STM32F103C8T6集成8种定时器,含高级、通用、基本三类。通用定时器应用广,具定时、输入捕获、输出波形等多功能,为本文技术实践基石。
PWM原理剖析:占空比定义为周期内高电平占比,精准调控此参数可塑造多样电平持续时长,为PWM控制设备核心机制,如呼吸灯亮度渐变依赖占空比动态调整。
(2)基于HAL库的定时器控制实践
CUBEMX配置流程:项目起始于芯片选型STM32F103C8T6,细致配置SYS、RCC、GPIO、定时器(TIM2与TIM3)、USART1及系统时钟树参数,同时激活定时器中断,为功能实现设硬件基础与中断响应框架。
代码编写策略:主函数开启定时器中断后,于中断回调函数部署功能逻辑。借定时器周期与计数值关联,精准达成5秒串口数据发送与2秒LED闪烁多任务协同,展示定时器精准定时调度能力,满足复杂任务时序需求。
实践成果展示:借FLYMCU与USB to TTL高效烧录,运行呈现预期效果。LED闪烁规律、串口数据按时发送,验证定时器设置与中断处理精准可靠,为实际多任务场景提供可行范例。
(3)PWM实现呼吸灯控制探索
配置环节要点:SYS、RCC沿用通用设置,TIM3和TIM4定时器配置独特点在开启PWM通道2及适配计数次数,因PWM输出固有IO特性,免额外GPIO配置,简化硬件布局且契合PWM信号传输路径。
代码功能架构:代码以while (1)
循环为核心,借HAL_TIM_PWM_Start
开启定时器与PWM通道,__HAL_TIM_SetCompare
动态调控占空比。依占空比增减逻辑与阈值判定,驱动呼吸灯亮暗循环切换,双灯不同呼吸频率设定凸显PWM灵活控制优势。
实践成果反馈:烧录后呼吸灯动态变化契合预期,Keil仿真借逻辑分析仪呈现TIM3与TIM4通道2 PWM波形。占空比渐变波形直观展示呼吸灯控制原理,为优化与故障排查供有力支撑,提升开发效率与精准度。