【STM32学习】——定时器输出比较功能&PWM脉宽调制&通用/高级定时器输出比较通道&舵机/直流电机简介&PWM驱动呼吸灯/舵机/直流电机代码实操

文章目录

  • 前言
  • 一、输出比较简介
  • 二、PWM波形
  • 三、输出比较通道
  • 1.通用定时器
  • 2.高级定时器
  • 三、外设简介
  • 1.舵机
  • 2.直流电机
  • 四、实操案例
  • 1.PWM驱动LED呼吸灯
  • 2.PWM驱动舵机
  • 3.PWM驱动直流电机
  • 总结

  • 声明:学习笔记根据b站江科大自化协stm32入门教程编辑,仅供学习交流使用!

    前言

    定时器输出比较功能比较重要,主要用来输出PWM波形,PWM波形又是驱动电机的必要条件,智能车、机器人的电机都可能用到!!
    本次学习有三个实操,分别是PWM驱动LED呼吸灯、PWM驱动舵机、PWM驱动直流电机。


    一、输出比较简介


    1、OC(Output Compare)输出比较,IC(Input Capture)为输入捕获,CC(Capture/Compare)一般表示输入捕获和输出比较的单元!
    2、输出比较可以通过比较CNT与CCR寄存器值的关系,来对输出电平进行置1、置0或翻转的操作,用于输出一定频率和占空比的PWM波形。(可参考上节通用定时器或高级定时器图如上图)CNT为时基单元里的计数器,CCR即捕获/比较寄存器(输入捕获和输出比较共用的)。输出比较时,电路会比较CNT和CCR的值,CNT计数自增,CCR是我们给定的一个值,当CNT>CCR、<CCR或=CCR时,输出就会对应置1、置0、置1、置0,即得一个电平不断跳变的PWM波形!!
    3、每个高级定时器和通用定时器都拥有4个输出比较通道,且高级定时器的前3个通道额外拥有死区生成和互补输出的功能

    二、PWM波形

    PWM(Pulse Width Modulation)脉冲宽度调制
    在具有惯性的系统中,可以通过对一系列脉冲的宽度进行调制,来等效地获得所需要的模拟参量,常应用于电机控速等领域。也就是用来等效地实现一个模拟信号的输出(左图虚线余弦),也就是最开始提出的问题:数字输出端口控制LED,按理说只有完全亮和完全灭两种状态,但通过PWM就可实现呼吸灯,当不断点亮熄灭点亮熄灭…的频率足够大时就不会时闪烁而是呈现中等亮度!!

    但是PWM波的应用场景必须是一个惯性系统,就是LED在熄灭时由于余晖和人眼视觉暂留现象,LED不会立马熄灭而是具有一定惯性,电机也是同样。
    PWM参数:
    频率 = 1 / TS 。一般在几十——几十千
    占空比 = TON / TS
    分辨率 = 占空比变化步距。分辨率是指输出电压梯度,而频率是指输出变化的次数,例如LED的分辨率为1%即说明LED的亮度时1%亮、2%亮、3%…一直到100%亮。
    基本结构:

    时基单元部分配置与上节相似,只不过更新事件的中断申请不再需要了,输出PWM暂时不需要中断,配置完了时基单元,CNT就可开始不断自增运行。
    下面为输出比较单元,总共有4路,CNT不断自增的同时与CCR不断进行比较,此图为PWM模式1的执行逻辑(REF怎么置),右上角蓝线为CNT的值,黄线为ARR的值,蓝线从0开始自增一直到黄线ARR即99,之后清零继续自增,再设置一条红线CCR(此处为30),之后根据执行逻辑1得到绿色线输出,图中的REF就是一个频率可调,占空比也可调的PWM波形,再经过极性选择输出使能通向GPIO口完成PWM波的输出!!
    参数计算:
    PWM频率: Freq = CK_PSC / (PSC + 1) / (ARR + 1)
    PWM占空比: Duty = CCR / (ARR + 1)
    PWM分辨率: Reso = 1 / (ARR + 1)

    三、输出比较通道

    普通定时器没有这些通道,通用和高级的有!!

    1.通用定时器

    代码如下(示例):
    CNT计数器与CRR1第一路的捕获/比较寄存器进行比较后,当>或=时会给输出模式控制器传一个信号,然后该控制器会改变输出oc1ref的高低电平(ref为reference的缩写,意思为参考信号),ETRF输入为定时器的一个小功能,一般不用!ref信号可前往主模式控制器,可以把REF映射到主模式的TRGO的输出上,但主要去向为下面的极性选择,给CC1P寄存器写0信号会往上走即信号电平不反转,写1经过非门反转后到输出使能电路,输出使能电路选择要不要输出,最后OC1引脚是CH1通道的引脚,在引脚定义表里可知道具体是哪个GPIO口了!
    输出比较八种模式:

    注:可把有效电平当作高电平,无效电平当作低电平;输出波形频率=更新频率/2,因为更新两次输出才为1个周期。

    2.高级定时器

    这个先了解下就行!!与通用定时器不同的只有红圈内的电路。以前在介绍高级定时器时也有相关介绍

    三、外设简介

    1.舵机

    舵机是一种根据输入PWM信号占空比来控制输出角度的装置
    输入PWM信号要求:周期为20ms,高电平宽度为0.5ms~2.5ms

    第二个图为拆解图,舵机并不是一个单独的直流电机,舵机内部电板是一个控制电路板,PWM信号输入到控制板,给控制板一个指定的目标角度,然后电位器检测输出轴的当前角度,如果大于目标角度电机反转,小于的话电机正转,最终使输出轴固定在指定角度!!
    周期20ms指第一个上升沿到下一个上升沿的时间,0.5ms、1ms等指高电平的时间,这里的PWM波形被当作一个通信协议使用,与之前说的等效一个模拟输出关系不大!!
    机器人关节,小车小船方向!!

    一般推荐单独供电,供电的负极要与stm32共地,5v供电可从STLINK的5V输出引脚引一根线,如果没有单独供电条件要看电源功率是否达标,不达标驱动会遇到问题!

    2.直流电机

    直流电机是一种将电能转换为机械能的装置,有两个电极,当电极正接时,电机正转,当电极反接时,电机反转。
    直流电机属于大功率器件,GPIO口无法直接驱动,需要配合电机驱动电路来操作。
    TB6612是一款双路H桥型的直流电机驱动芯片(如下图两个推挽电路组合),可以驱动两个直流电机并且控制其转速和方向,舵机就不需要了内部有驱动电路。其他还有DRV8833、L9110、L298N等等。还有一些用分离元件MOS管搭建的电路,这种的功率可以做得更大!!

    硬件电路,VM一般与电机额定电压保持一致,VCC与stm32保持一致3.3v;3个GND一样随便选一个;AO1、AO2、BO2、BO1为两路电机的输出,PWMA、AIN2、AIN1三个引脚控制A路电机,PWMA要接输出PWM波的GPIO口,B路电机同理;最后STBY(stand by)为待机控制脚,如果接GND芯片就不工作处于待机状态,若接VCC则正常工作:

    右下角这个表说明了怎么控制正反转等!!
    更多详细内容可多参考芯片手册!!

    四、实操案例

    1.PWM驱动LED呼吸灯

    LED接线采用正极性驱动,即输出高电平点亮,现象更加直观,即占空比越大LED越亮反之越暗:

    //PWM.c
    #include "stm32f10x.h"                  // Device header
    //初始化,也是根据PWM基本结构图把每个环节打通:
    //1、RCC开启时钟,打开要用的TIM外设和GPIO外设的时钟
    //2、配置时基单元,包括图中未画出的时钟源选择
    //3、配置输出比较单元,包括CCR的值、输出比较模式、极性选择、输出使能这些参数(库函数也是用结构体统一配置)
    //4、配置GPIO,把PWM对应的GPIO口初始化为复用推挽输出的配置,对应关系可参考引脚定义表
    //5、运行控制,启动计数器就可以输出PWM波了
    void PWM_Init(void){//上半部分可借鉴定时器中断
    	RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2,ENABLE );//使用APB1的开启时钟函数,因为TIM2位APB1总线的外设
    	TIM_InternalClockConfig(TIM2);//选择内部时钟
    	
    	TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStructure;
    	TIM_TimeBaseInitStructure.TIM_ClockDivision= TIM_CKD_DIV1;
    	TIM_TimeBaseInitStructure.TIM_CounterMode= TIM_CounterMode_Up;
    	TIM_TimeBaseInitStructure.TIM_Period= 10000-1;//ARR自动重装器的值
    	TIM_TimeBaseInitStructure.TIM_Prescaler= 7200-1;//PSC预分频器的值
    	TIM_TimeBaseInitStructure.TIM_RepetitionCounter= 0 ;
    	TIM_TimeBaseInit(TIM2,&TIM_TimeBaseInitStructure);
    	
    	TIM_OCInitTypeDef TIM_OCInitStructure;
    	TIM_OCStructInit(&TIM_OCInitStructure);//给结构体赋初始值
    	TIM_OCInitStructure.TIM_OCMode= TIM_OCMode_PWM1;//Timing冻结模式、Active相等时置有效电平、Inactive相等置无效电平、Togg相等时电平翻转、PWM1、PWM2
    	TIM_OCInitStructure.TIM_OCNPolarity= TIM_OCNPolarity_High;
    	TIM_OCInitStructure.TIM_OutputState= TIM_OutputState_Enable;
    	TIM_OCInitStructure.TIM_Pulse= 0;//CCR寄存器的值,与上ARR和PSC值共同决定PWM的周期和占空比,此处为1KHz、占空比待定
    	TIM_OC1Init(TIM2,&TIM_OCInitStructure);
    	
    	//GPIO配置,需要参照引脚定义表,IO口默认复用功能、重映射位置,哪个对应哪个是定死的
    	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);//可放在最上面,这里为了便于分块理解就放这里
    	
    	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(GPIOB, &GPIO_InitStructure);
    	
    	TIM_Cmd(TIM2,ENABLE);//启动定时器,PWM通过PA0输出
    }
    
    void PWM_SetCompare1(uint16_t Compare){//为了不断更改占空比,达到呼吸灯目的的函数
    	TIM_SetCompare1(TIM2,Compare);//该函数可单独修改CCR的值,不断改变占空比	,参数在main.c中利用for循环赋予
    }
    
    //PWM.h
    #ifndef __PWM_H
    #define __PWM_H
    void PWM_Init(void);
    void PWM_SetCompare1(uint16_t Compare);
    #endif
    
    //main.c
    #include "stm32f10x.h"   // Device header
    #include "Delay.h"   
    #include "OLED.h"
    #include "PWM.h"
    
    uint8_t i;
    
    int main(void){
    	OLED_Init();
    	PWM_Init();
    	
    	while(1){
    		for (i=0;i<=100;i++){
    			PWM_SetCompare1(i);//不断更改CCR,以改变占空比(占空比由CCR和ARR共同决定,ARR为100一直不变),逐渐变亮
    			Delay_ms(10);//防止太快
    		}
    		for (i=0;i<=100;i++){
    			PWM_SetCompare1(100-i);//不断更改CCR以改变占空比,逐渐变暗
    			Delay_ms(10);//防止太快
    		}
    	}
    }
    

    补充:试验下刚才所说的引脚重映射,例如引脚定义表上TIM2的CH1可以从PA0挪到PA15引脚上,怎么操作?需要用到AFIO
    如果想让PA15、PB3、PB4三个引脚当作GPIO使用:
    1、开启AFIO的时钟(第一句)
    2、再用AFIO将JTAG复用解除掉(第三句)
    如果想重映射定时器或者其他外设的复用引脚:
    1、打开AFIO时钟(第一句)
    2、再用AFIO重映射外设复用的引脚(第二句)
    如果重映射的引脚正好是调试端口,三句代码都要!!
    1、打开AFIO时钟
    2、重映射引脚
    3、解除调试端口

    RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO, ENABLE);
    GPIO_PinRemapConfig(GPIO_PartialRemap1_TIM2 ,ENABLE);//引脚重映射函数,手册第8章8.3复用功能I/O和调试配置(AFIO)
    GPIO_PinRemapConfig(GPIO_Remap_SWJ_JTAGDisable,ENABLE);//解除PA15调试端口的复用
    

    2.PWM驱动舵机

    //PWM.c
    //与驱动呼吸灯大同小异
    #include "stm32f10x.h"                  
    void PWM_Init(void){
    	RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2,ENABLE );
    	TIM_InternalClockConfig(TIM2);
    	
    	TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStructure;
    	TIM_TimeBaseInitStructure.TIM_ClockDivision= TIM_CKD_DIV1;
    	TIM_TimeBaseInitStructure.TIM_CounterMode= TIM_CounterMode_Up;
    	TIM_TimeBaseInitStructure.TIM_Period= 20000-1;//ARR不同
    	TIM_TimeBaseInitStructure.TIM_Prescaler= 72-1;//PSC不同
    	TIM_TimeBaseInitStructure.TIM_RepetitionCounter= 0 ;//CCR(500-2500,对应0.5-2.5ms,对应0-180°
    	TIM_TimeBaseInit(TIM2,&TIM_TimeBaseInitStructure);
    	
    	TIM_OCInitTypeDef TIM_OCInitStructure;
    	TIM_OCStructInit(&TIM_OCInitStructure);
    	TIM_OCInitStructure.TIM_OCMode= TIM_OCMode_PWM1;
    	TIM_OCInitStructure.TIM_OCNPolarity= TIM_OCNPolarity_High;
    	TIM_OCInitStructure.TIM_OutputState= TIM_OutputState_Enable;
    	TIM_OCInitStructure.TIM_Pulse= 0;
    	TIM_OC2Init(TIM2,&TIM_OCInitStructure);//不同点,OC2通道2配置
    	
    	
    	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);
    	GPIO_InitTypeDef GPIO_InitStructure;
     	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
    	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_1;//不同点,用的是PA1口的通道2
    	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
     	GPIO_Init(GPIOB, &GPIO_InitStructure);
    	
    	TIM_Cmd(TIM2,ENABLE);
    }
    
    void PWM_SetCompare2(uint16_t Compare){//不同点,改为compare2
    	TIM_SetCompare2(TIM2,Compare);//不同点,改为compare2
    }
    
    
    //Servo.c
    #include "stm32f10x.h"                 
    #include "PWM.h"
    
    //为了使PWM输出与舵机角度的对应关系更加直观,特封装此.c文件
    void Servo_Init(){
    	PWM_Init();
    	
    }
    //0°   500
    //180° 2500
    void Servo_SetAngle(float Angle){
    	PWM_SetCompare2(Angle/180*2000 + 500);//角度到CCR值的映射
    	
    	
    }
    
    
    //main.c
    #include "stm32f10x.h"   
    #include "Delay.h"   
    #include "OLED.h"
    #include "Servo.h"
    #include "key.h"
    uint8_t KeyNum;
    float Angle;//默认为0
    
    int main(void){
    	OLED_Init();
    	Servo_Init();
    	Key_Init();
    	
    	OLED_ShowString(1,1,"Angle:");
    	
    	while(1){
    		KeyNum = Key_GetNum();
    		if (KeyNum == 1){
    			Angle +=30;//每按一下按键加30°
    			if(Angle > 180){
    				Angle = 0;
    			}
    		}
    		Servo_SetAngle(Angle);
    		OLED_ShowNum(1,7,Angle,3);//Angle:字符占了6列
    	}
    }
    

    3.PWM驱动直流电机

    //PWM.c
    //同样与呼吸灯大同小异
    #include "stm32f10x.h"                  
    void PWM_Init(void){
    	RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2,ENABLE );
    	TIM_InternalClockConfig(TIM2);
    	
    	TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStructure;
    	TIM_TimeBaseInitStructure.TIM_ClockDivision= TIM_CKD_DIV1;
    	TIM_TimeBaseInitStructure.TIM_CounterMode= TIM_CounterMode_Up;
    	TIM_TimeBaseInitStructure.TIM_Period= 10000-1;
    	TIM_TimeBaseInitStructure.TIM_Prescaler= 7200-1;
    	TIM_TimeBaseInitStructure.TIM_RepetitionCounter= 0 ;
    	TIM_TimeBaseInit(TIM2,&TIM_TimeBaseInitStructure);
    	
    	TIM_OCInitTypeDef TIM_OCInitStructure;
    	TIM_OCStructInit(&TIM_OCInitStructure);
    	TIM_OCInitStructure.TIM_OCMode= TIM_OCMode_PWM1;
    	TIM_OCInitStructure.TIM_OCNPolarity= TIM_OCNPolarity_High;
    	TIM_OCInitStructure.TIM_OutputState= TIM_OutputState_Enable;
    	TIM_OCInitStructure.TIM_Pulse= 0;
    	TIM_OC3Init(TIM2,&TIM_OCInitStructure);//不同点,通道3
    	
    	
    	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);
    	GPIO_InitTypeDef GPIO_InitStructure;
     	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
    	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_2;//不同点,电机接在通道3
    	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
     	GPIO_Init(GPIOB, &GPIO_InitStructure);
    	
    	TIM_Cmd(TIM2,ENABLE);
    }
    
    void PWM_SetCompare3(uint16_t Compare){//不同点,通道3
    	TIM_SetCompare3(TIM2,Compare);
    }
    
    //Motor.c
    #include "stm32f10x.h"                  
    #include "PWM.h"
    
    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();
    }
    
    void Motor_SetSpeed(int8_t Speed){//该函数参数可+可-
    	if(Speed >= 0){//正转
    		//方向设置
    		GPIO_SetBits(GPIOA,GPIO_Pin_4);
    		GPIO_ResetBits(GPIOA,GPIO_Pin_5);
    		//速度设置
    		PWM_SetCompare3(Speed);
    	}
    	else{
    		//方向设置
    		GPIO_SetBits(GPIOA,GPIO_Pin_5);
    		GPIO_ResetBits(GPIOA,GPIO_Pin_4);
    		//速度设置
    		PWM_SetCompare3(-Speed);//该函数参数为无符号数,Speed为负,-Speed为正
    	}
    }
    
    
    //main.c
    #include "stm32f10x.h"   // Device header
    #include "Delay.h"   
    #include "OLED.h"
    #include "Motor.h"
    #include "key.h"
    
    uint8_t KeyNum;
    int8_t Speed;
    
    int main(void){
    	OLED_Init();
    	Motor_Init();
    	Key_Init();
    	
    	OLED_ShowString(1,1,"Speed:");
    	
    	while(1){
    		KeyNum = Key_GetNum();//按键控制速度-100~100
    		if (KeyNum == 1){
    			Speed +=20;
    			if (Speed >100){
    				Speed = -100;
    			}
    		}
    		Motor_SetSpeed(Speed);
    	//电机旋转时会发出蜂鸣器的声音,这是因为PWM频率在人耳范围导致的,可通过改变预分频PSC完善
    		OLED_ShowNum(1,7,Speed,3);
    	}
    }
    

    总结

    电机正反转的更改可以改变接线,也可以改变代码!在实操时注意与上面的理论相结合,三个实操之间也可对比学习,有助于加深理解!!
    遇到挫折,要有勇往直前的信念,马上行动,坚持到底,决不放弃,成功者决不放弃,放弃者绝不会成功。成功的道路上,肯定会有失败;对于失败,我们要正确地看待和对待,不怕失败者,则必成功;怕失败者,则一无是处,会更失败。

    往期精彩:
    STM32定时中断
    STM32外部中断
    STM32GPIO精讲

    物联沃分享整理
    物联沃-IOTWORD物联网 » 【STM32学习】——定时器输出比较功能&PWM脉宽调制&通用/高级定时器输出比较通道&舵机/直流电机简介&PWM驱动呼吸灯/舵机/直流电机代码实操

    发表评论