【STM32】定时器 —— 输出比较&PWM

使用的单片机机型为STM32F103C8T6

文章目录

  • PWM
  • 输出比较
  • 编程实例
  • 输出比较呼吸灯
  • 舵机转向
  • PWM控制直流电机
  • PWM

    对于5V电路来说,输出只有高电平5V和低电平0V,控制LED灯就是点亮和熄灭,但如果想要控制其亮度呢?这就需要PWM

    PWM

    PWM (Pulse Width Modulation) 脉冲宽度调制

    具有惯性的系统中,可以通过对一系列脉冲的宽度进行调制,来等效地获得所需要的模拟参量,常用于电机控速等领域

    拿LED灯举例
    当输出高电平时,LED灯点亮;当输出低电平时,LED灯熄灭。但如果在一定周期内,比如1ms内,前100us高电平,后900us低电平,那么在人眼看来,LED灯就是很低的亮度。随着高电平所占时间增多,LED亮度就会增大

    换成电机,由于惯性,从高电平转为低电平,电机并不会立刻停止,同样在较短时间内分配高低电平的比率就可以实现不同档位的电机


    PWM参数

  • Ts:周期,如1ms
  • Ton:高电平时间,如400us
  • Toff:低电平时间,如600us
  • 频率 = 1 / Ts = 1KHz
    占空比:高电平时间占周期的比率。Ton / Ts = 40%
    分辨率:1 / (计数器最大值 + 1)


    输出比较

    定时器输出比较模块如下

    OC (Output Compare) 输出比较

    通过比较 CNT 和 CCR 寄存器值的关系,对输出电平置1、置0 或翻转的操作,用于输出一定频率和占空比的 PWM 波形

    通用定时器和高级定时器都拥有4个输出比较通道,如上图的 TIMx_CH1 ~ TIMx_CH4
    高级定时器的前3个通道额外拥有死去生成和互补输出的功能

    输出部分电路如下:

    通过 CNT 和 CCR比较控制输出使能电路
    例如:当CNT < CRR 时,输出高电平,CNT >= CRR 时输出低电平。
    配合时基单元(参看【STM32】定时中断),即可输出PWM。

    如定时器频率为1MHz,即每1微秒CNT计数加一,设置重装值ARR = 100,总周期为100us,比较器CRR = 40。
    CNT计数前40us都输出高电平,后60us输出低电平。如此就输出了频率为10KHz,占空比为40%,分辨率为1%


    输出比较模式

    常用的为 PWM模式1 和 PWM模式2,二者效果相反,根据实际场景选择

    编程实例

    输出比较呼吸灯

  • 程序目标:通过定时器输出比较实现呼吸灯(亮度周期变化)
  • 程序原理:每20ms修改CRR值,输出不同PWM波形
  • 接线图如下:

    通过引脚定义得知,TIM2 的 CH1输出引脚为 A0
    同时,将LED灯的正极接在输出引脚上,方便占空比的计算

    PWM基本结构


    PWM使用步骤:

    1. 开启相关外设时钟
    //开启时钟
    RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2, ENABLE);
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);
    
    1. 定时器使用内部时钟源72MHz
    //时钟源使用内部时钟源,72MHz
    TIM_InternalClockConfig(TIM2);
    
    1. 配置GPIO,注意引脚使用复用推挽输出
    //初始化GPIO
    GPIO_InitTypeDef GPIO_InitStructure;
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;				//注意使用复用推挽输出
    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0;
    GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
    GPIO_Init(GPIOA, &GPIO_InitStructure);
    
    1. 配置时基单元,频率为1KHz,分辨率为1%
    //时基单元
    TIM_TimeBaseInitTypeDef TIM_InitStructure;
    TIM_TimeBaseStructInit(&TIM_InitStructure);
    TIM_InitStructure.TIM_ClockDivision = TIM_CKD_DIV1;			//时钟分频
    TIM_InitStructure.TIM_CounterMode = TIM_CounterMode_Up;		//向上计数
    TIM_InitStructure.TIM_Period = 100 - 1;						//重装值
    TIM_InitStructure.TIM_Prescaler = 720 - 1;					//预分频
    TIM_InitStructure.TIM_RepetitionCounter = 0;				//重复计数,高级定时器才有
    TIM_TimeBaseInit(TIM2, &TIM_InitStructure);
    
    1. 配置输出比较,通过修改CCR值改变占空比
    //输入/比较器模块
    TIM_OCInitTypeDef TIM_OCInitStructure;
    TIM_OCStructInit(&TIM_OCInitStructure);		//因为一些属性是高级定时器的,初始化给个初始值
    //配置
    TIM_OCInitStructure.TIM_OCMode = TIM_OCMode_PWM1;				//输出比较模式
    TIM_OCInitStructure.TIM_OCPolarity = TIM_OCPolarity_High;		//极性,此处设置为高电平
    TIM_OCInitStructure.TIM_OutputState = TIM_OutputState_Enable;	//使能
    TIM_OCInitStructure.TIM_Pulse = 0;								//比较值,即CCR
    TIM_OC1Init(TIM2, &TIM_OCInitStructure);
    
    1. 启动定时器
    //开启时钟
    TIM_Cmd(TIM2, ENABLE);
    

    完整代码如下:

    /**
      * @brief		初始化PWM功能
      * @parm		无
      * @retval		无
      */
    void PWM_Init(void)
    {
    	//开启时钟
    	RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2, ENABLE);
    	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);
    	
    	//时钟源使用内部时钟源,72MHz
    	TIM_InternalClockConfig(TIM2);
    	
    //	//引脚重定义
    //	RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO, ENABLE);
    //	GPIO_PinRemapConfig(GPIO_PartialRemap1_TIM2, ENABLE);		//TIM2引脚重定向
    //	GPIO_PinRemapConfig(GPIO_Remap_SWJ_JTAGDisable, ENABLE);	//关闭A15的调试接口,使其变成普通引脚使用
    	
    	//初始化GPIO
    	GPIO_InitTypeDef GPIO_InitStructure;
    	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;				//注意使用复用推挽输出
    	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0;
    	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
    	GPIO_Init(GPIOA, &GPIO_InitStructure);
    	
    	//时基单元
    	TIM_TimeBaseInitTypeDef TIM_InitStructure;
    	TIM_TimeBaseStructInit(&TIM_InitStructure);
    	TIM_InitStructure.TIM_ClockDivision = TIM_CKD_DIV1;			//时钟分频
    	TIM_InitStructure.TIM_CounterMode = TIM_CounterMode_Up;		//向上计数
    	TIM_InitStructure.TIM_Period = 100 - 1;						//重装值
    	TIM_InitStructure.TIM_Prescaler = 720 - 1;					//预分频
    	TIM_InitStructure.TIM_RepetitionCounter = 0;				//重复计数,高级定时器才有
    	TIM_TimeBaseInit(TIM2, &TIM_InitStructure);
    	
    	//输入/比较器模块
    	TIM_OCInitTypeDef TIM_OCInitStructure;
    	TIM_OCStructInit(&TIM_OCInitStructure);		//因为一些属性是高级定时器的,初始化给个初始值
    	//配置
    	TIM_OCInitStructure.TIM_OCMode = TIM_OCMode_PWM1;				//输出比较模式
    	TIM_OCInitStructure.TIM_OCPolarity = TIM_OCPolarity_High;		//极性,此处设置为高电平
    	TIM_OCInitStructure.TIM_OutputState = TIM_OutputState_Enable;	//使能
    	TIM_OCInitStructure.TIM_Pulse = 0;								//比较值,即CCR
    	TIM_OC1Init(TIM2, &TIM_OCInitStructure);
    	
    	//开启时钟
    	TIM_Cmd(TIM2, ENABLE);
    }
    

    同时提供修改CRR值的函数

    /**
      * @brief		设置比较值
      * @parm		Compare:比较值  范围:0 ~ 255
      * @retval		无
      */
    void PWM_SetCompare(uint8_t Compare)
    {
    	TIM_SetCompare1(TIM2, Compare);
    }
    

    主程序每隔20ms修改CRR值,实现LED灯由 熄灭 -> 亮度增大 -> 完全点亮 -> 亮度减小 -> 熄灭 的循环

    int main()
    {
    	PWM_Init();
    	while(1)
    	{
    		for(uint8_t i = 0; i < 100; ++i)
    		{
    			PWM_SetCompare(i);
    			Delay_ms(20);//延迟20ms
    		}
    		for(uint8_t i = 0; i < 100; ++i)
    		{
    			PWM_SetCompare(100 - i);
    			Delay_ms(20);//延迟20ms
    		}
    	}
    }
    

    完整程序链接:【STM32】输出比较PWM

    舵机转向

    舵机是一种根据输入PWM信号占空比来控制输出角度的装置

    输入PWM信号要求:周期为20ms,高电平宽度为0.5ms ~ 2.5ms

    硬件电路如下:

    接线图如下:舵机需要5V驱动,正极接STLINK

    PWM初始化大抵相同,需要注意 ARR、PSC 的设置

    /**
      * @brief		初始化PWM功能
      * @parm		无
      * @retval		无
      */
    void PWM_Init(void)
    {
    	//开启时钟
    	RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2, ENABLE);
    	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);	
    	
    	//初始化GPIO
    	GPIO_InitTypeDef GPIO_InitStructure;
    	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;				//注意使用复用推挽输出
    	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_1;
    	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
    	GPIO_Init(GPIOA, &GPIO_InitStructure);
    	
    	//内部时钟
    	TIM_InternalClockConfig(TIM2);
    	
    	//时基单元
    	TIM_TimeBaseInitTypeDef TIM_InitStructure;
    	TIM_InitStructure.TIM_ClockDivision = TIM_CKD_DIV1;			//时钟分频
    	TIM_InitStructure.TIM_CounterMode = TIM_CounterMode_Up;		//向上计数
    	TIM_InitStructure.TIM_Period = 20000 - 1;					//重装值,计数周期
    	TIM_InitStructure.TIM_Prescaler = 72 - 1;					//预分频
    	TIM_InitStructure.TIM_RepetitionCounter = 0;				//重复计数,高级定时器才有
    	TIM_TimeBaseInit(TIM2, &TIM_InitStructure);
    	
    	//输入/比较器模块
    	TIM_OCInitTypeDef TIM_OCInitStructure;
    	TIM_OCStructInit(&TIM_OCInitStructure);		//因为一些属性是高级定时器的,初始化给个初始值
    	//配置
    	TIM_OCInitStructure.TIM_OCMode = TIM_OCMode_PWM1;				//输出比较模式
    	TIM_OCInitStructure.TIM_OCPolarity = TIM_OCPolarity_High;		//极性,此处设置为高电平
    	TIM_OCInitStructure.TIM_OutputState = TIM_OutputState_Enable;	//使能
    	TIM_OCInitStructure.TIM_Pulse = 0;								//比较值,即CCR
    	TIM_OC2Init(TIM2, &TIM_OCInitStructure);
    	
    	//开启时钟
    	TIM_Cmd(TIM2, ENABLE);
    }
    

    此处设置 PSC = 72,ARR = 20000
    频率为50Hz,即周期为20ms

    -90° 为0.5ms,对应CRR为500,-45°对应为1000…

    /**
      * @brief		设置舵机角度  -90为0.5ms,对应CRR为500,-45°对应为1000.....	
      * @parm		角度   范围:0 ~ 180
      * @retval		无
      */
    void Servos_SetAngle(float Angle)
    {
    	PWM_SetCompare(Angle / 180 * 2000 + 500);
    }
    

    完整程序链接:

    PWM控制直流电机

    直流电机是一种将电能转换为机械能的装置,有两个电极,当电极正接时,电机正转,当电极反接时,电机反转
    直流电机属于大功率器件,GPIO口无法直接驱动,需要配合电机驱动电路来操作
    TB6612是一款双路H桥型的直流电机驱动芯片,可以驱动两个直流电机并且控制其转速和方向

    硬件电路

    PWMA 和 PWMB 接收PWM波形
    AIN1、AIN2 和 BIN1、BIN2控制电机正反转
    定义AIN1高电平,AIN2低电平为正转,则AIN1低电平,AIN2高电平为反转

    接线图如下

    PWM配置

    /**
      * @brief		初始化PWM功能
      * @parm		无
      * @retval		无
      */
    void PWM_Init(void)
    {
    	//开启时钟
    	RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2, ENABLE);
    	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);	
    	
    	//初始化GPIO
    	GPIO_InitTypeDef GPIO_InitStructure;
    	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;				//注意使用复用推挽输出
    	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_2;
    	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
    	GPIO_Init(GPIOA, &GPIO_InitStructure);
    	
    	//内部时钟
    	TIM_InternalClockConfig(TIM2);
    	
    	//时基单元
    	TIM_TimeBaseInitTypeDef TIM_InitStructure;
    	TIM_InitStructure.TIM_ClockDivision = TIM_CKD_DIV1;			//时钟分频
    	TIM_InitStructure.TIM_CounterMode = TIM_CounterMode_Up;		//向上计数
    	TIM_InitStructure.TIM_Period = 100 - 1;						//重装值,计数周期
    	TIM_InitStructure.TIM_Prescaler = 36 - 1;					//预分频
    	TIM_InitStructure.TIM_RepetitionCounter = 0;				//重复计数,高级定时器才有
    	TIM_TimeBaseInit(TIM2, &TIM_InitStructure);
    	
    	//输入/比较器模块
    	TIM_OCInitTypeDef TIM_OCInitStructure;
    	TIM_OCStructInit(&TIM_OCInitStructure);		//因为一些属性是高级定时器的,初始化给个初始值
    	//配置
    	TIM_OCInitStructure.TIM_OCMode = TIM_OCMode_PWM1;				//输出比较模式
    	TIM_OCInitStructure.TIM_OCPolarity = TIM_OCPolarity_High;		//极性,此处设置为高电平
    	TIM_OCInitStructure.TIM_OutputState = TIM_OutputState_Enable;	//使能
    	TIM_OCInitStructure.TIM_Pulse = 0;								//比较值,即CCR
    	TIM_OC3Init(TIM2, &TIM_OCInitStructure);
    	
    	//开启时钟
    	TIM_Cmd(TIM2, ENABLE);
    }
    

    还需要配置 A4 和 A5引脚控制电机正反转

    /**
      * @brief		舵机初始化
      * @parm		无
      * @retval		无
      */
    void Motor_Init(void)
    {
    	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);
    	//初始化输出高低电平的引脚
    	GPIO_InitTypeDef GPIO_InitStructure;
    	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;
    	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_4 | GPIO_Pin_5;
    	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
    	GPIO_Init(GPIOA, &GPIO_InitStructure);
    	PWM_Init();
    }
    
    /**
      * @brief		设置舵机角度  0为500,45为1000,90为1500....	
      * @parm		角度   范围:0 ~ 180
      * @retval		无
      */
    void Motor_SetSpeed(int16_t Speed)
    {
    	if(Speed >= 0)//正转
    	{
    		//设置GPIO引脚高低电平
    		GPIO_SetBits(GPIOA, GPIO_Pin_4);
    		GPIO_ResetBits(GPIOA, GPIO_Pin_5);
    		//设置占空比,输出PWM波形
    		PWM_SetCompare(Speed);
    	}
    	else//反转
    	{
    		GPIO_ResetBits(GPIOA, GPIO_Pin_4);
    		GPIO_SetBits(GPIOA, GPIO_Pin_5);
    		PWM_SetCompare(-Speed);
    	}
    }
    

    完整程序链接:


    以上就是本篇博客的所有内容,感谢你的阅读
    如果觉得本篇文章对你有所帮助的话,不妨点个赞支持一下博主,拜托啦,这对我真的很重要。

    作者:好想有猫猫

    物联沃分享整理
    物联沃-IOTWORD物联网 » 【STM32】定时器 —— 输出比较&PWM

    发表回复