Ros With Rb8 4 Rosserial

5 minute read

拿到機器人後就先來測試馬達的運作吧!目前機器人身上裝載的為 RoBoard,因此我是採用 Rosserail + RoBoIo Lib,如果之後有玩到 86Duino 為控制核心的機器人我會直接使用 86ME XD,關於 86ME,之後應該會寫一篇非此系列的文章來介紹唷!

Rosserial_86Duino

那麼言歸正傳,今天主要重點會放更多在 Rosserial 上面,RoBoIo 的詳細用法若是 RoBoard 玩家的話可以參考上面我給的連結。其實 Rosserial_86Duino 是個充滿特異功能的非官方套件,Rosserial_86Duino 可以在 86Duino/DOS/Windows/Linux 上編譯並順利執行(方法各有不同):

  • 86Duino:與官方的 Rosserial_Arduino 用法相同。目前的 Coding 210 版本都已經內建,所以不必特別安裝,也有範例可以參考。
  • DOS:要使用 DJGPP,我這邊是在 Windows 裡頭用 DJGPP 編譯出 DOS 可跑的程式,再將程式放入 RoBoard 讓其執行。這邊就附上我的 Makefile,裡頭有包含 RoBoIo Lib,所以我是有自己再修改一些 IO Lib 的,但若不用使用 RoBoIo Lib,可以把相關的部分刪去(包含引入的部分和 一些 .o 檔的部分)。
IFLAGS    = -I./ros_lib -I./ros_lib/vortex86/utilities -I./RoBoIO/libsrc
OBJFILES  = HelloROS.o 86DuinoHardware.o time.o duration.o
OBJFILES += io.o irq.o err.o uart.o vortex86.o com.o queue.o usb.o usb_desc.o can.o
OBJFILES += common.o pwm.o pwmdx.o rcservo.o spi.o spidx.o ad79x8.o i2c.o i2cdx.o
EXEFILES  = HelloROS.exe
LIBFILES  = -L./ros_lib/vortex86/utilities -lswsk
OPTIONS   = -Wall -Wno-write-strings -trigraphs

.PHONY : everything all clean

everything : $(OBJFILES) $(EXEFILES)

all : clean everything

clean :
	-rm -f $(EXEFILES) $(OBJFILES)

%.o: %.cpp
	$(CXX) -c $< $(IFLAGS) $(OPTIONS)

%.o: ./ros_lib/%.cpp
	$(CXX) -c $< $(IFLAGS) $(OPTIONS)

%.o: ./ros_lib/vortex86/utilities/%.cpp
	$(CXX) -c $< $(IFLAGS) $(OPTIONS)

%.o: ./RoBoIO/libsrc/%.cpp
	$(CXX) -c $< $(IFLAGS) $(OPTIONS)

HelloROS.exe : $(OBJFILES)
	$(CXX) -o $@ $(OBJFILES) $(LIBFILES) $(OPTIONS)
  • Windows:我是採用 VS2013 社群版來編譯,將 ros_lib 與 ros_lib/vortex86/ 裡面兩個資料的 header file 夾都引入專案,而連結方面需要連結以下函式庫:WinIo.libkernel32.libuser32.libgdi32.libwinspool.libcomdlg32.libadvapi32.libshell32.libole32.liboleaut32.libuuid.libodbc32.libodbccp32.lib
  • Linux:與 DOS 較為類似,不過 compiler 為 GCC。一樣附上 Makefile:
IFLAGS    = -I./ros_lib -I./ros_lib/vortex86/utilities
OBJFILES  = HelloROS.o 86DuinoHardware.o time.o duration.o io.o irq.o err.o vortex86.o
OBJFILES += 
EXEFILES  = HelloROS.exe
LIBFILES  = -lrt
OPTIONS   = 
# GNU Make will set CXX to correct C++ compiler if it is not set.
# You can use other compiler from command line of make, ex:
# make CXX=gcc
#CXX       = gcc

.PHONY : everything all clean

everything : $(OBJFILES) $(EXEFILES)

all : clean everything

clean :
	-rm -f $(EXEFILES) $(OBJFILES)

%.o: %.cpp
	$(CXX) -c $< $(IFLAGS) $(OPTIONS)

%.o: ./ros_lib/%.cpp
	$(CXX) -c $< $(IFLAGS) $(OPTIONS)

%.o: ./ros_lib/vortex86/utilities/%.cpp
	$(CXX) -c $< $(IFLAGS) $(OPTIONS)

HelloROS.exe : $(OBJFILES)
	$(CXX) -o $@ $(OBJFILES) $(LIBFILES) $(OPTIONS)

除此之外,還在 DOS 平台提供網路連線的連線方式,用法會與 Linux/Windows 上用網路有些差別,一般的網路連接是在 init NodeHandle 時給 server 的 ip 當作參數,而 DOS 下則是要先用 NodeHandleX.getHardware()->setEthernet(ip) 來設定自己的 IP,再用 NodeHandleX.initNode(srvIp) 來初始化。

程式的部分,86Duino 裡頭有 Rosserial86 範例可參考;DOSWindowsLinux 基本上程式可以是同一份,並不需要修改,我就在此附上我用 RoBoIo 來控制 AX-12 馬達的程式,這支程式會去聽取 chatter 話題,並且依據收到的訊息來決定要怎麼讓輪子移動:

#include <ros.h>
#include <std_msgs/String.h>
#include <std_msgs/Float64MultiArray.h>
#include <geometry_msgs/Twist.h>
#include <stdio.h>
#include <stdlib.h>
#include <roboard.h>

// AX-12 commands
#define READ_DATA       0x02
#define WRITE_DATA      0x03

// AX-12 registers
#define ID_ADDR         0x03
#define CW_ANG_LIMIT_L  0x06
#define CCW_ANG_LIMIT_L 0x08
#define GOAL_POSITION   0x1e
#define MOVING_SPEED    0x20

COMPort* ax12_com;
unsigned char comd[128] = {0};
unsigned char rx_status[128] = {0};
unsigned char speed = 0x01;

bool com3_ServoTRX(COMPort *port, unsigned char* cmd, int csize, unsigned char* buf, int bsize)
{
    com_ClearRFIFO(port);

    if (com_Send(port, cmd, csize) == false) return false;

    if (port->EnableHalfDuplex == uart_EnableHalfDuplex)  // discard the first-received csize bytes in case of TX/RX-short
    {
        int i;
        unsigned int tmpbyte;
        for (i=0; i<csize; i++)
        {
            if ((tmpbyte = com_Read(port)) == 0xffff)
                return false;
            if ((unsigned char)tmpbyte != cmd[i])
            {
                printf("receive a wrong self-feedback byte\n");
                return false;
            }
        }
    }

    if (buf != NULL)
        if (com_Receive(port, buf, bsize) == false)
            return false;

    return true;
}

unsigned char chk_sum( unsigned char *data, int len )
{
    unsigned int i, sum=0;
    for( i=2;i<(len-1);i++ )
        sum += data[i];
    sum = (~sum) & 0x00ff;
    return (unsigned char)sum;
}

void set_comd( char cmd, int ID, int ax12_reg)
{
    unsigned char flag = 0x00;
    if( ID == 15)
        flag = 0x04;
    switch(cmd)
    {
        case 'w':
            comd[6] = 0xf0;
            comd[7] = flag | speed;
            break;
        case 's':
            comd[6] = 0xf0;
            comd[7] = (0x04 xor flag) | speed;
            break;
        case 'd':
            comd[6] = 0xf0;
            comd[7] = 0x00 | speed;
            break;
        case 'a':
            comd[6] = 0xf0;
            comd[7] = 0x04 | speed;
            break;
        case 'p':
            comd[6] = 0x00;
            comd[7] = 0x00;
            break;
        default:
            break;
    }

    comd[0] = 0xff;
    comd[1] = 0xff;
    comd[2] = ID;
    comd[3] = 5;// LENGTH : LENGTH INSTRUCTION PARAMETER1 PARAMETER2 PARAMETER3
    comd[4] = WRITE_DATA;// INSTRUCTION
    comd[5] = ax12_reg;// register
    comd[8] = chk_sum(comd, 9);// Calculate the checksum.
}

ros::NodeHandle nh;

void wheelCB(const std_msgs::String & msg)
{
    set_comd( msg.data[0], 15, MOVING_SPEED);
    com3_ServoTRX(ax12_com, comd, 9, rx_status, 6);

    set_comd( msg.data[0], 15, GOAL_POSITION);
    com3_ServoTRX(ax12_com, comd, 9, rx_status, 6);

    set_comd( msg.data[0], 18, MOVING_SPEED);
    com3_ServoTRX(ax12_com, comd, 9, rx_status, 6);

    set_comd( msg.data[0], 18, GOAL_POSITION);
    com3_ServoTRX(ax12_com, comd, 9, rx_status, 6);

    nh.getHardware()->delay(500);

    set_comd( 'p', 15, MOVING_SPEED);
    com3_ServoTRX(ax12_com, comd, 9, rx_status, 6);

    set_comd( 'p', 18, MOVING_SPEED);
    com3_ServoTRX(ax12_com, comd, 9, rx_status, 6);

    if( msg.data[0] == 'l' )
    {
        for(int action_round=0; action_round<2; action_round++)
        {
            motion_frame[17] = 1600;
            rcservo_SetAction( motion_frame, 200);
            while( rcservo_PlayAction() != RCSERVO_PLAYEND )
                ;
            motion_frame[17] = 1400;
            rcservo_SetAction( motion_frame, 200);
            while( rcservo_PlayAction() != RCSERVO_PLAYEND )
                ;
        }
    }
    if( msg.data[0] == 'r' )
    {
        for(int action_round=0; action_round<2; action_round++)
        {
            motion_frame[22] = 1250;
            rcservo_SetAction( motion_frame, 200);
            while( rcservo_PlayAction() != RCSERVO_PLAYEND )
                ;
            motion_frame[22] = 1450;
            rcservo_SetAction( motion_frame, 200);
            while( rcservo_PlayAction() != RCSERVO_PLAYEND )
                ;
        }
    }
    if( (msg.data[0] == 'e') && (speed < 0x03) )
    {
        speed++;
    }
    if( (msg.data[0] == 'q') && (speed > 0x01) )
    {
        speed--;
    }
}

double abs_d( double num )
{
    if( num < 0 )
        return -1*num;
    return num;
}

ros::Subscriber<std_msgs::String> wheelSub("chatter", &wheelCB);

void WaitMs(unsigned long millisecond)
{
    nh.getHardware()->delay(millisecond);
}

int main(int argc, char* argv[])
{
    roboio_SetRBVer(RB_100RD);  // set the RoBoard version

    ax12_com = com_Init(COM3);
    com_SetFormat(ax12_com, BYTESIZE8 | STOPBIT1 | NOPARITY);
    com_SetBPS(ax12_com, COM_UARTBAUD_115200BPS);

    if (strcmp(argv[1], "d") == 0)
    {
        printf("%s %s %s\n", argv[1], argv[2], argv[3]);
        nh.getHardware()->setEthernet(argv[3]);
        nh.initNode(argv[2]);
    }

    nh.subscribe(wheelSub);

    while (1)
    {
        nh.spinOnce();
        WaitMs(30);
    }
}

Updated:

Leave a comment