使用STM32实现呼吸灯效果的PWM输出

本文主要介绍在STM32F103C8T6上,利用定时器输出PWM波形,进而驱动LED实现呼吸灯。

一、任务要求
使用TIM3和TIM4,分别输出一个PWM波形,PWM的占空比随时间变化,去驱动你外接的一个LED以及最小开发板上已焊接的LED(固定接在 PC13 GPIO端口),实现2个 LED呼吸灯的效果。

二、PWM简介
1、什么是PWM
脉冲宽度调制(PWM),是英文“Pulse Width Modulation”的缩写,简称脉宽调制,是利用微处理器的数字输出来对模拟电路进行控制的一种非常有效的技术,广泛应用在从测量、通信到功率控制与变换的许多领域中。

通俗讲,PWM是一种 对模拟信号电平进行数字编码 的方法。通过高分辨率计数器的使用,方波的占空比被调制用来对一个具体模拟信号的电平进行编码。

PWM信号仍然是数字的 ,因为在给定的任何时刻,满幅值的直流供电要么完全有(ON),要么完全无(OFF)。电压或电流源是以一种通(ON)或断(OFF)的重复脉冲序列被加到模拟负载上去的。通的时候即是直流供电被加到负载上的时候,断的时候即是供电被断开的时候。只要带宽足够,任何模拟值都可以使用PWM进行编码。

2、相关概念
pwm的频率:
是指1秒钟内信号从高电平到低电平再回到高电平的次数(一个周期);也就是说一秒钟PWM有多少个周期
单位: Hz
表示方式: 50Hz 100Hz

pwm的周期:
周期: 一个脉冲信号的时间
1s内测周期次数等于频率:T=1/f
如:50Hz = 20ms 一个周期,频率为50Hz ,也就是说一个周期是20ms 那么一秒钟就有 50次PWM周期

占空比:
是一个脉冲周期内,高电平的时间与整个周期时间的比例
单位: % (0%-100%)
表示方式:20%

 

  • 脉宽时间: 高电平时间
    脉宽时间占总周期时间的比例,就是占空比

  • 3、PWM的产生
    通过STM32控制板,有两种方式能产生PWM,第一是利用 普通IO口 输出PWM,第二种是利用 定时器的PWM的IO口 或 复用IO口。

    (1)PWM端口
    STM32 的定时器除了 TIM6 和 TIM7。其他的定时器都可以用来产生 PWM 输出。其中高级定时器 TIM1 和 TIM8 可以同时产生多达 7 路的 PWM 输出。而通用定时器也能同时产生多达 4路的 PWM 输出,这样,STM32 最多可以同时产生 30 路 PWM 输出。

    (2)普通IO口
    一般能够输出PWM的端口都会在主要功能那一栏出现CHx的标志,而普通定时器没有出现这种标志。如图所示,上面的红框就是普通的定时器,不是专用的PWM端口。

    STM32F103C8T6的PWM口
    由于本人的实验环境是在stm32最小系统上实现的,因此在此给出 STM32F103C8T6 的PWM口配置。不是所有的芯片都有重映像功能的,STM32F103C8T6 的四个定时器就不需要重映像

    TIM1_CH1->PA8;
    TIM1_CH2->PA9;
    TIM1_CH3->PA10;
    TIM1_CH4->PA11;

    TIM2_CH1->PA0;
    TIM2_CH2->PA1;
    TIM2_CH3->PA2;
    TIM2_CH4->PA3;

     

    TIM3_CH1->PA6;
    TIM3_CH2->PA7;
    TIM3_CH3->PB0;
    TIM3_CH4->PB1; 

     TIM4_CH1->PB6;
    TIM4_CH1->PB7;
    TIM4_CH1->PB8;
    TIM4_CH1->PB9;

    普通IO口产生PWM
    普通IO口产生一个pwm其实就是通过一个高低电平周期性的变化。确定频率就可以确定周期(T=1/f)也就是在一个周期内产生pwm的时间。
    改变占空比: 确定了时间,高电平的时间不就是想要的占空比么,比如要产生一个频率1khz,占空比为70%的pwm,根据频率我们知道了周期为1ms,产生一个占空比为70%的不就是0.7ms的时间给高电平么。我们用定时器中断的方式,使0.1ms产生一次中断,计数中断次数,中断处理函数前七次中断都给高电平就可以产生对应的波形了。

    区别
    1)一般而言,尽量选用PWM口进行PWM输出,因为普通IO口模拟PWM的输出频率越高,进入定时器中断的次数就越快,中断间隔的时间越短,如果再有其他类型的中断也要处理时,会因为中断的优先级嵌套等待响应,影响控制精度,PWM输出误差增大,也会影响其他如ADC等中断处理,甚至会较出现单片机逻辑出错,死机或者跑飞的情况。

    2)普通IO也可以输出PWM,只是产生PWM一般用转用芯片(开关电源上用的较多)或者单片机的PWM内置模块如定时器,很小直接用MCU的IO口线直接输出因为那样太耗MCU资源了。

    4、PWM的通道
    每一个捕获/比较通道都是围绕着一个捕获/比较寄存器(包含影子寄存器),包括捕获的输入部分(数字滤波、多路复用和预分频器),和输出部分(比较器和输出控制)

    捕获/比较模块由一个预装载寄存器和一个影子寄存器组成。读写过程仅操作预装载寄存器

    在捕获模式下,捕获发生在影子寄存器上,然后再复制到预装载寄存器中

    在比较模式下,预装载寄存器的内容被复制到影子寄存器中,然后影子寄存器的内容和计数器进行比较

     

    5、PWM工作过程 

    每个定时器有四个通道,每一个通道都有一个捕获比较寄存器,将寄存器值和计数器值比较,通过比较结果输出高低电平,实现输出PWM信号

    如图为向上计数:
    定时器重装载值为ARR,比较值CCRx
    t时刻对计数器值和比较值进行比较
    如果计数器值小于CCRx值,输出低电平
    如果计数器值大于CCRx值,输出高电平

    PWM的一个周期
    定时器从0开始向上计数
    当0-t1段,定时器计数器TIMx_CNT值小于CCRx值,输出低电平
    t1-t2段,定时器计数器TIMx_CNT值大于CCRx值,输出高电平
    当TIMx_CNT值达到ARR时,定时器溢出,重新向上计数…循环此过程
    至此一个PWM周期完成

    影响因素
    ARR : 决定PWM周期(在时钟频率一定的情况下,当前为默认内部时钟CK_INT)
    CCRx : 决定PWM占空比(高低电平所占整个周期比例)

    PWM工作过程(以通道1为例)

     

    TIMx_CCMR1 寄存器的 OC1M[2:0] 位,设置输出模式控制器
    110: PWM模式1
    111: PWM模式2

    计数器值 TIMx_CNT 与通道1捕获比较寄存器 CCR1 进行比较,通过比较结果输出有效电平和无效电平
    OC1REF =0 无效电平
    OC1REF =1 无效电平

    通过输出模式控制器产生的信号
    TIMx_CCER 寄存器的 CC1P 位,设置输入/捕获通道1输出极性
    0: 高电平有效
    1: 低电平有效

    TIMx_CCER:CC1E 位控制输出使能电路,信号由此输出到对应引脚
    0: 关闭
    1: 开启

    6、PWM输出高低电平
    计数器值 TIMx_CNT 与捕获比较寄存器值 CCRx 比较后,最终输出高电平还是低电平, 由TIMx_CCMR1:OC1M 位和 TIMx_CCER:CC1P 位共同决定

    (1)TIMx_CCMR1 寄存器的 OC1M[2:0] 位,设置PWM 模式1 或 模式2)

    通过设置模式1或模式2,决定了比较结果输出有效或无效电平

    (2)TIMx_CCER 寄存器的 CC1P 位,设置输入/捕获通道1输出极性

    通过设置输出极性,确定有效或无效电平为最终输出的高电平或低电平

    总结:
    模式1:
    CNT<CCR 为有效电平 //(OC1REF = 1)
    CNT>CCR 为无效电平 //(OC1REF = 0)
    模式2:
    CNT<CCR 为无效电平 //(OC1REF = 0)
    CNT>CCR 为有效电平 //(OC1REF = 1)
    CC1P:
    0:高电平有效
    1:低电平有效

    7、PWM的计数模式
    向上计数模式:
    下面是一个PWM模式1的例子。当 TIMx_CNT<TIMx_CCRx 时PWM信号参考 OCxREF 为高,否则为低。如果 TIMx_CCRx 中的比较值大于自动重装载值 (TIMx_ARR) ,则 OCxREF 保持为’1’。如果比较值为0,则 OCxREF 保持为’0’:

    向下计数模式:
    在PWM模式1,当 TIMx_CNT>TIMx_CCRx 时参考信号 OCxREF 为低,否则为高。如果 TIMx_CCRx 中的比较值大于 TIMx_ARR 中的自动重装载值,则 OCxREF 保持为’1’。该模式下不能产生0%的PWM波形

    中央对齐模式:
    当 TIMx_CR1 寄存器中的 CMS 位不为’00’时,为中央对齐模式(所有其他的配置对 OCxREF/OCx 信号都有相同的作用)。根据不同的 CMS 位设置,比较标志可以在计数器向上计数时被置’1’、在计数器向下计数时被置’1’、或在计数器向上和向下计数时被置’1’。TIMx_CR1 寄存器中的计数方向位(DIR)由硬件更新,不要用软件修改它。

    8、PWM相关寄存器
    包含三个寄存器: 捕获/比较模式寄存器(TIMx_CCMR1/2)、捕获/比较使能寄存器(TIMx_CCER)、捕获/比较寄存器(TIMx_CCR1~4) 。设置 TIMx_CCMRx 寄存器 OCxPE 位以使能相应的预装载寄存器,最后还要设置 TIMx_CR1 寄存器的 ARPE 位,(在向上计数或中心对称模式中)使能自动重装载的预装载寄存器。其中模式设置位 OCxM 位,此位由3位组成,一共可以配置成7种模式,我们使用的是PWM模式,所以 这三位必须为110/111。

    捕获/比较模式寄存器总共2个,TIMx_CCMR1 和 TIMx_CCMR2;TIMx_CCMR1 控制CH1和CH2,TIMx_CCMR2 控制CH3和CH4。

    下面来简单介绍一下这三种寄存器:

    捕获/比较模式寄存器(TIMx_CCMR1)
    作用:在PWM输出模式下,确定PWM的模式、使能相应的预装载寄存器等操作:

     

    捕获/比较使能寄存器(TIMx_CCER)
    作用:在PWM输出模式下,确定PWM的输出极性和输出使能: 

     

    捕获/比较寄存器(TIMx_CCR1)
    作用:在PWM输出模式下,确定比较的值: 

     

    三、创建工程
    (1)打开 STM32CubeMX,点击 ACCEE TO MCU SELECTOR 

     

    (2)在搜索框输入 STM32F103C8,双击选择 STM32F103C8Tx

    (3)选择调试接口,点击 System Core,选择 SYS,在右侧弹出的菜单栏中选 Serial Wire

     

    (4)打开外部时钟,点击 System Core,选择 RCC,在右侧弹出的菜单栏中选择Crystal/Ceramic Resonator 

     

    (5)配置 TIM3,勾选 Internal Clock(内部时钟);将 Channel1 设置为 PWM Generation CH1(PWM输出通道1);
    Prtscaler (定时器分频系数) 设置为71,即72分频——1MHz;Counter Mode(计数模式)设置为Up(向上计数模式);Counter Period(自动重装载值) 设置为50000,计数器从0向上计数(递增)到自动装载值,然后再次回到0开始计数,并产生一个计数溢出事件,即0.05s;CKD(时钟分频因子) 设置为No Division (不分频 )

    (6)将 TIM4 设置的和 TIM3 一样

     

    (7)配置时钟,将 HCLK 设置为 72MHz

    (8)设置项目的名称、位置和编译环境,生成项目

    (9)打开项目,准备代码编辑

    四、代码编写
    主要利用下面两个函数来控制定时器实现PWM输出

    //开启 TIMx 的通道x,输出PWM
    void HAL_TIM_PWM_Start(&htimx, TIM_CHANNEL_x);
    /*
     * @Descript    PWM占空比更改
     * @param        htimx            相关定时器指针    
     * @param        TIM_CHANNEL_x    相关PWM通道
     * @param        Pulse            Duty_cycle = Pulse / Counter Period 
     * @return        void
    */

    //修改TIMx的通道x的PWM波形的占空比
    void __HAL_TIM_SET_COMPARE(&htimx, TIM_CHANNEL_x, Pulse);
    /*
     * @Descript    PWM频率更改
     * @param        htimx            相关定时器指针    
     * @param        Counter_Period    PWM_fre = TIM_frq / ( Prescaler + 1 ) / ( Counter Period + 1 )
     * @return        void
    */

    (1)在 main.c 文件中定义一个变量来记录 pwm 波形的占空比

    uint16_t pwm=0;   //占空比

     

    (2)打开定时器 TIM3 和 TIM4 的 PWM 通道1

        HAL_TIM_PWM_Start(&htim3,TIM_CHANNEL_1); 
        HAL_TIM_PWM_Start(&htim4,TIM_CHANNEL_1);

    (3)在主函数 while 循环里加入如下代码

        while (pwm< 500)                                       //在0.5s的时间内逐渐增大输出PWM的占空比,即让LED慢慢变亮
        {
            pwm++;
            __HAL_TIM_SetCompare(&htim3, TIM_CHANNEL_1, pwm);    //修改TIM3的占空比
            __HAL_TIM_SetCompare(&htim4, TIM_CHANNEL_1, pwm);      //修改TIM4的占空比        
            HAL_Delay(1);
        }
        while (pwm>=0)                                        //在0.5s的时间内逐渐减小输出PWM的占空比,即让LED慢慢变暗
        {
            pwm–;
            __HAL_TIM_SetCompare(&htim3, TIM_CHANNEL_1, pwm);   //修改TIM3的占空比 
            __HAL_TIM_SetCompare(&htim4, TIM_CHANNEL_1, pwm);    //修改TIM4的占空比
            HAL_Delay(1);
        }
        HAL_Delay(100);                                         //延时0.1s

     

    五、编译
    (1)点击 Options for Target…,在 Output 下勾选 Create HEX File

    (2)在 Debug 下勾选 Use Simulator,将 Dialog DLL下的输入框改为 DARMSTM.DLL,Parameter 输入框改为 -pSTM32F103C8

    (3)在 Target 选择使用 V5编译器

    (4)点击 Rebuild进行编译

    六、总结

    本文主要介绍PWM相关理论知识,在学习理论知识的基础上,实现在STM32F103C8T6上,利用定时器TIM3和TIM4输出PWM波形,PWM的占空比随时间变化,去驱动你外接的一个LED以及最小开发板上已焊接的LED(固定接在 PC13 GPIO端口),实现2个 LED呼吸灯的效果。通过本次的练习,让我更加深入的理解了STM32的定时器功能以及PWM的相关知识。

    学习要把理论和实践结合起来。只有在实践过程中一步一步的试错,在“发现问题——分析问题——解决问题”的过程中,逐步加深自己对理论和知识的理解。就比如在本次实践过程中,如果仅仅只阅读一些理论知识,那么对定时器和PWM的理解就会大打折扣。并且在实现呼吸灯的过程中,我也遇到了不少问题,如果没有进行实践,那将很难发现这些问题,也将不会知道如何实现PWM输出和定时器的操作。

    7、学习课程心得

    通过这门课程的学习,我了解到了嵌入式系统是一种为特定设备服务,软硬件可裁剪的计算机系统,其英文名称是Embedded System。嵌入式系统的范围很广,特点是形式变化多样、体积小,可以灵活地适应各种设备的需求。嵌入式系统的一些例子:手机、汽车、ATM、数字电视、医疗仪器等等。嵌入式系统本身是一个相对模糊的定义,一个手持的MP3和一个PC104的微型工业控制计算机都可以认为是嵌入式系统。总体来说,嵌入式系统是“用于控制,监视或者辅助操作机器和设备的装备”。一个典型的桌面Linux系统包括3个主要的软件层—linux内核、C库和应用程序代码。内核是唯一可以完全控制硬件的层,内核驱动程序代表应用程序与硬件之间进行会话。内核之上是C库,负责把POSIX API转换为内核可以识别的形式,然后调用内核,从应用程序向内核传递参数。应用程序依靠驱动内核来完成特定的任务。嵌入式系统的发展是从电子计算机诞生以来,计算机的发展有两个方向:一个方向是体积大型化、处理能力超强的大型计算机;另一个是向体积小型化,功能多样化的方向发展。嵌入式微控制器,即传统意义上的单片机,是目前嵌入式系统的前身。

    在了解了基础知识的情况下,我们还同步地进行了上机操作,当然,其中遇到很多的难题,很多东西都是第一次接触,又没有很多的指导操作,主要还是要凭借自己去摸索练习。其中的困难可想而知。然而坚持就是胜利,只要坚持做下去。通过这学期的实验课程,我感觉收获还是蛮多的。可能我对于嵌入式的知识学习的还是不太多,但是这之外的东西收获颇丰。它让我学会了如何通过自己的努力去认知一个新事物,更重要的是端正自己的学习态度,只有真正下功夫去学习,才能有收获,正所谓“一份耕耘,一份收获”。没有付出,何谈回报呢?再者,通过这学期的实验课程,我也学会了如何去分析问题,如何找出自己设计中的不足,继而去排除解决问题,这就是一个自我学习的过程。当我们通过实验去学习理论知识时,自己动手得出的结论,不仅能加深我们对嵌入式的理解,更能加深我们对此的记忆。

    物联沃分享整理
    物联沃-IOTWORD物联网 » 使用STM32实现呼吸灯效果的PWM输出

    发表评论