pic pwm 占空比可调 源码_STM32实例PWM 呼吸灯实验

    我们已经介绍过了 STM32F1的通用定时器,使用 TIM4 的更新溢出中断控制D2 指示灯闪烁。这一章我们来学习如何使用通用定时器产生 PWM 输出。本文要实现的功能是:通过 TIM3 的通道 1 输出 PWM 信号,控制 D7 指示灯的亮度。

PWM 简介

    PWM 是 Pulse Width Modulation 的缩写,中文意思就是脉冲宽度调制,简称脉宽调制。它是利用微处理器的数字输出来对模拟电路进行控制的一种非常有效的技术,其控制简单、灵活和动态响应好等优点而成为电力电子技术最广泛应用的控制方式,其应用领域包括测量,通信,功率控制与变换,电动机控制、伺服控制、调光、开关电源,甚至某些音频放大器,因此学习 PWM 具有十分重要的现实意义。

    其实我们也可以这样理解,PWM 是一种对模拟信号电平进行数字编码的方法。通过高分辨率计数器的使用,方波的占空比被调制用来对一个具体模拟信号的电平进行编码。PWM 信号仍然是数字的,因为在给定的任何时刻,满幅值的直流供电要么完全有(ON),要么完全无(OFF)。电压或电流源是以一种通(ON)或断(OFF)的重复脉冲序列被加到模拟负载上去的。通的时候即是直流供电被加到负载上的时候,断的时候即是供电被断开的时候。只要带宽足够,任何模拟值都可以使用 PWM 进行编码。PWM 对应模拟信号的等效图,如下图所示:

pic pwm 占空比可调 源码_STM32实例PWM 呼吸灯实验_第1张图片

    从图中可以看到,上图 a 是一个正弦波即模拟信号,b 是一个数字脉冲波形即数字信号。我们知道在计算机系统中只能识别是 1 和 0,对于STM32F1芯片,要么输出高电平(3.3V),要么输出低电平(0),假如要输出 1.5V 的电压,那么就必须通过相应的处理,比如本章所要讲解的 PWM 输出,其实从上图也可以看到,只要保证数字信号脉宽足够就可以使用 PWM 进行编码,从而输出 1.5V 的电压。

STM32F1 PWM 介绍

    STM32F1 除了基本定时器 TIM6 和 TIM7,其他定时器都可以产生 PWM 输出。其中高级定时器 TIM1 和 TIM8 可以同时产生多达 7 路的 PWM 输出。而通用定时器也能同时产生多达 4 路的 PWM 输出,这些在定时器中断章节中已经介绍过。

    PWM 的输出其实就是对外输出脉宽可调(即占空比调节)的方波信号,信号频率是由自动重装寄存器 ARR 的值决定,占空比由比较寄存器 CCR 的值决定。PWM输出占空比调节图,示意图如下图所示:

pic pwm 占空比可调 源码_STM32实例PWM 呼吸灯实验_第2张图片

    从图 20.1.2 中可以看到,PWM 输出频率是不变的,改变的是 CCR 寄存器内的值,此值的改变将导致 PWM 输出信号占空比的改变。占空比其实就是一个周期内高电平时间与周期的比值。

    PWM 输出比较模式总共有8种,具体由寄存器 CCMRx 的位 OCxM[2:0]配置。我们这里只讲解最常用的两种 PWM 输出模式:PWM1 和 PWM2,其他几种模式可以参考《STM32F10x 中文参考手册》13、14 定时器章节。PWM1 和 PWM2 这两种模式用法差不多,区别之处就是输出电平的极性不同。PWM1与PWM2模式区别如下图所示:

pic pwm 占空比可调 源码_STM32实例PWM 呼吸灯实验_第3张图片

    PWM 模式根据计数器CNT计数方式,可分为边沿对齐模式和中心对齐模式。

(1)PWM 边沿对齐模式

    当 TIMx_CR1 寄存器中的 DIR 位为低时执行递增计数, 计数器 CNT从 0 计数到自动重载值(TIMx_ARR 寄存器的内容),然后重新从 0 开始计数并生成计数器上溢事件。

    以 PWM 模式 1 为例。只要 TIMx_CNT < TIMx_CCRx, PWM 参考信号 OCxREF便为有效的高电平,否则为无效的低电平。如果 TIMx_CCRx 中的比较值大于自动重载值 (TIMx_ARR 中) , 则 OCxREF 保持为 “ 1” 。如果比较值为 0,则 OCxREF保持为“ 0”。边沿对齐模式的 PWM波形图如下图所示:

pic pwm 占空比可调 源码_STM32实例PWM 呼吸灯实验_第4张图片

    当 TIMx_CR1 寄存器中的 DIR 位为高时执行递减计数,计数器 CNT 从自动重载值(TIMx_ARR 寄存器的内容)递减计数到 0,然后重新从 TIMx_ARR值开始计数并生成计数器下溢事件。

    以 PWM 模式 1 为例。只要TIMx_CNT >TIMx_CCRx, PWM 参考信号 OCxREF便为无效的低电平,否则为有效的高电平。如果 TIMx_CCRx 中的比较值大于自动重载值(TIMx_ARR 中),则 OCxREF 保持为“ 1”。此模式下不能产生0%的PWM 波形。

(2)PWM 中心对齐模式

    在中心对齐模式下,计数器 CNT 是工作在递增/递减模式下。开始的时候,计数器 CNT 从 0 开始计数到自动重载值减 1(ARR-1),生成计数器上溢事件;然后从自动重载值开始向下计数到 1 并生成计数器下溢事件。之后从 0 开始重新计数。中心对齐模式的 PWM波形图如下图所示:

pic pwm 占空比可调 源码_STM32实例PWM 呼吸灯实验_第5张图片

    我们以 ARR=8,CCRx=4 为例进行介绍。第一阶段计数器 CNT 工作在递增计数方式,从 0 开始计数,当 TIMx_CNT < TIMx_CCRx 时,PWM 参考信号 OCxREF 为有效的高电平,当 TIMx_CNT >= TIMx_CCRx 时,PWM 参考信号 OCxREF 为无效的低电平。第二阶段计数器 CNT 工作在递减计数方式,从 ARR 开始递减计数,当TIMx_CNT > TIMx_CCRx 时,PWM 参考信号 OCxREF 为无效的低电平,当 TIMx_CNT<= TIMx_CCRx 时,PWM 参考信号 OCxREF 为有效的高电平。

    中心对齐模式又分为中心对齐模式 1/2/3 三种,具体由寄存器 CR1 位

CMS[1:0]配置。具体的区别就是比较中断标志位 CCxIF 在何时置 1:中心模式 1在 CNT 递减计数的时候置 1,中心对齐模式 2 在 CNT 递增计数时置 1,中心模式 3 在 CNT 递增和递减计数时都置 1。

    上述涉及到的寄存器可以参考《STM32F10x 中文参考手册》-13、14、15 定时器章节的寄存器部分,里面有详细寄存器功能介绍。如果看不懂的可以暂时放下,因为我们使用的是库函数开发。

通用定时器 PWM 输出配置步骤

    接下来我们介绍下如何使用库函数对通用定时器的 PWM 输出进行配置。这个也是在编写程序中必须要了解的。其实 PWM 输出和上一章一样也是通用定时器的一个功能,因此还是要用到定时器的相关配置函数,具体步骤如下:(定时器相关库函数在 stm32f10x_tim.c和stm32f10x_tim.h 文件中)

(1)使能定时器及端口时钟,并设置引脚复用器映射

    因为 PWM 输出也是通用定时器的一个功能,所以需要使能相应定时器时钟。由于 PWM 输出通道是对应着 STM32F1 芯片的 IO 口,所以需要使能对应的端口时钟,并将对应 IO 口配置为复用输出功能。例如本章 PWM 呼吸灯实验,我们使用的是 TIM3 的 CH1 通道输出PWM信号,因此需要使能 TIM3 时钟,调用的库函数如下:

RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM3,ENABLE);//使能TIM3 时钟

    而 TIM3 的 CH1 通道对应的管脚是 PA6,但是我们开发板上的 LED 灯并没有接在PA6引脚上, 如果要让这个通道映射到LED所接的IO口上, 则需要使用GPIO的复用功能重映射, 在 《STM32F1xx 中文参考手册》 -8 通用和复用功能 I/O(GPIO和 AFIO)-8.3.7 定时器复用功能重映射章节都有介绍如下:

pic pwm 占空比可调 源码_STM32实例PWM 呼吸灯实验_第6张图片

    LED模块电路的8个小灯, 其中D7就是连接在PC6口的, 所以可以将TIM3_CH1配置为完全重映像即可映射到 PC6 脚,这样 PC6 就可以输出 PWM了。使用到外设的复用功能重映射就需要开启 AFIO 时钟,所以开启 AFIO 时钟函数如下:

RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO,ENABLE);

    从图中可以看到,TIM3_CH1 有部分重映像和完全重映像选择,那么就需要调用引脚复用映射功能函数:

void GPIO_PinRemapConfig(uint32_t GPIO_Remap, FunctionalStateNewState);

    第二个参数很好理解,用来使能还是失能;第一个参数是选择是部分重映射还是完全重映射,这个参数也很好选择,因为可选的参数在 stm32f10x_gpio.h都已经列出来非常详细,如下:

pic pwm 占空比可调 源码_STM32实例PWM 呼吸灯实验_第7张图片

    这 里 我 们 使 用 的 是 TIM3_CH1 完 全 重 映 射 , 所 以 参 数 为GPIO_FullRemap_TIM3。调用函数如下:

GPIO_PinRemapConfig(GPIO_FullRemap_TIM3,ENABLE);//改变指定管脚的映射

    最后还要记得将 PF9管脚模式配置为复用推挽输出

GPIO_InitStructure.GPIO_Mode=GPIO_Mode_AF_PP;//复用推挽输出

(2)初始化定时器参数,包含自动重装值,分频系数,计数方式等

要使用定时器功能,必须对定时器内相关参数初始化,其库函数如下:

voidTIM_TimeBaseInit(TIM_TypeDef*TIMx,TIM_TimeBaseInitTypeDef*TIM_TimeBaseInitStruct);

    这个在定时器中断章节就已经介绍。

(3)初始化 PWM 输出参数,包含 PWM 模式、输出极性,使能等初始化定时器后,需要设置对应通道 PWM 的输出参数,比如 PWM模式、输出极性、是否使能 PWM 输出等。PWM 通道设置函数如下,

void TIM_OCxInit(TIM_TypeDef* TIMx, TIM_OCInitTypeDef*TIM_OCInitStruct);

    我们知道每个通用定时器有多达 4 路 PWM 输出通道, 所以 TIM_OCxInit函数名中的x 值可以为 1/2/3/4。函数的第一个参数相信大家一看就清楚,是用来选择定时器的。第二个参数是一个结构体指针变量,同样我们看下这个结构体TIM_OCInitTypeDef 成员变量:

typedef struct{
      uint16_t TIM_OCMode; //比较输出模式uint16_t TIM_OutputState; //比较输出使能uint16_t TIM_OutputNState; //比较互补输出使能uint32_t TIM_Pulse; //脉冲宽度uint16_t TIM_OCPolarity; //输出极性uint16_t TIM_OCNPolarity; //互补比较输出极性uint16_t TIM_OCIdleState; //空闲状态下比较输出状态uint16_t TIM_OCNIdleState; //空闲状态下比较输出状态} TIM_OCInitTypeDef;

    这里我们就讲解下比较常用的 PWM 模式所需的成员变量:

    TIM_OCMode:比较输出模式选择,总共有 8 种,最常用的是PWM1和PWM2。

    TIM_OutputState:比较输出使能,用来使能 PWM 输出到 IO口。

    TIM_OCPolarity:输出极性,用来设定输出通道电平的极性,是高电平还是低电平。

    结 构 体 内 其 他 的 成 员 变 量 TIM_OutputNState , TIM_OCNPolarity ,TIM_OCIdleState 和 TIM_OCNIdleState 是高级定时器才用到的。如大家使用到高级定时器,可以查看中文参考手册高级定时器章节。

    所以如果我们要配置TIM3的 CH1 为 PWM1 模式,输出极性为低电平,并且使能 PWM输出,可以如下配置:

TIM_OCInitTypeDef TIM_OCInitStructure;TIM_OCInitStructure.TIM_OCMode=TIM_OCMode_PWM1;TIM_OCInitStructure.TIM_OCPolarity=TIM_OCPolarity_Low;TIM_OCInitStructure.TIM_OutputState=TIM_OutputState_Enable;TIM_OC1Init(TIM3,&TIM_OCInitStructure); //输出比较通道1初始化

(4)开启定时器

    前面几个步骤已经将定时器及 PWM 配置好,但 PWM 还不能正常使用,只有开启定时器了才能让它正常工作,开启定时器的库函数如下:

void TIM_Cmd(TIM_TypeDef* TIMx, FunctionalState NewState);

    第一个参数是用来选择定时器。

    第二个参数是用来使能或者失能定时器,也就是开启或者关闭定时器功能。同样可以选择 ENABLE 和 DISABLE。

    例如我们要开启 TIM3,那么调用此函数如下:

TIM_Cmd(TIM3,ENABLE); //开启定时器

(5)修改 TIMx_CCRx的值控制占空比

    其实经过前面几个步骤的配置,PWM 已经开始输出了,只是占空比和频率是固定的,例如本章要实现呼吸灯效果,那么就需要调节 TIM3 通道 1的占空比,通过修改 TIM3_CCR1 值控制。调节占空比函数是:

void TIM_SetCompare1(TIM_TypeDef* TIMx, uint32_t Compare1);

    对于其他通道,分别有对应的函数名,函数格式是 TIM_SetComparex

(x=1/2/3/4)。

(6)使能 TIMx 在 CCRx上的预装载寄存器

    使能输出比较预装载库函数是:

void TIM_OCxPreloadConfig(TIM_TypeDef* TIMx, uint16_tTIM_OCPreload);

    第一个参数用于选择定时器, 第二个参数用于选择使能还是失能输出比较预装载寄存器,可选择为 TIM_OCPreload_Enable、TIM_OCPreload_Disable。

(7)使能 TIMx 在 ARR 上的预装载寄存器允许位

    使能 TIMx 在 ARR 上的预装载寄存器允许位库函数是:

void TIM_ARRPreloadConfig(TIM_TypeDef* TIMx, FunctionalStateNewState);

    第一个参数用于选择定时器,第二个参数用于选择使能还是失能。

    将以上几步全部配置好后,我们就可以控制通用定时器相应的通道输出PWM波形了,这里要特别提醒下,虽然高级定时器和通用定时器类似,但是高级定时器要想输出 PWM 波形,必须要设置一个 MOE 位(TIMx_BDTR 的第 15 位),以使能主输出,否则不会输出 PWM。库函数设置的函数为:

void TIM_CtrlPWMOutputs(TIM_TypeDef* TIMx, FunctionalStateNewState);

硬件设计

    硬件电路非常简单,只使用到开发板上的 LED(D7),因为 D7 指示灯接在 PC6 管脚,而通过对 TIM3_CH1 复用功能完全重映射就可以映射到 PC6 脚。所以可以通过 TIM3 的 CH1输出PWM 信号,实现 D7 指示灯呼吸灯的控制。

    所要实现的功能是:通过 TIM3 的 CH1 输出一个 PWM 信号,控制D7 指示灯由暗变亮,再由亮变暗,类似于人的呼吸。程序框架如下:

(1)初始化 PC6 管脚为PWM 输出功能

(2)PWM 输出控制程序

    在前面介绍通用定时器PWM配置步骤时,就已经讲解如何初始化 PWM。下面我们打开“PWM 呼吸灯实验”工程,在 APP工程组添加 pwm.c 文件,在 StdPeriph_Driver 工程组中添加stm32f10x_tim.c 库文件。定时器操作的库函数都放在 stm32f10x_tim.c 和stm32f10x_tim.h 文件中,所以使用到定时器功能就必须加入 stm32f10x_tim.c文件,同时还要包含对应的头文件路径。

TIM3 通道 1的 PWM初始化函数

    要使用定时器的 PWM 输出功能,我们必须先对它进行配置。TIM3 通道 1 的PWM 初始化代码如下:

/***************************************************************** 函 数 名 : TIM3_CH1_PWM_Init* 函数功能 : TIM3 通道 1 PWM 初始化函数* 输 入 : per:重装载值 psc:分频系数* 输 出 : 无*****************************************************************/void TIM3_CH1_PWM_Init(u16 per,u16 psc){
      TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStructure;TIM_OCInitTypeDef TIM_OCInitStructure;GPIO_InitTypeDef GPIO_InitStructure;/* 开启时钟 */RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOC,ENABLE);RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM3,ENABLE);RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO,ENABLE);/* 配置 GPIO 的模式和IO 口 */GPIO_InitStructure.GPIO_Pin=GPIO_Pin_6;GPIO_InitStructure.GPIO_Speed=GPIO_Speed_50MHz;GPIO_InitStructure.GPIO_Mode=GPIO_Mode_AF_PP;//复用推挽输出GPIO_Init(GPIOC,&GPIO_InitStructure);GPIO_PinRemapConfig(GPIO_FullRemap_TIM3,ENABLE);//改变指定管脚的映射TIM_TimeBaseInitStructure.TIM_Period=per; //自动装载值TIM_TimeBaseInitStructure.TIM_Prescaler=psc; //分频系数TIM_TimeBaseInitStructure.TIM_ClockDivision=TIM_CKD_DIV1;TIM_TimeBaseInitStructure.TIM_CounterMode=TIM_CounterMode_Up;//设置向上计数模式TIM_TimeBaseInit(TIM3,&TIM_TimeBaseInitStructure);TIM_OCInitStructure.TIM_OCMode=TIM_OCMode_PWM1;TIM_OCInitStructure.TIM_OCPolarity=TIM_OCPolarity_Low;TIM_OCInitStructure.TIM_OutputState=TIM_OutputState_Enable;TIM_OC1Init(TIM3,&TIM_OCInitStructure); //输出比较通道1初始化TIM_OC1PreloadConfig(TIM3,TIM_OCPreload_Enable); //使能 TIMx 在CCR1 上的预装载寄存器TIM_ARRPreloadConfig(TIM3,ENABLE);//使能预装载寄存器TIM_Cmd(TIM3,ENABLE); //使能定时器}

    在 TIM3_CH1_PWM_Init()函数中,首先使能 GPIOC 端口时钟、TIM3 时钟和AFIO时钟,其次配置 TIM3_CH1 为完全复用重映射功能,并将 PC6 管脚模式配置为复用推挽输出。然后配置定时器结构体 TIM_TimeBaseInitStructure,初始化PWM 输出参数,由于我们的 LED 指示灯是低电平点亮,而我们希望当 CCR1 的值小的时候,LED 暗, CCR1 值大的时候,LED 亮,所以我们设置为 PWM1 模式,输出极性为低电平,使能 PWM 输出。最后就是开启 TIM3。这一过程在前面步骤介绍中已经提了。程序中我们看到最后调用了 TIM_OC1PreloadConfig()和TIM_ARRPreloadConfig,他们是用来使能 TIM3 在 CCR1 上的预装载寄存器和自动重装载寄存器,第一个库函数必须调用,第二个的话如果不调用也没有关系。

    TIM3_CH1_PWM_Init()函数有两个参数,用来设置定时器的自动装载值和分频系数,方便大家修改 PWM频率。其实如果你会使用通用定时器 TIM3 的 CH1 输出 PWM,那么其他通用定时器通道都一样。

主函数

    编写好 PWM 初始化函数后,接下来就可以编写主函数了,代码如下:

/***************************************************************** 函 数 名 : main* 函数功能 : 主函数* 输 入 : 无* 输 出 : 无*****************************************************************/int main(){
        u16 i=0;  u8 fx=0;  SysTick_Init(72);  NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2); //中断优先级分组 分2 组  LED_Init();  TIM3_CH1_PWM_Init(500,72-1); //频率是 2Kh  while(1)  {
          if(fx==0)    {
            i++;      if(i==300)      {
              fx=1;      }    }    else    {
            i--;    if(i==0)    {
            fx=0;    }  }  TIM_SetCompare1(TIM3,i); //i 值最大可以取 499,因为 ARR最大值是 499.  delay_ms(10); }}

    主函数实现的功能很简单,首先初始化对应的硬件端口时钟和 IO 口,然后

调用我们前面编写的 TIM3_CH1_PWM_Init 函数, 这里我们设定定时器自动重装载值为 500,预分频系数为72-1,定时周期即为 500us,频率即为 2KHz,这里为什么减 1在定时器中断已经介绍。

    初始化后,定时器开始工作,PC6 开始输出 PWM 波形,波形频率为 2K,你也可以修改这个频率值,但是要注意,不能将频率设置过大,否则会看到 D7 指示灯有明显的闪烁。通过变量 fx 控制 i 的方向,如果 fx=0,i 值累加,否则递减,然后将这个变化的 i 值传递给TIM_SetCompare1 函数, 这个函数功能是改变占空比的,因此可以实现 D1 指示灯亮度的调节,呈现呼吸灯的效果。程序中将 i 值控制在 300 内,主要是因为 PWM 输出波形占空比达到这个值时,D1 指示灯亮度变化就不明显了,而且我们在初始化定时器时,将自动重装载值设置为499,所以这个i 值也不能超过。

    将工程程序编译后下载到开发板内,可以看到 D7 指示灯由暗变亮,再由亮变暗,呈现呼吸灯的效果。

你可能感兴趣的:(pic,pwm,占空比可调,源码)