正点原子STM32HAL库学习记录(stm32f103zet6)——通用定时器PWM输出实验(实现呼吸灯效果)

前言

PWM是什么:脉宽调制,PWM(Pulse Width Modulation),通过对一系列脉冲的宽度进行调制,来等效地获得所需要波形(含形状和幅值)。
本次实验涉及到通用定时器框图中的⑤和⑥部分,具体可以点击传送门👉🕳👈

通用定时器输出PWM原理

我们可以让定时器产生PWM,在计数器频率固定时, PWM 频率或者周期由自动重载寄存器(TIMx_ARR)的值决定,其占空
比由捕获/比较寄存器(TIMx_CCRx)的值决定
。 PWM 产生原理示意图如下图所示:
在这里插入图片描述
上图中,定时器工作在递增计数模式,纵轴是计数器的计数值 CNT,横轴表示时间。当CNT<CCRx 时IO 输出低电平(逻辑 0)当 CNT>=CCRx 时IO 输出高电平(逻辑 1);当CNT=ARR 时,定时器溢出, CNT 的值被清零,然后继续递增,依次循环。在这个循环中,改变 CCRx 的值,就可以改变 PWM 的占空比,改变 ARR 的值,就可以改变 PWM 的频率,这就是 PWM 输出的原理

定时器产生PWM有多种,如👇图所示:
在这里插入图片描述
有/无效状态由TIMx_CCER决定CCxP=0:OCx高电平有效CCxP=1:Ocx低电平有效

寄存器以及配置

除了之前涉及的寄存器(基本定时器👉🕳👈和通用定时器介绍👉🕳👈),这里还用到了其他的三个,来控制 PWM。这三个寄存器分别是:捕获/比较模式寄存器(TIMx_CCMR1/2)、捕获/比较使能寄存器(TIMx_CCER)、捕获/比较寄存器(TIMx_CCR1~4)。

捕获/比较模式寄存器 1/2(TIMx_CCMR1/2)

该寄存器一般有 2 个:TIMx _CCMR1 和 TIMx _CCMR2。 TIMx_CCMR1 控制 CH1 和 CH2,而 TIMx_CCMR2 控制CH3 和 CH4。

TIMx _CCMR1寄存器描述如下:在这里插入图片描述

该寄存器的有些位在不同模式下,功能不一样,我们现在只用到输出比较,输入捕获后面的实验再讲解。关于该寄存器的详细说明,请参考《STM32F10xxx 参考手册_V10(中文版) .pdf》第 288 页, 14.4.7 节。比如我们要让 TIM3 的 CH2 输出 PWM 波为例进行介绍,该寄存器的模式设置位 OC2M[2:0]就是对应着通道 2 的模式设置,此部分由 3 位组成。总共可以配置成 8 种模式,我们使用的是 PWM 模式,所以这 3 位必须设置为 110 或者 111,分别对应 PWM 模式 1和 PWM 模式 2。这两种 PWM 模式的区别就是输出有效电平的极性相反。 位 OC2PE 控制输出比较通道 2 的预装载使能,实际就是控制 CCR2 寄存器是否进行缓冲。因为 CCR2 寄存器也是有影子寄存器的,影子寄存器才是真正起作用的寄存器。 CC2S[1:0]用于设置通道 2 的方向(输入/输出)默认设置为 0,就是设置通道作为输出使用。

捕获/比较使能寄存器(TIMx_CCER)

该寄存器控制着各个输入输出通道的开关和极性。 TIMx_CCER 寄存器描述如图:
在这里插入图片描述
该寄存器比较简单,要让 TIM3 的 CH2 输出 PWM 波,这里我们要使能 CC2E 位,该位是通道 2 输入/输出使能位,要想 PWM 从 IO 口输出,这个位必须设置为 1。 CC2P 位是设置通道2 的输出极性,我们默认设置 0。

捕获/比较寄存器 1/2/3/4(TIMx_CCR1/2/3/4)

捕获/比较寄存器(TIMx_CCR1/2/3/4),该寄存器总共有 4 个,对应 4 个通道 CH1~CH4。我们使用的是通道 2,所以来看看 TIMx_CCR2 寄存器, 描述如图:
在这里插入图片描述
在输出模式下,捕获/比较寄存器影子寄存器的值与 CNT 的值比较,根据比较结果产生相应动作,利用这点,我们通过修改这个寄存器的值,就可以控制 PWM 的占空比了。

硬件设计

实现功能

使用 TIM3 通道 2(由 PB5 复用)输出 PWM, PB5 引脚连接了 LED0, 从而实现 PWM 输出控制 LED0 亮度(实现呼吸灯的效果)。

硬件资源

本实验基于正点原子战舰V4开发板
1) LED 灯
LED0 – PB5
2)定时器 3 输出通道 2(由 PB5 复用)

程序设计

涉及的HAL库函数

1. HAL_TIM_PWM_Init 函数

定时器的 PWM 输出模式初始化函数,其声明如下:
HAL_StatusTypeDef HAL_TIM_PWM_Init(TIM_HandleTypeDef *htim);
该函数用于初始化定时器的 PWM 输出模式。形参 1 是 TIM_HandleTypeDef 结构体类型指针变量,基本定时器的时候已经介绍。
注意事项:该函数实现的功能以及使用方法和 HAL_TIM_Base_Init 类似,作用都是初始化定时器的ARR 和 PSC 等参数。为什么 HAL 库要提供这个函数而不直接让我们使用 HAL_TIM_Base_Init函数呢?这是因为 HAL 库为定时器的针对 PWM 输出定义了单独的 MSP 回调函数HAL_TIM_PWM_MspInit,所以当我们调用 HAL_TIM_PWM_Init 进行 PWM 初始化之后,该函数内部会调用 MSP 回调函数 HAL_TIM_PWM_MspInit。当我们使用 HAL_TIM_Base_Init 初始化定时器参数的时候,它内部调用的回调函数是HAL_TIM_Base_MspInit。

2. HAL_TIM_PWM_ConfigChannel 函数

定时器的 PWM 通道设置初始化函数。其声明如下:
HAL_StatusTypeDef HAL_TIM_PWM_ConfigChannel(TIM_HandleTypeDef *htim,
TIM_OC_InitTypeDef *sConfig, uint32_t Channel);
该函数用于设置定时器的 PWM 通道。形参 1 是 TIM_HandleTypeDef 结构体类型指针变量,用于配置定时器基本参数。
形参 2 是 TIM_OC_InitTypeDef 结构体类型指针变量,用于配置定时器的输出比较参数。
重点了解一下 TIM_OC_InitTypeDef 结构体指针类型,其定义如下:

typedef struct
{
uint32_t OCMode; /* 输出比较模式选择,寄存器的时候说过了,共 8 种模式 */
uint32_t Pulse; /* 设置比较值 */
uint32_t OCPolarity; /* 设置输出比较极性 */
uint32_t OCNPolarity; /* 设置互补输出比较极性 */
uint32_t OCFastMode; /* 使能或失能输出比较快速模式 */
uint32_t OCIdleState; /* 选择空闲状态下的非工作状态(OC1 输出) */
uint32_t OCNIdleState; /* 设置空闲状态下的非工作状态(OC1N 输出) */
} TIM_OC_InitTypeDef;

我们重点关注前三个结构体成员。成员变量 OCMode 用来设置模式,这里我们设置为 PWM模式 1。成员变量 Pulse 用来设置捕获比较值。成员变量 TIM_OCPolarity 用来设置输出极性。
形参 3 是定时器通道,范围: TIM_CHANNEL_1 到 TIM_CHANNEL_4。这里我们使用的是定时器 3 的通道 2,

3. HAL_TIM_PWM_Start 函数

定时器的 PWM 输出启动函数,其声明如下:
HAL_StatusTypeDef HAL_TIM_PWM_Start(TIM_HandleTypeDef *htim, uint32_t Channel);
用于使能通道输出和启动计数器,即启动 PWM 输出。
形参都在前面有介绍过,不多说。
注意事项:对于单独使能定时器的方法,在基本定时器有提到。实际上, HAL 库也同样提供了单独使能定时器的输出通道函数,函数为:
void TIM_CCxChannelCmd(TIM_TypeDef *TIMx, uint32_t Channel,
uint32_t ChannelState);
HAL_TIM_PWM_Start 函数内部也调用了该函数。

4. HAL_TIM_ConfigClockSource 函数

配置定时器时钟源函数,其声明如下:
HAL_StatusTypeDef HAL_TIM_ConfigClockSource(TIM_HandleTypeDef *htim,
TIM_ClockConfigTypeDef *sClockSourceConfig);
形参 1 是 TIM_HandleTypeDef 结构体类型指针变量。
形参 2 是 TIM_ClockConfigTypeDef 结构体类型指针变量,用于配置定时器时钟源参数。
TIM_ClockConfigTypeDef 定义如下:

typedef struct
{
uint32_t ClockSource; /* 时钟源 */
uint32_t ClockPolarity; /* 时钟极性 */
uint32_t ClockPrescaler; /* 定时器预分频器 */
uint32_t ClockFilter; /* 时钟过滤器 */
} TIM_ClockConfigTypeDef;

注意事项:
该函数主要配置 TIMx_SMCR 寄存器。默认情况下,定时器的时钟源是内部时钟。本实验就是使用内部时钟的,所以我们不用对时钟源就行初始化,默认即可。这里只是让大家知道有这个函数可以设定时器的时钟源。比如用 HAL_TIM_ConfigClockSource 初始化选择内部时钟,
方法如下:

TIM_HandleTypeDef timx_handle; /* 定时器 x 句柄 */
TIM_ClockConfigTypeDef sClockSourceConfig = {0};
sClockSourceConfig.ClockSource = TIM_CLOCKSOURCE_INTERNAL; /* 选择内部时钟 */
HAL_TIM_ConfigClockSource(&timx_handle, &sClockSourceConfig);

配置步骤

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

代码实现

  • 代码基于战舰开发板中的跑马灯实验上实现的,所以只进行定时器3和定时器3通道二的编程
  1. 新建tim.c与tim.h文件
    在这里插入图片描述
  2. 编写定时器3通道2初始化函数
/* 通用定时器3 PWM输出通道2句柄 */
TIM_HandleTypeDef g_tim3_pwn_ch2_handle;

/* 通用定时器初始化函数 */
void TIM_3_PWM_CH2_init(uint16_t psc,uint16_t arr)
{
    TIM_OC_InitTypeDef tim3_oc_handle;                              //定时器的输出比较(OC)模式句柄。

    g_tim3_pwn_ch2_handle.Instance = TIM3;                         //定时器3
    g_tim3_pwn_ch2_handle.Init.Prescaler = psc;                     //预分频值
    g_tim3_pwn_ch2_handle.Init.Period = arr;                        //自动重装载值——设置PWM周期
    g_tim3_pwn_ch2_handle.Init.CounterMode = TIM_COUNTERMODE_UP;
    HAL_TIM_PWM_Init(&g_tim3_pwn_ch2_handle);

    tim3_oc_handle.OCMode = TIM_OCMODE_PWM1;                       //输出比较模式为PWM1
    tim3_oc_handle.Pulse = arr/2;                                //设置占空比为50%
    tim3_oc_handle.OCPolarity = TIM_OCPOLARITY_LOW;               //输出比较极性为低

    HAL_TIM_PWM_ConfigChannel(&g_tim3_pwn_ch2_handle, &tim3_oc_handle, TIM_CHANNEL_2);     //PWM输出通道2初始化
    HAL_TIM_PWM_Start(&g_tim3_pwn_ch2_handle, TIM_CHANNEL_2);                            //启动定时器3 PWM输出通道2

}

/* 定时器输出PWM MSP初始化函数 */
void HAL_TIM_PWM_MspInit(TIM_HandleTypeDef *htim)
{
    if(htim->Instance==TIM3)
    {
        GPIO_InitTypeDef gpio_init_struct;
        __HAL_RCC_GPIOB_CLK_ENABLE();                               /* 使能通道2时钟 */
        __HAL_RCC_TIM3_CLK_ENABLE();                                 /* TIM3时钟使能 */

        gpio_init_struct.Pin = GPIO_PIN_5;                   /* CH2引脚 */
        gpio_init_struct.Mode = GPIO_MODE_AF_PP;            /* 复用推挽输出 */
        gpio_init_struct.Pull = GPIO_PULLUP;                    /* 上拉 */
        gpio_init_struct.Speed = GPIO_SPEED_FREQ_HIGH;          /* 高速 */
        HAL_GPIO_Init(GPIOB, &gpio_init_struct);       /* 初始化CH2 */   
        
        __HAL_RCC_AFIO_CLK_ENABLE();                                /* 使能复用功能时钟 */
        __HAL_AFIO_REMAP_TIM3_PARTIAL();                           /* 重映射TIM3部分引脚 */
    
    }
}

  1. 在main.c文件实现定时器初始化以及呼吸灯的具体效果
/* 通用定时器3 PWM输出通道2句柄 */
extern TIM_HandleTypeDef g_tim3_pwn_ch2_handle;
int main(void)
{
    uint16_t ledrpwmval = 0;
    uint8_t dir = 1;
    HAL_Init();                                 /* 初始化HAL库 */
    sys_stm32_clock_init(RCC_PLL_MUL9);         /* 设置时钟,72M */
    delay_init(72);                             /* 初始化延时函数 */
    led_init();                                 /* 初始化LED */
    TIM_3_PWM_CH2_init(500-1,72-1);


    while(1)
    {
        delay_ms(10);

        if (dir)ledrpwmval++;               /* dir==1 ledrpwmval递增 */
        else ledrpwmval--;                  /* dir==0 ledrpwmval递减 */
        if (ledrpwmval > 300)dir = 0;       /* ledrpwmval到达300后,方向为递减 */
        if (ledrpwmval == 0)dir = 1;        /* ledrpwmval递减到0后,方向改为递增 */

        /* 修改比较值控制占空比 */
        __HAL_TIM_SET_COMPARE(&g_tim3_pwn_ch2_handle, TIM_CHANNEL_2, ledrpwmval);

    }
}

在代码中,通过if判断不断改变ledrpwmval的值,然后使用 __HAL_TIM_SET_COMPARE 修改定时器3的通道2的比较值,从而控制PWM信号的占空比,实现呼吸灯效果。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值