当前位置: 首页 > news >正文

STM32F4 | PWM输出实验

文章目录

    • 一、PWM 简介
    • 二、硬件设计
    • 三、软件设计
    • 四、实验现象
    • 五、STM32CubeMX 配置定时器 PWM 输出功能

  上一章,我们介绍了 STM32F429 的通用定时器 TIM3,用该定时器的中断来控制 DS1 的闪烁,这一章,我们将向大家介绍如何使用 STM32F429TIM3 来产生 PWM 输出。在本章中,我们将使用 TIM3 的通道 4 来产生 PWM 来控制 DS0 的亮度。

一、PWM 简介

  脉冲宽度调制(PWM),是英文“Pulse Width Modulation”的缩写,简称脉宽调制,是利用微处理器的数字输出来对模拟电路进行控制的一种非常有效的技术。简单一点,就是对脉冲宽度的控制,PWM 原理如图所示:
在这里插入图片描述
  上图就是一个简单的 PWM 原理示意图。图中,我们假定定时器工作在向上计数 PWM模式,且当CNT<CCRx 时,输出 0,当 CNT>=CCRx 时输出 1。那么就可以得到如上的 PWM示意图:当 CNT 值小于 CCRx 的时候,IO 输出低电平(0),当 CNT 值大于等于 CCRx 的时候,IO 输出高电平(1),当 CNT 达到 ARR 值的时候,重新归零,然后重新向上计数,依次循环。改变 CCRx 的值,就可以改变 PWM 输出的占空比,改变 ARR 的值,就可以改变 PWM 输出的频率,这就是 PWM 输出的原理。
  STM32F429 的定时器除了 TIM67。其他的定时器都可以用来产生 PWM 输出。其中高级定时器 TIM1TIM8 可以同时产生多达 7 路的 PWM 输出。而通用定时器也能同时产生多达 4 路的 PWM 输出!这里我们仅使用 TIM3CH4 产生一路 PWM 输出。
  要使 STM32F429 的通用定时器 TIMx 产生 PWM 输出,除了上一章介绍的寄存器外,我们还会用到 3 个寄存器来控制 PWM 的。这三个寄存器分别是:捕获/比较模式寄存器(TIMx_CCMR1/2)、捕获/比较使能寄存器(TIMx_CCER)、捕获/比较寄存器(TIMx_CCR1~4)。接下来我们简单介绍一下这三个寄存器。

  • 捕获/比较模式寄存器:TIMx_CCMR1/2
      该寄存器一般有 2 个:TIMx _CCMR1TIMx _CCMR2TIMx_CCMR1 控制 CH12,而TIMx_CCMR2 控制 CH34。以 TIM3 为例,TIM3_CCMR2 寄存器各位描述如图所示:
    在这里插入图片描述
    该寄存器的有些位在不同模式下,功能不一样,所以在图中,把寄存器分了 2 层,上面一层对应输出而下面的则对应输入。关于该寄存器的详细说明,请参考《STM32F4xx中文参考手册》第 435 页。这里我们需要说明的是模式设置位 OC4M,此部分由 3 位组成。总共可以配置成 7 种模式,我们使用的是 PWM 模式,所以这 3 位必须设置为 110/111。这两种 PWM 模式的区别就是输出电平的极性相反。另外 CC4S 用于设置通道的方向(输入/输出)默认设置为 0,就是设置通道作为输出使用。

  • 捕获/比较使能寄存器:TIMx_CCER
      该寄存器控制着各个输入输出通道的开关。该寄存器的各位描述如图所示:
    在这里插入图片描述
    该寄存器比较简单,我们这里只用到了 CC4E 位,该位是输入/捕获 4 输出使能位,要想PWMIO 口输出,这个位必须设置为 1,所以我们需要设置该位为 1。该寄存器更详细的介绍了,请参考《STM32F4xx 中文参考手册》第 436 页。

  • 捕获/比较寄存器:TIMx_CCR1~4
      该寄存器总共有 4 个,对应 4 个通道 CH1~4。我们使用的是通道 4,TIM3_CCR4 寄存器的各位描述如图所示:
    在这里插入图片描述
      在输出模式下,该寄存器的值与 CNT 的值比较,根据比较结果产生相应动作。利用这点,我们通过修改这个寄存器的值,就可以控制 PWM 的输出脉宽了。如果是通用定时器,则配置以上三个寄存器就够了,但是如果是高级定时器,则还需要配置:刹车和死区寄存器(TIMx_BDTR),该寄存器各位描述如图所示:
    在这里插入图片描述
      该寄存器,我们只需要关注最高位:MOE 位,要想高级定时器的 PWM 正常输出,则必须设置 MOE 位为 1,否则不会有输出。注意:通用定时器不需要配置这个。该寄存器更详细的介绍请参考《STM32F4xx 中文参考手册》第 386 页。

  本章,我们使用的是 TIM3 的通道 4,所以我们需要修改 TIM3_CCR4 以实现脉宽控制 DS0的亮度。
  下面介绍通过 HAL 库来配置该功能的步骤,相关的函数设置在库函数文件 stm32f4xx_tim.hstm32f4xx_tim.c 文件中。

  1. 开启 TIM3GPIO 时钟,配置 PB1 选择复用功能 AF1TIM3)输出
      要使用 TIM3,我们必须先开启 TIM3 的时钟。HAL 库使能 TIM3 时钟和 GPIO 时钟方法是:

    __HAL_RCC_TIM3_CLK_ENABLE(); //使能定时器 3
    __HAL_RCC_GPIOB_CLK_ENABLE(); //开启 GPIOB 时钟
    

      配置 PB1 为复用(AF1)输出,才可以实现 TIM3_CH4PWM 经过 PB1输出。接下来便是要配置 PB1 复用映射为 TIM3PWM 输出引脚。关于 IO 口复用映射,在串口通信实验中有详细讲解,主要是通过函数 HAL_GPIO_Init 来实现的:

    GPIO_InitTypeDef GPIO_Initure;
    
    GPIO_Initure.Pin=GPIO_PIN_1; //PB1
    GPIO_Initure.Mode=GPIO_MODE_AF_PP; //复用推挽输出
    GPIO_Initure.Pull=GPIO_PULLUP; //上拉
    GPIO_Initure.Speed=GPIO_SPEED_HIGH; //高速
    GPIO_Initure.Alternate= GPIO_AF2_TIM3; //PB1 复用为 TIM3_CH4
    
    HAL_GPIO_Init(GPIOB,&GPIO_Initure);
    

    IO 口初始化配置中,我们只需要将成员变量 Mode 配置为复用推挽输出,同时成员变量Alternate 配置为 GPIO_AF2_TIM3,即可实现 PB1 映射为定时器 3 通道 4 的 PWM输出引脚。
      这里还需要说明一下,对于定时器通道的引脚关系,大家可以查看 STM32F4 对应的数据手册,比如我们 PWM 实验,我们使用的是定时器 3 的通道 4,对应的引脚 PB1 可以从数据手册表中查看:
    在这里插入图片描述

  2. 初始化 TIM3,设置 TIM3ARRPSC 等参数
      根据上一章的讲解,初始化定时器的 ARRPSC 等参数是通过函数 HAL_TIM_Base_Init 来实现的,但是这里大家要注意,对于我们使用定时器的 PWM 输出功能时,HAL 库为我们提供了一个独立的定时器初始化函数 HAL_TIM_PWM_Init,该函数声明为:

    HAL_StatusTypeDef HAL_TIM_PWM_Init(TIM_HandleTypeDef *htim);
    

      该函数实现的功能以及使用方法和 HAL_TIM_Base_Init 都是类似的,作用都是初始化定时器的 ARRPSC 等参数。 为什么 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,这里大家注意区分。
      使用 HAL_TIM_PWM_Init 初始化定时器时,回调函数为:HAL_TIM_PWM_MspInit,该函数声明为:

    void HAL_TIM_PWM_MspInit(TIM_HandleTypeDef *htim);
    

    一般情况下,上面步骤 1 的时钟使能和 IO 口初始化映射都编写在回调函数内部。

  3. 设置 TIM3_CH4PWM 模式,输出比较极性,比较值等参数
      接下来,我们要设置 TIM3_CH4PWM 模式(默认是冻结的),因为我们的 DS0 是低电平亮,而我们希望当 CCR4 的值小的时候,DS0 就暗,CCR4 值大的时候,DS0 就亮,所以我们要通过配置 TIM3_CCMR2 的相关位来控制 TIM3_CH4 的模式。
      在 HAL 库中,PWM 通道设置是通过函数 HAL_TIM_PWM_ConfigChannel 来设置的:

    HAL_StatusTypeDef HAL_TIM_PWM_ConfigChannel(TIM_HandleTypeDef *htim,
    											TIM_OC_InitTypeDef* sConfig, uint32_t Channel);
    
    • 第一个参数 htim 是定时器初始化句柄,也就是 TIM_HandleTypeDef 结构体指针类型,这和 HAL_TIM_PWM_Init 函数调用时候参数保存一致即可。
    • 第二个参数 sConfigTIM_OC_InitTypeDef 结构体指针类型,这也是该函数最重要的参数。该参数用来设置 PWM 输出模式,极性,比较值等重要参数。其定义为:
      typedef struct
      {
      	uint32_t OCMode; //PWM 模式
      	uint32_t Pulse; //捕获比较值
      	uint32_t OCPolarity; //极性
      	uint32_t OCNPolarity; 
      	uint32_t OCFastMode; //快速模式
      	uint32_t OCIdleState;
      	uint32_t OCNIdleState; 
      } TIM_OC_InitTypeDef;
      
      • 成员变量 OCMode 用来设置模式,也就是我们前面讲解的7 种模式,这里我们设置为 PWM 模式 1。
      • 成员变量 Pulse 用来设置捕获比较值。
      • 成员变量TIM_OCPolarity 用来设置输出极性是高还是低。
      • 其他的参数 TIM_OutputNStateTIM_OCNPolarityTIM_OCIdleStateTIM_OCNIdleState 是高级定时器才用到的。
    • 第三个参数 Channel 用来选择定时器的通道,取值范围为 TIM_CHANNEL_1~TIM_CHANNEL_4。这里我们使用的是定时器3的通道4,所以取值为TIM_CHANNEL_4即可。

    例如我们要初始化定时器 3 的通道 4 为 PWM 模式 1,输出极性为低,那么实例代码为:

    TIM_OC_InitTypeDef TIM3_CH4Handler; //定时器 3 通道 4 句柄
    
    TIM3_CH4Handler.OCMode=TIM_OCMODE_PWM1; //模式选择 PWM1
    TIM3_CH4Handler.Pulse=arr/2; //设置比较值,此值用来确定占空比
    TIM3_CH4Handler.OCPolarity=TIM_OCPOLARITY_LOW; //输出比较极性为低
    
    HAL_TIM_PWM_ConfigChannel(&TIM3_Handler,&TIM3_CH4Handler,TIM_CHANNEL_4);
    
  4. 使能 TIM3,使能 TIM3CH4 输出
      在完成以上设置了之后,我们需要使能 TIM3 并且使能 TIM3_CH4 输出。在 HAL 库中,函数 HAL_TIM_PWM_Start 可以用来实现这两个功能,函数声明如下:

    HAL_StatusTypeDef HAL_TIM_PWM_Start(TIM_HandleTypeDef *htim, uint32_t Channel);
    

    该函数第二个入口参数 Channel 是用来设置要使能输出的通道号
    HAL 库也同样提供了单独使能定时器的输出通道函数,函数为:

    void TIM_CCxChannelCmd(TIM_TypeDef* TIMx, uint32_t Channel, uint32_t ChannelState);
    
  5. 修改 TIM3_CCR4 来控制占空比
      在经过以上设置之后,PWM 其实已经开始输出了,只是其占空比和频率都是固定的,而我们通过修改比较值TIM3_CCR4 则可以控制CH4的输出占空比,继而控制DS0 的亮度。HAL 库中并没有提供独立的修改占空比函数,这里编写这样一个函数如下:

    //设置 TIM3 通道 4 的占空比
    // compare:比较值
    void TIM_SetTIM3Compare4(u32 compare)
    {
    	TIM3->CCR4=compare;
    }
    

      实际上,因为调用函数 HAL_TIM_PWM_ConfigChanne 进行 PWM 配置的时候可以设置比较值,所以我们也可以直接使用该函数来达到修改占空比的目的:

    void TIM_SetCompare4(TIM_TypeDef *TIMx,u32 compare)
    {
    	TIM3_CH4Handler.Pulse=compare;
    	HAL_TIM_PWM_ConfigChannel(&TIM3_Handler,&TIM3_CH4Handler,TIM_CHANNEL_4);
    }
    

    这种方法因为要调用 HAL_TIM_PWM_ConfigChannel 函数对各种初始化参数进行重新设置,所以在使用中一定要注意,例如在实时系统中如果多个线程同时修改初始化结构体相关参数,可能导致结果混乱。

二、硬件设计

  本实验用到的硬件资源有:

  • 指示灯 DS0
  • 定时器 TIM3

这两个我们前面都已经介绍了,因为 TIM3_CH4 可以通过 PB1 输出 PWM,而 DS0 就是直接接在PB1 上面的,所以电路上并没有任何变化。

三、软件设计

   我们直接复制“定时器中断实验”的工程模板,将复制过来的模板文件夹重新命名为“8-PWM输出实验”。修改了timer.ctimer.h 的内容。
   打开timer.c,代码如下:

#include "timer.h"
#include "led.h"
	 
TIM_HandleTypeDef TIM3_Handler;         //定时器3PWM句柄 
TIM_OC_InitTypeDef TIM3_CH4Handler;	    //定时器3通道4句柄

//TIM3 PWM部分初始化 
//PWM输出初始化
//arr:自动重装值
//psc:时钟预分频数
void TIM3_PWM_Init(u16 arr,u16 psc)
{ 
	//初始化TMI3
    TIM3_Handler.Instance=TIM3;            //定时器3
    TIM3_Handler.Init.Prescaler=psc;       //定时器分频
    TIM3_Handler.Init.CounterMode=TIM_COUNTERMODE_UP;//向上计数模式
    TIM3_Handler.Init.Period=arr;          //自动重装载值
    TIM3_Handler.Init.ClockDivision=TIM_CLOCKDIVISION_DIV1;
    HAL_TIM_PWM_Init(&TIM3_Handler);       //初始化PWM
   
	//设置TIM3_CH4的PWM模式,输出比较极性,比较值等参数
    TIM3_CH4Handler.OCMode=TIM_OCMODE_PWM1; //模式选择PWM1
    TIM3_CH4Handler.Pulse=arr/2;            //设置比较值,此值用来确定占空比,默认比较值为自动重装载值的一半,即占空比为50%
    TIM3_CH4Handler.OCPolarity=TIM_OCPOLARITY_LOW; //输出比较极性为低 
    HAL_TIM_PWM_ConfigChannel(&TIM3_Handler,&TIM3_CH4Handler,TIM_CHANNEL_4);//配置TIM3通道4
	
	//使能 `TIM3`,使能 `TIM3` 的 `CH4` 输出
    HAL_TIM_PWM_Start(&TIM3_Handler,TIM_CHANNEL_4);//开启PWM通道4
}


//定时器底层驱动,时钟使能,引脚配置
//此函数会被HAL_TIM_PWM_Init()调用
//htim:定时器句柄
void HAL_TIM_PWM_MspInit(TIM_HandleTypeDef *htim)
{
    GPIO_InitTypeDef GPIO_Initure;
	
	__HAL_RCC_TIM3_CLK_ENABLE();			//使能定时器3
    __HAL_RCC_GPIOB_CLK_ENABLE();			//开启GPIOB时钟
	
    GPIO_Initure.Pin=GPIO_PIN_1;           	//PB1
    GPIO_Initure.Mode=GPIO_MODE_AF_PP;  	//复用推挽输出
    GPIO_Initure.Pull=GPIO_PULLUP;          //上拉
    GPIO_Initure.Speed=GPIO_SPEED_HIGH;     //高速
	GPIO_Initure.Alternate= GPIO_AF2_TIM3;	//PB1复用为TIM3_CH4
    HAL_GPIO_Init(GPIOB,&GPIO_Initure);
}


//设置TIM通道4的占空比
//compare:比较值
void TIM_SetTIM3Compare4(u32 compare)
{
	TIM3->CCR4=compare; 
}

其头文件timer.h

#ifndef __PWM_H
#define __PWM_H
#include "sys.h"

extern TIM_HandleTypeDef TIM3_Handler;      //定时器3PWM句柄 
extern TIM_OC_InitTypeDef TIM3_CH4Handler;  //定时器3通道4句柄

void TIM3_PWM_Init(u16 arr,u16 psc);
void TIM_SetTIM3Compare4(u32 compare);
#endif

  此部分代码包含三个函数,完全实现了前面的5 个配置步骤。

  • 第一个函数TIM3_PWM_Init 实现的是步骤 2-4,首先通过调用定时器 HAL 库函数HAL_TIM_PWM_Init 初始化 TIM3 并设置 TIM3ARRPSC 等参数,其次通过调用函数HAL_TIM_PWM_ConfigChannel 设置 TIM3_CH4PWM 模式以及比较值等参数,最后通过调用函数 HAL_TIM_PWM_Start 来使能 TIM3 以及使能 PWM 通道 TIM3_CH4 输出。
  • 第二个函数HAL_TIM_PWM_MspInitPWMMSP 初始化回调函数,该函数实现的是步骤 1,主要是使能相应时钟以及初始化定时器通道 TIM3_CH4 对应的 IO 口模式,同时设置复用映射关系。
  • 第三个函数 TIM_SetTIM3Compare4 是用户自定义的设置比较值函数,即步骤 5。

  主函数main.c代码如下:

#include "sys.h"
#include "delay.h"
#include "usart.h"
#include "led.h"
#include "timer.h"

int main(void)
{
    u8 dir=1;
    u16 led0pwmval=0;  
    HAL_Init();                     //初始化HAL库   
    Stm32_Clock_Init(360,25,2,8);   //设置时钟,180Mhz
    delay_init(180);                //初始化延时函数
    uart_init(115200);              //初始化USART
    LED_Init();                     //初始化LED 
    TIM3_PWM_Init(500-1,90-1);      //90M/90=1M的计数频率,自动重装载为500,那么PWM频率为1M/500=2kHZ
	
    while(1)
    {
		delay_ms(10);	 	
		if(dir)led0pwmval++;				//dir==1 led0pwmval递增
		else led0pwmval--;				//dir==0 led0pwmval递减 
		if(led0pwmval>300)dir=0;			//led0pwmval到达300后,方向为递减
		if(led0pwmval==0)dir=1;			//led0pwmval递减到0后,方向改为递增
		TIM_SetTIM3Compare4(led0pwmval);	//修改比较值,修改占空比
    }
}

  从死循环函数可以看出,我们控制 LED0_PWM_VAL 的值从 0 变到 300,然后又从 300 变到 0,如此循环,因此 DS0 的亮度也会跟着从暗变到亮,然后又从亮变到暗。至于这里的值,我们为什么取 300,是因为 PWM 的输出占空比达到这个值的时候,我们的 LED 亮度变化就不大了(虽然最大值可以设置到 499),因此设计过大的值在这里是没必要的。

四、实验现象

  使用 USB 线将开发板和电脑连接成功后(电脑能识别开发板上 CH340 串口),把编译后产生的.hex 文件烧入到芯片内。可以看到:DS0 不停的由暗变到亮,然后又从亮变到暗。
在这里插入图片描述

五、STM32CubeMX 配置定时器 PWM 输出功能

  使用 STM32CubeMX 配置 PWM 输出的配置步骤和配置定时器中断的配置步骤非常接近,步骤如下:
  在TIM3 配置项中,配置 Channel4 的值为 PWM generation CH4,然后 Clock SourceInternal Clock。操作过程如下图所示:
在这里插入图片描述
  进入 Configuration->TIM3 配置页,在弹出的界面中点击 Parameter Settings 选项卡,Counter Settings 配置栏下面的四个选项就是用来配置定时器的预分频系数,自动装载值,计数模式以及时钟分频因子。在界面的 PWM Generation Channel4 配置栏配置 PWM模式,比较值,极性等参数,操作方法如下图所示:
在这里插入图片描述
  本章 PWM 输出实验,我们并没有使用到中断,所以我们不需要使能中断和配置 NVIC。经过上面的配置就可以生成工程源码。

相关文章:

  • Nginx快速入门笔记
  • Elasticsearch:基于文件的用户认证
  • 【C】带你复习有趣的函数
  • .NET Framework杂记
  • 4线SPI驱动OLED常规操作
  • ESP32 OTA
  • Linux C编程一站式学习笔记2
  • RK3568平台开发系列讲解(摄像头篇)使用 Camera 的步骤
  • Kerberos的概述和认证原理
  • RocketMQ的TAG过滤和SQL过滤机制
  • 2023年电气,电子与信息工程国际会议(ISEEIE 2023)
  • 【前端开发学习】4.JavaScript
  • 【大数据技术Hadoop+Spark】HBase分布式数据库架构、特点、数据存储方式、寻址机制详解(图文解释)
  • K8s——Service、代理模式演示(二)
  • 哈希表及其与Java类集的关系
  • “大数据应用场景”之隔壁老王(连载四)
  • 《网管员必读——网络组建》(第2版)电子课件下载
  • 「译」Node.js Streams 基础
  • 【css3】浏览器内核及其兼容性
  • CSS 提示工具(Tooltip)
  • Date型的使用
  • ES6, React, Redux, Webpack写的一个爬 GitHub 的网页
  • HTML5新特性总结
  • JavaScript 基本功--面试宝典
  • JavaScript类型识别
  • JSDuck 与 AngularJS 融合技巧
  • mongodb--安装和初步使用教程
  • Mybatis初体验
  • Mysql优化
  • Synchronized 关键字使用、底层原理、JDK1.6 之后的底层优化以及 和ReenTrantLock 的对比...
  • Work@Alibaba 阿里巴巴的企业应用构建之路
  • 代理模式
  • 飞驰在Mesos的涡轮引擎上
  • 聊聊hikari连接池的leakDetectionThreshold
  • 如何利用MongoDB打造TOP榜小程序
  • 再次简单明了总结flex布局,一看就懂...
  • 400多位云计算专家和开发者,加入了同一个组织 ...
  • 数据库巡检项
  • ​如何防止网络攻击?
  • # Swust 12th acm 邀请赛# [ A ] A+B problem [题解]
  • # 睡眠3秒_床上这样睡觉的人,睡眠质量多半不好
  • #NOIP 2014# day.2 T2 寻找道路
  • (11)MATLAB PCA+SVM 人脸识别
  • (9)目标检测_SSD的原理
  • (LeetCode C++)盛最多水的容器
  • (读书笔记)Javascript高级程序设计---ECMAScript基础
  • (三分钟)速览传统边缘检测算子
  • (算法)Game
  • (原)记一次CentOS7 磁盘空间大小异常的解决过程
  • (转)利用ant在Mac 下自动化打包签名Android程序
  • (转载)hibernate缓存
  • *(长期更新)软考网络工程师学习笔记——Section 22 无线局域网
  • .h头文件 .lib动态链接库文件 .dll 动态链接库
  • .NET 5种线程安全集合
  • .Net Core/.Net6/.Net8 ,启动配置/Program.cs 配置