一、STM32定时功能介绍
TIM(Timer)定时器是一种用于计数和定时的高精度硬件设备。它内部包含16位计数器、预分频器和自动重装寄存器的时基单元,可以实现对输入时钟的精确计数。当计数值达到设定值时,定时器会触发中断,以便执行相应的操作。这种定时器具有高精度、长定时时间的优点,因此在许多嵌入式系统中得到广泛应用。
二、PWM功能介绍
PWM(Pulse width modulation)脉冲宽度调制。PWM是通过编程控制输出方波的频率和占空比(高低电平的比例)。应用:测量,通信,功率控制与变换等各种领域(呼吸灯、电机)。
STM32中PWM属于定时器的功能,通过直接配置定时器就可以使用PWM,除了定时器的基本配置以外,还要加入一个比较计数值确定一个周期内翻转电平的时机,还需要GPIO输出方波,需要用到GPIO的复用功能。
PWM波的高低带你平的顺序是由极性、PWM模式和计数模式共同决定。极性决定有效电平(默认电平),PWM模式指的是一个周期内有效电平和无效电平的顺序。
注意:只有有定时器服用功能的GPIO才可以输出PWM。
要实现PWM信号的输出,需要用到三个寄存器:自动重载寄存器TIMx_ARR,捕获/比较寄存器TIMx_CCRn(n表示通道编号1~4,下同)以及计数器寄存器TIMx_CNT,并通过通道引脚TIMx_CHn输出PWM信号。
为了表述方便,我们将TIMX_ARR寄存器的内容记为自动重载值ARR,TIMx_CCRn寄存器的内容记为捕获/比较值CCR,计数器存器TIMx_CNT的内容记为计数值CNT。整个PWM信号的输出过程下图所示
图中,我们假定定时器工作在向上计数PWM模式,且当CNT<CCRx时,输出0,当CNT>=CCRx时输出1。那么就可以得到如上的PWM示意图:当CNT值小于CCRx的时候,IO输出低电平(O0),当CNT值大于等于CCRx的时候,IO输出高电平(1),当CNT达到ARR值的时候,重新归零,然后重新向上计数,依次循环。改变CCRx的值,就可以改变PWM输出的占空比,改变ARR的值,就可以改变PWM输出的频率,这就是PWM输出的原理。
三、创建工程
创建基本的keil工程请参照这篇博客
https://blog.youkuaiyun.com/qq_69626808/article/details/133870540?spm=1001.2014.3001.5501
在原有基础工程上,我们在左边文件栏“Hardware"添加一个c文件,和一个头文件,统一命名为”PWM"。注意添加时把路径加上
我们在“PWM.h”中添加基本的函数语句
#ifndef __PWM_H
#define __PWM_H
#include "stm32f10x.h" // Device header
#endif
3.1内置时钟使能
在STM32中,控制TIM2使能的是APB1,且需要控制TIM2为内部时钟模式,配置的相关函数为
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2,ENABLE);
TIM_InternalClockConfig(TIM2);
3.2配置时基单元
配置时基单元有点类似于GPIO,也是需要定义一个结构体变量,再将其结构体成员引出来分别进行配置。声明结构体的语句为
TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStruct;
根据公式,时钟频率为72MHZ/(PSC+1)/(ARR+1),经过计算,当PSC设置为719,ARR设置为99,时钟频率为1000HZ,也就是0.001s,也就是说,每个电平存在时间为0.001s
//配置时基单元
TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStruct;
TIM_TimeBaseInitStruct.TIM_ClockDivision=TIM_CKD_DIV1;
TIM_TimeBaseInitStruct.TIM_CounterMode=TIM_CounterMode_Up;
TIM_TimeBaseInitStruct.TIM_Period=100-1;
TIM_TimeBaseInitStruct.TIM_Prescaler= 720-1;
TIM_TimeBaseInitStruct.TIM_RepetitionCounter=0;
TIM_TimeBaseInit(TIM2,&TIM_TimeBaseInitStruct);
TIM_ClearFlag(TIM2,TIM_FLAG_Update);
3.3使能更新中断
我们需要配置使能中断,设置为输出比较模式,这样才能将已激活的时钟信号进行输出比较从而得到PWM波形。
输出比较模式的配置有专门的标准库函数,也是结构体类型,我们一个个了解并将其配置。输出比较结构体定义为
//使能更新中断
TIM_ITConfig(TIM2,TIM_IT_Update,ENABLE);
TIM_OCInitTypeDef TIM_OCInitStruct;
TIM_OCStructInit(&TIM_OCInitStruct);
TIM_OCInitStruct.TIM_OCMode=TIM_OCMode_PWM1;
TIM_OCInitStruct.TIM_OCPolarity=TIM_OCPolarity_High;
TIM_OCInitStruct.TIM_OutputState= TIM_OutputState_Enable;
TIM_OCInitStruct.TIM_Pulse=0 ;
TIM_OC1Init(TIM2,&TIM_OCInitStruct);
3.4配置GPIO并启动定时器
此外,由于需要使用LED灯,所以还需要加上GPIO的配置函数。此外,TIM对应GPIO引脚需要查找定义表,TIM2_CH1_ETR引脚复用在PA0上,故我们配置PA0。
//配置GPIO
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);
GPIO_InitTypeDef GPIO_InitStructure;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOA, &GPIO_InitStructure);
GPIO_SetBits(GPIOA, GPIO_Pin_0);
//启动定时器
TIM_Cmd(TIM2,ENABLE);
3.5 输出比较函数
根据前面的公式我们知道,PWM的占空比的公式为CCR/ARR+1。前面我们设定为ARR为99,故我们可以设定CCR在0-100波动,这样算出的占空比就是0-100%了,具体结果会在主函数里展示。我们现在只是定义了一个输出比较函数
在STM32标准库里,系统给出了设置CCR函数为
void TIM_SetCompare1(TIM_TypeDef* TIMx, uint16_t Compare1)
我们可以通过这个函数来设定一个不断变化的CCR值。
void PWM_SetCompare1(uint16_t Compare)
{
TIM_SetCompare1(TIM2,Compare);
}
最后C文件主要代码
#include "stm32f10x.h" // Device header
void PWM_Init(void)
{
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2,ENABLE);
TIM_InternalClockConfig(TIM2);
TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStruct;
TIM_TimeBaseInitStruct.TIM_ClockDivision=TIM_CKD_DIV1;
TIM_TimeBaseInitStruct.TIM_CounterMode=TIM_CounterMode_Up;
TIM_TimeBaseInitStruct.TIM_Period=100-1;
TIM_TimeBaseInitStruct.TIM_Prescaler= 720-1;
TIM_TimeBaseInitStruct.TIM_RepetitionCounter=0;
TIM_TimeBaseInit(TIM2,&TIM_TimeBaseInitStruct);
TIM_ClearFlag(TIM2,TIM_FLAG_Update);
TIM_ITConfig(TIM2,TIM_IT_Update,ENABLE);
TIM_OCInitTypeDef TIM_OCInitStruct;
TIM_OCStructInit(&TIM_OCInitStruct);
TIM_OCInitStruct.TIM_OCMode=TIM_OCMode_PWM1;
TIM_OCInitStruct.TIM_OCPolarity=TIM_OCPolarity_High;
TIM_OCInitStruct.TIM_OutputState= TIM_OutputState_Enable;
TIM_OCInitStruct.TIM_Pulse=0 ;
TIM_OC1Init(TIM2,&TIM_OCInitStruct);
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);
GPIO_InitTypeDef GPIO_InitStructure;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOA, &GPIO_InitStructure);
GPIO_SetBits(GPIOA, GPIO_Pin_0);
TIM_Cmd(TIM2,ENABLE);
}
void PWM_SetCompare1(uint16_t Compare)
{
TIM_SetCompare1(TIM2,Compare);
}
H文件
#ifndef __PWM_H
#define __PWM_H
#include "stm32f10x.h" // Device header
void PWM_Init(void);
void PWM_SetCompare1(uint16_t Compare);
#endif
3.6主函数代码
在for循环中,不断调用设置CCR的函数,不断改变CCR从而实现呼吸灯
#include "stm32f10x.h" // Device header
#include "Delay.h"
//#include "OLED.h"
#include "PWM.h"
#include "LED.h"
#include "Delay.h"
uint16_t Num;
int main(void)
{
// OLED_Init();
PWM_Init();
// OLED_ShowString(1,1,"a:");
// OLED_ShowString(2,1,"b:");
while (1)
{
for(Num=0;Num<=100;Num++)
{
PWM_SetCompare1(Num);
Delay_ms(10);
}
for(Num=0;Num<=100;Num++)
{
PWM_SetCompare1(100-Num);
Delay_ms(10);
}
}
}
烧录
结果:
QQ视频20231104213936
打开魔法棒
打开keil的调试界面,点击逻辑分析仪
QQ录屏20231104214808
四.总结
本文主要介绍PWM相关理论知识,在学习理论知识的基础上,实现在STM32F103C8T6上,利用定时器TIM3和TIM4输出PWM波形,PWM的占空比随时间变化,去驱动你外接的一个LED以及最小开发板上已焊接的LED(固定接在 PC0 GPIO端口).通过本次的练习,让我更加深入的理解了STM32的定时器功能以及PWM的相关知识。
参考:
https://www.bilibili.com/video/BV1th411z7sn?p=16&vd_source=282ea78ce81b7a1817b467d1df493b2a
https://blog.youkuaiyun.com/qq_43533553/article/details/128206887