初学者STM32—PWM驱动电机与舵机

发布于:2025-07-22 ⋅ 阅读:(16) ⋅ 点赞:(0)

 一、简介

上一节课主要学习了输出比较和PWM的基本原理和结构,本节课就主要以实践为主通过STM32最小系统板和驱动器控制舵机和直流电机。

上一节课的坐标 初学者STM32—输出比较与PWM-CSDN博客

二、舵机

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

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

根据上图所示,舵机内部是由直流电机驱动的,还有一个控制电路版,PWM输入某个信号,它就会以某种角度转动。根据最右边的图可知,我们这个舵机是180度的舵机,当给予的PWM脉冲信号不同宽度的时候就会以不同的角度进行旋转。舵机在很多领域都会用上,比如遥控车的转弯、机械臂的关节等等。

上图是舵机的电路结构,主要由三根线组成,电源、地线和PWM信号线

不同颜色的舵机接线:

第一种        黑色:GND        红色:+5V        黄色:信号线

第二种        棕色 :GND       红色:+5V        橙色:信号线

三、直流电机驱动

直流电机是一种将电能转换为机械能的装置,有两个电极,当电极正接时,电机正转,当电极反接时,电机反转

直流电机属于大功率器件,GPIO口无法直接驱动,需要配合电机驱动电路来操作

TB6612是一款双路H桥型的直流电机驱动芯片,可以驱动两个直流电机并且控制其转速和方向

我们通过看原理图:电机接O1和O2,当左上和右下形成两路推挽电路就会导通驱动电机旋转,当右上和左下两路导通的时候电机就以另一个方向旋转。 

 

上图为电机的驱动芯片和引脚定义图。我们可以通过引脚定义图知道如何输入信号控制电机。

根据定义图所示:PWM给与的信号按照一定频率的高低运行就能实现电机的运行而且还能根据电平的时间实现速读的变化。

注意:VM必须要和驱动电压一致        H:高电平        L:低电平

四、PWM控制舵机

时钟源连接着时基单元,我们通过配置好时基单元后计数器就会根据已经配置好的模式进行输出控制,前面讲过用输出控制寄存器配置,通过配置好的参考电平,使能时候就可以控制GPIO口输出相应的PWM了。

但是GPIO控制PWM的时候必须要配置成复用推挽输出,我们参考下图复用功能,当我们配置了复用推挽输出,输出数据寄存器就会断开,片上外设就能够连接上输出控制然后才能够输出PWM波形。

代码部分

	RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM3,ENABLE);
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE);
	GPIO_InitTypeDef GPIO_InitStructure;
	GPIO_InitStructure.GPIO_Mode=GPIO_Mode_AF_PP;//复用推挽输出
	GPIO_InitStructure.GPIO_Pin=GPIO_Pin_7;
	GPIO_InitStructure.GPIO_Speed=GPIO_Speed_50MHz;
	GPIO_Init(GPIOA,&GPIO_InitStructure);	
	TIM_InternalClockConfig(TIM3);

首先第一步就是打开总线APB1和APB2,TIM3是在APB1中,GPIO在APB2中

配置GPIO,注意这里输出配置为复用推挽,上面说出了原因

开启GPIO_Pin_7(TIM3在PA7引脚)(后面要考)

开启TIM3的时基单元时钟

	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;		//重复计数器的值
	TIM_TimeBaseInit(TIM3,&TIM_TimeBAseInitStructure);

然后配置时基单元 

这个配置按照平常配置,但是ARR和PSC必须要满足舵机运行的频率

PWM频率:  Freq = CK_PSC / (PSC + 1) / (ARR + 1) = 50Hz

PWM占空比:  Duty = CCR / (ARR + 1)

PWM分辨率:  Reso = 1 / (ARR + 1)

这里设置的是20000的ARR 和 72的PSC,而后面的CCR的值是可变的,我设置为500到2500,这样占空比就能在0.005~0.025(对应高电平宽度0.5ms~2.5ms)满足舵机0~180度

	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(TIM3,&TIM_OCInitStructure);
	
	TIM_Cmd(TIM3,ENABLE);

然后配置输出比较的参数,同样也是用结构体配置的 

TIM_OCPolarity :极性选择为 高 ,当输出REF为高电平时就是高电平,反之

然后输出使能:TIM_OutputState_Enable

CCR:0(后面通过函数自己配置)

最后填入OC2Init中

由于我选择的是TIM3的通道2(CH2),所以配置的是OC2Init

 上图是通用定时器的部分结构,OC2Init中的OC2就是CH2通道,当然也可以选择其他通道,这里只是举个例子。因为每个通用定时器都能输出四路PWM。

void PWM_SetCompare2(uint16_t Compare)
{
	TIM_SetCompare2(TIM3,Compare);
}

void Servo_SetAngle(float Angle)
{
	PWM_SetCompare2(Angle / 180 * 2000 + 500);
}

TIM_SetCompare2这个函数也是因为用了OC2通道所以是TIM_SetCompare 而不是1或者其他

最后我们通过这个函数来填写CCR的值,其中Compare是一个变量,在500~2500之间,实现了舵机从0~180度的控制。

五、直流电机的控制

由于直流电机的控制和舵机控制相似,就省略讲解,但是也会重点讲解时基单元的配置和方向控制

时基单元的配置

	TIM_TimeBaseInitTypeDef TIM_TimeBAseInitStructure;
	TIM_TimeBAseInitStructure.TIM_ClockDivision =TIM_CKD_DIV1;		//分频模式
	TIM_TimeBAseInitStructure.TIM_CounterMode =TIM_CounterMode_Up;		//计数模式向上计数
	TIM_TimeBAseInitStructure.TIM_Period =100 - 1;		//重装器的值ARR
	TIM_TimeBAseInitStructure.TIM_Prescaler =36 - 1;		//预分频器的值PSC
	TIM_TimeBAseInitStructure.TIM_RepetitionCounter = 0;		//重复计数器的值
	TIM_TimeBaseInit(TIM2,&TIM_TimeBAseInitStructure);

PWM频率:  Freq = CK_PSC / (PSC + 1) / (ARR + 1) = 20KHz 

为什么要这个频率呢?因为频率太低当我们捂住电机的时候会发现有呜呜声,这样就会产生噪音。我们将频率提高到20KHz超出了让人耳能听到的频率就不会听到这个噪音。 

#include "stm32f10x.h"                  // Device header
#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_ResetBits(GPIOA,GPIO_Pin_4);
		GPIO_SetBits(GPIOA,GPIO_Pin_5);
		PWM_SetCompare3(-Speed);	
	}
}

GPIO_SetBits置1        GPIO_ResetBits置0

他们分别连接TB6612的AO1和AO2,当参数speed大于0就是正转,小于0就是反转

六、完整代码(按键控制舵机)

main.c

#include "stm32f10x.h"                  // Device header
#include "Delay.h"
#include "OLED.h"
#include "Servo.h"
#include "Key.h"

uint8_t KeyNum;
float Angle;
int main(void)
{
	Key_Init();
	OLED_Init();
	Servo_Init();
	OLED_ShowString(1,1,"Angle:");
	while(1)
	{
		KeyNum=Key_GetNum();
		if(KeyNum == 2)
		{
			Angle+=30;
			if(Angle > 180)
			{
				Angle = 0;
			}
		}
		Servo_SetAngle(Angle);
		OLED_ShowNum(1,7,Angle,3);
	}


}

Servo.c

#include "stm32f10x.h"                  // Device header
#include "PWM.h"

void Servo_Init(void)
{
	PWM_Init();
}
void Servo_SetAngle(float Angle)
{
	PWM_SetCompare2(Angle / 180 * 2000 + 500);
}

 PWM.c

#include "stm32f10x.h"                  // Device header

void PWM_Init(void)
{
	RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2,ENABLE);
	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;
	GPIO_InitStructure.GPIO_Speed=GPIO_Speed_50MHz;
	GPIO_Init(GPIOA,&GPIO_InitStructure);	
	
	
	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 =100 - 1;		//重装器的值ARR
	TIM_TimeBAseInitStructure.TIM_Prescaler =36 - 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;
	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);
}
void PWM_SetCompare3(uint16_t Compare)
{
	TIM_SetCompare3(TIM2,Compare);
}

PWM.h 

#ifndef __PWM_H
#define __PWM_H

void PWM_Init(void);
void PWM_SetCompare2(uint16_t Compare);
#endif

Servo.c 

#ifndef __SERVO_H
#define __SERVO_H

void Servo_Init(void);
void Servo_SetAngle(float Angle);

#endif

Key.c 

#include "stm32f10x.h"                  // Device header
#include "Delay.h"
void Key_Init(void)
{
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB,ENABLE);
	GPIO_InitTypeDef GPIO_InitStructure;
	GPIO_InitStructure.GPIO_Speed=GPIO_Speed_50MHz;
	GPIO_InitStructure.GPIO_Mode=GPIO_Mode_IPU;
	GPIO_InitStructure.GPIO_Pin=GPIO_Pin_1;
	GPIO_Init(GPIOB,&GPIO_InitStructure);
}
uint8_t Key_GetNum(void)
{
	uint8_t KeyNum=0; 
	 if(GPIO_ReadInputDataBit(GPIOB,GPIO_Pin_1)==0)
	 {
		Delay_ms(20);
		 while(GPIO_ReadInputDataBit(GPIOB,GPIO_Pin_1)==0)
			Delay_ms(20);
			KeyNum=1;
	 }
	return KeyNum;
}

Key.h 

#ifndef __KEY_H
#define __KEY_H

void Key_Init(void);
uint8_t Key_GetNum(void);

#endif

这段完整代码实现的效果:当按键按下的时候 舵机运动30度,加满180度的时候就会重新归位0度

七、完整代码(按键控制电机速度)

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)
{
	Key_Init();
	OLED_Init();
	Motor_init();
	OLED_ShowString(1,1,"Speed:");
	while(1)
	{
		KeyNum=Key_GetNum();
		if(KeyNum == 1)
		{
			Speed+=20;
			if(Speed>100)
				Speed=0;
		}
		Motor_SetSpeed(Speed);
		OLED_ShowSignedNum(1,7,Speed,3);
	}
}

  Motor.c

#include "stm32f10x.h"                  // Device header
#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_ResetBits(GPIOA,GPIO_Pin_4);
		GPIO_SetBits(GPIOA,GPIO_Pin_5);
		PWM_SetCompare3(-Speed);	
	}
}

 Motor.h

#ifndef __MOTOR_H
#define __MOTOR_H

void Motor_init(void);
void Motor_SetSpeed(int8_t Speed);

#endif

Key.c与Key.h和上面(按键控制舵机)的Key函数一样

最后实现的效果:按键每按一次,电机就会以百分之20的比例进行增速最后增到100,再次按下就会归零。


网站公告

今日签到

点亮在社区的每一天
去签到