stm32 TIM定时器 PWM脉冲输出[操作寄存器+库函数]

脉冲调制(PWM)是利用微处理器对数字输出来对模拟电路的一种非常有效的技术。简单点说就是对确定频率的信号,调整其占空比。
stm32的定时器除了TIM6和TIM7外,其他定时器都可以产生PWM输出。其中高级定时器TIM1和TIM8可以产生多达7路的PWM输出。通用定时器可以产生4路的PWM输出。
stm32 TIM定时器[操作寄存器+库函数]  中我们是通过在中断中,翻转指定引脚的电平。在stm32中可以通过配置一个捕获/比较模式寄存器(TIMx_CCMR),设置通道引脚输出模式为PWM脉冲模式,在计时器计数到捕获/比较模式寄存器的值,指定引脚会输出一个有效电平,这样就可以通过定时器直接产生 PWM脉冲。这种方式下不需要开启中断。
  • 这里说有效电平是因为这个电平不一定为1,这个在 捕获/比较使能寄存器(TIMx_CCER)中可以设置有效电平的极性。
  • 指定引脚不是任意的,这个stm32对每个定时器通道有特定的引脚对应 对应关系如下
    TIMx_CHx 对应的I/O口就是此通道对应的引脚
    IMG_20120417_143213.jpg
    可以看出 TIM2的 OC通道 1-4 对应的就是   GPIOA 0-3 
    此例直接操作寄存器实现 Led灯由暗到亮再由亮到暗的呼吸灯效果。库函数实现用PWM脉冲输出模式,产生4个不同频率的脉冲,让led闪烁。
直接操作寄存器
通用定时器的每个通道都有6种输出模式,其中有两种PWM模式。通过捕获/比较模式寄存器1(TIMx_CCMR1)设定,由OC1M[2:0]三位决定。6种模式如下:
  • 000:冻结。输出比较寄存器TIMx_CCR1与计数器TIMx_CNT间的比较对OC1REF不起作用;
  • 001:匹配时设置通道1为有效电平。当计数器TIMx_CNT的值与捕获/比较寄存器1 (TIMx_CCR1)相同时,强制OC1REF为高。
  • 010:匹配时设置通道1为无效电平。当计数器TIMx_CNT的值与捕获/比较寄存器1 (TIMx_CCR1)相同时,强制OC1REF为低。
  • 011:翻转。当TIMx_CCR1=TIMx_CNT时,翻转OC1REF的电平。
  • 100:强制为无效电平。强制OC1REF为低。
  • 101:强制为有效电平。强制OC1REF为高。
  • 110:PWM模式1- 在向上计数时,一旦TIMx_CNT<TIMx_CCR1时通道1为有效电平,否则为无效电平;在向下计数时,一旦TIMx_CNT>TIMx_CCR1时通道1为无效电平(OC1REF=0),否则为有效电平(OC1REF=1)。
  • 111:PWM模式2- 在向上计数时,一旦TIMx_CNT<TIMx_CCR1时通道1为无效电平,否则为有效电平;在向下计数时,一旦TIMx_CNT>TIMx_CCR1时通道1为有效电平,否则为无效电平。 
    两种PWM模式,区别在于通道的电平极性是相反的。
    首先需要设定TIMx_CCMR1寄存器:  
    TIMx_CCMR1.png
    OCxM[2:0]已经做了介绍,OC2CE:输出比较2清0使能  OC2PE:输出比较2预装载使能 
    通过设定OC2M[2:0]为 110/111 为PWM脉冲输出模式。
    设定TIMx_CCER寄存器相关位,使能通道输出,还可以设置有效电平极性。
    最后一个就是调整占空比的关键寄存器,捕获/比较寄存器(TIMx_CCRx),低16位有效,这个寄存器已经使用过,要实现PWM脉冲的占空比可调的原理就是不断改变这个寄存器的值。
    要实现led亮暗的渐变,PWM的频率不能太低,低于50Hz的时候就会明显感觉到闪烁。这里用8khz的频率,调整PWM输出占空比,从0到不断增大其占空比,再递减为0.
    代码如下: (system.h 和 stm32f10x_it.h 等相关代码参照 stm32 直接操作寄存器开发环境配置
    User/main.c
    #include <stm32f10x_lib.h>
    #include “system.h”
    #include “tim.h”

void Gpio_Init(void);

int main(void)
{
u32 var=0,flag=0;

Rcc_Init(9);              //系统时钟设置

// 相关TIM_x,CCR_x参数定义tim.h文件

Tim_Init(TIM_3,900,0);  //初始化TIM3定时器,设定重装值和分频值

Tim_OC_Set(TIM_3,OC_2,7);    //设定TIM3 通道1为PWM输出模式

Gpio_Init();

while(1){        

    delay(5000);      //延时5ms

    if(flag){
        var--;
    }else{
        var++;
    }

    if(var&gt;300) flag = 1; 

    if(var == 0) flag = 0;

    Tim_CCR_Set(TIM_3,OC_2,var);     
}

}

void Gpio_Init(void)
{
RCC->APB2ENR|=1<<2; //使能PORTA时钟

GPIOA-&gt;CRL&amp;=0X0FFFFFFF;//PA7输出
GPIOA-&gt;CRL|=0XB0000000;//复用功能输出       

}

Library/src/tm.c
#include <stm32f10x_lib.h>     

#include “tim.h”

//通用定时器初始化
//参数说明:TIM_x 为选择定时器 TIM_1为通用寄存器1又一次类推(定义于tim.h), arr为自动重装值 ;psc 为时钟预分频数
//要使用定时器的其他函数,必须先调用此函数,因为时钟在这个函数中开启
//TIM3用于PWM输出已测试
//待完善 目前只支持TIM2
//其他定时器只做了开启时钟处理
void Tim_Init(u8 TIM_x,u16 arr,u16 psc)
{
switch(TIM_x)
{
case 1 :{ RCC->APB2ENR |=1<<11; break; } //TIM1高级定时器设置
case 2 :{ //TIM2通用定时器设置

        RCC-&gt;APB1ENR |=1&lt;&lt;0;

        TIM2-&gt;ARR = arr;            //设定自动重装值
        TIM2-&gt;PSC = psc;            //设定预分频值
        TIM2-&gt;DIER |= 1&lt;&lt;0;            //允许更新中断
        TIM2-&gt;DIER |= 1&lt;&lt;6;            //允许触发中断

         TIM2-&gt;CR1 |= 0x81;            //使能定时器,自动重装允许     

        break;
    }

    case 3 :{

        RCC-&gt;APB1ENR |=1&lt;&lt;1;

        TIM3-&gt;ARR = arr;            //设定自动重装值
        TIM3-&gt;PSC = psc;            //设定预分频值
        //TIM3-&gt;DIER |= 1&lt;&lt;0;            //允许更新中断
        //TIM3-&gt;DIER |= 1&lt;&lt;6;            //允许触发中断
        TIM3-&gt;CR1 |= 0x81;            //使能定时器            

        break;
    }
    case 4 :{
        RCC-&gt;APB1ENR |=1&lt;&lt;2;

        TIM4-&gt;ARR = arr;            //设定自动重装值
        TIM4-&gt;PSC = psc;            //设定预分频值
        TIM4-&gt;DIER |= 1&lt;&lt;0;            //允许更新中断
        TIM4-&gt;DIER |= 1&lt;&lt;6;            //允许触发中断
        TIM4-&gt;CR1 |= 0x01;            //使能定时器                         

        break;
    }

    case 5 :{
        RCC-&gt;APB1ENR |=1&lt;&lt;3;

        TIM5-&gt;ARR = arr;            //设定自动重装值
        TIM5-&gt;PSC = psc;            //设定预分频值
        TIM5-&gt;DIER |= 1&lt;&lt;0;            //允许更新中断
        TIM5-&gt;DIER |= 1&lt;&lt;6;            //允许触发中断
        TIM5-&gt;CR1  |= 0x01;            //使能定时器            

        break;
    }
     case 6 :{  RCC-&gt;APB1ENR |=1&lt;&lt;4;   break;  }        
     case 7 :{  RCC-&gt;APB1ENR |=1&lt;&lt;5;   break;  }
     case 8 :{  RCC-&gt;APB2ENR |=1&lt;&lt;13;  break;  }

}

}

//捕获比较值设定函数
//参数说明:
// TIM_x 为选择定时器 TIM_1为通用寄存器1又一次类推(定义于tim.h)
// OC_x 为选择通道,以确定捕获/比较寄存器(1~4)(定义于tim.h)
// val 为要设定的捕获/比较寄存器的值
// TIM3,OC_2 用于PWM输出已测试
// 待完善,目前只支持TIM2

void Tim_CCR_Set(u8 TIM_x,u8 OC_x,u32 val)
{
switch(TIM_x)
{
case 1 :{ break;}
case 2 :{

        TIM2-&gt;DIER |= 1 &lt;&lt; OC_x;            //开启相应允许捕获/比较中断

        switch(OC_x){

            case 1: {
                TIM2 -&gt;CCR1 = val;         //设置捕获/比较1的值 
                break;
            }

            case 2: {
                TIM2 -&gt;CCR2 = val;         //设置捕获/比较2的值 
                break;
            }

            case 3: {
                TIM2 -&gt;CCR3 = val;         //设置捕获/比较3的值 
                break;
            }

            case 4: {
                TIM2 -&gt;CCR4 = val;         //设置捕获/比较4的值 
                break;
            }
        }

        break;
    }

    case 3 :{
        //TIM3-&gt;DIER |= 1 &lt;&lt; OC_x;            //开启相应允许捕获/比较中断

        switch(OC_x){

            case 1: {
                TIM3 -&gt;CCR1 = val;         //设置捕获/比较1的值 
                break;
            }

            case 2: {
                TIM3 -&gt;CCR2 = val;         //设置捕获/比较2的值 
                break;
            }

            case 3: {
                TIM3 -&gt;CCR3 = val;         //设置捕获/比较3的值 
                break;
            }

            case 4: {
                TIM3 -&gt;CCR4 = val;         //设置捕获/比较4的值 
                break;
            }
        }            

        break;
    }
    case 4 :{ break;}
    case 5 :{ break;}
     case 6 :{ break;}
     case 7 :{ break;}
     case 8 :{ break;}

}

}

//定时器通道引脚输出模式设定函数
//参数说明:
// TIM_x 为选择定时器 TIM_1为通用寄存器1又一次类推(定义于tim.h)
// OC_x 为选择输出通道选择(1~4)(定义于tim.h)
// Mode 为选择通道对应引脚输出模式(0~7)
// TIM3,OC_2 用于PWM输出已测试
// 待完善,目前只支持TIM2

void Tim_OC_Set(u8 TIM_x,u8 OC_x,u8 Mode)
{
switch(TIM_x)
{
case 1 :{ break;}

    case 2 :{

        switch(OC_x){

            case 1: {
                TIM2 -&gt;CCMR1 |= Mode &lt;&lt;4;     //设定引脚输出模式
                TIM2 -&gt;CCMR1 |= 1&lt;&lt;3;         //允许预装载

                //TIM2 -&gt;CCER  |= 1&lt;&lt;2;       //引脚输出低电平为有效
                TIM2 -&gt;CCER  |= 1&lt;&lt;0;         //OC1 输出使能
                break;
            }

            case 2: {
                TIM2 -&gt;CCMR1 |= Mode &lt;&lt;12;     //设定引脚输出模式
                TIM2 -&gt;CCMR1 |= 1&lt;&lt;11;         //允许预装载

                //TIM2 -&gt;CCER  |= 1&lt;&lt;5;       //引脚输出低电平为有效
                TIM2 -&gt;CCER  |= 1&lt;&lt;4;         //OC2 输出使能
                break;
            }

            case 3: {
                TIM2 -&gt;CCMR2 |= Mode &lt;&lt;4;     //设定引脚输出模式
                TIM2 -&gt;CCMR2 |= 1&lt;&lt;3;         //允许预装载

                //TIM2 -&gt;CCER  |= 1&lt;&lt;9;       //引脚输出低电平为有效
                TIM2 -&gt;CCER  |= 1&lt;&lt;8;         //OC3 输出使能
                break;
            }

            case 4: {
                TIM2 -&gt;CCMR2 |= Mode &lt;&lt;12;     //设定引脚输出模式
                TIM2 -&gt;CCMR2 |= 1&lt;&lt;11;         //允许预装载

                //TIM2 -&gt;CCER |= 1&lt;&lt;5;       //引脚输出低电平为有效
                TIM2 -&gt;CCER  |= 1&lt;&lt;4;         //OC1 输出使能
                break;
            }
        }

        break;
    }

    case 3 :{

        switch(OC_x){

            case 1: {
                TIM3 -&gt;CCMR1 |= Mode &lt;&lt;4;     //设定引脚输出模式
                TIM3 -&gt;CCMR1 |= 1&lt;&lt;3;         //允许预装载

                //TIM3 -&gt;CCER  |= 1&lt;&lt;2;       //引脚输出低电平为有效
                TIM3 -&gt;CCER  |= 1&lt;&lt;0;         //OC1 输出使能
                break;
            }

            case 2: {
                TIM3 -&gt;CCMR1 |= Mode &lt;&lt;12;     //设定引脚输出模式
                TIM3 -&gt;CCMR1 |= 1&lt;&lt;11;         //允许预装载

                TIM3 -&gt;CCER  |= 1&lt;&lt;5;       //引脚输出低电平为有效
                TIM3 -&gt;CCER  |= 1&lt;&lt;4;         //OC2 输出使能
                break;
            }

            case 3: {
                TIM3 -&gt;CCMR2 |= Mode &lt;&lt;4;     //设定引脚输出模式
                TIM3 -&gt;CCMR2 |= 1&lt;&lt;3;         //允许预装载

                //TIM3 -&gt;CCER  |= 1&lt;&lt;9;       //引脚输出低电平为有效
                TIM3 -&gt;CCER  |= 1&lt;&lt;8;         //OC3 输出使能
                break;
            }

            case 4: {
                TIM3 -&gt;CCMR2 |= Mode &lt;&lt;12;     //设定引脚输出模式
                TIM3 -&gt;CCMR2 |= 1&lt;&lt;11;         //允许预装载

                //TIM3 -&gt;CCER  |= 1&lt;&lt;5;       //引脚输出低电平为有效
                TIM3 -&gt;CCER  |= 1&lt;&lt;4;         //OC1 输出使能
                break;
            }
        }

        break;
    }
    case 4 :{ break;}
    case 5 :{ break;}
     case 6 :{ break;}    
     case 7 :{ break;}
     case 8 :{ break;}
}

}

Library/inc/tim.h
#include <stm32f10x_lib.h>

#define TIM_1 0x01

#define TIM_2 0x02

#define TIM_3 0x03

#define TIM_4 0x04

#define TIM_5 0x05

#define TIM_6 0x06

#define TIM_7 0x07

#define TIM_8 0x08

#define OC_1 0x01

#define OC_2 0x02

#define OC_3 0x03

#define OC_4 0x04

void Tim_Init(u8 TIM_x,u16 arr,u16 psc);
void Tim_CCR_Set(u8 TIM_x,u8 OC_x,u32 val);
void Tim_OC_Set(u8 TIM_x,u8 OC_x,u8 Mode);

这里还需要注意的是 Led的连接方式,我的led是低电平亮的 ,如果你的Led是高电平点亮,可以设置通道引脚输出极性为高电平有效。 在Tim_OC_Set()函数中可以设置 ,此例中选用TIM3的OC2通道,只需要注释 TIM3 ->CCER  |= 1<<5;       //引脚输出低电平为有效 这句代码即可。
库函数操作
要输出PWM脉冲 必须要 将io 设置为复用推挽    
     
代码如下: main.c 

#include “stm32f10x.h”

vu16 CCR1_Val = 60000;
vu16 CCR2_Val = 30000;
vu16 CCR3_Val = 15000;
vu16 CCR4_Val = 7500;

void RCC_Configuration(void);
void GPIO_Configuration(void);
void TIM_Configuration(void);

int main(void)
{

  RCC_Configuration();
  GPIO_Configuration();
TIM_Configuration();
while(1);

}

void TIM_Configuration(void)
{
TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure;
TIM_OCInitTypeDef TIM_OCInitStructure;
TIM_TimeBaseStructure.TIM_Period = 65535;
TIM_TimeBaseStructure.TIM_Prescaler = 7199;
TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up;
TIM_TimeBaseInit(TIM2,&TIM_TimeBaseStructure);

//TIM_PrescalerConfig(TIM2,7199,TIM_PSCReloadMode_Immediate);

TIM_OCInitStructure.TIM_OCMode = TIM_OCMode_PWM1;
TIM_OCInitStructure.TIM_OutputState = TIM_OutputState_Enable;     //使能TIM输出
TIM_OCInitStructure.TIM_OCPolarity = TIM_OCPolarity_High;
TIM_OCInitStructure.TIM_Pulse = CCR1_Val;
TIM_OC1Init(TIM2,&amp;TIM_OCInitStructure);
TIM_OCInitStructure.TIM_Pulse = CCR2_Val;
TIM_OC2Init(TIM2,&amp;TIM_OCInitStructure);
TIM_OCInitStructure.TIM_Pulse = CCR3_Val;
TIM_OC3Init(TIM2,&amp;TIM_OCInitStructure);
 TIM_OCInitStructure.TIM_Pulse = CCR4_Val;
TIM_OC4Init(TIM2,&amp;TIM_OCInitStructure);

TIM_OC1PreloadConfig(TIM2,TIM_OCPreload_Enable);
TIM_OC2PreloadConfig(TIM2,TIM_OCPreload_Enable);
TIM_OC3PreloadConfig(TIM2,TIM_OCPreload_Enable);
TIM_OC4PreloadConfig(TIM2,TIM_OCPreload_Enable);

//TIM_ITConfig(TIM2,TIM_IT_CC1|TIM_IT_CC2|TIM_IT_CC3|TIM_IT_CC4,ENABLE);

TIM_Cmd(TIM2,ENABLE);

}

void GPIO_Configuration(void)
{
GPIO_InitTypeDef GPIO_InitStructure;

GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0 | GPIO_Pin_1 | GPIO_Pin_2 | GPIO_Pin_3;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;            //设置为复用推挽
GPIO_Init(GPIOA , &amp;GPIO_InitStructure); 

}

void RCC_Configuration(void)
{
/ 定义枚举类型变量 HSEStartUpStatus /
ErrorStatus HSEStartUpStatus;

  /* 复位系统时钟设置*/
  RCC_DeInit();
  /* 开启HSE*/
  RCC_HSEConfig(RCC_HSE_ON);
  /* 等待HSE起振并稳定*/
  HSEStartUpStatus = RCC_WaitForHSEStartUp();
/* 判断HSE起是否振成功,是则进入if()内部 */
  if(HSEStartUpStatus == SUCCESS)
  {
    /* 选择HCLK(AHB)时钟源为SYSCLK 1分频 */
    RCC_HCLKConfig(RCC_SYSCLK_Div1); 
    /* 选择PCLK2时钟源为 HCLK(AHB) 1分频 */
    RCC_PCLK2Config(RCC_HCLK_Div1); 
    /* 选择PCLK1时钟源为 HCLK(AHB) 2分频 */
    RCC_PCLK1Config(RCC_HCLK_Div2);
    /* 设置FLASH延时周期数为2 */
    FLASH_SetLatency(FLASH_Latency_2);
    /* 使能FLASH预取缓存 */
    FLASH_PrefetchBufferCmd(FLASH_PrefetchBuffer_Enable);
    /* 选择锁相环(PLL)时钟源为HSE 1分频,倍频数为9,则PLL输出频率为 8MHz * 9 = 72MHz */
    RCC_PLLConfig(RCC_PLLSource_HSE_Div1, RCC_PLLMul_9);
    /* 使能PLL */ 
    RCC_PLLCmd(ENABLE);
    /* 等待PLL输出稳定 */
    while(RCC_GetFlagStatus(RCC_FLAG_PLLRDY) == RESET);
    /* 选择SYSCLK时钟源为PLL */
    RCC_SYSCLKConfig(RCC_SYSCLKSource_PLLCLK);
    /* 等待PLL成为SYSCLK时钟源 */
    while(RCC_GetSYSCLKSource() != 0x08);
  } 
  /* 打开APB2总线上的GPIOA时钟*/
  RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA , ENABLE);

RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2 , ENABLE);

}