STM32控制步进电机运三种方式控制源码详解:主从定时器+编码器闭环+GPIO模拟(基于【TB6600】【DRV8825】驱动器)

关于步进电机

步进电机在非常多的场合有着广泛的用途。通常情况下对运动控制有较高精度需求时就可以使用步进电机,初学来说常用的步进电机有42、57两种系列的步进电机。42电机的体积合适做一些小型的设备,它输出的扭矩较小,比较适合做小车的底盘驱动电机,小型3D打印机驱动电机,桌面机械臂的驱动电机等等。
相对于42步进电机,还有57步进电机,它的体积和质量有了较大的提升,当然其扭矩也有很大的提高。适用于做一些有负载需求的场景,比如小型的搬运机械臂驱动、特殊的滑台场景等。
总之选用步进之前要考虑到:对控制精度的需求,精度需求不高可以使用更简单稳定的直流电机。对负载输出的需求,负载输出较大时无论直流或者步进都需要考虑加合适的减速器。使用场景对电机性能要求,如果对电机转速,负载,精度等都有很高的要求那就考虑选用高品质的(无刷电机=¥¥¥¥)。
接下来针对两个驱动器:TB6600 DRV8825简单说明一下他们的控制42(同57)步进电机的驱动代码,至于硬件接线,理论上,只要硬件设计得当,电机使用场景不复杂,那这两种驱动器都只需要两根接线:脉冲信号输入+正反转控制

DRV8825+42步进

硬件的主要参考:https://zhuanlan.zhihu.com/p/210266085 建议在使用该模块之前一定要先认真研读一下这个网址的说明。
DRV8825模块的体积比较小,最大输出电流3A,最大细分32分。驱动的控制方式比较简单。核心就是3个脚:EN, STEP, DIR,其中EN负责控制驱动器的使能端口,STEP负责输入驱动脉冲信号,DIR负责控制电机的正反转。
接线图:
接线简图

至于其它的控制细分数的引脚可以直接通过布线接需要的电平。
细分设置

TB6600

硬件参考:TB6600的GPIO控制.
这个驱动器的接线核心和DRV8825其实差不多,主要也是三个信号:使能端口,脉冲信号输入,方向控制。不同的是TB6612内部需要差分信号,所以就需要有涉及共阴或者共阳的接线,一般来说我个人推荐使用共阳的接线方式,将信号阳极全部接到3.3V的电源正极,信号负极再一对一接到单片机的控制引脚。因为32的引脚驱动能力有限,在阴极输出可以保证信号的有效性。
接线图:(图中的EN没有接线,因为默认EN不接线时是有效状态,信号线接线是共阳极)
在这里插入图片描述

再另外的TB6600的驱动能力相对于DRV8825的驱动能力也要更强,它最大的带载能力达到了4A,24V,可以控制从小到大的接大部分42和57步进电机。限制它的应该就是驱动器的体积了。


废话不多说,直接来代码部分:

GPIO模拟

这里无论使用TB6612还是DRV8825,接线就不在赘述。
使用GPIO模拟的核心就是改变循环中间隔定时改变引脚的电平,模拟PWM的脉冲输出,这个方式是驱动代码写起来最简单的,但不是特别稳定。
代码参考文章最后的完整代码。

主从定时器模式控制

下面通过STM32F407控制器为例,使用两个定时器,TIM9+TIM10,主定时器负责定时,从定时器负责输出固定频率的脉冲。通过两个定时器的配合达到最终在固定时间段内(控制电机转速)输出一定脉冲(控制电机旋转角度)控制步进电机转动。
首先需要介绍一下头文件的宏定义,在代码中通过定义结构体的方式来定义一个电机的运动状态。并进行条件选择,在.文件中进行条件编译,达到一套代码适用多种情况的方式,更加便捷和易用。

#define TIMECount	0		//无编码器,主从定时器定时器控制模式
#define AS5600		0		//有编码器,编码器角度闭环模式
#define GPIO_Simulation 1	//无编码器,通过GPIO模拟控制电机转动

/**
 * @brief 设置步进电机控制结构体
 * @par Direction:设置电机旋转方向,FORWARD为正转,REVERSE为反转
 * @par State:设置电机是否可以被设置,Free为使能,Busy为禁用
 * @par setAngle:电机旋转角度
 */
typedef struct STEPMotor
{
	char Direction;
	char State;
	float setAngle;
	float realAngle;
}STEPMotor;


#define DRIVER_DIR   PFout(3) 	// DVR8825设置旋转方向
#define DRIVER_EN    PFout(5) 	// 使能脚 低电平有效 

#define     FORWARD         0       //步进正转
#define     REVERSE         1       //步进反转
#define		Free			0		//步进电机状态空闲
#define		Busy			1		//步进电机状态忙,不可被设置
#define     MotorCorrectionAngle  6400  //步进电机走360度需要的步数

好了我们来直接说一下具体的控制代码:

初始化电机状态结构体
/**
 * @brief 初始化步进电机控制机构体的所有数据
 * @param motor 步进电机控制机构体
 */
void StepMotor_InitData_STEPMotorStruct(STEPMotor *motor)
{
	motor->Direction = FORWARD;
	motor->realAngle = 0;
	motor->setAngle = 0;
	motor->State = Free;
}
定时器10输出PWM脉冲初始化函数,TIM9初始化
/**
 * @brief 定时器10输出PWM脉冲初始化函数
 * @param Period :定时器自动重装载值
 * @param Prescaler :定时器分频系数
 * Time10时钟总线为APB2,是SYSCLK的2分频,为84MHz。当Prescaler=1680,Period=50时,输出频率为
 * 													(84M/1680)/50=1kHz
 */
void StepMotor_TIM10PWMsteep_ProduceInit(int Period, int Prescaler)
{
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_TIM10,ENABLE);  	//TIM10时钟使能
	RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOF, ENABLE); 
	GPIO_PinAFConfig(GPIOF,GPIO_PinSource6,GPIO_AF_TIM10);

	GPIO_InitTypeDef GPIO_InitStructure;
	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_6;           
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF;        //复用功能
	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_100MHz;	//速度100MHz
	GPIO_InitStructure.GPIO_OType = GPIO_OType_PP;      //推挽复用输出
	GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_UP;        //上拉
	GPIO_Init(GPIOF,&GPIO_InitStructure);

	TIM_TimeBaseInitTypeDef  TIM_TimeBaseStructure;
	TIM_TimeBaseStructure.TIM_Prescaler=50;                  //定时器分频
	TIM_TimeBaseStructure.TIM_CounterMode=TIM_CounterMode_Up; //向上计数模式
	TIM_TimeBaseStructure.TIM_Period=1680;                     //自动重装载值
	TIM_TimeBaseStructure.TIM_ClockDivision=TIM_CKD_DIV1; 	
	TIM_TimeBaseInit(TIM10,&TIM_TimeBaseStructure);            //x=9~14

	TIM_OCInitTypeDef  TIM_OCInitStructure;
	TIM_OCInitStructure.TIM_OCMode = TIM_OCMode_PWM1;             //选择PWM模式
	TIM_OCInitStructure.TIM_OutputState = TIM_OutputState_Enable; //比较输出使能
	TIM_OCInitStructure.TIM_OCPolarity = TIM_OCNPolarity_High;      //输出极性低
	TIM_OC1Init(TIM10, &TIM_OCInitStructure);                      //初始化定时器x通道1,x=9~14
	TIM_OC1PreloadConfig(TIM10, TIM_OCPreload_Enable);  //使能定时器x在CCR1上的预装载寄存器,x=9~14
	TIM_ARRPreloadConfig(TIM10,ENABLE);//定时器x的ARPE使能,x=9~14
	TIM_Cmd(TIM10, DISABLE);
	TIM_SetCompare1(TIM10,420);
}

/**
 * @brief 定时器9定时初始化
 * 定时频率为4kHz,用于计时使能TIM10输出脉冲
 */
void StepMotor_TIM9Timing_ProduceInit(void)
{
	TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStructure;
	NVIC_InitTypeDef NVIC_InitStructure;

	RCC_APB2PeriphClockCmd(RCC_APB2Periph_TIM9, ENABLE); ///使能TIM7时钟

	TIM_TimeBaseInitStructure.TIM_Period = 0;						//自动重装载值
	TIM_TimeBaseInitStructure.TIM_Prescaler = 42000;				//定时器分频	频率4kHz
	TIM_TimeBaseInitStructure.TIM_CounterMode = TIM_CounterMode_Up; //向上计数模式
	TIM_TimeBaseInitStructure.TIM_ClockDivision = TIM_CKD_DIV1;

	TIM_TimeBaseInit(TIM9, &TIM_TimeBaseInitStructure); //初始化TIM9

	TIM_ITConfig(TIM9, TIM_IT_Update, ENABLE); //允许定时器9更新中断

	NVIC_InitStructure.NVIC_IRQChannel = TIM1_BRK_TIM9_IRQn;	 //定时器9中断
	NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0x03; //抢占优先级1
	//抢占优先级高的会优先抢占优先级低的,优先得到执行。(注意:优先级数字越小,优先级越高)
	NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0x03; //子优先级1
	//抢占优先级相同,不涉及到中断嵌套,响应优先级不同,响应优先级高的先响应
	NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
	NVIC_Init(&NVIC_InitStructure);
}

/**
 * @brief 定时器9的中断处理函数
 * 
 */
void TIM1_BRK_TIM9_IRQHandler(void)
{
	if(TIM_GetITStatus(TIM9,TIM_IT_Update)==SET) //溢出中断
	{
		TIM_Cmd(TIM10,DISABLE);
		ActionReady = 0;		//定时器时长结束标志
	}
	TIM_ClearITPendingBit(TIM9,TIM_IT_Update);  //清除中断标志位
	TIM_Cmd(TIM9,DISABLE);
}
步进电机驱动器DRV8825驱动器初始化
/**
 * @brief 步进电机驱动器DRV8825驱动器初始化
 * //DIR 6
//STEP 5
//MS 4
//EN 3
//VCC 2
//GND 1
 */
void StepMotor_Driver_GpioInit(void)
{
	GPIO_InitTypeDef GPIO_InitStructure;

	RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOC | RCC_AHB1Periph_GPIOG | RCC_AHB1Periph_GPIOB, ENABLE); //使能GPIOG时钟

	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_3 | GPIO_Pin_4; // DRIVER_DIR DRIVER_OE对应引脚
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_OUT;		   //普通输出模式
	GPIO_InitStructure.GPIO_OType = GPIO_OType_PP;		   //推挽输出
	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_100MHz;	   // 100M
	GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_UP;		   //上拉
	GPIO_Init(GPIOG, &GPIO_InitStructure);				   //初始化GPIOG3,4

	GPIO_ResetBits(GPIOG, GPIO_Pin_3); // PG3输出低 使能输出  DRIVER_ENA
	GPIO_SetBits(GPIOG, GPIO_Pin_4);   // PG4输出高 顺时针方向  DRIVER_DIR

	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_10 | GPIO_Pin_11 | GPIO_Pin_12; // DRIVER_OE对应引脚
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_OUT;						   //普通输出模式
	GPIO_InitStructure.GPIO_OType = GPIO_OType_PP;						   //推挽输出
	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_100MHz;					   // 100M
	GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_UP;						   //上拉
	GPIO_Init(GPIOC, &GPIO_InitStructure);								   //初始化GPIOG3,4
	GPIO_SetBits(GPIOC, GPIO_Pin_10);									   //全部拉高32细分,可以不接这3个GPIO,在电路上全部给3.3V
	GPIO_SetBits(GPIOC, GPIO_Pin_11);
	GPIO_SetBits(GPIOC, GPIO_Pin_12);

	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_2;
	GPIO_Init(GPIOB, &GPIO_InitStructure);
	GPIO_ResetBits(GPIOB, GPIO_Pin_2);

}
步进电机控制转动给定角度函数
/**
 * @brief 步进电机控制转动给定角度函数
 * @param STEPMotor 传入电机控制结构体指针
 *  * @par Direction:设置电机旋转方向,FORWARD为正转,REVERSE为反转
 *  * @par State:设置电机是否可以被设置,Free为空闲,Busy为忙不可设置,最好不要人工改变,初始化的时候赋值为0就可以
 *  * @par setAngle:电机旋转角度,最小值0,最大值720度
 *
 * 函数使用举例:
 *  在主函数中使用该函数控制步进电机正向旋转90度:
		int main() {
			......
			STEPMotor stpmotor = {0};
			stpmotor.Direction = FORWARD;	//正转
			stpmotor.setAngle = 90;		//90度
			StepMotor_SetRotationAngle(&stpmotor);	//旋转一次
			}
 */
void StepMotor_SetRotationAngle(STEPMotor *motor)
{	if(ActionReady == 0) motor->State = Free;
	if(motor->State == Free)
	{
		if(motor->Direction == FORWARD)
		{
			DRIVER_DIR = 1;
		}
		else DRIVER_DIR = 0;
		float tim = 0;
		tim = (motor->setAngle/360)*MotorCorrectionAngle;
		tim = tim / 2000*4000;	// 2000为PWM脉冲发出定时器TIM10的频率,4000为时长定时器TIM9频率
		motor->State = Busy;
		TIM_SetAutoreload(TIM9, tim);
		TIM_Cmd(TIM9, ENABLE);
    	TIM_Cmd(TIM10, ENABLE);
		ActionReady = 1;
	}
}

注意在移植使用的时候要灵活选择配置引脚,32的每个外设都有多个引脚通道,能够合适的使用对应引脚会大大的降低硬件布线的复杂性,这里的代码主要是参考一个配置的思路。


编码器闭环模式

这里使用的编码器为AS5600磁霍尔式编码器,淘宝有成品可以买到,他是模拟采集数字输出的一个传感器,AD转换的精度达到了12位,在一般的场景中这个精度的编码器完全够用了。AS5600在一些店家的设计下有PWM输出和模拟电压输出以及I2C输出等等的方式,这里直接使用I2C模式,这个模式下读取到的数据直接就是编码器内部的寄存器的值,而且配置也更加简单。
在使用这个模式时,默认你应该明白了上面的使用一种驱动器让步进电机转起来,并了解I2C总线的原理。

IIC读取AS5600的角度数据
/**
 * @brief IIC读取AS5600的角度数据
 * @param deviceaddr 器件的从机地址,从机的7位地址是0x36 (二进制为0110110)
 * @param readaddr 需要读取的数据寄存器地址,AS5600的角度为两个0x0E(8:11)和0x0F(0:7)两个寄存器前7位组成
 * @return u8 返回一个寄存器中读出的1bit数据
 */
u8 AS5600_IIC_Read_OneByte(u8 deviceaddr,u8 readaddr)
{
  u8 temp;
  IIC_Start();
  IIC_Send_Byte(deviceaddr&0xfe);
  IIC_Wait_Ack();
  IIC_Send_Byte(readaddr);
  IIC_Wait_Ack();

  IIC_Start();
  IIC_Send_Byte(deviceaddr|0x01);
  IIC_Wait_Ack();
  temp=IIC_Read_Byte(0);
  IIC_Stop();
  return temp;
}
读取当前编码器的旋转角度
/**
 * @brief 读取当前编码器的旋转角度
 * @param motor 步进电机控制机构体
 */
void StepMotor_ReadAS5600_Date(STEPMotor *motor)
{
	unsigned int value = 5000;
	value =  AS5600_IIC_Read_OneByte((0x36<<1),0x0e);   
    value <<= 8;
    value |= AS5600_IIC_Read_OneByte((0x36<<1),0x0f); 
	if(value<=4096)
		motor->realAngle = (float)(value/4096)*360;
}
步进电机角度初始化

这里相当于舵机归中,就是在上电的时候将步进轴旋转到初始位置。

/**
 * @brief 步进电机角度初始化
 */
void StepMotor_Init_SetAngle(STEPMotor *motor)
{
	StepMotor_ReadAS5600_Date(motor);
	if(motor->realAngle > motor->setAngle)
	{
		DRIVER_DIR = FORWARD;
		int state = 1;
		TIM_Cmd(TIM10, ENABLE);
		do
		{
			StepMotor_ReadAS5600_Date(motor);
			if((motor->realAngle - motor->setAngle) <= 4) state = 0;
		}while (state);
		TIM_Cmd(TIM10,DISABLE);
	}
	else if(motor->realAngle < motor->setAngle)
	{
		DRIVER_DIR = REVERSE;
		int state = 1;
		TIM_Cmd(TIM10, ENABLE);
		do
		{
			StepMotor_ReadAS5600_Date(motor);
			if((motor->setAngle - motor->realAngle) <= 4) state = 0;
		}while (state);
		TIM_Cmd(TIM10,DISABLE);
	}
}
步进电机闭环旋转到固定角度
/**
 * @brief 步进电机闭环旋转到固定角度
 * @param motor 步进电机控制机构体
 * 使用函数举例:
 * 		STEPMotor stpmotor;
 * 		StepMotor_InitData_STEPMotorStruct(&stpmotor);
 * 		StepMotor_Init_SetAngle(&stpmotor);
 * 		stpmotor->setAngle = 98;
 * 		StepMotor_SetRotationAngle(&stpmotor);
 */
void StepMotor_SetRotationAngle(STEPMotor *motor)
{
	if (motor->State == Free)
	{
		motor->State = Busy;
		if (motor->realAngle > motor->setAngle)
		{
			DRIVER_DIR = FORWARD;
			int state = 1;
			TIM_Cmd(TIM10, ENABLE);
			do
			{
				StepMotor_ReadAS5600_Date(motor);
				if ((motor->realAngle - motor->setAngle) <= 4)
					state = 0;
			} while (state);
			TIM_Cmd(TIM10, DISABLE);
		}
		else if (motor->realAngle < motor->setAngle)
		{
			DRIVER_DIR = REVERSE;
			int state = 1;
			TIM_Cmd(TIM10, ENABLE);
			do
			{
				StepMotor_ReadAS5600_Date(motor);
				if ((motor->setAngle - motor->realAngle) <= 4)
					state = 0;
			} while (state);
			TIM_Cmd(TIM10, DISABLE);
		}
	}
}

代码完整版

代码的配置是相对不变的,引脚的分配和控制的逻辑要灵活运用

StepperMotor.h

#ifndef __STEPPERMOTOR_H
#define __STEPPERMOTOR_H  

#include "sys.h"

#define TIMECount	0		//无编码器,主从定时器定时器控制模式
#define AS5600		0		//有编码器,编码器角度闭环模式
#define GPIO_Simulation 1

/**
 * @brief 设置步进电机控制结构体
 * @par Direction:设置电机旋转方向,FORWARD为正转,REVERSE为反转
 * @par State:设置电机是否可以被设置,Free为使能,Busy为禁用
 * @par setAngle:电机旋转角度
 */
typedef struct STEPMotor
{
	char Direction;
	char State;
	float setAngle;
	float realAngle;
}STEPMotor;


#define DRIVER_DIR   PFout(3) 	// DVR8825设置旋转方向
#define DRIVER_EN    PFout(5) 	// 使能脚 低电平有效 

#define     FORWARD         0       //步进正转
#define     REVERSE         1       //步进反转
#define		Free			0		//步进电机状态空闲
#define		Busy			1		//步进电机状态忙,不可被设置
#define     MotorCorrectionAngle  6400  //步进电机走360度需要的步数

/************** 共用API **************/

void StepMotor_TIM10PWMsteep_ProduceInit(int Period, int Prescaler);
void StepMotor_InitData_STEPMotorStruct(STEPMotor *motor);
void StepMotor_Driver_GpioInit(void);
void StepMotor_SetRotationAngle(STEPMotor *motor);

/************** 定时器计数模式下私有API **************/

#if TIMECount
void StepMotor_TIM9Timing_ProduceInit(void);
void TIM1_BRK_TIM9_IRQHandler(void);
#endif

/************** 编码器闭环模式下私有API **************/
#if AS5600
u8 AS5600_IIC_Read_OneByte(u8 deviceaddr,u8 readaddr);
void StepMotor_ReadAS5600_Date(STEPMotor *motor);
void StepMotor_Init_SetAngle(STEPMotor *motor);
#endif


/************** GPIO模拟脉冲模式下私有API **************/
#if GPIO_Simulation
#define Rise 	0x04
#define Decline 0x05
void StepMotor1_SetRotationRise(uint8_t DIR);
void StepMotor2_SetRotationRise(uint8_t DIR);
void StepMotor3_SetRotationRise(uint8_t DIR);

#endif

#endif

StepperMotor.c

#include "StepperMotor.h"
#include "myiic.h"
#include "delay.h"

/**
 * @brief 初始化步进电机控制机构体的所有数据
 * @param motor 步进电机控制机构体
 */
void StepMotor_InitData_STEPMotorStruct(STEPMotor *motor)
{
	motor->Direction = FORWARD;
	motor->realAngle = 0;
	motor->setAngle = 0;
	motor->State = Free;
}

/**
 * @brief 定时器10输出PWM脉冲初始化函数
 * @param Period :定时器自动重装载值
 * @param Prescaler :定时器分频系数
 * Time10时钟总线为APB2,是SYSCLK的2分频,为84MHz。当Prescaler=1680,Period=50时,输出频率为
 * 													(84M/1680)/50=1kHz
 */
void StepMotor_TIM10PWMsteep_ProduceInit(int Period, int Prescaler)
{
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_TIM10,ENABLE);  	//TIM10时钟使能
	RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOF, ENABLE); 
	GPIO_PinAFConfig(GPIOF,GPIO_PinSource6,GPIO_AF_TIM10);

	GPIO_InitTypeDef GPIO_InitStructure;
	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_6;           
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF;        //复用功能
	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_100MHz;	//速度100MHz
	GPIO_InitStructure.GPIO_OType = GPIO_OType_PP;      //推挽复用输出
	GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_UP;        //上拉
	GPIO_Init(GPIOF,&GPIO_InitStructure);

	TIM_TimeBaseInitTypeDef  TIM_TimeBaseStructure;
	TIM_TimeBaseStructure.TIM_Prescaler=50;                  //定时器分频
	TIM_TimeBaseStructure.TIM_CounterMode=TIM_CounterMode_Up; //向上计数模式
	TIM_TimeBaseStructure.TIM_Period=1680;                     //自动重装载值
	TIM_TimeBaseStructure.TIM_ClockDivision=TIM_CKD_DIV1; 	
	TIM_TimeBaseInit(TIM10,&TIM_TimeBaseStructure);            //x=9~14

	TIM_OCInitTypeDef  TIM_OCInitStructure;
	TIM_OCInitStructure.TIM_OCMode = TIM_OCMode_PWM1;             //选择PWM模式
	TIM_OCInitStructure.TIM_OutputState = TIM_OutputState_Enable; //比较输出使能
	TIM_OCInitStructure.TIM_OCPolarity = TIM_OCNPolarity_High;      //输出极性低
	TIM_OC1Init(TIM10, &TIM_OCInitStructure);                      //初始化定时器x通道1,x=9~14
	TIM_OC1PreloadConfig(TIM10, TIM_OCPreload_Enable);  //使能定时器x在CCR1上的预装载寄存器,x=9~14
	TIM_ARRPreloadConfig(TIM10,ENABLE);//定时器x的ARPE使能,x=9~14
	TIM_Cmd(TIM10, DISABLE);
	TIM_SetCompare1(TIM10,420);
}

#if TIMECount
char ActionReady = 0;

/**
 * @brief 步进电机驱动器DRV8825驱动器初始化
 * //DIR 6
//STEP 5
//MS 4
//EN 3
//VCC 2
//GND 1
 */
void StepMotor_Driver_GpioInit(void)
{
	GPIO_InitTypeDef GPIO_InitStructure;

	RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOC | RCC_AHB1Periph_GPIOG | RCC_AHB1Periph_GPIOB, ENABLE); //使能GPIOG时钟

	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_3 | GPIO_Pin_4; // DRIVER_DIR DRIVER_OE对应引脚
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_OUT;		   //普通输出模式
	GPIO_InitStructure.GPIO_OType = GPIO_OType_PP;		   //推挽输出
	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_100MHz;	   // 100M
	GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_UP;		   //上拉
	GPIO_Init(GPIOG, &GPIO_InitStructure);				   //初始化GPIOG3,4

	GPIO_ResetBits(GPIOG, GPIO_Pin_3); // PG3输出低 使能输出  DRIVER_ENA
	GPIO_SetBits(GPIOG, GPIO_Pin_4);   // PG4输出高 顺时针方向  DRIVER_DIR

	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_10 | GPIO_Pin_11 | GPIO_Pin_12; // DRIVER_OE对应引脚
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_OUT;						   //普通输出模式
	GPIO_InitStructure.GPIO_OType = GPIO_OType_PP;						   //推挽输出
	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_100MHz;					   // 100M
	GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_UP;						   //上拉
	GPIO_Init(GPIOC, &GPIO_InitStructure);								   //初始化GPIOG3,4
	GPIO_SetBits(GPIOC, GPIO_Pin_10);									   //全部拉高32细分,可以不接这3个GPIO,在电路上全部给3.3V
	GPIO_SetBits(GPIOC, GPIO_Pin_11);
	GPIO_SetBits(GPIOC, GPIO_Pin_12);

	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_2;
	GPIO_Init(GPIOB, &GPIO_InitStructure);
	GPIO_ResetBits(GPIOB, GPIO_Pin_2);

}


/**
 * @brief 定时器9定时初始化
 * 定时频率为4kHz,用于计时使能TIM10输出脉冲
 */
void StepMotor_TIM9Timing_ProduceInit(void)
{
	TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStructure;
	NVIC_InitTypeDef NVIC_InitStructure;

	RCC_APB2PeriphClockCmd(RCC_APB2Periph_TIM9, ENABLE); ///使能TIM7时钟

	TIM_TimeBaseInitStructure.TIM_Period = 0;						//自动重装载值
	TIM_TimeBaseInitStructure.TIM_Prescaler = 42000;				//定时器分频	频率4kHz
	TIM_TimeBaseInitStructure.TIM_CounterMode = TIM_CounterMode_Up; //向上计数模式
	TIM_TimeBaseInitStructure.TIM_ClockDivision = TIM_CKD_DIV1;

	TIM_TimeBaseInit(TIM9, &TIM_TimeBaseInitStructure); //初始化TIM7

	TIM_ITConfig(TIM9, TIM_IT_Update, ENABLE); //允许定时器9更新中断

	NVIC_InitStructure.NVIC_IRQChannel = TIM1_BRK_TIM9_IRQn;	 //定时器9中断
	NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0x03; //抢占优先级1
	//抢占优先级高的会优先抢占优先级低的,优先得到执行。(注意:优先级数字越小,优先级越高)
	NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0x03; //子优先级1
	//抢占优先级相同,不涉及到中断嵌套,响应优先级不同,响应优先级高的先响应
	NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
	NVIC_Init(&NVIC_InitStructure);
}


/**
 * @brief 定时器9的中断处理函数
 * 
 */
void TIM1_BRK_TIM9_IRQHandler(void)
{
	if(TIM_GetITStatus(TIM9,TIM_IT_Update)==SET) //溢出中断
	{
		TIM_Cmd(TIM10,DISABLE);
		ActionReady = 0;		//定时器时长结束标志
	}
	TIM_ClearITPendingBit(TIM9,TIM_IT_Update);  //清除中断标志位
	TIM_Cmd(TIM9,DISABLE);
}

/**
 * @brief 步进电机控制转动给定角度函数
 * @param STEPMotor 传入电机控制结构体指针
 *  * @par Direction:设置电机旋转方向,FORWARD为正转,REVERSE为反转
 *  * @par State:设置电机是否可以被设置,Free为空闲,Busy为忙不可设置,最好不要人工改变,初始化的时候赋值为0就可以
 *  * @par setAngle:电机旋转角度,最小值0,最大值720度
 *
 * 函数使用举例:
 *  在主函数中使用该函数控制步进电机正向旋转90度:
		int main() {
			STEPMotor stpmotor = {0};
			stpmotor.Direction = FORWARD;	//正转
			stpmotor.setAngle = 90;		//90度
			StepMotor_SetRotationAngle(&stpmotor);	//旋转一次
			}
 */
void StepMotor_SetRotationAngle(STEPMotor *motor)
{	if(ActionReady == 0) motor->State = Free;
	if(motor->State == Free)
	{
		if(motor->Direction == FORWARD)
		{
			DRIVER_DIR = 1;
		}
		else DRIVER_DIR = 0;
		float tim = 0;
		tim = (motor->setAngle/360)*MotorCorrectionAngle;
		tim = tim / 2000*4000;	// 2000为PWM脉冲发出定时器TIM10的频率,4000为时长定时器TIM9频率
		motor->State = Busy;
		TIM_SetAutoreload(TIM9, tim);
		TIM_Cmd(TIM9, ENABLE);
    	TIM_Cmd(TIM10, ENABLE);
		ActionReady = 1;
	}
}
#endif

#if AS5600
#include <math.h>

/**
 * @brief 步进电机驱动器DRV8825驱动器初始化
 * //DIR 6		PF3
//STEP 5
//MS 4
//EN 3			PF5
//VCC 2
//GND 1
 */
void StepMotor_Driver_GpioInit(void)
{
	GPIO_InitTypeDef GPIO_InitStructure;

	RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOF , ENABLE); //使能GPIOG时钟

	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_3 | GPIO_Pin_5; // DRIVER_DIR DRIVER_EN对应引脚
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_OUT;		   //普通输出模式
	GPIO_InitStructure.GPIO_OType = GPIO_OType_PP;		   //推挽输出
	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_100MHz;	   // 100M
	GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_UP;		   //上拉
	GPIO_Init(GPIOF, &GPIO_InitStructure);				   //初始化GPIOG3,4

	GPIO_ResetBits(GPIOF, GPIO_Pin_5); // PG3输出低 使能输出  DRIVER_ENA
	GPIO_SetBits(GPIOF, GPIO_Pin_3);   // PG4输出高 顺时针方向  DRIVER_DIR
}

/**
 * @brief IIC读取AS5600的角度数据
 * @param deviceaddr 器件的从机地址,从机的7位地址是0x36 (二进制为0110110)
 * @param readaddr 需要读取的数据寄存器地址,AS5600的角度为两个0x0E(8:11)和0x0F(0:7)两个寄存器前7位组成
 * @return u8 返回一个寄存器中读出的1bit数据
 */
u8 AS5600_IIC_Read_OneByte(u8 deviceaddr,u8 readaddr)
{
  u8 temp;
  IIC_Start();
  IIC_Send_Byte(deviceaddr&0xfe);
  IIC_Wait_Ack();
  IIC_Send_Byte(readaddr);
  IIC_Wait_Ack();

  IIC_Start();
  IIC_Send_Byte(deviceaddr|0x01);
  IIC_Wait_Ack();
  temp=IIC_Read_Byte(0);
  IIC_Stop();
  return temp;
}


/**
 * @brief 读取当前编码器的旋转角度
 * @param motor 步进电机控制机构体
 */
void StepMotor_ReadAS5600_Date(STEPMotor *motor)
{
	unsigned int value = 5000;
	value =  AS5600_IIC_Read_OneByte((0x36<<1),0x0e);   
    value <<= 8;
    value |= AS5600_IIC_Read_OneByte((0x36<<1),0x0f); 
	if(value<=4096)
		motor->realAngle = (float)(value/4096)*360;
}


/**
 * @brief 步进电机角度初始化
 */
void StepMotor_Init_SetAngle(STEPMotor *motor)
{
	StepMotor_ReadAS5600_Date(motor);
	if(motor->realAngle > motor->setAngle)
	{
		DRIVER_DIR = FORWARD;
		int state = 1;
		TIM_Cmd(TIM10, ENABLE);
		do
		{
			StepMotor_ReadAS5600_Date(motor);
			if((motor->realAngle - motor->setAngle) <= 4) state = 0;
		}while (state);
		TIM_Cmd(TIM10,DISABLE);
	}
	else if(motor->realAngle < motor->setAngle)
	{
		DRIVER_DIR = REVERSE;
		int state = 1;
		TIM_Cmd(TIM10, ENABLE);
		do
		{
			StepMotor_ReadAS5600_Date(motor);
			if((motor->setAngle - motor->realAngle) <= 4) state = 0;
		}while (state);
		TIM_Cmd(TIM10,DISABLE);
	}
}

/**
 * @brief 步进电机闭环旋转到固定角度
 * @param motor 步进电机控制机构体
 * 使用函数举例:
 * 		STEPMotor stpmotor;
 * 		StepMotor_InitData_STEPMotorStruct(&stpmotor);
 * 		StepMotor_Init_SetAngle(&stpmotor);
 * 		stpmotor->setAngle = 98;
 * 		StepMotor_SetRotationAngle(&stpmotor);
 */
void StepMotor_SetRotationAngle(STEPMotor *motor)
{
	if (motor->State == Free)
	{
		motor->State = Busy;
		if (motor->realAngle > motor->setAngle)
		{
			DRIVER_DIR = FORWARD;
			int state = 1;
			TIM_Cmd(TIM10, ENABLE);
			do
			{
				StepMotor_ReadAS5600_Date(motor);
				if ((motor->realAngle - motor->setAngle) <= 4)
					state = 0;
			} while (state);
			TIM_Cmd(TIM10, DISABLE);
		}
		else if (motor->realAngle < motor->setAngle)
		{
			DRIVER_DIR = REVERSE;
			int state = 1;
			TIM_Cmd(TIM10, ENABLE);
			do
			{
				StepMotor_ReadAS5600_Date(motor);
				if ((motor->setAngle - motor->realAngle) <= 4)
					state = 0;
			} while (state);
			TIM_Cmd(TIM10, DISABLE);
		}
	}
}

#endif

#if GPIO_Simulation

void StepMotor_Driver_GpioInit(void)
{
	GPIO_InitTypeDef GPIO_InitStructure;

	RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOF | RCC_AHB1Periph_GPIOC | RCC_AHB1Periph_GPIOE | RCC_AHB1Periph_GPIOG, ENABLE); //使能GPIOG时钟

	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_5 | GPIO_Pin_3 | GPIO_Pin_1; // DRIVER_DIR DRIVER_EN对应引脚
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_OUT;		   //普通输出模式
	GPIO_InitStructure.GPIO_OType = GPIO_OType_PP;		   //推挽输出
	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;	   // 100M
	GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_UP;		   //上拉
	GPIO_Init(GPIOF, &GPIO_InitStructure);				   //初始化GPIOG3,4
	GPIO_ResetBits(GPIOF, GPIO_Pin_5 | GPIO_Pin_3 | GPIO_Pin_1); // PG3输出低 使能输出  DRIVER_ENA

	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_13;
	GPIO_Init(GPIOC, &GPIO_InitStructure);
	GPIO_ResetBits(GPIOC, GPIO_Pin_13);
	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_4 | GPIO_Pin_6;
	GPIO_Init(GPIOE, &GPIO_InitStructure);
	GPIO_ResetBits(GPIOE, GPIO_Pin_4 | GPIO_Pin_6);

	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0 | GPIO_Pin_2;
	GPIO_Init(GPIOE, &GPIO_InitStructure);
	GPIO_ResetBits(GPIOE, GPIO_Pin_0 | GPIO_Pin_2);
	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_13;
	GPIO_Init(GPIOG, &GPIO_InitStructure);
	GPIO_ResetBits(GPIOG, GPIO_Pin_13);
}

/**
 * @brief 通过GPIO模拟PWM信号,驱动步进电机旋转
 * 
 * @param DIR 正反转信号
 * 		PF3 PF1 PF5
 */
void StepMotor1_SetRotationRise(uint8_t DIR)
{
	if(DIR == Rise)
	{
		GPIO_SetBits(GPIOF, GPIO_Pin_3);
	}
	GPIO_SetBits(GPIOF, GPIO_Pin_1);
	int rise = 5000;
	int i = 0;
	while (i<rise)
	{
		GPIO_SetBits(GPIOF, GPIO_Pin_5);
		delay_us(500);
		GPIO_ResetBits(GPIOF, GPIO_Pin_5);
		delay_us(500);
		i++;
	}
	GPIO_ResetBits(GPIOF, GPIO_Pin_5 | GPIO_Pin_3 | GPIO_Pin_1); 
}

/**
 * @brief PE6 PE4 PC13
 * 
 * @param DIR 
 */
void StepMotor2_SetRotationRise(uint8_t DIR)
{
	if(DIR == Rise)
	{
		GPIO_SetBits(GPIOE, GPIO_Pin_6);
	}
	GPIO_SetBits(GPIOE, GPIO_Pin_4);
	int rise = 5000;
	int i = 0;
	while (i<rise)
	{
		GPIO_SetBits(GPIOC, GPIO_Pin_13);
		delay_us(500);
		GPIO_SetBits(GPIOC, GPIO_Pin_13);
		delay_us(500);
		i++;
	}
	GPIO_ResetBits(GPIOE, GPIO_Pin_4 | GPIO_Pin_6);
	GPIO_ResetBits(GPIOC, GPIO_Pin_13);
}

/**
 * @brief PG13 PE0 PE2
 * 
 * @param DIR 
 */
void StepMotor3_SetRotationRise(uint8_t DIR)
{
	if(DIR == Rise)
	{
		GPIO_SetBits(GPIOG, GPIO_Pin_13);
	}
	GPIO_SetBits(GPIOE, GPIO_Pin_0);
	int rise = 5000;
	int i = 0;
	while (i<rise)
	{
		GPIO_SetBits(GPIOE, GPIO_Pin_2);
		delay_us(500);
		GPIO_SetBits(GPIOE, GPIO_Pin_2);
		delay_us(500);
		i++;
	}
	GPIO_ResetBits(GPIOE, GPIO_Pin_0 | GPIO_Pin_2);
	GPIO_ResetBits(GPIOG, GPIO_Pin_13);
}


#endif // DEBUG 


猜你喜欢

转载自blog.csdn.net/weixin_47407066/article/details/124657473