单片机
返回首页

嵌入式STM32学习笔记(3)——pwm波及呼吸灯

2020-06-16 来源:eefocus

写pwm波函数可以调用stm32固件库函数直接生成,也可以通过中断来写pwm波;下面就介绍这两种方法,这里先说一下呼吸灯,其原理就是让LED灯由暗变亮再由亮变暗循环,类似呼吸的效果,亮-暗是一个大周期,而LED灯亮或暗是由其刷新的占空比决定,高电平时间占比长则亮,反之则暗;


stm32 的定时器除了 TIM6 和 7。其他的定时器都可以用来产生 PWM 输出。其中高级定时器 TIM1 和 TIM8 可以同时产生多达 7 路的 PWM 输出。而通用定时器也能同时产生多达4路的 PWM 输出,这样, STM32 最多可以同时产生 30 路 PWM 输出。关于映射及原理大家可查手册吧,这里不做具体叙述了;个人见解:很多知识用到再仔细研究是节省精力的好办法,工程实验做多了,就会发现有些细微的知识可能一直都不需要了解。


补充一下关于stm32 TIMx的管脚映射,每个定时器的通道都有特定的引脚,其可以通过如下两个函数映射跟换引脚:


GPIO_PinRemapConfig(GPIO_PartialRemap_TIMx, ENABLE);  //重映射引脚

GPIO_PinRemapConfig(GPIO_FullRemap_TIMx, ENABLE);     //完全重映

但可更换的引脚也是固定的,具体如下表:

下面代码包含软件版本pwm波和固件库函数pwm波,具体如下:


1.编译器IAR8,系统win10;


2.板子:STM32F103C8T6核心板,如下:

3.下载器:ST-LINK/V2仿真下载器;


4.板子上LED对应的引脚是GPIOC, GPIO_Pin_13;在IAR对应的stm32F103X模板DRIVER目录下添加:led.c,led.h,timer.c,timer.h文件,如下:

5.led.c 代码如下:


#include 'led.h'

 

/*LED_G 驱动 GPIO 初始化函数*/

void led_gpio_config(void)

{

  GPIO_InitTypeDef GPIO_InitStructure; //调用GPIO结构体

  RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOC, ENABLE); //配置RCC时钟,使得引脚使能

  GPIO_InitStructure.GPIO_Pin = GPIO_Pin_13; //设置的引脚

  GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; //设置引脚速度

  GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP; //设置引脚模式,推挽式输出

  GPIO_Init(GPIOC, &GPIO_InitStructure); //初始化引脚

}

6.led.h的代码:


#ifndef _LED_H

#define _LED_H

 

/*包含相关的头文件*/

#include 'stm32f10x.h'

#include 'stm32f10x_gpio.h'

#include 'stm32f10x_rcc.h'

void led_gpio_config(void);//声明,初始化LED对应引脚

 

#endif

7.timer.c代码:


#include 'timer.h'

__IO uint32_t TimingDelay; //计数变量,加要加“_IO”,不然会被编译优化

__IO uint32_t TimingDelay2; //计数变量2

 

u8 breath_pwm_high;

u8 breath_pwm_step;

u8 breath_pwm_flag;

u8 breath_circle;

 

/*SystemCoreClock / 1000000  -------   1us*/

/*SystemCoreClock / 100000   -------   10us*/

/*SystemCoreClock / 10000    -------   100us*/

/*SystemCoreClock / 1000     -------   1ms*/

 

 

//////////////////////////

//设置系统滴答中断延时程序//

/////////////////////////

 

void Systick_Init(void)

{

  //装载系统时钟中断计数值,系统时钟累计达到72000时候溢出产生中断

    if (SysTick_Config(72000))

    { 

      /* Capture error */ 

      while (1);

    }

}

 

//延时计数函数,如果不是0,每个系统滴答中断周期自减

void TimingDelay_Decrement(void)

{

    if (TimingDelay != 0x00)

    {

        TimingDelay--;

    }

}

 

//延迟函数,设置为 US

void delay_ms(__IO uint32_t nTime)

{

    TimingDelay = nTime;//自减初始值

    while(TimingDelay != 0);

}

 

//中断事件函数,原函数在stm32f10x_it.c里面,复制到这里后要将原位置里的注释掉,不然报错

void SysTick_Handler(void)

{

    TimingDelay_Decrement();

}

 

//////////////////////////

//设置定时器中断延时程序//

/////////////////////////

 

//配置嵌套中断控制器 NVCI

void tim2_nvic_config(void)

{

    NVIC_InitTypeDef NVIC_Init_Struct; //调用NVCI结构体

    

    NVIC_PriorityGroupConfig(NVIC_PriorityGroup_0); //设置组优先级

    

    NVIC_Init_Struct.NVIC_IRQChannel = TIM2_IRQn; //设置定时器 2 中断

    NVIC_Init_Struct.NVIC_IRQChannelPreemptionPriority = 0; //设置抢占优先级

    NVIC_Init_Struct.NVIC_IRQChannelSubPriority = 0; //设置子优先级

    NVIC_Init_Struct.NVIC_IRQChannelCmd = ENABLE; //使能IRQ中断

    NVIC_Init(&NVIC_Init_Struct); //初始化NVIC

}

 

 

//定时器初始化配置

void tim2_config(void)

{

    TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStruct; //调用定时器结构体

    

    tim2_nvic_config(); //加载嵌套中断控制器 NVCI

    

    RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2,ENABLE); //配置RCC时钟,使得中断使能

    TIM_DeInit(TIM2); //将外设 TIMx 寄存器重设为缺省值,复位寄存器

    

    /*

定时计数计算方法如下:

发生中断时间 = (TIM_Prescaler+1)* (TIM_Period+1)/FLK

以定时 1s 为例 TIM_Period=2000-1, TIM_Prescaler=(36000-1)

         FLK=72M

    */   

    

    //设置36*10/72000000=0.000005s=5us

    TIM_TimeBaseInitStruct.TIM_Prescaler = 36-1; //时钟预先分频数

    TIM_TimeBaseInitStruct.TIM_Period = 10-1;  //自动重装载寄存器的值

    TIM_TimeBaseInitStruct.TIM_CounterMode = TIM_CounterMode_Up; //计数模式,向上计数方式

    TIM_TimeBaseInitStruct.TIM_ClockDivision = TIM_CKD_DIV1; //采样分频

    TIM_TimeBaseInit(TIM2, &TIM_TimeBaseInitStruct);    //初始化TIM2配置

    TIM_ClearFlag(TIM2,TIM_FLAG_Update); //清除溢出中断标志

    TIM_ITConfig(TIM2,TIM_IT_Update,ENABLE); //使能或者失能指定的 TIM 中断

    TIM_Cmd(TIM2,ENABLE); //开启时钟

}

 

 

 

 

//延时计数函数2,如果不是0,每个系统滴答中断周期自减

void TimingDelay_Decrement2(void)

{

    if (TimingDelay2 != 0x00)

    {

        TimingDelay2--;

    }

}

 

//延迟函数,设置为 US

void delay_us2(__IO uint32_t nTime2)

{

    TimingDelay2 = nTime2;//时钟滴答数

    while(TimingDelay2 != 0);

}

 

//装载初始值,及呼吸循环检测

 void breath_circle_change()

{

    //判断呼吸周期是否到了,上限值代表呼吸周期时间

    if(breath_circle>=80)

    {

        //判断占空比分割点,是否已经超过99%占比

if(breath_pwm_high>99) 

  {

      breath_pwm_flag=1;//添加标记,准备自减

  }

 

//判断占空比分割点,是否小于2%占比

  if(breath_pwm_high<2)

  {

      breath_pwm_flag=0;//添加标记,准备自加

  }

//修改占空比,设置自加或自减,当占比低于2%时候自加,否则自减

  if(breath_pwm_high<100 && breath_pwm_flag==0)

  {

       breath_pwm_high++;

  }

  else

  {

       breath_pwm_high-=2;//快呼慢吸,自减1的话就是1:1呼吸

       //breath_pwm_high--;//快呼慢吸,自减1的话就是1:1呼吸

  }

     

breath_circle=0;//呼吸循环置零,重新装载检测

      }

      

 

}

 

//定时器2,中断事件函数

void TIM2_IRQHandler(void)

{

    if(TIM_GetITStatus(TIM2, TIM_IT_Update) != RESET)//固件库函数,判断是否发生TIM2中断

    {

//      TimingDelay_Decrement2(); //调用延时计数函数

      

      //呼吸分割步长step  

      breath_pwm_step++;   

      //设置步长小于分割点时候点亮灯

      if(breath_pwm_step      {

        GPIO_SetBits(GPIOC, GPIO_Pin_13);    //将PB13设置成高电平

      }

      //设置分割点后,熄灭灯

      else if(breath_pwm_step<100)

      {      

      GPIO_ResetBits(GPIOC, GPIO_Pin_13);   //将PB13设置成低电平

      }

      else

      {

breath_pwm_step=0;//置0,重新计数

breath_circle++;//计算呼吸循环,调整分割点

 

      }

       

           

      

    }

    TIM_ClearITPendingBit(TIM2,TIM_FLAG_Update);//标志位清除,固件库函数

}

 

 

 

//////////////////////////

////定时器3,通过固件函数产生PWM波

////////////////////////

 

 

//定时器3初始化

void TIM3_Int_Init(u16 arr,u16 psc)

{

   

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

      

      TIM_TimeBaseInitTypeDef  TIM_TimeBaseStructure;//调用TIM结构体       

      TIM_TimeBaseStructure.TIM_Period = arr; //设置自动重装载周期值

      TIM_TimeBaseStructure.TIM_Prescaler =psc; //设置预分频值 不分频

      TIM_TimeBaseStructure.TIM_ClockDivision = 0; //设置时钟分割:TDTS = Tck_tim

      TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up; //向上计数

      TIM_TimeBaseInit(TIM3, &TIM_TimeBaseStructure); //初始化 TIMx

//      TIM_ClearFlag(TIM3,TIM_FLAG_Update); //清除溢出中断标志

      TIM_ITConfig(TIM3,TIM_IT_Update,ENABLE ); //使能或者失能指定的 TIM3 中断

      TIM_Cmd(TIM3,ENABLE); //开启时钟

      

      

      NVIC_InitTypeDef NVIC_InitStructure;//调用NVCI结构体

      NVIC_InitStructure.NVIC_IRQChannel = TIM3_IRQn;  //设置定时器 23中断

      NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0;  //设置抢占优先级

      NVIC_InitStructure.NVIC_IRQChannelSubPriority = 3;  //设置子优先级

      NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; //使能IRQ中断

      NVIC_Init(&NVIC_InitStructure);  //初始化NVIC           

       

}

 

//定时器3,中断事件函数

void TIM3_IRQHandler(void)   

{

if (TIM_GetITStatus(TIM3, TIM_IT_Update) != RESET) //固件库函数,判断是否发生TIM3中断 

{

TIM_ClearITPendingBit(TIM3, TIM_IT_Update  );  //清除TIMx 的中断待处理位

// LED1=!LED1;

}

}

 

 

//PWM波初始化函数

void TIM3_PWM_Init(u16 arr,u16 psc)

{   

//全部映射,将TIM3_CH2映射到PB5

//根据STM32中文参考手册2010中第第119页可知:

//当没有重映射时,TIM3的四个通道CH1,CH2,CH3,CH4分别对应PA6,PA7,PB0,PB1

//当部分重映射时,TIM3的四个通道CH1,CH2,CH3,CH4分别对应PB4,PB5,PB0,PB1

//当完全重映射时,TIM3的四个通道CH1,CH2,CH3,CH4分别对应PC6,PC7,PC8,PC9

    

        //1.开启时钟,配置引脚复用形式,产生PWM波

GPIO_InitTypeDef  GPIO_InitStructure;//GPIO结构体

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

RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA

      |RCC_APB2Periph_GPIOB

      |RCC_APB2Periph_GPIOC

      |RCC_APB2Periph_AFIO, ENABLE);//使能GPIO外设及ADIO

// GPIO_PinRemapConfig(GPIO_PartialRemap_TIM3, ENABLE); //   重映射引脚,TIM3_CH2->PB5   

// GPIO_PinRemapConfig(GPIO_FullRemap_TIM3, ENABLE);//完全重映

// GPIO_InitStructure.GPIO_Pin = GPIO_Pin_4|GPIO_Pin_5; //TIM_CH1和TIM_CH2,重映射引脚后

GPIO_InitStructure.GPIO_Pin = GPIO_Pin_6|GPIO_Pin_7; //TIM_CH1和TIM_CH2

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

GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;

GPIO_Init(GPIOA, &GPIO_InitStructure);//初始化GPIO

// GPIO_Init(GPIOB, &GPIO_InitStructure);//初始化GPIO化G

  

//2.设置通用定时器,分频及初始化

TIM_TimeBaseInitTypeDef  TIM_TimeBaseStructure;//调用TIM结构体          

TIM_TimeBaseStructure.TIM_Period = arr; //设置自动重装载周期值

TIM_TimeBaseStructure.TIM_Prescaler =psc; //设置预分频值 不分频

TIM_TimeBaseStructure.TIM_ClockDivision = 0; //设置时钟分割:TDTS = Tck_tim

TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up; //向上计数

TIM_TimeBaseInit(TIM3, &TIM_TimeBaseStructure); //初始化 TIMx

TIM_ITConfig(TIM3,TIM_IT_Update,ENABLE ); //使能或者失能指定的 TIM3 中断

//3.初始化TIM3 Channel 通道及PWM模式

TIM_OCInitTypeDef  TIM_OCInitStructure;//对应结构体

TIM_OCInitStructure.TIM_OCMode = TIM_OCMode_PWM2; //选择定时器模式,TIM脉宽调制模式2

  TIM_OCInitStructure.TIM_OutputState = TIM_OutputState_Enable; //比较输出使能

TIM_OCInitStructure.TIM_OCPolarity = TIM_OCPolarity_High; //输出极性,TIM输出比较极性高

TIM_OCInitStructure.TIM_Pulse = 80;   //占空比设置,与自动装载周期,arr值比较  

TIM_OC1Init(TIM3,&TIM_OCInitStructure);//通道1

TIM_OC1PreloadConfig(TIM3, TIM_OCPreload_Enable);  //使能TIM3预装载寄存器

 

TIM_OCInitStructure.TIM_Pulse = 350; //占空比设置,与自动装载周期,arr值比较  

TIM_OC2Init(TIM3, &TIM_OCInitStructure);  //根据参数初始化TIM3,通道2

TIM_OC2PreloadConfig(TIM3, TIM_OCPreload_Enable);  //使能TIM3预装载寄存器

TIM_ARRPreloadConfig(TIM3, ENABLE);//允许或禁止在定时器工作时向ARR的缓冲器中写入新值,以便在更新事件发生时载入覆盖以前的值

TIM_Cmd(TIM3,ENABLE); //开启时钟

}

8.timer.h代码:


#ifndef __TIMER_H_

#define __TIMER_H_

 

#include 'stm32f10x_tim.h'

#include 'stm32f10x_rcc.h'

#include 'stm32f10x_it.h'

#include 'misc.h'

 

extern __IO uint32_t TimingDelay; //计数变量,加要加“_IO”,不然会被编译优化

void Systick_Init(void); //初始化系统滴答

void TimingDelay_Decrement(void); //计数函数

void delay_ms(__IO uint32_t nTime);//延迟函数,设置为 US

 

extern __IO uint32_t TimingDelay2; //计数变量2

void tim2_nvic_config(void); //初始化中断

void tim2_config(void); //初始化计数器

void delay_us2(__IO uint32_t nTime2);//延迟函数2,设置为 US

void TimingDelay_Decrement2(void); //计数函数2

 

extern u8 breath_pwm_high;

extern u8 breath_pwm_step;

extern u8 breath_pwm_flag;

extern u8 breath_circle;

void breath_circle_change(void);

 

#endif

9.主函数main.c代码:


#include 'led.h'

#include 'timer.h'

 

int main(void)

{

    SystemInit();  //初始化系统时钟

    Systick_Init(); //配置系统时钟滴答参数

    led_gpio_config();  //配置GPIO

    tim2_config();//配置定时器

   

    //调用固件库PWM函数时,用以下变量

    u16 led0pwmval=0;//呼吸灯周期

    u8 pwm_flag=1; //脉宽调整方向标记,为1则++,为0则--

//    TIM3_PWM_Init(899,0); //不分频 PWM频率=72000000/900=80Khz

    TIM3_PWM_Init(360,100); //PWM=设置360*100/72000000=0.0005s

       

    while(1)

    {

      

      //调用系统滴答延时函数做LED灯的闪烁,可任意引脚设置//

//        GPIO_SetBits(GPIOC, GPIO_Pin_13);    //将PB13设置成高电平

//        delay_ms(100);                    //调用系统滴答延时函数

//        GPIO_ResetBits(GPIOC, GPIO_Pin_13);    //将PB13设置成低电平

//        delay_ms(100);                       //调用系统滴答延时函数

      

//        //调用定时器延时函数做LED灯闪烁//

//        GPIO_SetBits(GPIOC, GPIO_Pin_13);    //将PB13设置成高电平

//        delay_us2(3);                     //调用定时器延时函数

//        GPIO_ResetBits(GPIOC, GPIO_Pin_13);   //将PB13设置成低电平

//        delay_us2(97);                     //调用定时器延时函数

      

// breath_circle_change();//呼吸灯实验,装载初始置,设置占空比分割值

     

      //采用固件库PWM波函数的方式进行呼吸灯脉宽调制,可将LED灯接到对应引脚上即可;

      delay_ms(10);//延时,刷新率

      if(pwm_flag) led0pwmval++;//如果dir大于0,则脉宽调宽+1;

      else led0pwmval--;//否则,脉宽调宽-1;

      if(led0pwmval>355) pwm_flag=0;//如果脉宽调制大于355(对应TIM3_PWM_Init的设置),则置dir为0;

      if(led0pwmval==0) pwm_flag=1;//如果脉宽调宽已经等于0,则置dir为1;

          

      TIM_SetCompare1(TIM3,led0pwmval);//设置TIM_CH1重载值

      TIM_SetCompare2(TIM3,led0pwmval);//设置TIM_CH2重载值,以此类推 TIM_SetCompare3和4对应的是通道3和4

            

    }

    return 0;

}


10.实验后用示波器连接对应的引脚,可以看到如下波形就是可以的,因为固件库函数版本pwm波的引脚是固定的,且映射时候,由于我的核心板子GPIO_PB04在线调试时候被ST-LINK占用,所以无法映射;

进入单片机查看更多内容>>
相关视频
  • RISC-V嵌入式系统开发

  • SOC系统级芯片设计实验

  • 云龙51单片机实训视频教程(王云,字幕版)

  • 2022 Digi-Key KOL 系列: 你见过1GHz主频的单片机吗?Teensy 4.1开发板介绍

  • TI 新一代 C2000™ 微控制器:全方位助力伺服及马达驱动应用

  • MSP430电容触摸技术 - 防水Demo演示

精选电路图
  • 家用电源无载自动断电装置的设计与制作

  • 短波AM发射器电路设计图

  • 带有短路保护系统的5V直流稳压电源电路图

  • 如何调制IC555振荡器

  • 基于ICL296的大电流开关稳压器电源电路

  • 基于TDA2003的简单低功耗汽车立体声放大器电路

    相关电子头条文章