packages feed

RMP-0.0.2: src/System/RMP/canio_rmpusb.cpp

/* Taken and modified from http://www.ai.sri.com/~vincent/segway.php */
#include <sys/time.h>
#include <time.h>
#include <unistd.h>
#include <cstdlib>

#include "ftd2xx.h"
#include "canio.h"

#include "canio_rmpusb.h"

#define DEBUG_PRINT(fmt, ...)

CANIOrmpusb::CANIOrmpusb(const char* serial) : ready(false)
{
    this->serial = serial;
    DEBUG_PRINT("serial is set in CANIO : %s\n",this->serial);
}

CANIOrmpusb::~CANIOrmpusb()
{
}

int
CANIOrmpusb::Init()
{
    DWORD iVID = 0x0403;  // Vendor ID for Future Technology Devices Inc
    DWORD iPID = 0xE729;  // Product ID for FTD245BM chip in the Segway RMP
    FT_DEVICE ftDevice;
    DWORD deviceID;
    char SerialNumber[16];
    char Description[64];


    ftStatus = FT_SetVIDPID(iVID, iPID);	// use our VID and PID
    if(ftStatus != FT_OK) {
        DEBUG_PRINT("Unable to set appropriate VID/PID for USB device\n");
        return ftStatus;
    }

    if(this->serial !=  NULL) {
	strncpy(SerialNumber,this->serial,16); 
        DEBUG_PRINT("Looking to connect to Segway #:%s\n",SerialNumber);
        ftStatus = FT_OpenEx(SerialNumber, FT_OPEN_BY_SERIAL_NUMBER, &ftHandle);
    }
    else {
        char desc[] = "Robotic Mobile Platform";
        ftStatus = FT_OpenEx(desc,FT_OPEN_BY_DESCRIPTION,&ftHandle);
    }
    if(ftStatus != FT_OK) {
        DEBUG_PRINT("FT_Open(0) failed\n");
        return ftStatus;
    }

    // Get the info
    ftStatus = FT_GetDeviceInfo(ftHandle, &ftDevice,&deviceID,SerialNumber,Description,NULL);
//   if (ftStatus == FT_OK) {
//     DEBUG_PRINT("  SerialNumber=%s\n",SerialNumber);
//     DEBUG_PRINT("  Description=[%s]\n",Description);
//     DEBUG_PRINT("  ftHandle=0x%x\n",ftHandle);
//   }

    
    // Boost the baud rate
    ftStatus = FT_SetBaudRate(ftHandle,FT_BAUD_460800);
    if(ftStatus != FT_OK) {
        DEBUG_PRINT("Unable to increase USB baud rate\n");
        FT_Close(ftHandle);
        return ftStatus;
    }

    // Decrease the internal latency timer
    ftStatus = FT_SetLatencyTimer(ftHandle,2);
    if (ftStatus != FT_OK) {
        DEBUG_PRINT("Unable to decrease latency timer...continuing anyway\n");
    }
    

    // Set a timeout value of 10ms for reads and writes
    ftStatus = FT_SetTimeouts(ftHandle,10,10);
    if (ftStatus != FT_OK) {
        DEBUG_PRINT("FT_SetTimeouts failed\n");
        FT_Close(ftHandle);
        return ftStatus;
    }
    
    DWORD rsize;
    ftStatus = FT_GetQueueStatus(ftHandle,&rsize);
    if (ftStatus != FT_OK) {
        DEBUG_PRINT("Unable to get Queue status\n");
    } else {
        DEBUG_PRINT("At Init there are %d characters in read queue\n",rsize);
    }

    ftStatus = FT_ResetDevice(ftHandle);
    if (ftStatus != FT_OK) {
        DEBUG_PRINT("Unable to reset USB FIFO\n");
        FT_Close(ftHandle);
        return ftStatus;
    }
 
    ftStatus = FT_Purge(ftHandle,FT_PURGE_RX | FT_PURGE_TX);
    if (ftStatus != FT_OK) {
        DEBUG_PRINT("Unable to clear USB read/write buffers\n");
        FT_Close(ftHandle);
        return ftStatus;
    }

    ftStatus = FT_ResetDevice(ftHandle);
    if (ftStatus != FT_OK) {
        DEBUG_PRINT("Unable to reset USB FIFO\n");
        FT_Close(ftHandle);
        return ftStatus;
    }

    ftStatus = FT_GetQueueStatus(ftHandle,&rsize);
    if (ftStatus != FT_OK) {
        DEBUG_PRINT("Unable to get Queue status\n");
    } else {
        DEBUG_PRINT("After purge/reset there are %d characters in read queue\n",rsize);
    }
    ready = true;
    rcount = timeouts = 0;
    rxbufcount = 0;
    writecount = readcount = 0;
    usbreadcount = nomsgreadcount = 0;
    return 0;
}

/* Closes the CAN channel
 *
 * returns: 0 on success, nonzero error code otherwise
 */
int
CANIOrmpusb::Shutdown()
{
    ftStatus = FT_Close(ftHandle);
    if(ftStatus != FT_OK) {
        DEBUG_PRINT("FT_Close failed\n");
        return ftStatus;
    }
    ready = false;
    return 0;
}
  
/* Writes the given packet
 *
 * returns: 0 on success, nonzero error code otherwise
 */
int
CANIOrmpusb::WritePacket(CanPacket &cpkt)
{
    DWORD bytes_written;
    DWORD bytes_to_write;

    writecount++;
#ifdef PRINTSTATS
    if ((writecount % 100) == 0) {
        DEBUG_PRINT("writes %d  read calls %d  device reads %d  aborted reads %d\n",writecount,readcount,usbreadcount,nomsgreadcount);
    }
#endif

    if (!ready) {
        return -1;
    }

    static struct timeval *last=NULL;
    struct timeval curr;
    
    // initialize the first timeval if necessary
    if (!last) {
        last = new struct timeval;
        gettimeofday(last,NULL);
    }

    // get the current time
    gettimeofday(&curr,NULL);
    
    // calculate how long since the last write
    double msecs = (curr.tv_sec - last->tv_sec)*1000 + (curr.tv_usec - last->tv_usec) / 1000;
    
    // if it's been less than 30 milliseconds, sleep so that we don't
    // overload the CAN bus
    if (msecs < 30) {
        usleep((30-msecs)*1000);
//        usleep(100*1000);
    }

    // create a USB packet from our CAN packet
    CANIOrmpusb::rmpUsbPacket packet(cpkt);

    DEBUG_PRINT("USB packet created: %s\n",packet.toString());
    
    bytes_to_write = 18;
    unsigned char *pData = packet.bytes;
    while (bytes_to_write > 0) {
        bytes_written = 0;
        ftStatus = FT_Write(ftHandle,pData,bytes_to_write,&bytes_written);
        if (ftStatus != FT_OK) {
            DEBUG_PRINT("Error while trying to write packet to USB port\n");
            return ftStatus;
        }
        if (bytes_written > bytes_to_write) {
            DEBUG_PRINT("Erronous bytes written returned by FT_Write: %lu", bytes_written);
            return -1;
        }
        bytes_to_write -= bytes_written;
        pData += bytes_written;
    }
    //    DEBUG_PRINT("USB packet sent: %s\n",packet.toString());

    // record the time
    gettimeofday(last,NULL);

    return 0;
}
        

/* Reads a packet from the USB bus, and extracts the CAN data.
 */
int
CANIOrmpusb::ReadPacket(CanPacket *pkt)
{
    // We'll read as much as we possibly can, since the read call is slow.
    // The extra bytes will be kept around until next time, and just processed
    // from memory.

    readcount++;
    DWORD bytes_read;
    
    if (!ready) {
        return -1;
    }

    CANIOrmpusb::rmpUsbPacket packet;


    if (rxbufcount > RX_WARNING_SIZE) {
        // Player is falling behind in processing the messages, so throw out
        // the oldest bytes
        memcpy(rxbuf,rxbuf+rxbufcount-RX_KEEP_SIZE,RX_KEEP_SIZE);
	//        DEBUG_PRINT("Warning: purged %d old bytes from USB interface\n", rxbufcount-RX_KEEP_SIZE);
        rxbufcount = RX_KEEP_SIZE;
    }
        
    if (rxbufcount < 180) {
        // only read more from the device if we're running low
        DWORD rsize;
        ftStatus = FT_GetQueueStatus(ftHandle,&rsize);
        if (ftStatus != FT_OK) {
            DEBUG_PRINT("Unable to get Queue status\n");
            return -1;
        }
        
        // Limit what we will read to the space left in the buffer
        if (rsize > (RX_BUFFER_SIZE - rxbufcount)) {
            rsize = RX_BUFFER_SIZE - rxbufcount;
        }
        
        if (rsize < 18) {
            // not a full packet available
            // it seems that if we try to read something now, it
            // provokes the FTDI chip into producing garbage, so instead
            // we'll wait until later to get at least 1 full packet
            return 0;
        }
        ftStatus = FT_Read(ftHandle,rxbuf+rxbufcount,rsize,&bytes_read);
        usbreadcount++;
        if (ftStatus != FT_OK) {
            DEBUG_PRINT("Error reading from USB device\n");
            return -1;
        }

        rxbufcount += bytes_read;

    }
    
    int i;
    // Now, search for the next valid packet
    for ( i = 0; i < ((int)rxbufcount-17); i++) {
        if ((rxbuf[i] != 0xF0) || (rxbuf[i+1] != 0x55) || (rxbuf[i+2] != 0xAA)) {
            continue;
        }
        
        // move bytes into 18-byte USB packet and check
        memcpy(packet.bytes,rxbuf+i,18);
        if(packet.computeChecksum() != packet.bytes[17]) {
            continue;
        }

        // extract CAN packet
        // this is what the v2.0 preliminary documentation says: ID is located in
        // bytes 6 and 7, but it doesn't actually say which 11 bits to use, so I was
        // using the low bits.  This is how packets are created when sending, but
        // I've learned it's totally wrong for received packets.
        // pkt->id = ((packet.bytes[6] << 8) + packet.bytes[7]) & 0x7FF;
        
        // this is what the Segway RMI_DEMO does for received packets.  It uses all 8
        // bits from byte 4 and the first 3 bits from byte 5.
        pkt->id = ((packet.bytes[4] << 3) | ((packet.bytes[5] >> 5) & 7)) & 0xfff;
        memcpy(pkt->msg,packet.pDATA,8);
        //  DEBUG_PRINT("CAN packet extracted: %s\n", pkt->toString());
        
        // shift the receive buffer
        memcpy(rxbuf,rxbuf+i+18,rxbufcount-i-18);
        rxbufcount -= (i+18);
        

        // return the CAN bytes received
        return 10;
    }

    // we didn't find any valid messages
    // shift the receive buffer
    memcpy(rxbuf,rxbuf+i,rxbufcount-i);
    rxbufcount -= i;
    
    nomsgreadcount++;
    return 0;

}


CANIOrmpusb::rmpUsbPacket::rmpUsbPacket() {
    InitPacket();
}

CANIOrmpusb::rmpUsbPacket::rmpUsbPacket(CanPacket &cpkt) {
    InitPacket();
    bytes[6] = (cpkt.id >> 8) & 0xff;
    bytes[7] = cpkt.id & 0xff;
    memcpy(pDATA,cpkt.msg,8);
    addChecksum();
}

void CANIOrmpusb::rmpUsbPacket::InitPacket() {
    bytes[0] = 0xF0;
    bytes[1] = 0x55;
    bytes[2] = 0;
    bytes[3] = 0;
    bytes[4] = 0;
    bytes[5] = 0;
    bytes[6] = 0;
    bytes[7] = 0;
    bytes[8] = 0;
    bytes[9] = 0;
    bytes[10] = 0;
    bytes[11] = 0;
    bytes[12] = 0;
    bytes[13] = 0;
    bytes[14] = 0;
    bytes[15] = 0;
    bytes[16] = 0;
    bytes[17] = 0;
    pID = bytes+6;
    pDATA = bytes+9;
}

CANIOrmpusb::rmpUsbPacket::~rmpUsbPacket() {
}

unsigned char CANIOrmpusb::rmpUsbPacket::computeChecksum() {
    // code copied from Segway RMP Interface Guide
    unsigned short checksum;
    unsigned short checksum_high;
    checksum = 0;
    for (int i = 0; i < 17; i++) {
        checksum += (short)bytes[i];
    }
    checksum_high = checksum >> 8;
    checksum &= 0xff;
    checksum += checksum_high;
    checksum_high = checksum >> 8;
    checksum &= 0xff;
    checksum += checksum_high;
    checksum = (~checksum +1) & 0xff;
    return (unsigned char) checksum;
}

void CANIOrmpusb::rmpUsbPacket::addChecksum() {
    bytes[17] = computeChecksum();
}

char * CANIOrmpusb::rmpUsbPacket::toString() {
    static char buf[256];
    buf[0] = 0;
    for (int i = 0; i < 18; i++) {
        sprintf(buf+strlen(buf),"%d:%02X ",i,bytes[i]);
    }
    return buf;
}