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

文章目录

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


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

前言

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


一、输出比较简介

【STM32学习】——定时器输出比较功能&PWM脉宽调制&通用/高级定时器输出比较通道&舵机/直流电机简介&PWM驱动呼吸灯/舵机/直流电机代码实操_第1张图片
1、OC(Output Compare)输出比较,IC(Input Capture)为输入捕获,CC(Capture/Compare)一般表示输入捕获和输出比较的单元!
2、输出比较可以通过比较CNT与CCR寄存器值的关系,来对输出电平进行置1、置0或翻转的操作,用于输出一定频率和占空比的PWM波形。(可参考上节通用定时器或高级定时器图如上图)CNT为时基单元里的计数器,CCR即捕获/比较寄存器(输入捕获和输出比较共用的)。输出比较时,电路会比较CNT和CCR的值,CNT计数自增,CCR是我们给定的一个值,当CNT>CCR、 3、每个高级定时器和通用定时器都拥有4个输出比较通道,且高级定时器的前3个通道额外拥有死区生成和互补输出的功能

二、PWM波形

PWM(Pulse Width Modulation)脉冲宽度调制
在具有惯性的系统中,可以通过对一系列脉冲的宽度进行调制,来等效地获得所需要的模拟参量,常应用于电机控速等领域。也就是用来等效地实现一个模拟信号的输出(左图虚线余弦),也就是最开始提出的问题:数字输出端口控制LED,按理说只有完全亮和完全灭两种状态,但通过PWM就可实现呼吸灯,当不断点亮熄灭点亮熄灭…的频率足够大时就不会时闪烁而是呈现中等亮度!!
【STM32学习】——定时器输出比较功能&PWM脉宽调制&通用/高级定时器输出比较通道&舵机/直流电机简介&PWM驱动呼吸灯/舵机/直流电机代码实操_第2张图片
但是PWM波的应用场景必须是一个惯性系统,就是LED在熄灭时由于余晖和人眼视觉暂留现象,LED不会立马熄灭而是具有一定惯性,电机也是同样。
PWM参数:
频率 = 1 / TS 。一般在几十——几十千
占空比 = TON / TS
分辨率 = 占空比变化步距。分辨率是指输出电压梯度,而频率是指输出变化的次数,例如LED的分辨率为1%即说明LED的亮度时1%亮、2%亮、3%…一直到100%亮。
基本结构:
【STM32学习】——定时器输出比较功能&PWM脉宽调制&通用/高级定时器输出比较通道&舵机/直流电机简介&PWM驱动呼吸灯/舵机/直流电机代码实操_第3张图片
时基单元部分配置与上节相似,只不过更新事件的中断申请不再需要了,输出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.通用定时器

【STM32学习】——定时器输出比较功能&PWM脉宽调制&通用/高级定时器输出比较通道&舵机/直流电机简介&PWM驱动呼吸灯/舵机/直流电机代码实操_第4张图片
CNT计数器与CRR1第一路的捕获/比较寄存器进行比较后,当>或=时会给输出模式控制器传一个信号,然后该控制器会改变输出oc1ref的高低电平(ref为reference的缩写,意思为参考信号),ETRF输入为定时器的一个小功能,一般不用!ref信号可前往主模式控制器,可以把REF映射到主模式的TRGO的输出上,但主要去向为下面的极性选择,给CC1P寄存器写0信号会往上走即信号电平不反转,写1经过非门反转后到输出使能电路,输出使能电路选择要不要输出,最后OC1引脚是CH1通道的引脚,在引脚定义表里可知道具体是哪个GPIO口了!
输出比较八种模式:
【STM32学习】——定时器输出比较功能&PWM脉宽调制&通用/高级定时器输出比较通道&舵机/直流电机简介&PWM驱动呼吸灯/舵机/直流电机代码实操_第5张图片
注:可把有效电平当作高电平,无效电平当作低电平;输出波形频率=更新频率/2,因为更新两次输出才为1个周期。

2.高级定时器

这个先了解下就行!!与通用定时器不同的只有红圈内的电路。以前在介绍高级定时器时也有相关介绍
【STM32学习】——定时器输出比较功能&PWM脉宽调制&通用/高级定时器输出比较通道&舵机/直流电机简介&PWM驱动呼吸灯/舵机/直流电机代码实操_第6张图片

三、外设简介

1.舵机

舵机是一种根据输入PWM信号占空比来控制输出角度的装置
输入PWM信号要求:周期为20ms,高电平宽度为0.5ms~2.5ms
【STM32学习】——定时器输出比较功能&PWM脉宽调制&通用/高级定时器输出比较通道&舵机/直流电机简介&PWM驱动呼吸灯/舵机/直流电机代码实操_第7张图片
第二个图为拆解图,舵机并不是一个单独的直流电机,舵机内部电板是一个控制电路板,PWM信号输入到控制板,给控制板一个指定的目标角度,然后电位器检测输出轴的当前角度,如果大于目标角度电机反转,小于的话电机正转,最终使输出轴固定在指定角度!!
周期20ms指第一个上升沿到下一个上升沿的时间,0.5ms、1ms等指高电平的时间,这里的PWM波形被当作一个通信协议使用,与之前说的等效一个模拟输出关系不大!!
机器人关节,小车小船方向!!
【STM32学习】——定时器输出比较功能&PWM脉宽调制&通用/高级定时器输出比较通道&舵机/直流电机简介&PWM驱动呼吸灯/舵机/直流电机代码实操_第8张图片
一般推荐单独供电,供电的负极要与stm32共地,5v供电可从STLINK的5V输出引脚引一根线,如果没有单独供电条件要看电源功率是否达标,不达标驱动会遇到问题!

2.直流电机

直流电机是一种将电能转换为机械能的装置,有两个电极,当电极正接时,电机正转,当电极反接时,电机反转。
直流电机属于大功率器件,GPIO口无法直接驱动,需要配合电机驱动电路来操作。
TB6612是一款双路H桥型的直流电机驱动芯片(如下图两个推挽电路组合),可以驱动两个直流电机并且控制其转速和方向,舵机就不需要了内部有驱动电路。其他还有DRV8833、L9110、L298N等等。还有一些用分离元件MOS管搭建的电路,这种的功率可以做得更大!!
【STM32学习】——定时器输出比较功能&PWM脉宽调制&通用/高级定时器输出比较通道&舵机/直流电机简介&PWM驱动呼吸灯/舵机/直流电机代码实操_第9张图片
硬件电路,VM一般与电机额定电压保持一致,VCC与stm32保持一致3.3v;3个GND一样随便选一个;AO1、AO2、BO2、BO1为两路电机的输出,PWMA、AIN2、AIN1三个引脚控制A路电机,PWMA要接输出PWM波的GPIO口,B路电机同理;最后STBY(stand by)为待机控制脚,如果接GND芯片就不工作处于待机状态,若接VCC则正常工作:
【STM32学习】——定时器输出比较功能&PWM脉宽调制&通用/高级定时器输出比较通道&舵机/直流电机简介&PWM驱动呼吸灯/舵机/直流电机代码实操_第10张图片
右下角这个表说明了怎么控制正反转等!!
更多详细内容可多参考芯片手册!!

四、实操案例

1.PWM驱动LED呼吸灯

LED接线采用正极性驱动,即输出高电平点亮,现象更加直观,即占空比越大LED越亮反之越暗:
【STM32学习】——定时器输出比较功能&PWM脉宽调制&通用/高级定时器输出比较通道&舵机/直流电机简介&PWM驱动呼吸灯/舵机/直流电机代码实操_第11张图片

//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驱动舵机

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

//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驱动直流电机

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

//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学习】——定时器输出比较功能&PWM脉宽调制&通用/高级定时器输出比较通道&舵机/直流电机简介&PWM驱动呼吸灯/舵机/直流电机代码实操_第14张图片
往期精彩:
STM32定时中断
STM32外部中断
STM32GPIO精讲

你可能感兴趣的:(STM32学习,stm32,单片机,学习)