Important changes to forums and questions
All forums and questions are now archived. To start a new conversation or read the latest updates go to forums.mbed.com.
10 years, 10 months ago.
Help port Dynamixel library for the community
Hello all,
I dabble in robotics and often use Dynamixel servos. I've used Arbotix controllers, the Robotic CM9.04, and a Raspberry Pi using the Robotics Dynamixel libraries. Unforunately, there are no libraries explicitly for the mbed platform. There is an AX12 library floating around here, but it is rather limited. Fortunately, Robotis supplies sort of a library template so that you can adapt their C libraries to your own platform.
http://support.robotis.com/en/software/dynamixel_sdk/sourcestructure.htm
It boils down to the one low level function file in the library where you must fill in your platform's method for doing the given functions.
// Dynamixel SDK platform dependent source #include "dxl_hal.h" int dxl_hal_open( int devIndex, float baudrate ) { // Opening device // devIndex: Device index // baudrate: Real baudrate (ex> 115200, 57600, 38400...) // Return: 0(Failed), 1(Succeed) } void dxl_hal_close() { // Closing device } void dxl_hal_clear(void) { // Clear communication buffer } int dxl_hal_tx( unsigned char *pPacket, int numPacket ) { // Transmiting date // *pPacket: data array pointer // numPacket: number of data array // Return: number of data transmitted. -1 is error. } int dxl_hal_rx( unsigned char *pPacket, int numPacket ) { // Recieving date // *pPacket: data array pointer // numPacket: number of data array // Return: number of data recieved. -1 is error. } void dxl_hal_set_timeout( int NumRcvByte ) { // Start stop watch // NumRcvByte: number of recieving data(to calculate maximum waiting time) } int dxl_hal_timeout(void) { // Check timeout // Return: 0 is false, 1 is true(timeout occurred) }
As an example, here is the same file for their linux library:
#include <stdio.h> #include <string.h> #include <unistd.h> #include <fcntl.h> #include <termios.h> #include <linux/serial.h> #include <sys/ioctl.h> #include <sys/time.h> #include "dxl_hal.h" int gSocket_fd = -1; long glStartTime = 0; float gfRcvWaitTime = 0.0f; float gfByteTransTime = 0.0f; char gDeviceName[20]; int dxl_hal_open(int deviceIndex, float baudrate) { struct termios newtio; struct serial_struct serinfo; char dev_name[100] = {0, }; sprintf(dev_name, "/dev/ttyUSB%d", deviceIndex); strcpy(gDeviceName, dev_name); memset(&newtio, 0, sizeof(newtio)); dxl_hal_close(); if((gSocket_fd = open(gDeviceName, O_RDWR|O_NOCTTY|O_NONBLOCK)) < 0) { fprintf(stderr, "device open error: %s\n", dev_name); goto DXL_HAL_OPEN_ERROR; } newtio.c_cflag = B38400|CS8|CLOCAL|CREAD; newtio.c_iflag = IGNPAR; newtio.c_oflag = 0; newtio.c_lflag = 0; newtio.c_cc[VTIME] = 0; // time-out 값 (TIME * 0.1초) 0 : disable newtio.c_cc[VMIN] = 0; // MIN 은 read 가 return 되기 위한 최소 문자 개수 tcflush(gSocket_fd, TCIFLUSH); tcsetattr(gSocket_fd, TCSANOW, &newtio); if(gSocket_fd == -1) return 0; if(ioctl(gSocket_fd, TIOCGSERIAL, &serinfo) < 0) { fprintf(stderr, "Cannot get serial info\n"); return 0; } serinfo.flags &= ~ASYNC_SPD_MASK; serinfo.flags |= ASYNC_SPD_CUST; serinfo.custom_divisor = serinfo.baud_base / baudrate; if(ioctl(gSocket_fd, TIOCSSERIAL, &serinfo) < 0) { fprintf(stderr, "Cannot set serial info\n"); return 0; } dxl_hal_close(); gfByteTransTime = (float)((1000.0f / baudrate) * 12.0f); strcpy(gDeviceName, dev_name); memset(&newtio, 0, sizeof(newtio)); dxl_hal_close(); if((gSocket_fd = open(gDeviceName, O_RDWR|O_NOCTTY|O_NONBLOCK)) < 0) { fprintf(stderr, "device open error: %s\n", dev_name); goto DXL_HAL_OPEN_ERROR; } newtio.c_cflag = B38400|CS8|CLOCAL|CREAD; newtio.c_iflag = IGNPAR; newtio.c_oflag = 0; newtio.c_lflag = 0; newtio.c_cc[VTIME] = 0; // time-out 값 (TIME * 0.1초) 0 : disable newtio.c_cc[VMIN] = 0; // MIN 은 read 가 return 되기 위한 최소 문자 개수 tcflush(gSocket_fd, TCIFLUSH); tcsetattr(gSocket_fd, TCSANOW, &newtio); return 1; DXL_HAL_OPEN_ERROR: dxl_hal_close(); return 0; } void dxl_hal_close() { if(gSocket_fd != -1) close(gSocket_fd); gSocket_fd = -1; } int dxl_hal_set_baud( float baudrate ) { struct serial_struct serinfo; if(gSocket_fd == -1) return 0; if(ioctl(gSocket_fd, TIOCGSERIAL, &serinfo) < 0) { fprintf(stderr, "Cannot get serial info\n"); return 0; } serinfo.flags &= ~ASYNC_SPD_MASK; serinfo.flags |= ASYNC_SPD_CUST; serinfo.custom_divisor = serinfo.baud_base / baudrate; if(ioctl(gSocket_fd, TIOCSSERIAL, &serinfo) < 0) { fprintf(stderr, "Cannot set serial info\n"); return 0; } //dxl_hal_close(); //dxl_hal_open(gDeviceName, baudrate); gfByteTransTime = (float)((1000.0f / baudrate) * 12.0f); return 1; } void dxl_hal_clear(void) { tcflush(gSocket_fd, TCIFLUSH); } int dxl_hal_tx( unsigned char *pPacket, int numPacket ) { return write(gSocket_fd, pPacket, numPacket); } int dxl_hal_rx( unsigned char *pPacket, int numPacket ) { memset(pPacket, 0, numPacket); return read(gSocket_fd, pPacket, numPacket); } static inline long myclock() { struct timeval tv; gettimeofday (&tv, NULL); return (tv.tv_sec * 1000 + tv.tv_usec / 1000); } void dxl_hal_set_timeout( int NumRcvByte ) { glStartTime = myclock(); gfRcvWaitTime = (float)(gfByteTransTime*(float)NumRcvByte + 5.0f); } int dxl_hal_timeout(void) { long time; time = myclock() - glStartTime; if(time > gfRcvWaitTime) return 1; else if(time < 0) glStartTime = myclock(); return 0; }
So far this is what I've filled in. Please tell me how far off base I am (i filled in a bunch of return 1s to satisfy the compiler):
// Dynamixel SDK platform dependent source #include "dxl_hal.h" #include "mbed.h" int dxl_hal_open( int devIndex, float baudrate ) { // Opening device // devIndex: Device index // baudrate: Real baudrate (ex> 115200, 57600, 38400...) // Return: 0(Failed), 1(Succeed) Serial dxl(P0_0, P0_1); // UART3 TX, RX for LPC4088 dxl.baud(1000000); return 1; } void dxl_hal_close() { // Closing device } void dxl_hal_clear(void) { // Clear communication buffer //DAN's function used elsewhere. proper? char dumpster = 0; while (dxl.readable()) { dumpster = dxl.getc(); } } int dxl_hal_tx( unsigned char *pPacket, int numPacket ) { // Transmiting date // *pPacket: data array pointer // numPacket: number of data array // Return: number of data transmitted. -1 is error. //dxl.getc() or some sort //DAN return 1; } int dxl_hal_rx( unsigned char *pPacket, int numPacket ) { // Recieving date // *pPacket: data array pointer // numPacket: number of data array // Return: number of data recieved. -1 is error. //dxl.getc() of some sort. //DAN return 1; } void dxl_hal_set_timeout( int NumRcvByte ) { // Start stop watch // NumRcvByte: number of recieving data(to calculate maximum waiting time) } int dxl_hal_timeout(void) { // Check timeout // Return: 0 is false, 1 is true(timeout occurred) //Time function? //DAN }
If you guys would like to chip in and knock out a function or two i'd much appreciate it. I'll thoroughly test things and bundle it up and submit it to the repository for all to use. Thanks!
2 Answers
10 years, 10 months ago.
Serial dxl should be global otherwise the different functions cant access that object. You need getc() or putc() to send or receive bytes and you may want to add timeouts to avoid blocking.
// Dynamixel SDK platform dependent source #include "dxl_hal.h" #include "mbed.h" Serial dxl(P0_0, P0_1); // Global, UART3 TX, RX for LPC4088 int dxl_hal_open( int devIndex, float baudrate ) { // Opening device // devIndex: Device index // baudrate: Real baudrate (ex> 115200, 57600, 38400...) // Return: 0(Failed), 1(Succeed) // Serial dxl(P0_0, P0_1); // Global, UART3 TX, RX for LPC4088 // dxl.baud(1000000); dxl.baud(baudrate); /use the parameter here return 1; } void dxl_hal_close() { // Closing device } void dxl_hal_clear(void) { // Clear communication buffer //DAN's function used elsewhere. proper? char dumpster = 0; while (dxl.readable()) { dumpster = dxl.getc(); } // A call to dxl.flush() may be better } int dxl_hal_tx( unsigned char *pPacket, int numPacket ) { // Transmiting date // *pPacket: data array pointer // numPacket: number of data array // Return: number of data transmitted. -1 is error. //dxl.getc() or some sort //DAN int i; for (i=0; i<numPacket; i++) { while (!dxl.writable()); // wait until you can write, may want to add timeout here dxl.putc(pPacket[i]); //write } return numPacket; // or -1 when there is timeout } int dxl_hal_rx( unsigned char *pPacket, int numPacket ) { // Recieving date // *pPacket: data array pointer // numPacket: number of data array // Return: number of data recieved. -1 is error. //dxl.getc() of some sort. //DAN int i; for (i=0; i<numPacket; i++) { while (!dxl.readable()); // wait until you can read, may want to add timeout here pPacket[i] = dxl.getc(); // read } return numPacket; // or -1 when there is timeout } void dxl_hal_set_timeout( int NumRcvByte ) { // Start stop watch // NumRcvByte: number of recieving data(to calculate maximum waiting time) } int dxl_hal_timeout(void) { // Check timeout // Return: 0 is false, 1 is true(timeout occurred) //Time function? //DAN }
10 years, 10 months ago.
Thanks for your input. I've incorporated iti and added some more with questions:
dxl_hal.cpp:
// Dynamixel SDK platform dependent source #include "dxl_hal.h" #include "mbed.h" Serial dxl(P0_0, P0_1); // UART3 TX, RX for LPC4088 Timer t; float gfRcvWaitTime = 0.0f; float gfByteTransTime = 0.0f; int dxl_hal_open( int devIndex, float baudrate ) { // Opening device // devIndex: Device index // baudrate: Real baudrate (ex> 115200, 57600, 38400...) // Return: 0(Failed), 1(Succeed) //it'd be cool to be able to select UARTs by parameter, but too many different device configurations... dxl.baud(1000000); //is there a way to check serial initialization health/status for return? return 1; } void dxl_hal_close() { // Closing device //is there a close method for Serial class? } void dxl_hal_clear(void) { // Clear communication buffer //dxl.flush(); //.flush() method doesn't exist char dumpster; while (dxl.readable()) { dumpster = dxl.getc(); } t.reset(); // i think they expect a reset on a dxl_hal_clear() t.stop(); } int dxl_hal_tx( unsigned char *pPacket, int numPacket ) { // Transmiting date // *pPacket: data array pointer // numPacket: number of data array // Return: number of data transmitted. -1 is error. int i; for (i=0; i<numPacket; i++) { while ( !dxl.writeable() ); // wait until you can write, may want to add timeout here dxl.putc(pPacket[i]); //write } return numPacket; } int dxl_hal_rx( unsigned char *pPacket, int numPacket ) { // Recieving date // *pPacket: data array pointer // numPacket: number of data array // Return: number of data recieved. -1 is error. int i; for (i=0; i<numPacket; i++) { while ( !dxl.readable() ); // wait until you can read, may want to add timeout here pPacket[i] = dxl.getc(); // read } //this assuming the number of data to be read matches the expected number to be read (numPacket). //should i read all available data and compare its length to numPacket? Looks like they want me to //return the actual number of data received. if they don't match, is that -1 error? return numPacket; //should i return expected or acutal number of data received? } void dxl_hal_set_timeout( int NumRcvByte ) { // Start stop watch // NumRcvByte: number of recieving data(to calculate maximum waiting time) t.start(); //start timer gfRcvWaitTime = (float)(gfByteTransTime*(float)NumRcvByte + 5.0f); // assuming units of (us), copied from linux SDK } int dxl_hal_timeout(void) { // Check timeout // Return: 0 is false (no timeout), 1 is true(timeout occurred) if(t.read_us() > gfRcvWaitTime){ //I'm assuming wait gfRcvWaitTime and gfByteTransTime are in units of (us) return 1; // timeout! } else return 0; }
Also for reference, here is the higher level file that calls dxl_hal.cpp, dynamixel.cpp:
#include "dxl_hal.h" #include "dynamixel.h" #define ID (2) #define LENGTH (3) #define INSTRUCTION (4) #define ERRBIT (4) #define PARAMETER (5) #define DEFAULT_BAUDNUMBER (1) unsigned char gbInstructionPacket[MAXNUM_TXPARAM+10] = {0}; unsigned char gbStatusPacket[MAXNUM_RXPARAM+10] = {0}; unsigned char gbRxPacketLength = 0; unsigned char gbRxGetLength = 0; int gbCommStatus = COMM_RXSUCCESS; int giBusUsing = 0; int dxl_initialize( int devIndex, int baudnum ) { float baudrate; baudrate = 2000000.0f / (float)(baudnum + 1); if( dxl_hal_open(devIndex, baudrate) == 0 ) return 0; gbCommStatus = COMM_RXSUCCESS; giBusUsing = 0; return 1; } void dxl_terminate() { dxl_hal_close(); } void dxl_tx_packet() { unsigned char i; unsigned char TxNumByte, RealTxNumByte; unsigned char checksum = 0; if( giBusUsing == 1 ) return; giBusUsing = 1; if( gbInstructionPacket[LENGTH] > (MAXNUM_TXPARAM+2) ) { gbCommStatus = COMM_TXERROR; giBusUsing = 0; return; } if( gbInstructionPacket[INSTRUCTION] != INST_PING && gbInstructionPacket[INSTRUCTION] != INST_READ && gbInstructionPacket[INSTRUCTION] != INST_WRITE && gbInstructionPacket[INSTRUCTION] != INST_REG_WRITE && gbInstructionPacket[INSTRUCTION] != INST_ACTION && gbInstructionPacket[INSTRUCTION] != INST_RESET && gbInstructionPacket[INSTRUCTION] != INST_SYNC_WRITE ) { gbCommStatus = COMM_TXERROR; giBusUsing = 0; return; } gbInstructionPacket[0] = 0xff; gbInstructionPacket[1] = 0xff; for( i=0; i<(gbInstructionPacket[LENGTH]+1); i++ ) checksum += gbInstructionPacket[i+2]; gbInstructionPacket[gbInstructionPacket[LENGTH]+3] = ~checksum; if( gbCommStatus == COMM_RXTIMEOUT || gbCommStatus == COMM_RXCORRUPT ) dxl_hal_clear(); TxNumByte = gbInstructionPacket[LENGTH] + 4; RealTxNumByte = dxl_hal_tx( (unsigned char*)gbInstructionPacket, TxNumByte ); if( TxNumByte != RealTxNumByte ) { gbCommStatus = COMM_TXFAIL; giBusUsing = 0; return; } if( gbInstructionPacket[INSTRUCTION] == INST_READ ) dxl_hal_set_timeout( gbInstructionPacket[PARAMETER+1] + 6 ); else dxl_hal_set_timeout( 6 ); gbCommStatus = COMM_TXSUCCESS; } void dxl_rx_packet() { unsigned char i, j, nRead; unsigned char checksum = 0; if( giBusUsing == 0 ) return; if( gbInstructionPacket[ID] == BROADCAST_ID ) { gbCommStatus = COMM_RXSUCCESS; giBusUsing = 0; return; } if( gbCommStatus == COMM_TXSUCCESS ) { gbRxGetLength = 0; gbRxPacketLength = 6; } nRead = dxl_hal_rx( (unsigned char*)&gbStatusPacket[gbRxGetLength], gbRxPacketLength - gbRxGetLength ); gbRxGetLength += nRead; if( gbRxGetLength < gbRxPacketLength ) { if( dxl_hal_timeout() == 1 ) { if(gbRxGetLength == 0) gbCommStatus = COMM_RXTIMEOUT; else gbCommStatus = COMM_RXCORRUPT; giBusUsing = 0; return; } } // Find packet header for( i=0; i<(gbRxGetLength-1); i++ ) { if( gbStatusPacket[i] == 0xff && gbStatusPacket[i+1] == 0xff ) { break; } else if( i == gbRxGetLength-2 && gbStatusPacket[gbRxGetLength-1] == 0xff ) { break; } } if( i > 0 ) { for( j=0; j<(gbRxGetLength-i); j++ ) gbStatusPacket[j] = gbStatusPacket[j + i]; gbRxGetLength -= i; } if( gbRxGetLength < gbRxPacketLength ) { gbCommStatus = COMM_RXWAITING; return; } // Check id pairing if( gbInstructionPacket[ID] != gbStatusPacket[ID]) { gbCommStatus = COMM_RXCORRUPT; giBusUsing = 0; return; } gbRxPacketLength = gbStatusPacket[LENGTH] + 4; if( gbRxGetLength < gbRxPacketLength ) { nRead = dxl_hal_rx( (unsigned char*)&gbStatusPacket[gbRxGetLength], gbRxPacketLength - gbRxGetLength ); gbRxGetLength += nRead; if( gbRxGetLength < gbRxPacketLength ) { gbCommStatus = COMM_RXWAITING; return; } } // Check checksum for( i=0; i<(gbStatusPacket[LENGTH]+1); i++ ) checksum += gbStatusPacket[i+2]; checksum = ~checksum; if( gbStatusPacket[gbStatusPacket[LENGTH]+3] != checksum ) { gbCommStatus = COMM_RXCORRUPT; giBusUsing = 0; return; } gbCommStatus = COMM_RXSUCCESS; giBusUsing = 0; } void dxl_txrx_packet() { dxl_tx_packet(); if( gbCommStatus != COMM_TXSUCCESS ) return; do{ dxl_rx_packet(); }while( gbCommStatus == COMM_RXWAITING ); } int dxl_get_result() { return gbCommStatus; } void dxl_set_txpacket_id( int id ) { gbInstructionPacket[ID] = (unsigned char)id; } void dxl_set_txpacket_instruction( int instruction ) { gbInstructionPacket[INSTRUCTION] = (unsigned char)instruction; } void dxl_set_txpacket_parameter( int index, int value ) { gbInstructionPacket[PARAMETER+index] = (unsigned char)value; } void dxl_set_txpacket_length( int length ) { gbInstructionPacket[LENGTH] = (unsigned char)length; } int dxl_get_rxpacket_error( int errbit ) { if( gbStatusPacket[ERRBIT] & (unsigned char)errbit ) return 1; return 0; } int dxl_get_rxpacket_length() { return (int)gbStatusPacket[LENGTH]; } int dxl_get_rxpacket_parameter( int index ) { return (int)gbStatusPacket[PARAMETER+index]; } int dxl_makeword( int lowbyte, int highbyte ) { unsigned short word; word = highbyte; word = word << 8; word = word + lowbyte; return (int)word; } int dxl_get_lowbyte( int word ) { unsigned short temp; temp = word & 0xff; return (int)temp; } int dxl_get_highbyte( int word ) { unsigned short temp; temp = word & 0xff00; temp = temp >> 8; return (int)temp; } void dxl_ping( int id ) { while(giBusUsing); gbInstructionPacket[ID] = (unsigned char)id; gbInstructionPacket[INSTRUCTION] = INST_PING; gbInstructionPacket[LENGTH] = 2; dxl_txrx_packet(); } int dxl_read_byte( int id, int address ) { while(giBusUsing); gbInstructionPacket[ID] = (unsigned char)id; gbInstructionPacket[INSTRUCTION] = INST_READ; gbInstructionPacket[PARAMETER] = (unsigned char)address; gbInstructionPacket[PARAMETER+1] = 1; gbInstructionPacket[LENGTH] = 4; dxl_txrx_packet(); return (int)gbStatusPacket[PARAMETER]; } void dxl_write_byte( int id, int address, int value ) { while(giBusUsing); gbInstructionPacket[ID] = (unsigned char)id; gbInstructionPacket[INSTRUCTION] = INST_WRITE; gbInstructionPacket[PARAMETER] = (unsigned char)address; gbInstructionPacket[PARAMETER+1] = (unsigned char)value; gbInstructionPacket[LENGTH] = 4; dxl_txrx_packet(); } int dxl_read_word( int id, int address ) { while(giBusUsing); gbInstructionPacket[ID] = (unsigned char)id; gbInstructionPacket[INSTRUCTION] = INST_READ; gbInstructionPacket[PARAMETER] = (unsigned char)address; gbInstructionPacket[PARAMETER+1] = 2; gbInstructionPacket[LENGTH] = 4; dxl_txrx_packet(); return dxl_makeword((int)gbStatusPacket[PARAMETER], (int)gbStatusPacket[PARAMETER+1]); } void dxl_write_word( int id, int address, int value ) { while(giBusUsing); gbInstructionPacket[ID] = (unsigned char)id; gbInstructionPacket[INSTRUCTION] = INST_WRITE; gbInstructionPacket[PARAMETER] = (unsigned char)address; gbInstructionPacket[PARAMETER+1] = (unsigned char)dxl_get_lowbyte(value); gbInstructionPacket[PARAMETER+2] = (unsigned char)dxl_get_highbyte(value); gbInstructionPacket[LENGTH] = 5; dxl_txrx_packet(); }
The example dxl_hal_rx() and dxl_hal_rx() that I showed above will receive or transmit the exact numPacket bytes. The functions will wait and block until this number is reached. You may want to include timeouts in the while loops to break out when the maximum wait time has passed. You could then return the number of bytes that was actually received or transmitted, but it makes more sense to return a -1 indicating error. You dont get any other usable errors back from Serial anyhow,
posted by 08 Feb 2014