目录

目录

1.PWM原理

1.1PWM演示

1.2PWM原理(数学表达)

2.STM32如何输出PWM

2.1STM32时钟系统基本结构

2.2输出PWM使用的寄存器

2.2.1RCC_CR

2.2.2RCC_CFGR 

 2.2.3在Keil5(MDK-RAM)配置时钟

2.2.4TIM定时器的使用

3.实例1:呼吸灯(点灯大师)

4.实例2:PID控制直流有刷电机(开环)


1.PWM原理

1.1PWM演示

Pulse-width modulation (PWM) 脉宽调制,顾名思义,即能对脉冲宽度进行调整的技术。

直接来看一个演示,如同下面的GIF图1.1所示,随着PWM Duty Cycle的改变,图片右边的LED灯的亮度和电源电压也随之改变,通过观察图片我们可以得出表1.1的内容:

图1.1 PWM演示

PWM Duty Cycle电压LED亮度
上升上升上升
减小减小减小

表1.1 PWM变化规律

那么PWM Duty Cycle也就是我们常说的占空比,那么下面来介绍下具体原理。

1.2PWM原理(数学表达)

在介绍原理之前我们首先需要理解几个概念:

1.频率(frequency),是指单位时间内完成周期性变化的次数,通常用 f 表示,单位是HZ。

2.周期(period),是指完成往复运动一次所需要的时间,通常用 T 表示,单位s。

3.赫兹(Hertz),是指每一秒(s)周期性(T)事件发生的次数,通常用 HZ 表示。

4.频率(f)还可以定义为周期(T)的倒数:

f=1/T(1.1)

了解完概念后,我们来看图1.2

 图1.2 PWM

T_{ON}高电平持续时间,单位是s,也叫做脉冲的宽度
T_{OFF}低电平持续时间,单位是s
Period\left( T \right)脉冲的周期,单位是s

表1.2 

它们的关系可以表示为:

T=T_{ON}+T_{OFF}=\frac{1}{f}(1.2)

而在上面提到的占空比在这里就可以表示为:

D\left( DutyCycle \right) \left[ \% \right] =\frac{T_{ON}}{T_{ON}+T_{OFF}}\left[ \% \right] =\frac{T_{ON}}{T}\left[ \% \right](1.3)

 占空比通常用%表示,为了方便,占空比(Duty Cycle)在下文中用D表示。

下面举个例子:

有一电源电压(Von)为5V,现在我希望得到一个3.3V的平均电压(Vavg),求占空比D。

对于平均电压(Vavg)可以表示为:

Vavg=V_{on}\times D(1.4)

 根据式(1.4)即可求出占空比D:

D=\frac{Vavg}{V_{on}}=\frac{3.3}{5}=66\%

 假设周期为2s,还可以结合式(1.3),得出高电平持续时间(T_{ON}),也叫做脉宽:

T_{ON}=D\left( DutyCycle \right) \times T=66\%\times 2=1.32\left( s \right)


 那么对于PWM的原理就介绍到这里,其实PWM的核心就是占空比(D),在接下来我会和STM32的内部寄存器来进行结合。

其实上面这些内容,我相信很多人都或多或少在其他地方了解过,但是当我们了解了PWM的原理后,你会写程序了吗?答案是并不会,我们的目的是要用STM32去实现PWM,而STM32要实现PWM的核心在于STM32的时钟系统

2.STM32如何输出PWM

STM32如何输出PWM?首先我们要知道PWM是一个持续变化的连续脉冲,那么怎么让STM32持续输出脉冲呢?

答案是要有一个频率源,而在电路中最常用的频率源就是晶振,可以是在STM32芯片的内部,也可以是外部。在实际中最常用的就是8MHZ的晶振。

2.1STM32时钟系统基本结构

在有了频率源之后,下面我重点介绍以下STM32的时钟系统部分。

图2.1 STM32系统时钟 

图2.1展示了STM32时钟系统的基本结构,可以分为三大部分:

1.时钟的来源(时钟源),包括内部高速RC时钟、外部高速时钟、内部低速RC时钟、外部低速时钟等。

2.系统时钟SYSCLK、AHB总线时钟以及USB模块时钟、I2S模块时钟、RTC模块时钟、独立“看门狗”时钟等,这里的AHB总线时钟还会分支为APB1总线时钟和APB2总线时钟,可以为更多的其他外设提供时钟。

3.PLL锁相环、时钟选择电路、分频电路等在时钟源和最终的系统时钟以及外设时钟之间架起了桥梁。

我在这介绍下最常用的时钟配置方式:

图2.2 常用时钟配置 

 首先由外部晶振(8MHZ),作为系统的频率源,通过STM32的高速外部接口(HSE)进入,经过PLL锁相环对输入的8MHZ进行倍频处理,将频率提高到72MHZ,将72MHZ输入到系统时钟,在经由APB总线,将72MHZ分为APB1、APB2两条总线,如图2.3。

图2.3 APB1、APB2总线

 APB1:APB1总线时钟的最大频率为36MHz,也称为低速外设时钟。APB1总线时钟连接的外设包括定时器TIM2~TIM7、串口USART2~USART5、SPI2、SPI3、I2C1、I2C2、USB、RTC、CAN、DAC等。

APB2:APB2总线时钟的最大频率为72MHz,也称为高速外设时钟。APB2总线时钟连接的外设包括定时器TIM1和TIM8、模数转换器ADC1~ADC3、串口USART1和所有的GPIO端口等。

 这里要注意的是,定时器的时钟配置有一个倍频选择的条件和APB1与APB2预分频器的分频系数相关,即分频系数若不等于1,则送入定时器的时钟会自动倍频。这样一来,当APB1工作在最大频率36MHz、APB2工作在最大频率72MHz时,送入定时器的时钟信号都是72MHz。

2.2输出PWM使用的寄存器

在了解完STM32的时钟系统后,我们应该怎样去通过代码去设置呢?在这之前我们还应该了解STM32内部的时钟控制(RCC_CR)寄存器时钟配置寄存器(RCC_CFGR),给这两个寄存器写入相应的数值,就能够完成对STM32时钟系统的配置

2.2.1RCC_CR

图2.4 RCC_CR寄存器

RCC_CR寄存器是32位的,但日常使用中并不会全部用到,以下图片中标记出的位数,是我们经常使用的位数。

图2.4.a RCC_CR寄存器说明

 图2.4.b RCC_CR寄存器说明

2.2.2RCC_CFGR 

图2.5 RCC_CFGR寄存器 

以下红色标注的常用的寄存器位:

图2.6.a RCC_CFGR寄存器说明

图2.6.b RCC_CFGR寄存器说明 

图2.6.c RCC_CFGR寄存器说明 

图2.6.d RCC_CFGR寄存器说明 

 2.2.3在Keil5(MDK-RAM)配置时钟

STM32的时钟结构非常复杂,需要配置的参数也很多,如果完全依靠编程人员手工配置,将是非常烦琐的,所以在实际开发中,一般使用标准库开进行开发,或者是使用STM32官方推出的CubeMX来配置,但是对于初学者我推荐还是使用标准库。

1.在STM32复位时,首先会进入SystemInit()函数,作用就是使RCC_CR寄存器最低位置1,开启内部时钟,接下来会复位RCC_CFGR,复位操作完成后会在最后调用SetSysClock()函数。

void SystemInit (void)
{
  /* Reset the RCC clock configuration to the default reset state(for debug purpose) */
  /* Set HSION bit */
  RCC->CR |= (uint32_t)0x00000001;
    SetSysClock();
}
static void SetSysClock(void)
{
#ifdef SYSCLK_FREQ_HSE
  SetSysClockToHSE();
#elif defined SYSCLK_FREQ_24MHz
  SetSysClockTo24();
#elif defined SYSCLK_FREQ_36MHz
  SetSysClockTo36();
#elif defined SYSCLK_FREQ_48MHz
  SetSysClockTo48();
#elif defined SYSCLK_FREQ_56MHz
  SetSysClockTo56();  
#elif defined SYSCLK_FREQ_72MHz
  SetSysClockTo72();
#endif

 以上的SystemInit()函数和SetSysClock()函数在system_stm32f10x.c中(本文使用的是STM32F103C8T6),在system_stm32f10x.c文件的最前面可以看到有以下宏定义:

/* #define SYSCLK_FREQ_HSE    HSE_VALUE */
/* #define SYSCLK_FREQ_24MHz  24000000 */ 
/* #define SYSCLK_FREQ_36MHz  36000000 */
/* #define SYSCLK_FREQ_48MHz  48000000 */
/* #define SYSCLK_FREQ_56MHz  56000000 */
#define SYSCLK_FREQ_72MHz  72000000

 默认定义的是7ZMHZ,可以自己修改。

所以说一开始就定义了系统时钟为72MHZ,当程序执行到SetSysClock()函数时,只有最后一个能满足条件,将执行SetSysClockTo72()函数,此函数是也是最关键的部分

以下是SetSysClockTo72()函数,我附有详细的注释,同时建议结合图2.4.a、图2.4.b、图2.6.a、图2.6.b、图2.6.c对照查看。

#elif defined SYSCLK_FREQ_72MHz
/**
  * @brief  设置系统时钟频率为72MHz,并配置HCLK、PCLK2 
  *         和PCLK1预分频器. 
  * @note   此功能只能在复位后使用.
  * @param  None
  * @retval None
  */
static void SetSysClockTo72(void)
{
  __IO uint32_t StartUpCounter = 0, HSEStatus = 0;
  
  /* SYSCLK, HCLK, PCLK2 和 PCLK1 配置 ---------------------------*/    
  /* 使能 HSE */    
  /*RCC_CR寄存器第16位置1,开启HSE
  *RCC_CR_HSEON定义为,此定义以及后面的都在stm32f10x.h文件内
  *#define  RCC_CR_HSEON       ((uint32_t)0x00010000) 
  *意思就是使用外部高速时钟,对应的就是外部的晶振
  */
  RCC->CR |= ((uint32_t)RCC_CR_HSEON);
 
  /* 等待,直到HSE就绪,如果超时就退出 */
  do
  {
    HSEStatus = RCC->CR & RCC_CR_HSERDY;//读取RCC_CR第17位,如果HSE就绪,就置1
    StartUpCounter++;  
  } while((HSEStatus == 0) && (StartUpCounter != HSE_STARTUP_TIMEOUT));//超时退出

  if ((RCC->CR & RCC_CR_HSERDY) != RESET)//读取RCC_CR第17位
  {
    HSEStatus = (uint32_t)0x01;//HSE状态写1
  }
  else
  {
    HSEStatus = (uint32_t)0x00;//HSE状态写0
  }  

  if (HSEStatus == (uint32_t)0x01)
  {
    /* 使能 Prefetch Buffer 
     *Perfetch Buffer翻译成[预取缓冲区]
    */
    FLASH->ACR |= FLASH_ACR_PRFTBE;

    /* Flash 2 wait state */
    FLASH->ACR &= (uint32_t)((uint32_t)~FLASH_ACR_LATENCY);
    FLASH->ACR |= (uint32_t)FLASH_ACR_LATENCY_2;    

   //以上是Flash的内容,有兴趣的可以自己了解,这里不做讲解
   //---------------------------------------------
   
    //建议这里对照图2.6.a 图2.6.b  图2.6.c  
    /* HCLK = SYSCLK */
    //RCC_CFGR寄存器第4~7位写0,即AHB不分频,即HCLK使用72MHz时钟
    RCC->CFGR |= (uint32_t)RCC_CFGR_HPRE_DIV1;
      
    /* PCLK2 = HCLK */
    //RCC_CFGR寄存器第11~13位写0,APB2不分频,即PCLK2使用72MHz时钟
    RCC->CFGR |= (uint32_t)RCC_CFGR_PPRE2_DIV1;
    
    /* PCLK1 = HCLK */
    //RCC_CFGR寄存器第8~10位写100,APB1二分频,即PCLK1使用36MHz时钟
    RCC->CFGR |= (uint32_t)RCC_CFGR_PPRE1_DIV2;

//------------------------------------------------------------------------------------
#ifdef STM32F10X_CL
    /* Configure PLLs ------------------------------------------------------*/
    /* PLL2 configuration: PLL2CLK = (HSE / 5) * 8 = 40 MHz */
    /* PREDIV1 configuration: PREDIV1CLK = PLL2 / 5 = 8 MHz */
        
    RCC->CFGR2 &= (uint32_t)~(RCC_CFGR2_PREDIV2 | RCC_CFGR2_PLL2MUL |
                              RCC_CFGR2_PREDIV1 | RCC_CFGR2_PREDIV1SRC);
    RCC->CFGR2 |= (uint32_t)(RCC_CFGR2_PREDIV2_DIV5 | RCC_CFGR2_PLL2MUL8 |
                             RCC_CFGR2_PREDIV1SRC_PLL2 | RCC_CFGR2_PREDIV1_DIV5);
  
    /* Enable PLL2 */
    RCC->CR |= RCC_CR_PLL2ON;
    /* Wait till PLL2 is ready */
    while((RCC->CR & RCC_CR_PLL2RDY) == 0)
    {
    }
    
   
    /* PLL configuration: PLLCLK = PREDIV1 * 9 = 72 MHz */ 
    RCC->CFGR &= (uint32_t)~(RCC_CFGR_PLLXTPRE | RCC_CFGR_PLLSRC | RCC_CFGR_PLLMULL);
    RCC->CFGR |= (uint32_t)(RCC_CFGR_PLLXTPRE_PREDIV1 | RCC_CFGR_PLLSRC_PREDIV1 | 
                            RCC_CFGR_PLLMULL9); 
//-----------------------------------------------------------------------------------
//上面这段是对STM32F10X_CL系列芯片的配置,STM32互联系列微处理器也就是STM32F105和STM32F107系 
//列处理器,我们一般使用的是STM32F10X_LD、STM32F10X_MD、STM32F10X_HD,所以不在本次讲解的范畴 
 //内,程序正常执行时也会跳过上面这段代码。
#else    
    /*  PLL 配置: PLLCLK = HSE * 9 = 72 MHz */
    RCC->CFGR &= (uint32_t)((uint32_t)~(RCC_CFGR_PLLSRC | RCC_CFGR_PLLXTPRE |
                                        RCC_CFGR_PLLMULL));//RCC_CFGR寄存器的16~21位清零
//RCC_CFGR寄存器第16位置1,使HSE作为PLL输入时钟;17位置0不变,使HSE作为PLL输入时钟时不分频。 
 //RCC_CFGR寄存器第18~21位置0111,配置位PLL 9倍频输出给PLLCLK,即PLLCLK为72MHz
    RCC->CFGR |= (uint32_t)(RCC_CFGR_PLLSRC_HSE | RCC_CFGR_PLLMULL9);
#endif /* STM32F10X_CL */

    /* 使能 PLL */
    RCC->CR |= RCC_CR_PLLON;//RCC->CR寄存器第24位置1,使能PLL

    /* 等待PLL就绪 */
    while((RCC->CR & RCC_CR_PLLRDY) == 0) //读取PLL时钟就绪标志位(第25位)
    {
    }
    
    /* 选择PLL作为系统时钟源 */
    RCC->CFGR &= (uint32_t)((uint32_t)~(RCC_CFGR_SW));//RCC->CFGR寄存器第0~1位清零
    //RCC->CFGR寄存器第0~1位(SW位)置10,即PLL输出作为系统时钟
    RCC->CFGR |= (uint32_t)RCC_CFGR_SW_PLL;    

    /* 等待PLL被用作系统时钟源就绪 */
    //等待RCC->CFGR寄存器第2~3位(SWS位)为10即就绪
    while ((RCC->CFGR & (uint32_t)RCC_CFGR_SWS) != (uint32_t)0x08)
    {
    }
  }
  else
  { /* 如果HSE启动失败,应用程序将出现错误时钟

     *配置。用户可以在这里添加一些代码来处理这个错误
    */
  }
}
#endif

那么STM32的时钟系统的讲解就到这里,希望读者能够仔细的对照图2.4.a、图2.4.b、图2.6.a、图2.6.b、图2.6.c查看,这对于理解STM32时钟非常有帮助。

下面我附上一张使用CubeMX配置STM32系统时钟的图片:

图2.7 

可以看到非常的直观,配置好后会直接生成代码。当然这都是后话了。  


那么到此为止,介绍了PWM原理、STM32的时钟系统和系统时钟的配置,所以现在可以开始做PWM了吗?    还是不行,虽然配置了系统时钟,但是我们还需要一个能实际输出PWM的外设,还记得APB1和APB2这两条总线吗,APB1上挂载了TIM1和TIM8定时器,APB2上挂载了TIM2~TIM7,没错,定时器就能够通过一些配置,最终重映射到GPIO引脚上,完成PWM的输出!

2.2.4TIM定时器介绍

在STM32F10这款系列的芯片中,共有三种类型的定时器:

1.高级控制定时器(TIM1和TIM8)
2.通用定时器(TIM2~5)
3.基本定时器(TIM6和TIM7)

1.高级控制定时器(TIM1和TIM8)

图2.8 TIM1和TIM8定时器框图 

 图2.9 TIM1和TIM8简介

图2.10 TIM1和TIM8主要特性  

2.通用定时器(TIM2~5)

 图3.10 TIM2~TIM5框图

图3.11 TIM2~TIM5简介 

 图2.12 TIM2~TIM5主要特性

3.基本定时器(TIM6和TIM7)

 图2.12 TIM6和TIM7框图 

图2.13 TIM6和TIM7简介 

 图2.14 TIM6和TIM7主要特性

 根据定时器的主要特性,只有TIM1 TIM2 TIM3 TIM4 TIM5 TIM8能够生成PWM!。

而且TIM1和TIM8是具有死区时间可编程互补输出功能,这个功能在三相逆变半桥电路中会用到,这里先不深入。

2.2.5TIM定时器寄存器

要想使TIM输出PWM,我们需要了解以下几个寄存器:

1.定时器控制寄存器(TIMx_CR1)
2.计数器寄存器(TIMx_CNT)
3.预分频器寄存器(TIMx_PSC)
4.自动装载寄存器(TIMx_ARR)
5.捕获/比较寄存器(TIMx_CCRx)

1.定时器控制寄存器(TIMx_CR1)

 

 2.计数器寄存器(TIMx_CNT)

3. 预分频器寄存器(TIMx_PSC)

 4.自动装载寄存器(TIMx_ARR)

5.捕获/比较寄存器(TIMx_CCRx)

 

3.实例1:呼吸灯(点灯大师)

4.实例2:PID控制直流有刷电机(开环)

Logo

旨在为数千万中国开发者提供一个无缝且高效的云端环境,以支持学习、使用和贡献开源项目。

更多推荐