STM32教程第十课:PWM波生成与实现详解

文章目录

  • 需求
  • 一、PWM波
  • 二、PWM波配置RGB流程
  • 1.配置RGB彩灯时钟和IO
  • 2.配置定时器(PWM模式)
  • 1.开时钟
  • 2.基本配置
  • 3.PWM配置
  • 三、需求的实现
  • 总结

  • 需求

    利用PWM波,实现RGB三色彩灯的循环呼吸闪烁。

    PWM实现彩色呼吸灯


    一、PWM波

      脉冲宽度调制(PWM),是英文“Pulse Width Modulation”的缩写,简称脉宽调制,是利用微处理器的数字输出来对模拟电路进行控制的一种非常有效的技术,广泛应用在从测量、通信到功率控制与变换的许多领域中。 ​
    PWM波调频的原理就是控制占空比,所谓的占空比就是有效电平所所占总周期的时间。

    彩灯的亮度就是通过调整PWM占空比的大小来控制。
    占空比越大,亮度越小(本例程的RGB灯是低电平使能)

    二、PWM波配置RGB流程

    1.配置RGB彩灯时钟和IO

    先看原理图,找到RGB彩灯的位置

    可以看到:该彩灯是由RGB三个引脚进行控制,并且低电平有效。

    再通过该引脚找到对应CPU引脚的位置

    由上图可知:
          B对应的是PA6引脚,TIM3定时器,通道为1
          B对应的是PA7引脚,TIM3定时器,通道为2
          B对应的是PA8引脚,TIM1定时器,通道为1
    此时打开该引脚的时钟并将模式配置为复用推挽输出(选择复用为了同时使用定时器和引脚)
    代码如下(示例):

    	//开时钟 PA
    	    RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE);
    	//配置io PA6 蓝灯
    	    GPIO_InitTypeDef GPIO_InitStruct = {0};
    		GPIO_InitStruct.GPIO_Mode = GPIO_Mode_AF_PP;
    		GPIO_InitStruct.GPIO_Pin = GPIO_Pin_6;
    		GPIO_InitStruct.GPIO_Speed = GPIO_Speed_50MHz;
    		GPIO_Init(GPIOA,&GPIO_InitStruct);	
    	//配置io PA7 绿灯
    		GPIO_InitStruct.GPIO_Mode = GPIO_Mode_AF_PP;
    		GPIO_InitStruct.GPIO_Pin = GPIO_Pin_7;
    		GPIO_InitStruct.GPIO_Speed = GPIO_Speed_50MHz;
    	    GPIO_Init(GPIOA,&GPIO_InitStruct);	
    		
    	//配置io PA8  红灯
    		GPIO_InitStruct.GPIO_Mode = GPIO_Mode_AF_PP;
    		GPIO_InitStruct.GPIO_Pin = GPIO_Pin_8;
    		GPIO_InitStruct.GPIO_Speed = GPIO_Speed_50MHz;
    	    GPIO_Init(GPIOA,&GPIO_InitStruct);
    

    2.配置定时器(PWM模式)

    先配置蓝灯的,TIM3_CH1

    1.开时钟


    由图中可以看到,该定时器的时钟在APB1上,直接找到对应位置打开就行

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

    2.基本配置

    还是配分频器,装载值、计数器和计数模式
    先找到对应的寄存器。

    由于是在APB1时钟总上,该总线的频率为72M
    所以此时我们将分频器配为72-1(计数一次用时1/1000 000 s=1us)

    此时定时器的重载值就是PWM波的一个周期。
    这里为了方便,我们设置成100-1,刚好100us

    计数器直接清零就行。

    计数模式配置在CR1上
    中央对齐模式我们选择00:边沿对齐模式就行,为了实现一直向上计数。

    代码如下:

    	//基本配置:分频器, 装载值, 计数器
    	TIM3->PSC = 72-1;//计数一次用时1/1000 000 s=1us    
    	TIM3->ARR = 100-1;//pwm的周期 100us
    	TIM3->CNT = 0;//把计数器值清0
    	
    	TIM3->CR1 &= ~(0x3<<5);//中央对齐模式选择只向上或者只向下计数
    	TIM3->CR1 &= ~(0x1<<4);//选择向上计数
    

    3.PWM配置

    先找到TIM3的捕获/比较寄存器,将其清零,方便后续操作

    开始配置比较模式,先找到对应的寄存器


    此处设置计数器为PWM1模式,使其计数值<比较值时,输出有效电平。

    接下来就是设置有效电平了,此处的彩灯是低电平亮,所以我们要设置为低电平有效。将该寄存器位一置1

    捕获使能一下,开启通道,让pwm信号能通过引脚发送。


    定时器通道选择位输出比较模式,在CCMR1中配置
    最后为了使切换比较值和装载更加稳定,需要为这两开个预装载使能

    	//pwm配置:比较值(选择pwm模式1or2,有效电平)
    	TIM3->CCR1 = 0;//默认先不操作比较值
    	TIM3->CCMR1 &= ~(0x7<<4);//把输出模式选择位清空
    	TIM3->CCMR1 |= (0x6<<4);//把输出模式选择位置为110(pwm模式1)
    	TIM3->CCER |= 0x1<<1;//选择低电平有效
    	
    	TIM3->CCER |= 0x1<<0;//开启通道,能够让pwm信号通过引脚发送
    	
    	TIM3->CCMR1 &= ~(0x3<<0);//定时器通道选择位输出比较模式
    	//为了切换比较值和装载更加稳定,我们给他两个开预装载使能
    	TIM3->CR1 |= 0x1<<7;//装载值的缓冲
    	TIM3->CCMR1 |= (0x1<<3);//比较值的缓冲
    

    开完之后使能一下计数器,蓝灯就配完了。

    TIM3->CR1 |= 0x1<<0;//最后使能计数器
    

    绿灯跟蓝灯一样,只不过通道不一样就不一步一步配了。
    红灯的定时器是TIM1高级定时器,需要将DTG使能一下,其他也跟蓝灯差不多。

    三、需求的实现

    关键代码如下:
    main.c

    #include "stm32f10x.h"
    #include "usart.h"
    #include "stdio.h"
    #include "delay.h"
    #include "string.h"
    #include "pwm.h"
    
    
    int main()
    {
    		NVIC_SetPriorityGrouping(5);//两位抢占两位次级
        Usart1_Config(); 
    	  SysTick_Config(72000);
    	  RGBpwm_Config();
    	  uint8_t cai_count=0;
    	  uint16_t cont=0;
        while(1)
        {	
    	
    			if(cont<=100)
    			RGB_light(cai_count,0,0);
    			if(cont>100&&cont<=200)
    			RGB_light(0,cai_count,0);
    			if(cont>=200)
    			{
    		  RGB_light(0,0,cai_count);
    				if(cont >= 300)
    				{
    					cont=0;
    				}
    			}
    			if(ledcnt[0]>=ledcnt[1]){//过去500ms
    			ledcnt[0]=0;
    					cai_count++;
    					cont++;
    					cai_count%=100;
    		}
        }
    }
    
    

    pwm.c

    #include "pwm.h"
    
    void RGBpwm_Config()
    {
    	//开时钟 PA
    	 RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE);
    	//配置io PA6 蓝灯
    	  GPIO_InitTypeDef GPIO_InitStruct = {0};
    		GPIO_InitStruct.GPIO_Mode = GPIO_Mode_AF_PP;
    		GPIO_InitStruct.GPIO_Pin = GPIO_Pin_6;
    		GPIO_InitStruct.GPIO_Speed = GPIO_Speed_50MHz;
    		GPIO_Init(GPIOA,&GPIO_InitStruct);	
    	//配置io PA7 绿灯
    		GPIO_InitStruct.GPIO_Mode = GPIO_Mode_AF_PP;
    		GPIO_InitStruct.GPIO_Pin = GPIO_Pin_7;
    		GPIO_InitStruct.GPIO_Speed = GPIO_Speed_50MHz;
    	  GPIO_Init(GPIOA,&GPIO_InitStruct);	
    		
    	//配置io PA8  红灯
    		GPIO_InitStruct.GPIO_Mode = GPIO_Mode_AF_PP;
    		GPIO_InitStruct.GPIO_Pin = GPIO_Pin_8;
    		GPIO_InitStruct.GPIO_Speed = GPIO_Speed_50MHz;
    	  GPIO_Init(GPIOA,&GPIO_InitStruct);
    		
    		
    	//配置蓝灯TIM3_CH1
    		RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM3,ENABLE);//TIM3的时钟使能
    	
    	//基本配置:分频器, 装载值, 计数器
    	TIM3->PSC = 72-1;//计数一次用时1/1000 000 s=1us    
    	TIM3->ARR = 100-1;//pwm的周期 100us
    	TIM3->CNT = 0;//把计数器值清0
    	
    	TIM3->CR1 &= ~(0x3<<5);//中央对齐模式选择只向上或者只向下计数
    	TIM3->CR1 &= ~(0x1<<4);//选择向上计数
    	
    	//pwm配置:比较值(选择pwm模式1or2,有效电平)
    	TIM3->CCR1 = 0;//默认先不操作比较值
    	
    	/*
    	以下三行确定了	
    	*/
    	TIM3->CCMR1 &= ~(0x7<<4);//把输出模式选择位清空
    	TIM3->CCMR1 |= (0x6<<4);//把输出模式选择位置为110(pwm模式1)
    	TIM3->CCER |= 0x1<<1;//选择低电平有效
    	
    	TIM3->CCER |= 0x1<<0;//开启通道,能够让pwm信号通过引脚发送
    	
    	TIM3->CCMR1 &= ~(0x3<<0);//定时器通道选择位输出比较模式
    	//为了切换比较值和装载更加稳定,我们给他两个开预装载使能
    	TIM3->CR1 |= 0x1<<7;//装载值的缓冲
    	TIM3->CCMR1 |= (0x1<<3);//比较值的缓冲	
    		
    		//库函数配置绿灯TIM3_CH2
    	TIM_OCInitTypeDef TIM_OCInitStructure={0};
      TIM_OCInitStructure.TIM_OCMode = TIM_OCMode_PWM1;//选择PWM模式1
      TIM_OCInitStructure.TIM_OutputState = TIM_OutputState_Enable;
      TIM_OCInitStructure.TIM_Pulse = 0;
      TIM_OCInitStructure.TIM_OCPolarity = TIM_OCPolarity_Low;
    
      TIM_OC2Init(TIM3, &TIM_OCInitStructure);
      TIM_OC2PreloadConfig(TIM3, TIM_OCPreload_Enable);//比较值的预装载使能缓冲
    	TIM_ARRPreloadConfig(TIM3, ENABLE);//装载值的预装载使能缓冲
    	
    	TIM3->CR1 |= 0x1<<0;//最后使能计数器
    		
    		//配置红灯TIM1_CH1
    		//时钟使能
    		RCC->APB2ENR |= 0X01 << 11;
    		//基本配置:分频器,装载值,计数器
    		TIM1->PSC = 72-1;//计数一次用时1/1000 000 
    		TIM1->ARR = 100-1;//pwm的周期100us
    		TIM1->CR1 &= ~(0x03<<5);//中央对齐,选择只向上
    		TIM1->CR1 &= ~(0x01<<4);//选择只向上计数
    		TIM1->CCR1 = 0;//默认先不操作比较值
    		TIM1->BDTR |= 0x01<<15;
    	//以下三行确定了计数器的值小于比较值时,让通道输出有效电平(低电平)
    		TIM1->CCMR1 &= ~(0x07<<4);//把输出模式选择位清空
    		TIM1->CCMR1 |= 0x06<<4;//输出模式选择位置为110,小于1的时候为有效电平
    		TIM1->CCER |= 0x01<<1;//选择低电平有效
    		TIM1->CCER |= 0x01<<0;//开启通道,能够让pwm信号通过引脚发送出去
    		TIM1->CCMR1 &= ~(0x03<<0);//定时器通道选择为输出比较模式
    		//为了切换比较值和装载值更加稳定,开个预装载使能
    		TIM1->CR1 |=0x01<<7; //装载值的缓冲
    		TIM1->CCMR1 |=0x01<<3; //比较值的缓冲
    		TIM1->CR1 |= 0x01<<0;//使能计数器
    		
    }
    
    
    void RGB_light(uint16_t R,uint16_t G,uint16_t B)
    {
    	TIM3->CCR1 = B;
    	TIM3->CCR2 = G;
    	TIM1->CCR1 = R;
    //	TIM_SetCompare1(TIM3,B);
    //	TIM_SetCompare2(TIM3,G);
    //	TIM_SetCompare1(TIM1,R);
    }
    
    

    pwm.h

    #ifndef _PWM_H_
    #define _PWM_H_
    #include "stm32f10x.h"
    void RGBpwm_Config();
    void RGB_light(uint16_t R,uint16_t G,uint16_t B);
    #endif
    		
    

    delay.c

    #include "stm32f10x.h"
    #include "delay.h"
    
    uint32_t systicktime=0;
    
    uint16_t ledcnt[2]={0,10};//500ms   每个任务执行的时间
    uint16_t led2cnt[2]={0,2000};//700ms
    uint16_t keycnt[2]={0,10};//10ms检测一次
    void SysTick_Handler(void)//1ms调用一次
    {
    	//不需要清中断挂起位
    	systicktime++;
    	ledcnt[0]++;
    	led2cnt[0]++;
    	keycnt[0]++;
    }
    
    void Delay_ms(uint32_t time)
    {
        uint32_t nowtime = systicktime;
    		while(systicktime < time+nowtime);
    }
    
    void Delay_nus(uint32_t time)
    {
        uint32_t i=0;
        for(i=0;i<time;i++){
            delay1us();
        }    
    }
    
    void Delay_nms(uint32_t time)
    {
        uint32_t i=0;
        for(i=0;i<time;i++){
             Delay_nus(1000);//延时1ms
        }    
    }
    
    

    delay.h

    #ifndef _DELAY_H_
    #define _DELAY_H_
    #include "stm32f10x.h"
    
    #define delay1us() {__NOP();__NOP();__NOP();__NOP();__NOP();__NOP();__NOP();__NOP();\
        __NOP();__NOP();__NOP();__NOP();__NOP();__NOP();__NOP();__NOP();\
        __NOP();__NOP();__NOP();__NOP();__NOP();__NOP();__NOP();__NOP();\
        __NOP();__NOP();__NOP();__NOP();__NOP();__NOP();__NOP();__NOP();\
        __NOP();__NOP();__NOP();__NOP();__NOP();__NOP();__NOP();__NOP();\
        __NOP();__NOP();__NOP();__NOP();__NOP();__NOP();__NOP();__NOP();\
        __NOP();__NOP();__NOP();__NOP();__NOP();__NOP();__NOP();__NOP();\
        __NOP();__NOP();__NOP();__NOP();__NOP();__NOP();__NOP();__NOP();\
        __NOP();__NOP();__NOP();__NOP();__NOP();__NOP();__NOP();__NOP();}
    
    extern uint16_t ledcnt[2];
    extern uint16_t led2cnt[2];
    extern uint16_t keycnt[2];		
    		
    void Delay_nus(uint32_t time);
    void Delay_ms(uint32_t time);
    void Delay_nms(uint32_t time);
    #endif
    		
    

    总结

    1.先开时钟配置引脚。
    2.配置定时器的分频器,装载值和计数器,中央对齐模式和计数方向。
    3.接下来配置pwm的部分。先配置默认比较值,一般置为0,使占空比为0,不让引脚有效。
    4.配置输出模式(pwm)以及有效的电平是高还是低。
    5.开启通道,使pwm波通过引脚传输。
    6.定时器通道选择一下比较模式,为防止突变,再设置一下定时器和装载值的缓冲,最后使能计数器。

    作者:小白橘颂

    物联沃分享整理
    物联沃-IOTWORD物联网 » STM32教程第十课:PWM波生成与实现详解

    发表回复