步进电机的应用非常广泛,在各种设备中经常会遇到,而步进电机的驱动则是使用步进电机必不可少的部分,可以有多种方式来实现步进电机的驱动,在这里我们来考虑一下基于TMC2660驱动芯片的步进电机驱动。
TMC2660是德国TRINAMIC公司产的步进电机驱动芯片。TMC2660驱动器提供了业界领先的功能集,包括高分辨率微步、无传感器机械负载测量、负载自适应功率优化和低共振斩波操作。拥有标准SPI和STEP/DIR两种接口模式。集成功率MOSFET处理电机电流高达2.2A。集成的保护和诊断功能支持稳健和可靠的运行。其结构图如下:
TMC2660的参数配置通过SPI接口来实现。TMC2660具有5个配置和控制寄存器,通过SPI端口来访问这些寄存器。这些寄存器的结构定义如下所示:
所有的寄存器都是20位,在最高的2位或3位表示的是寄存器地址也称为操作码。根据具体的寄存器我们选择不同的操作码实现对寄存器的写操作。每一个写操作都会有一个20位的数据返回。而返回数据的内容可以通过修改配置寄存器来定义。具体的格式如下图所示:
我们已经了解了TMC2660步进电机驱动芯片的基本技术参数,接下来我们就需要据此来实现TMC2660步进电机驱动芯片的驱动程序的设计与实现。
我们依然是居于对象来实现相关的操作。所以我们首先要定义对象,出于适用性考虑,我们要定义对象的类型并将具体的对象实例化,接下来我们就来抽象对象类型和实例化对象的操作。
对于一个对象最主要包括属性与操作两方面内容,所以我们先来考虑TMC2660对象具有哪些属性和操作,并抽象出较为通用的TMC2660对象类型。
对于步进电机的驱动都具有哪些属性呢?我们考虑到一台步进电机至少具备启停控制命令、方向控制命令、速度设定以及运行状态等,这些对于每一台步进电机来说,在不同的设置下代表不同的状态,所以我们将其作为其属性来处理。此外,与具体的电机相关的参数如固有步距角、微步设置及当前脉冲频率等。以及与TMC2660相关的状态、寄存器的值、速度规划等都与具体的应用需求相关、用以记录其运行和配置状态,所以我们将其作为属性。
然后再来看一看TMC2660对象需要实现的操作。对于TMC2660对象来说,我们要操作它,需要向其发送和读取数据,需要操作片选信号和使能信号,而这些行为依赖于具体的操作平台,所以我们将其作为对象的操作来设定。TMC2660可以工作在SPI模式或者SD模式,而在SD模式时,存在脉冲和方向的控制,这同样依赖于具体的软硬件操作平台,所以我们也将其作为对象的操作来实现。更具以上的分析我们可以抽象出TMC2660对象类型如下:
/*定义TMC2660对象类型*/
typedef struct TMC2660Object {
float microStep; //微步设置
float stepAngle; //固有步进角
float frequency; //运行频率
uint16_t *pStartStop; //启停操作命令
uint16_t *pDirection; //方向控制
uint16_t *pRotateSet; //转速设定
uint16_t *pMotorState; //电机状态
uint32_t status; //TMC通讯返回状态
uint32_t Register[5]; //寄存器
void (*WriteRead)(uint8_t *wData,uint16_t wSize,uint8_t *rData,uint16_t rSize);
void (*ChipSelcet)(TMC2660CSType cs); //片选信号
void (*StartStop)(TMC2660SSType ss); //启停操作函数
void (*Direct)(TMC2660DIRType dir); //方向操作函数
void (*Enable)(TMC2660ENNType enn); //使能操作函数
CurveObjectType curve; //电机调速曲线
}TMC2660ObjectType;
我们定义了对象类型,可以实现基于对象的操作,但定义的对象变量需要进行初始化才能让不同的对象按照我们的配置的方式去运行。所以在开始对象的使用之前我们先对其进行初始化,具体的初始化函数如下:
/*初始化TMC2660对象*/
void Tmc2660Initialization(TMC2660ObjectType *tmc, //待初始化的TMC对象变量
TMC2660SdoffType interface, //驱动接口类型
TMC2660MicroStepType microStep, //微步设置
uint16_t Power, //电流量程
uint16_t stepAngle, //固有步进角
uint16_t *pStartStop, //启停操作命令
uint16_t *pDirection, //方向控制
uint16_t *pRotateSet, //转速设定
uint16_t *pMotorState, //电机状态
TMC2660WriteReadType writeRead, //读写函数指针
TMC2660ChipSelcetType cs, //片选操作函数指针
TMC2660StartStopType startStop, //启停操作函数指针
TMC2660DirectType direct, //方向设置函数指针
TMC2660EnableType enable //使能控制函数指针
)
{
uint32_t MicroStep[9]={0x08,0x07,0x06,0x05,0x04,0x03,0x02,0x01,0x00};
uint16_t MicroStepNum[9]={1,2,4,8,16,32,64,128,256};
if((tmc==NULL)||(writeRead==NULL)||(cs==NULL)||(enable==NULL))
{
return;
}
tmc->WriteRead=writeRead;
tmc->ChipSelcet=cs;
tmc->StartStop=startStop;
tmc->Direct=direct;
tmc->Enable=enable;
tmc->pStartStop=pStartStop;
tmc->pDirection=pDirection;
tmc->pRotateSet=pRotateSet;
tmc->pMotorState=pMotorState;
tmc->microStep=MicroStepNum[microStep];
tmc->stepAngle=(float)stepAngle/10.0;
tmc->curve.stepSpeed=0.02;
tmc->curve.currentSpeed=0;
tmc->curve.startSpeed=0;
tmc->curve.speedMax=300;
tmc->curve.speedMin=1.0;
tmc->curve.curveMode=CURVE_SPTA;
tmc->curve.flexible=10.0;
tmc->Register[Reg_DRVCTRL]=DRVCTRL;
tmc->Register[Reg_CHOPCONF]=CHOPCONF;
tmc->Register[Reg_SMARTEN]=SMARTEN;
tmc->Register[Reg_SGCSCONF]=SGCSCONF;
tmc->Register[Reg_DRVCONF]=DRVCONF;
tmc->Register[Reg_CHOPCONF]=tmc->Register[Reg_CHOPCONF]|0x1B1;
tmc->Register[Reg_SMARTEN]=tmc->Register[Reg_SMARTEN]|0x202;
tmc->Register[Reg_SGCSCONF]=tmc->Register[Reg_SGCSCONF]|0x10000;
WriteReadTmc2660Register(tmc,Reg_CHOPCONF);
WriteReadTmc2660Register(tmc,Reg_SGCSCONF);
if(interface==TMC2660_SPI)
{
tmc->Register[Reg_DRVCONF]=tmc->Register[Reg_DRVCONF]|0xA190;
WriteReadTmc2660Register(tmc,Reg_DRVCONF);
}
else
{
tmc->Register[Reg_DRVCONF]=tmc->Register[Reg_DRVCONF]|0xA140;
WriteReadTmc2660Register(tmc,Reg_DRVCONF);
tmc->Register[Reg_DRVCTRL]=tmc->Register[Reg_DRVCTRL]|0x100|MicroStep[microStep];
WriteReadTmc2660Register(tmc,Reg_DRVCTRL);
}
WriteReadTmc2660Register(tmc,Reg_SMARTEN);
SetMotorPower(tmc,Power);
}
接下来我们考虑对TMC2660进行的操作问题。我们已经知道TMC2660拥有5个寄存器,而对TMC2660的各种配置都是通过这5个寄存器来实现的。即使使用SD模式来实现电机驱动也是通过寄存器配置才能实现,所以对TMC2660基本的操作则是读写TMC2660寄存器。至于SD模式下,输入脉冲和方向信号依赖于具体平台,我们已将其定义为对象的回调函数。
/*读写寄存器*/
static void WriteReadTmc2660Register(TMC2660ObjectType *tmc,TMC2660RegType reg)
{
uint8_t wData[3];
uint8_t rData[3];
uint32_t status=0;
uint32_t regValue;
tmc->ChipSelcet(TMC2660CS_Enable);
regValue=tmc->Register[reg]&0xFFFFF;
wData[0]=(uint8_t)(regValue>>16);
wData[1]=(uint8_t)(regValue>>8);
wData[2]=(uint8_t)regValue;
tmc->WriteRead(wData,3,rData,3);
status=rData[0];
status=(status<<8)+rData[1];
status=(status<<8)+rData[2];
tmc->status= status;
tmc->ChipSelcet(TMC2660CS_Disable);
}
我们已经设计并实现了TMC2660步进电机驱动芯片的驱动程序,接下来我们实现一个实例来验证这一驱动设定是否符合要求。
在开始一切操作之前,首先我们需要一个对象。前面的设计中,我们已经定义了一个TMC2660对象类型,所以我们使用它定义一个对象变量。
TMC2660ObjectType tmc;
定义了tmc对象变量之后,还没有办法使用,因为我们需要对其进行初始化。前面我们已经设计了对象初始化函数,我们需要使用它来初始化tmc对象变量。初始化函数需要如下参数:
TMC2660ObjectType *tmc, //待初始化的TMC对象变量
TMC2660SdoffType interface, //驱动接口类型
TMC2660MicroStepType microStep, //微步设置
uint16_t Power, //电流量程
uint16_t stepAngle, //固有步进角
uint16_t *pStartStop, //启停操作命令
uint16_t *pDirection, //方向控制
uint16_t *pRotateSet, //转速设定
uint16_t *pMotorState, //电机状态
TMC2660WriteReadType writeRead, //读写函数指针
TMC2660ChipSelcetType cs, //片选操作函数指针
TMC2660StartStopType startStop, //启停操作函数指针
TMC2660DirectType direct, //方向设置函数指针
TMC2660EnableType enable //使能控制函数指针
在这些参数中,操作变量将具体的变量指针传入即可,而其它参数如接口类型,步距角等则根据具体的应用情况输入即可。需要注意的是,5歌操作函数指针,其函数原型定义如下:
typedef void (*TMC2660WriteReadType)(uint8_t *wData,uint16_t wSize,uint8_t *rData,uint16_t rSize);
typedef void (*TMC2660ChipSelcetType)(TMC2660CSType cs); //片选信号
typedef void (*TMC2660StartStopType)(TMC2660SSType ss); //启停操作函数
typedef void (*TMC2660DirectType)(TMC2660DIRType dir); //方向操作函数
typedef void (*TMC2660EnableType)(TMC2660ENNType enn); //使能操作函数
这些操作函数依赖于具体的操作平台,我们采用的是已于STM32F103CBT6和HAL库函数的操作平台,所以根据函数原型定义来收集这些函数如下:
/*TMC2660片选操作函数*/
static void TMC2660ChipSelcet(TMC2660CSType cs)
{
if(cs==TMC2660CS_Enable)
{
TMC_CSN_ENABLE();
}
else
{
TMC_CSN_DISABLE();
}
}
/*启停操作函数*/
static void MotorStartStop(TMC2660SSType ss)
{
if(ss==TMC2660SS_Start)
{
if(HAL_TIM_PWM_Start(&htim1, TIM_CHANNEL_3) != HAL_OK)
{
}
}
else
{
if(HAL_TIM_PWM_Stop(&htim1, TIM_CHANNEL_3) != HAL_OK)
{
}
}
}
/*方向操作函数*/
static void MotorDirect(TMC2660DIRType dir)
{
if(dir==TMC2660DIR_CCW)
{
TMC_DIR_DISABLE();
}
else
{
TMC_DIR_ENABLE();
}
}
/*使能操作函数*/
static void TMC2660Enable(TMC2660ENNType enn)
{
if(enn==TMC2660ENN_Enable)
{
TMC_ENN_ENABLE();
}
else
{
TMC_ENN_DISABLE();
}
}
/*通过SPI2端口读写数据*/
static void WriteReadBySPI2(uint8_t *wData,uint16_t wSize,uint8_t *rData,uint16_t rSize)
{
HAL_SPI_TransmitReceive (&hspi2, wData, rData, wSize, 1000);
}
至此,我们已经明白了初始化函数所需要的全部参数,我们可以使用改初始换函数初始化tmc对象变量如下:
/*初始化TMC2660对象*/
Tmc2660Initialization(&tmc,
TMC2660_SD,
MicroStep_256,
aPara.phyPara.sm42PowerRange,
aPara.phyPara.sm42StepAngle,
&aPara.phyPara.sm42StartStop,
&aPara.phyPara.sm42Direction,
&aPara.phyPara.sm42RotateSet,
&aPara.phyPara.sm42RunStatus,
WriteReadBySPI2,
TMC2660ChipSelcet,
MotorStartStop,
MotorDirect,
TMC2660Enable
);
初始化之后,我们就可以使用该对象来事项我们想要的操作了。我们设计一个应用函数调用相关驱动实现操作,并判断速度的设定是否改变来决定是否调整电机的运行速度。
/* 步进电机驱动控制处理函数 */
void SM42Tmc2660Driver(void)
{
float temp=0;
if(aPara.phyPara.sm42RotateSet<=0)
{
//return;
aPara.phyPara.sm42StartStop=0;
}
Tmc2660ControlBySD(&tmc);
SpeedSet(tmc.frequency);
//计算转速
temp=tmc.frequency*((float)aPara.phyPara.sm42StepAngle);
temp=temp/((float)aPara.phyPara.sm42MicroStep);
aPara.phyPara.sm42RotateSpeed=(uint16_t)(temp*100/30.0);
}
/* 速度调整函数 */
static void SpeedSet(float freq)
{
uint16_t period=0;
float temp=24000000;
if(freq>0)
{
temp=temp/freq;
period=(uint16_t)temp;
if((2<=period)&&(period<65535))
{
__HAL_TIM_SET_AUTORELOAD(&htim1,period-1);
__HAL_TIM_SET_COMPARE(&htim1,TIM_CHANNEL_3,period/2-1);
}
}
else
{
__HAL_TIM_SET_AUTORELOAD(&htim1,0);
__HAL_TIM_SET_COMPARE(&htim1,TIM_CHANNEL_3,0);
}
}
在本篇中,我们设计并实现了TMC2660的驱动程序,并基于驱动程序设计了一个验证程序,测试结果良好。事实上,该驱动已经使用到我们的多个项目之中,运行效果目前还是不错的。
在使用驱动程序时需要注意,片选信号并非必须实现。因为有些时候我们可能需要在硬件上直接将其选中,此时添加片选操作函数是没有什么意义的,我们可以在初始化时传入NULL来完成。
在配置TMC2660的寄存器时,一定要仔细根据自己的应用需求来配置,如电流保护、波形输出等这些参数的配置对力矩以及电机的运行噪声有很大关系,所以需要特别注意。