- 最近有一个项目需要用到数字舵机。和普通的pwm舵机直接用主机发送pwm波控制不同,数字舵机内部有一个小单片机,它直接控制数字舵机工作,主机通过向内部单片机发送指令间接控制舵机。
- 数字舵机功能强大,我用的这款XL430-W250-T舵机,其RAM区和ROM区一共有上百个参数可以自由读写,我们可以命令它在四种不同的运行模式间切换,甚至还能修改内部pid控制的参数。当然,这种舵机价格比较高,我这一款要三百多块
- 数字舵机一般使用半双工串口控制,给它编程,主要工作就是写一些数据帧收发、编码解码的函数
一、XL430-W250-T舵机
- 此舵机是ROBOTIS在2018年推出的一块数字舵机
- 舵机基本介绍
- 重点关注一下这个:
![在这里插入图片描述](https://i-blog.csdnimg.cn/blog_migrate/3a55e809956aa159e88550d9d6ddb0ee.png)
可见舵机使用TTL电平的半双工异步串行通信接口通信,而且是长8位,1停止位,无奇偶校验的
- 舵机详细介绍:重点关注里面
Control Table
部分内容,我们给舵机发的各种指令本质就是读写这个控制表 - 通信协议:这是此舵机使用的通信协议,我们要依据这个进行指令帧(Instruction Packet)编码和状态帧(Status Packet)解码
- 注意里面
Packet Process
部分介绍了逐位接收解析状态帧的方法,不过我使用的是DMA接受方法,直接收到整帧,和这部分说的有所不同
二、半双工串口配置
- stm32的串口支持半双工配置,可以参考我以前的这篇文章:stm32 USART串口半双工功能测试
- 经过一些测试,我发现不能用DMA方法给这个舵机发送数据,其内置单片机应该是用逐位解析的方法来收主机数据的,不过我们可以用DMA方法接受舵机数据。
- usart配置示例如下:
#define USART1_Rec_DMA
#define USART2_Rec_DMA
#define readOnly(x) x->CR1 |= 4; x->CR1 &= 0xFFFFFFF7;
#define sendOnly(x) x->CR1 |= 8; x->CR1 &= 0xFFFFFFFB;
void USART2_Half_Configuration(u32 buad)
{
GPIO_InitTypeDef GPIO_InitStructure;
USART_InitTypeDef USART_InitStructure;
NVIC_InitTypeDef NVIC_InitStructure;
RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOD,ENABLE);
RCC_APB1PeriphClockCmd(RCC_APB1Periph_USART2,ENABLE);
GPIO_PinAFConfig(GPIOD,GPIO_PinSource5,GPIO_AF_USART2);
GPIO_InitStructure.GPIO_OType = GPIO_OType_OD;
GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_UP;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF;
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_5;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOD, &GPIO_InitStructure);
USART_InitStructure.USART_BaudRate = buad;
USART_InitStructure.USART_WordLength = USART_WordLength_8b;
USART_InitStructure.USART_StopBits = USART_StopBits_1;
USART_InitStructure.USART_Parity = USART_Parity_No;
USART_InitStructure.USART_HardwareFlowControl = USART_HardwareFlowControl_None;
USART_InitStructure.USART_Mode = USART_Mode_Rx | USART_Mode_Tx;
USART_Init(USART2,&USART_InitStructure);
USART_HalfDuplexCmd(USART2, ENABLE);
NVIC_InitStructure.NVIC_IRQChannel = USART2_IRQn;
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority=3;
NVIC_InitStructure.NVIC_IRQChannelSubPriority =3;
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
NVIC_Init(&NVIC_InitStructure);
USART_ClearFlag(USART2, USART_FLAG_TC);
#ifndef USART2_Rec_DMA
USART_ITConfig(USART2, USART_IT_RXNE, ENABLE);
#else
USART_ITConfig(USART2, USART_IT_IDLE, ENABLE);
USART_DMACmd(USART2, USART_DMAReq_Rx, ENABLE);
USART_DMACmd(USART2, USART_DMAReq_Tx, ENABLE);
DMA_Config(DMA1_Stream5,DMA_Channel_4,
(uint32_t)&(USART2->DR),
(uint32_t)USART2_Rx_DMA_Buffer,
DMA_DIR_PeripheralToMemory,
USART2_RX_BUFFER_SIZE);
#endif
#ifdef USART2_Send_DMA
DMA_Config(DMA1_Stream6,DMA_Channel_4,
(uint32_t)&(USART2->DR),
(uint32_t)USART2_Tx_DMA_Buffer,
DMA_DIR_MemoryToPeripheral,
USART2_RX_BUFFER_SIZE);
#endif
USART_Cmd(USART2,ENABLE);
}
#ifdef USART2_Rec_DMA
long long errorCodeCnt_2[12];
void USART2_IRQHandler(void)
{
u8 errorCode;
uint8_t rc_tmp;
uint16_t rc_len;
USART_ClearITPendingBit(USART2,USART_IT_RXNE);
if(USART_GetITStatus(USART2,USART_IT_IDLE)!=RESET)
{
rc_tmp=USART2->SR;
rc_tmp=USART2->DR;
DMA_Cmd(DMA1_Stream5, DISABLE);
DMA_ClearITPendingBit(DMA1_Stream5, DMA_IT_TCIF5);
DMA_ClearITPendingBit(DMA1_Stream5, DMA_IT_TEIF5);
rc_len = USART2_RX_BUFFER_SIZE - DMA_GetCurrDataCounter(DMA1_Stream5);
errorCode=Inst_Decoding_Handler(USART2_Rx_DMA_Buffer,rc_len,&serverData[2]);
errorCodeCnt_2[errorCode]++;
DMA_Enable(DMA1_Stream5,USART2_RX_BUFFER_SIZE);
}
serverData[2].busy=0;
}
#else
u8 cnt=0;
u8 rec[200];
void USART2_IRQHandler(void)
{
if(USART_GetITStatus(USART2,USART_IT_RXNE))
{
rec[cnt++] = USART_ReceiveData(USART2);
}
}
#endif
void DMA_Config(DMA_Stream_TypeDef *DMA_Streamx,uint32_t chx,uint32_t par,uint32_t mar,uint32_t dir,u16 ndtr)
{
DMA_InitTypeDef DMA_InitStructure;
if((u32)DMA_Streamx>(u32)DMA2)
{
RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_DMA2,ENABLE);
}else
{
RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_DMA1,ENABLE);
}
DMA_DeInit(DMA_Streamx);
while (DMA_GetCmdStatus(DMA_Streamx) != DISABLE){}
DMA_InitStructure.DMA_Channel = chx;
DMA_InitStructure.DMA_PeripheralBaseAddr = par;
DMA_InitStructure.DMA_Memory0BaseAddr = mar;
DMA_InitStructure.DMA_DIR = dir;
DMA_InitStructure.DMA_BufferSize = ndtr;
DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable;
DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable;
DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_Byte;
DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_Byte;
DMA_InitStructure.DMA_Mode = DMA_Mode_Normal;
DMA_InitStructure.DMA_Priority = DMA_Priority_High;
DMA_InitStructure.DMA_FIFOMode = DMA_FIFOMode_Disable;
DMA_InitStructure.DMA_FIFOThreshold = DMA_FIFOThreshold_Full;
DMA_InitStructure.DMA_MemoryBurst = DMA_MemoryBurst_Single;
DMA_InitStructure.DMA_PeripheralBurst = DMA_PeripheralBurst_Single;
DMA_Init(DMA_Streamx, &DMA_InitStructure);
DMA_Cmd(DMA_Streamx,ENABLE);
}
void DMA_Enable(DMA_Stream_TypeDef *DMA_Streamx,u16 ndtr)
{
DMA_Cmd(DMA_Streamx, DISABLE);
while (DMA_GetCmdStatus(DMA_Streamx) != DISABLE){}
DMA_SetCurrDataCounter(DMA_Streamx,ndtr);
DMA_Cmd(DMA_Streamx, ENABLE);
}
三、指令帧编码和数据帧解码
server.c
提供舵机通信数据编码和解码方法- 主要就是按照通信协议来处理数据
- server.h
#ifndef __SERVER_H
#define __SERVER_H
#include "sys.h"
#include "queue.h"
#define BroadcastID 0xFE
#define readBufMax 100
#define Velocity_Control_Mode 1
#define Position_Control_Mode 3
#define Extended_Position_Control_Mode 4
#define PWM_Control_Mode 16
#define Inst_Ping 0x01
#define Inst_Read 0x02
#define Inst_Write 0x03
#define Inst_RegWrite 0x04
#define Inst_Action 0x05
#define Inst_Reset 0x06
#define Inst_Reboot 0x08
#define Inst_Clear 0x10
#define Inst_Status 0x55
#define Inst_SyncRead 0x82
#define Inst_SyncWrite 0x83
#define Inst_BulkRead 0x92
#define Inst_BulkWrite 0x93
#define Inst_Ping_Len 10
#define Inst_Read_Len 14
#define Inst_Action_Len 10
#define DC_Success 0x00
#define DC_ResultFail 0x01
#define DC_InstructionError 0x02
#define DC_CRCError 0x03
#define DC_DataRangeError 0x04
#define DC_DataLenError 0x05
#define DC_DataLimitError 0x06
#define DC_AccessError 0x07
#define DC_MyHeadError 0x08
#define DC_MyLenError 0x09
#define DC_MyIDError 0x0A
#define DC_MyCRCError 0x0B
typedef struct EEPROM
{
u8 Model_Number[2];
u8 Model_Information[4];
u8 Firmware_Version;
u8 ID;
u8 Baud_Rate;
u8 Return_Delay_Time;
u8 Drive_Mode;
u8 Operating_Mode;
u8 Secondary_ID;
u8 Protocol_Type;
u8 RSRV1[6];
u8 Homing_Offset[4];
u8 Moving_Threshold[4];
u8 RSRV2[3];
u8 Temperature_Limit;
u8 Max_Voltage_Limit[2];
u8 Min_Voltage_Limit[2];
u8 PWM_Limit[2];
u8 RSRV3[6];
u8 Velocity_Limit[4];
u8 Max_Position_Limit[4];
u8 Min_Position_Limit[4];
u8 RSRV4[7];
u8 Shutdown;
}EEPROM;
typedef struct RAM
{
u8 Torque_Enable;
u8 LED;
u8 RSRV1[2];
u8 Status_Return_Level;
u8 Registered_Instruction;
u8 Hardware_Error_Status;
u8 RSRV2[5];
u8 Velocity_I_Gain[2];
u8 Velocity_P_Gain[2];
u8 Position_D_Gain[2];
u8 Position_I_Gain[2];
u8 Position_P_Gain[2];
u8 RSRV3[2];
u8 Feedforward_2nd_Gain[2];
u8 Feedforward_1st_Gain[2];
u8 RSRV4[7];
u8 Bus_Watchdog;
u8 Goal_PWM[2];
u8 RSRV5[2];
u8 Goal_Velocity[4];
u8 Profile_Acceleration[4];
u8 Profile_Velocity[4];
u8 Goal_Position[4];
u8 Realtime_Tick[2];
u8 Moving;
u8 Moving_Status;
u8 Present_PWM[2];
u8 Present_Load[2];
u8 Present_Velocity[4];
u8 Present_Position[4];
u8 Velocity_Trajectory[4];
u8 Position_Trajectory[4];
u8 Present_Input_Voltage[2];
u8 Present_Temperature;
}RAM;
typedef struct CONTROL_TABLE
{
EEPROM rom;
RAM ram;
}CONTROL_TABLE;
typedef struct RWINST
{
u16 RWAddr;
u16 RWLen;
u16 frameLen;
}RWINST;
typedef struct PINGINFO
{
u16 ModelNum;
u8 FirmwareVersion;
}PINGINFO;
typedef struct SERVERDATA
{
u8 inst;
u8 ID;
u8 recLen;
u8 IDInit;
u8 *writeBuf;
u8 busy;
u8 lock;
USART_TypeDef* USARTx;
RRQUEUE *rrQueue;
RWINST RWInstPara;
PINGINFO PingInfo;
CONTROL_TABLE ctrlTable;
}SERVERDATA;
extern u8 instBuf[INSTBUFLEN];
extern SERVERDATA serverData[5];
extern u8 write2CtrlTable[5][100];
u16 dataAssmeble(u8 data_L,u8 data_H);
void dataSplit(u16 data,u8 *data_L,u8 *data_H);
u32 dataAssmeble_32(u8 *buf);
void dataSplit_32(u32 data,u8 *buf);
void serverDataInit(void);
void readPara(SERVERDATA *sd,u16 addr,u16 len);
void writePara(SERVERDATA *sd,u16 addr,u16 len,u8 *paraBuf);
void PingCode(SERVERDATA *sd);
u8 Inst_Decoding_Handler(u8 *buf,u8 reclen,SERVERDATA *sd);
void Inst_Sending_Handler(u8 inst,SERVERDATA *sd);
void deQueue(SERVERDATA *sd,u8 *buf,u8 len);
#endif
- server.c
#include "server.h"
#include "crc.h"
#include "usart.h"
SERVERDATA serverData[5];
u8 instBuf[INSTBUFLEN];
u8 write2CtrlTable[5][100];
u16 dataAssmeble(u8 data_L,u8 data_H)
{
u16 temp=(data_H<<8)|data_L;
return temp;
}
void dataSplit(u16 data,u8 *data_L,u8 *data_H)
{
*data_L = data & 0xFF;
*data_H = (data & 0xFF00)>>8;
}
void dataSplit_32(u32 data,u8 *buf)
{
u32 temp=0xFF;
for(int i=0;i<4;i++)
buf[i]=(data & temp<<8*i)>>8*i;
}
u32 dataAssmeble_32(u8 *buf)
{
u32 temp=buf[3];
for(int i=2;i>=0;i--)
temp=(temp<<8)|buf[i];
return temp;
}
void clearInstBuf()
{
for(int i=0;i<INSTBUFLEN;i++)
instBuf[i]=0;
}
void serverDataInit()
{
initQueue();
for(int i=1;i<=4;i++)
{
serverData[i].IDInit=1;
serverData[i].writeBuf=write2CtrlTable[i];
serverData[i].busy=0;
serverData[i].rrQueue = &queue[i];
}
serverData[1].USARTx=USART1;
serverData[2].USARTx=USART2;
serverData[3].USARTx=USART3;
serverData[4].USARTx=UART4;
serverData[2].ID=BroadcastID;
sendOnly(USART2);
PingCode(&serverData[2]);
sendBuf(USART2,instBuf,10);
readOnly(USART2);
}
void setHead()
{
instBuf[0]=0xFF;
instBuf[1]=0xFF;
instBuf[2]=0xFD;
instBuf[3]=0x00;
}
u8 cheakHead(u8 *buf)
{
if(buf[0]!=0xFF || buf[1]!=0xFF || buf[2]!=0xFD || buf[3]!=0)
return 0;
return 1;
}
u8 cheakLen(u8 *buf,u8 len)
{
u16 L=dataAssmeble(buf[5],buf[6]);
if(len!=L)
return 0;
return 1;
}
u8 cheakCRC(u8 *buf,u8 size)
{
u16 crc=update_crc(0,buf,size);
if(buf[size]!=(crc&0xFF) || buf[size+1]!=((crc>>8)&0xFF))
return 0;
return 1;
}
void readPara(SERVERDATA *sd,u16 addr,u16 len)
{
sd->RWInstPara.RWAddr=addr;
sd->RWInstPara.RWLen=len;
}
void writePara(SERVERDATA *sd,u16 addr,u16 len,u8 *paraBuf)
{
sd->RWInstPara.RWAddr=addr;
sd->RWInstPara.RWLen=len;
copyBuf(paraBuf,sd->writeBuf,len);
}
void PingCode(SERVERDATA *sd)
{
setHead();
instBuf[4]=sd->ID;
instBuf[5]=0x03;
instBuf[6]=0x00;
instBuf[7]=Inst_Ping;
setCrc(instBuf,8);
sd->RWInstPara.frameLen=Inst_Ping_Len;
}
void ReadCode(SERVERDATA *sd)
{
setHead();
instBuf[4]=sd->ID;
instBuf[5]=0x07;
instBuf[6]=0x00;
instBuf[7]=Inst_Read;
dataSplit(sd->RWInstPara.RWAddr,&instBuf[8],&instBuf[9]);
dataSplit(sd->RWInstPara.RWLen,&instBuf[10],&instBuf[11]);
setCrc(instBuf,12);
sd->RWInstPara.frameLen=Inst_Read_Len;
}
void WriteCode(SERVERDATA *sd,u8 regFlag)
{
setHead();
instBuf[4]=sd->ID;
u16 len=sd->RWInstPara.RWLen+2+3;
dataSplit(len,&instBuf[5],&instBuf[6]);
if(regFlag)
instBuf[7]=Inst_RegWrite;
else
instBuf[7]=Inst_Write;
dataSplit(sd->RWInstPara.RWAddr,&instBuf[8],&instBuf[9]);
copyBuf(sd->writeBuf,&instBuf[10],sd->RWInstPara.RWLen);
setCrc(instBuf,len+5);
sd->RWInstPara.frameLen=len+7;
}
void ActionCode(SERVERDATA *sd)
{
setHead();
instBuf[4]=sd->ID;
instBuf[5]=0x03;
instBuf[6]=0x00;
instBuf[7]=Inst_Action;
setCrc(instBuf,8);
sd->RWInstPara.frameLen=Inst_Action_Len;
}
void Inst_Sending_Handler(u8 inst,SERVERDATA *sd)
{
sd->inst=inst;
switch(inst)
{
case Inst_Ping: PingCode(sd); break;
case Inst_Read: ReadCode(sd); break;
case Inst_Write: WriteCode(sd,0); break;
case Inst_RegWrite: WriteCode(sd,1); break;
case Inst_Action: ActionCode(sd); break;
case Inst_Reset: break;
case Inst_Reboot: break;
case Inst_Clear: break;
case Inst_Status: break;
case Inst_SyncRead: break;
case Inst_SyncWrite: break;
case Inst_BulkRead: break;
case Inst_BulkWrite: break;
}
enQueue(sd->rrQueue,instBuf,sd->RWInstPara.frameLen);
}
void PindDecode(u8 *buf,SERVERDATA *sd)
{
sd->PingInfo.ModelNum=dataAssmeble(buf[9],buf[10]);
sd->PingInfo.FirmwareVersion=buf[11];
}
void ReadDecode(u8 *buf,SERVERDATA *sd)
{
u16 len=dataAssmeble(buf[5],buf[6]);
u8 *p=(u8 *)(&sd->ctrlTable)+sd->RWInstPara.RWAddr;
for(int i=0;i<len-4;i++)
p[i]=buf[9+i];
}
void WriteDecode()
{
;
}
void RegWriteDecode()
{
;
}
void ActionDecode()
{
;
}
u8 Inst_Decoding_Handler(u8 *buf,u8 reclen,SERVERDATA *sd)
{
if(!cheakHead(buf))
return DC_MyHeadError;
if(!cheakLen(buf,reclen-7))
return DC_MyLenError;
if(!cheakCRC(buf,reclen-2))
return DC_MyCRCError;
if(buf[8]!=0)
return buf[8];
if(sd->IDInit)
{
sd->ID=buf[4];
sd->IDInit=0;
return DC_Success;
}
else
{
if(buf[4]!=sd->ID)
return DC_MyIDError;
switch(sd->inst)
{
case Inst_Ping: PindDecode(buf,sd); break;
case Inst_Read: ReadDecode(buf,sd); break;
case Inst_Write: WriteDecode(); break;
case Inst_RegWrite: RegWriteDecode(); break;
case Inst_Action: ActionDecode(); break;
case Inst_Reset: break;
case Inst_Reboot: break;
case Inst_Clear: break;
case Inst_Status: break;
case Inst_SyncRead: break;
case Inst_SyncWrite: break;
case Inst_BulkRead: break;
case Inst_BulkWrite: break;
}
return DC_Success;
}
}
四、指令循环队列
- 在测试中,我发现每一条指令从主机发送指令帧到主机接受到状态帧耗时挺长的,如果还没收到上一条指令返回数据就发送下一条指令,有可能出现问题。因此我把通信做成了阻塞式的。必须收到前一条指令的返回数据,才能发送下一条指令。
- 为此我设置了一个循环队列。每一条指令帧编码完成后,先存入循环队列(上一部分在
Inst_Sending_Handler
函数最后就是指令入队位置),设置一个定时器中断循环检测循环队列和舵机通信情况,当条件满足时出队并发送指令 queue.h
定义了循环队列的数据结构
#ifndef __QUEUE_H
#define __QUEUE_H
#include "sys.h"
#define queueLen 10
#define INSTBUFLEN 30
typedef struct QINST
{
u8 buf[INSTBUFLEN];
u16 readAddr;
u16 readLen;
u8 instLen;
}QINST;
typedef struct RRQUEUE
{
u8 head;
u8 tail;
QINST qinst[queueLen];
}RRQUEUE;
extern RRQUEUE queue[5];
void copyBuf(u8 *from,u8 *to,u8 len);
void initQueue(void);
void enQueue(RRQUEUE *q,u8 *buf,u8 len);
u8 queueEmpty(RRQUEUE *q);
#endif
queue.c
提供了循环队列操作
#include "queue.h"
#include "server.h"
RRQUEUE queue[5];
void copyBuf(u8 *from,u8 *to,u8 len)
{
for(int i=0;i<len;i++)
to[i]=from[i];
}
void initQueue()
{
for(int i=1;i<=4;i++)
queue[i].head=queue[i].tail=0;
}
void enQueue(RRQUEUE *q,u8 *buf,u8 len)
{
u8 tailTemp=q->tail;
tailTemp++;
if(tailTemp == queueLen)
tailTemp=0;
if(tailTemp == q->head)
return;
copyBuf(buf,q->qinst[q->tail].buf,len);
switch(buf[7])
{
case Inst_Read:
q->qinst[q->tail].readAddr = dataAssmeble(buf[8],buf[9]);
q->qinst[q->tail].readLen = dataAssmeble(buf[10],buf[11]); break;
}
q->qinst[q->tail].instLen = len;
q->tail=tailTemp;
}
void deQueue(SERVERDATA *sd,u8 *buf,u8 len)
{
RRQUEUE *q = sd->rrQueue;
copyBuf(q->qinst[q->head].buf,buf,len);
u8 headTemp=q->head;
headTemp++;
if(headTemp==queueLen)
headTemp=0;
q->head=headTemp;
switch(buf[7])
{
case Inst_Read:
sd->RWInstPara.RWAddr = dataAssmeble(buf[8],buf[9]);
sd->RWInstPara.RWLen = dataAssmeble(buf[10],buf[11]); break;
}
}
u8 queueEmpty(RRQUEUE *q)
{
u8 res = q->head == q->tail ? 1:0;
return res;
}
- 定时器中断
void TIM3_IRQHandler(void)
{
if(TIM_GetITStatus(TIM3,TIM_IT_Update)==SET)
{
for(int i=1;i<=4;i++)
{
if(!serverData[i].busy && !serverData[i].lock && !queueEmpty(serverData[i].rrQueue))
{
u8 headPos = serverData[i].rrQueue->head;
len = serverData[i].rrQueue->qinst[headPos].instLen;
deQueue(&serverData[i],instTemp,len);
serverData[i].busy=1;
USART_TypeDef* USARTx = serverData[i].USARTx;
sendOnly(USARTx);
sendBuf(USARTx,instTemp,len);
readOnly(USARTx);
}
}
}
TIM_ClearITPendingBit(TIM3,TIM_IT_Update);
}
五、完整代码下载
下载完整代码