加入收藏 在线留言 联系我们
关注微信
手机扫一扫 立刻联系商家
全国服务热线15915421161
公司新闻
代码实现与分析
发布时间: 2024-05-21 15:06 更新时间: 2024-06-23 07:00
4代码实现与分析

上面介绍了定时器的基础知识与PWM的输出原理,下面就来实际看一下,如何编写对应的代码(以STM32F407为例)。

4.1 定时器初始化

定时器的初始化,因为需要用到对应的引脚输出PWM,因此要先初始化GPIO引脚,然后,还要初始化定时器的时基(计数的时钟)以及输出通道(用于配置PWM的输出模式)。

4.1.1 复用引脚初始化

这里用到的是定时器3,根据STM32F407的数据手册“3 Pinouts and pin description”中的“Table 9. Alternate function mapping”复用引脚说明表,可以看到定时器3通道1对应的引脚位A6:

因此程序中对A6引脚可以这样配置,注意一定要配置引脚的复用功能:













  • GPIO_InitTypeDef GPIO_InitStructure; /*引脚配置 结构体*/ RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOA, ENABLE);   //使能PORTA时钟GPIO_PinAFConfig(GPIOA,GPIO_PinSource6,GPIO_AF_TIM3);  /*GPIOA6复用为定时器3*/ /*复用引脚配置*/GPIO_InitStructure.GPIO_Pin = GPIO_Pin_6;           //GPIOA6GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF;        /*复用功能*/GPIO_InitStructure.GPIO_Speed = GPIO_Speed_100MHz;  //速度100MHzGPIO_InitStructure.GPIO_OType = GPIO_OType_PP;      //推挽复用输出GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_UP;        //上拉GPIO_Init(GPIOA,&GPIO_InitStructure);               //初始化PA6


    4.1.2 时基初始化

    时基初始化,主要是配置定时器的计数频率(psc)和自动重装置值(每次计数的周期,arr),比如TIM3_PWM_Init(500-1,84-1);

    (关于psc与arr的知识点,可以再回顾一下上面1.3节的知识)

    这里将arr的值设置为500,即计数器每计够500个数就会重新从0开始计数,这个500再乘以计数器计数的周期,就是PWM真正的周期,那计数器计数的频率是多少呢(频率的倒数为周期)?

    这里将psc的值设置为84-1,即TIM3的输入频率为84MHz再将频率降低1/84,即使用1MHz的频率计数(1s能计1,000,000个数,也即1us计1个数),那么PWM的真正周期就是500*1us=500us(0.5ms),通过改变占空比的值(ccr),就可以调节PWM的输出占空比。


    时基初始化配置如下:











  • TIM_TimeBaseInitTypeDef  TIM_TimeBaseStructure; /*时基 结构体*/ RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM3,ENABLE);     //TIM3时钟使能    /*时基初始化*/TIM_TimeBaseStructure.TIM_Period=arr;                     /*ARR 自动重装载值(周期),例如500*/TIM_TimeBaseStructure.TIM_Prescaler=psc;                  /*PSC 定时器分频,例如84*/TIM_TimeBaseStructure.TIM_ClockDivision=TIM_CKD_DIV1;     /*时钟分割*/TIM_TimeBaseStructure.TIM_CounterMode=TIM_CounterMode_Up; /*向上计数模式*/TIM_TimeBaseInit(TIM3,&TIM_TimeBaseStructure);            /*初始化定时器3*/

    Zui后一句的时基初始化,起始就是对定时的寄存器进行配置,该函数的内部实现如下:






































  • void TIM_TimeBaseInit(TIM_TypeDef* TIMx, TIM_TimeBaseInitTypeDef* TIM_TimeBaseInitStruct){  uint16_t tmpcr1 = 0;  tmpcr1 = TIMx->CR1;     if((TIMx == TIM1) || (TIMx == TIM8)|| /*gaoji定时器TIM和TIM8*/     (TIMx == TIM2) || (TIMx == TIM3)||(TIMx == TIM4) || (TIMx == TIM5)) /*通用定时器中的TIM2~TIM5*/  {    /* 设置为计数器模式 */    tmpcr1 &= (uint16_t)(~(TIM_CR1_DIR | TIM_CR1_CMS));    tmpcr1 |= (uint32_t)TIM_TimeBaseInitStruct->TIM_CounterMode;  }   if((TIMx != TIM6) && (TIMx != TIM7)) /*基本定时器TIM6和TIM7无此功能*/  {    /* 设置时钟分频 */    tmpcr1 &=  (uint16_t)(~TIM_CR1_CKD);    tmpcr1 |= (uint32_t)TIM_TimeBaseInitStruct->TIM_ClockDivision;  }   /* 配置CR1寄存器 */  TIMx->CR1 = tmpcr1;   /* 配置ARR寄存器,设置自动重转载值 */  TIMx->ARR = TIM_TimeBaseInitStruct->TIM_Period ;   /* 配置PSC寄存器,设置预分频值 */  TIMx->PSC = TIM_TimeBaseInitStruct->TIM_Prescaler;   if ((TIMx == TIM1) || (TIMx == TIM8))  /*gaoji定时器TIM和TIM8*/  {    /*  配置RCR寄存器,设置重复计数值 */      TIMx->RCR = TIM_TimeBaseInitStruct->TIM_RepetitionCounter;  }   /* 生成一个更新事件来立即重新加载预分频器和重复计数器(仅针对gaoji定时器TIM1和TIM8)值 */  TIMx->EGR = TIM_PSCReloadMode_Immediate;          }

    4.1.3 输出通道初始化

    输出通道初始化,主要是配置输出的一些参数,这里主要关注TIM_OCMode(模式)与TIM_OCPolarity(极性),这两个参数是配合使用的:

  • PWM模式1

  • 向上计数时,一旦TIMx_CNT<TIMx_CCR1时通道1为有效电平,否则为无效电平;

  • 向下计数时,一旦TIMx_CNT>TIMx_CCR1时通道1为无效电平,否则为有效电平。

  • PWM模式2

  • 向上计数时,一旦TIMx_CNT<TIMx_CCR1时通道1为无效电平,否则为有效电平;

  • 向下计数时,一旦TIMx_CNT>TIMx_CCR1时通道1为有效电平,否则为无效电平。

  • 这里的有效电平又是什么意思呢?怎么算有效电平?它就是通过极性来配置的:

  • 输出High模式:有效电平为高电平

  • 输出Low模式:有效电平为低电平

  • 对比着再来看这张图:

    当CNT的计数值小于CCR时,即t1这个时间段,输出有效电平(TIM_OCMode_PWM1模式),而有效电平是高电平(极性为TIM_OCPolarity_High),所以PWM的IO逻辑在t1这个时间段输出了高电平。


    输出通道的配置如下:











  • TIM_OCInitTypeDef  TIM_OCInitStructure; /*输出通道 结构体*//*输出通道初始化,初始化TIM3 Channel1 PWM模式*/   TIM_OCInitStructure.TIM_OCMode = TIM_OCMode_PWM1;              /*选择定时器模式:TIM脉冲宽度调制模式1*/TIM_OCInitStructure.TIM_OutputState = TIM_OutputState_Enable;  //比较输出使能TIM_OCInitStructure.TIM_OCPolarity = TIM_OCPolarity_High;      /*输出极性:TIM输出比较极性高*/TIM_OC1Init(TIM3, &TIM_OCInitStructure);                       //根据指定的参数初始化外设TIM3 OC1 TIM_OC1PreloadConfig(TIM3, TIM_OCPreload_Enable);  /*使能TIM3在CCR1上的预装载寄存器*/TIM_ARRPreloadConfig(TIM3,ENABLE);/*ARPE使能:使能控制寄存器CR的第8位:ARPR, Auto-reload preload enable*/TIM_Cmd(TIM3, ENABLE);  /*使能TIM3:使能控制寄存器CR的第0位:CEN, counter enable*/

  • 关于配置CCMR1、CCER寄存器

  • CCMR1:


    CCER:

    TIM_OC1Init函数对应于输入通道的初始化,其实就是操作CCMR1、CCER等寄存器:




























  • void TIM_OC1Init(TIM_TypeDef* TIMx, TIM_OCInitTypeDef* TIM_OCInitStruct){  uint16_t tmpccmrx = 0, tmpccer = 0, tmpcr2 = 0;  TIMx->CCER &= (uint16_t)~TIM_CCER_CC1E;/* 关闭通道1: 复位CC1E位 */   tmpccer = TIMx->CCER;/* 获取 TIMx CCER 寄存器的值 */  tmpcr2 =  TIMx->CR2; /* 获取 TIMx CR2 寄存器的值 */   tmpccmrx = TIMx->CCMR1;/* 获取TIMx CCMR1 寄存器的值 */  tmpccmrx &= (uint16_t)~TIM_CCMR1_OC1M;   /* 复位输出比较模式OC1M位 */  tmpccmrx &= (uint16_t)~TIM_CCMR1_CC1S;  tmpccmrx |= TIM_OCInitStruct->TIM_OCMode;/* 设置为输出比较模式 */   tmpccer &= (uint16_t)~TIM_CCER_CC1P;          /* 复位输出极性CC1P */  tmpccer |= TIM_OCInitStruct->TIM_OCPolarity;  /* 设置输出极性 */  tmpccer |= TIM_OCInitStruct->TIM_OutputState; /* 设置输出状态 */   if((TIMx == TIM1) || (TIMx == TIM8)) /*gaoji定时器的特殊配置*/  {    //省略。。。  }   TIMx->CR2 = tmpcr2;     /* 写数据到TIMx的CR2寄存器 */  TIMx->CCMR1 = tmpccmrx; /* 写数据到TIMx的CCMR1寄存器 */  TIMx->CCR1 = TIM_OCInitStruct->TIM_Pulse;/* 设置CCR1寄存器 */  TIMx->CCER = tmpccer;  /* 写数据到TIMx的CCER寄存器 */}

    4.2 动态改变占空比

    占空比是通过修改CCR寄存器的值进行修改的,如果定时器初始化时只设置了1次CCR的值,那么会输出恒定占空比的PWM波;如果在定时器运行的时候,动态修改CCR的值,则可以实现PWM占空比的动态调整。

    如下程序,实现了每隔10ms对占空比进行一次修改,每次将高电平计数值增加5,当增大道500(占空比)时,再逐渐减小到0(占空比0%),不断循环。



























  • u16 led0pwmval=0;    u8 dir=1;TIM3_PWM_Init(500-1,84-1);  //84M/84=1Mhz的计数频率,重装载值500,所以PWM频率为 1M/500=2Khz.  while(1) //实现比较值从0-500递增,到500后从500-0递减,循环{    delay_ms(10);     if(dir)    {        led0pwmval+=5;  //dir==1 led0pwmval递增    }    else     {        led0pwmval-=5;  //dir==0 led0pwmval递减     }    if(led0pwmval>500)    {        dir=0;          //led0pwmval到达500后,方向为递减    }    if(led0pwmval==0)    {        dir=1;          //led0pwmval递减到0后,方向改为递增    }     TIM_SetCompare1(TIM3,led0pwmval);   /*CCR 修改比较值(占空比)*/}5 测试效果

    将程序下载到板子,我用的一块STM32F407的板,A6引脚上接了一个LED灯,实际效果的LED逐渐变亮,再逐渐变暗,依次循环。

    再通过逻辑分析仪来查看实际的输出波形,如下图,测得的pwm周期0.5ms(频率2kHz),与软件中设定的一致。

    在某一时刻,脉宽55us。

    在另一时刻,脉宽0.365ms,即实现了PWM脉宽的动态调整。\


    联系方式

    • 电  话:15903418770
    • 联系人:张经理
    • 手  机:15915421161
    • 微  信:15915421161