步进电机在非常多的场合有着广泛的用途。通常情况下对运动控制有较高精度需求时就可以使用步进电机,初学来说常用的步进电机有42、57两种系列的步进电机。42电机的体积合适做一些小型的设备,它输出的扭矩较小,比较适合做小车的底盘驱动电机,小型3D打印机驱动电机,桌面机械臂的驱动电机等等。
相对于42步进电机,还有57步进电机,它的体积和质量有了较大的提升,当然其扭矩也有很大的提高。适用于做一些有负载需求的场景,比如小型的搬运机械臂驱动、特殊的滑台场景等。
总之选用步进之前要考虑到:对控制精度的需求,精度需求不高可以使用更简单稳定的直流电机。对负载输出的需求,负载输出较大时无论直流或者步进都需要考虑加合适的减速器。使用场景对电机性能要求,如果对电机转速,负载,精度等都有很高的要求那就考虑选用高品质的(无刷电机=¥¥¥¥)。
接下来针对两个驱动器:TB6600 DRV8825简单说明一下他们的控制42(同57)步进电机的驱动代码,至于硬件接线,理论上,只要硬件设计得当,电机使用场景不复杂,那这两种驱动器都只需要两根接线:脉冲信号输入+正反转控制
硬件的主要参考:https://zhuanlan.zhihu.com/p/210266085
建议在使用该模块之前一定要先认真研读一下这个网址的说明。
DRV8825模块的体积比较小,最大输出电流3A,最大细分32分。驱动的控制方式比较简单。核心就是3个脚:EN
, STEP
, DIR
,其中EN负责控制驱动器的使能端口,STEP负责输入驱动脉冲信号,DIR负责控制电机的正反转。
接线图:
硬件参考:TB6600的GPIO控制.
这个驱动器的接线核心和DRV8825其实差不多,主要也是三个信号:使能端口,脉冲信号输入,方向控制。不同的是TB6612内部需要差分信号,所以就需要有涉及共阴或者共阳的接线,一般来说我个人推荐使用共阳的接线方式,将信号阳极全部接到3.3V的电源正极,信号负极再一对一接到单片机的控制引脚。因为32的引脚驱动能力有限,在阴极输出可以保证信号的有效性。
接线图:(图中的EN没有接线,因为默认EN不接线时是有效状态,信号线接线是共阳极)
再另外的TB6600的驱动能力相对于DRV8825的驱动能力也要更强,它最大的带载能力达到了4A,24V,可以控制从小到大的接大部分42和57步进电机。限制它的应该就是驱动器的体积了。
废话不多说,直接来代码部分:
这里无论使用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;
}
/**
* @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); //初始化TIM7
TIM_ITConfig(TIM9, TIM_IT_Update, ENABLE); //允许定时器7更新中断
NVIC_InitStructure.NVIC_IRQChannel = TIM1_BRK_TIM9_IRQn; //定时器6中断
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 步进电机驱动器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总线的原理。
/**
* @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);
}
}
}
代码的配置是相对不变的,引脚的分配和控制的逻辑要灵活运用
#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
#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); //允许定时器7更新中断
NVIC_InitStructure.NVIC_IRQChannel = TIM1_BRK_TIM9_IRQn; //定时器6中断
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
/**
* @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