domingo, 14 de julio de 2013

Communicating with the LLP via XBee

Introduction

There are several ways to interact with the Asctec Pelican. The most direct way to do so is to communicate with the LLP using the Xbees provided.
One of the Xbees is under the arm of the Pelican as you can see below:

The other one is provided with a cable so you can plug it to any USB port:

Communication Protocol

Briefly, Xbees allow you to communicate wireslessly using serial communication standards.
The communication works as follows: you can poll data sending the right instruction, in which case the data will be sent back to your computer, and you can send (pitch, roll, yaw, thrust commands) specifying the fly mode with a minimum frequency. This communication protocol is explained in more detail in the Official Manual.

Hardware Setup

In order for your computer to recognize the USB Xbee module you must download the FTDI drivers. They are available in this website (download D2XX drivers, not VCP drivers).

Also, the Xbee connected to your computer should have the name Quad_USB1 (this is recommended in the Simulink Toolkit Manual). To do this download MPROG (http://www.ftdichip.com/Support/Utilities/MProg3.5.zip), read the data from the Xbee, change the name and then write the data.

The Xbee in the Pelican comes already set up, but in case check it is connected to the HL Serial 0.

Remember that in order to poll data it is not necessary that the Futaba control is on, but to send commands the Futaba control must be on, with the serial interface switch enabled:

Software (the code!)

This code uses the LibSerial library.

The code is split into 4 files:
   - definitions.h: Defines the data structures needed according to the official manual.
   - Serial.h and Serial.cpp: Implements basic functions to communicate with the Pelican via serial.
   - main.cpp: Implements two basic examples. The first one requests IMU data and displays it. The second one starts the motors and stops them every 5 seconds.

With this, you can have an idea of how the communication works and start coding on top. The most important thing you must remember is that to accelerate the motors you must first start them. The signal required to start/stop the motors is equivalent to a full left yaw with zero thrust.

definitions.h

 #ifndef DEFINITIONS_H_  
 #define DEFINITIONS_H_  
 struct CTRL_INPUT  
 {  
      short pitch;  
      short roll;  
      short yaw;  
      short thrust;  
      short ctrl;  
      short chksum;  
 };  
 struct IMU_RAWDATA  
     {  
       //pressure sensor 24-bit value, not scaled but bias free  
       int pressure;  
       //16-bit gyro readings; 32768 = 2.5V  
       short gyro_x;  
       short gyro_y;  
       short gyro_z;  
       //10-bit magnetic field sensor readings  
       short mag_x;  
       short mag_y;  
       short mag_z;  
       //16-bit accelerometer readings  
       short acc_x;  
       short acc_y;  
       short acc_z;  
       //16-bit temperature measurement using yaw-gyro internal sensor  
       unsigned short temp_gyro;  
       //16-bit temperature measurement using ADC internal sensor  
       unsigned int temp_ADC;  
     };  
 struct IMU_CALCDATA  
 {  
      //angles derived by integration of gyro_outputs, drift compensated by data  
      //fusion; -90000..+90000 pitch(nick) and roll, 0..360000 yaw; 1000 = 1  
      //degree  
      int angle_nick;  
      int angle_roll;  
      int angle_yaw;  
      //angular velocities, raw values [16 bit] but bias free  
      int angvel_nick;  
      int angvel_roll;  
      int angvel_yaw;  
      //acc-sensor outputs, calibrated: -10000..+10000 = -1g..+1g  
      short acc_x_calib;  
      short acc_y_calib;  
      short acc_z_calib;  
      //horizontal / vertical accelerations: -10000..+10000 = -1g..+1g  
      short acc_x;  
      short acc_y;  
      short acc_z;  
      //reference angles derived by accelerations only: -90000..+90000; 1000 = 1  
      //degree  
      int acc_angle_nick;  
      int acc_angle_roll;  
      //total acceleration measured (10000 = 1g)  
      int acc_absolute_value;  
      //magnetic field sensors output, offset free and scaled; units not  
      //determined, as only the direction of the field vector is taken into  
      //account  
      int Hx;  
      int Hy;  
      int Hz;  
      //compass reading: angle reference for angle_yaw: 0..360000; 1000 = 1 degree  
      int mag_heading;  
      //pseudo speed measurements: integrated accelerations, pulled towards zero;  
      //units unknown; used for short-term position stabilization  
      int speed_x;  
      int speed_y;  
      int speed_z;  
      //height in mm (after data fusion)  
      int height;  
      //diff. height in mm/s (after data fusion)  
      int dheight;  
      //diff. height measured by the pressure sensor [mm/s]  
      int dheight_reference;  
      //height measured by the pressure sensor [mm]  
      int height_reference;  
 };  
 #endif /* DEFINITIONS_H_ */  

Serial.h

 #ifndef SERIAL_H_
#define SERIAL_H_

#include <SerialStream.h>
#include "definitions.h"
#include <cstdlib>

class Serial {
 LibSerial::SerialStream sstream;
public:
 Serial();
 virtual ~Serial();
 int sendmsg(char* msg, int buffersize);
 void request_IMUCalc();
 void read(char* data);
 void requestandread();
 IMU_CALCDATA processIMU(char* data, int size);
 void displayData(IMU_CALCDATA imudata);
 void motors_on();
 void closeports();
};

#endif /* SERIAL_H_ */

Serial.cpp

 #include "Serial.h"
#include <cstdlib>
#include <iostream>
#include <string.h>
using namespace LibSerial;


Serial::Serial() {
 // TODO Auto-generated constructor stub
 const char* const SERIAL_PORT_DEVICE = "/dev/ttyUSB0" ;
     sstream.Open( SERIAL_PORT_DEVICE ) ;
     if ( ! sstream.good() )
     {
         std::cout << "Error: Could not open serial port "
                   << SERIAL_PORT_DEVICE
                   << std::endl ;
         exit(1) ;
     }
     //
     // Set the baud rate of the serial port.
     //
     sstream.SetBaudRate( SerialStreamBuf::BAUD_57600 ) ;
     if ( ! sstream.good() )
     {
         std::cout << "Error: Could not set the baud rate." << std::endl ;
         exit(1) ;
     }
     //
     // Set the number of data bits.
     //
     sstream.SetCharSize( SerialStreamBuf::CHAR_SIZE_8 ) ;
     if ( ! sstream.good() )
     {
         std::cout << "Error: Could not set the character size." << std::endl ;
         exit(1) ;
     }
     //
     // Disable parity.
     //
     sstream.SetParity( SerialStreamBuf::PARITY_NONE ) ;
     if ( ! sstream.good() )
     {
         std::cout << "Error: Could not disable the parity." << std::endl ;
         exit(1) ;
     }
     //
     // Set the number of stop bits.
     //
     sstream.SetNumOfStopBits( 1 ) ;
     if ( ! sstream.good() )
     {
         std::cout << "Error: Could not set the number of stop bits."
                   << std::endl ;
         exit(1) ;
     }
     //
     // Turn on hardware flow control.
     //
     sstream.SetFlowControl( SerialStreamBuf::FLOW_CONTROL_HARD ) ;
     if ( ! sstream.good() )
     {
         std::cout << "Error: Could not use hardware flow control."
                   << std::endl ;
         exit(1) ;
     }
}

Serial::~Serial() {
 // TODO Auto-generated destructor stub
}

void Serial::closeports(){
 sstream.Close();
}

int Serial::sendmsg(char* msg, int buffersize){
 puts("Sending message...");
  this->sstream.write(msg , buffersize);
  puts("Message sent");
  return 1;
}

void Serial::request_IMUCalc(){
 sstream.write(">*>p4",6); //not sure of buffer size needed
 puts("IMU requested");
}

void Serial::read(char* data){
 std::cout << "Reading..." << std::endl ;
 int i = 0;
 usleep(1000*80); //wait for the message to arrive
     while( sstream.rdbuf()->in_avail() > 0  )
         {

             char next_byte;
             sstream.get(next_byte);
             std::cout << (int)next_byte << " ";
             data[i] = next_byte;
             ++i;
         }

     std::cout << std::endl ;
     std::cout << "Done reading" << std::endl ;
}

void Serial::requestandread(){
 std::cout << "Dumping file to serial port." << std::endl ;
     sstream.write(">*>p4",6);
     sleep(1);
     std::cout << "Reading..." << std::endl ;
     while( sstream.rdbuf()->in_avail() > 0 )
         {

             char next_byte;
             sstream.get(next_byte);
             std::cout << (int)next_byte << " ";
         }

     std::cout << std::endl ;
     std::cout << "Done." << std::endl ;
}

IMU_CALCDATA Serial::processIMU(char* data, int size){

 int indice_inicio;
 for(int i=0;i<size-3;i++){
  if((int)((data)[i])==62 && (int)((data)[i+1])=='*' && (int)((data)[i+2])=='>'){
   indice_inicio = i;
   break;
  }
 }
 std::cout << "Start index: " << (int)indice_inicio << std::endl ;
 std::cout << data[indice_inicio] << data[indice_inicio+1] << data[indice_inicio+2] << std::endl ;

 unsigned short length = (data[indice_inicio+4] << 8) | data[indice_inicio+3];
 std::cout << "Length: " << length << std::endl ;

 char imudata[length];
 for(int i=0;i<length;i++){
  imudata[i] = data[indice_inicio+6+i];
  std::cout << (int)imudata[i] << " ";
 }
 std::cout << std::endl;
 //Packet Descriptor
 unsigned char descriptor = data[indice_inicio+5] ;
 std::cout << "Packet descriptor: " << (int)descriptor << std::endl;

 IMU_CALCDATA* imustructp = (IMU_CALCDATA*) imudata;

 //CRC16
 unsigned short crc16 = (data[indice_inicio+5+length+2] << 8) | data[indice_inicio+5+length+1];

 //Endstring
 std::cout << "Endstring: " << data[indice_inicio+5+length+3] << data[indice_inicio+5+length+4]
           << data[indice_inicio+5+length+5] << std::endl;

 //CRC16 comprobation not implemented

 return *imustructp;

}

void Serial::displayData(IMU_CALCDATA data){
 //clear console
 std::cout << std::string( 10, '\n' );
 //print data
 std::cout << "Acc X: " << data.acc_x_calib << std::endl;
 std::cout << "Acc Y: " << data.acc_y_calib << std::endl;
 std::cout << "Acc Z: " << data.acc_z_calib << std::endl;
 std::cout << "Angle nick: " << data.angle_nick << std::endl;
 std::cout << "Angle roll: " << data.angle_roll << std::endl;
 std::cout << "Angle yaw: " << data.angle_yaw << std::endl;
 std::cout << "Speed X: " << data.speed_x << std::endl;
 std::cout << "Speed Y: " << data.speed_y << std::endl;
 std::cout << "Speed Z: " << data.speed_z << std::endl;
 std::cout << "Height: " << data.height << std::endl;
}

void Serial::motors_on(){
 CTRL_INPUT on_input;
  on_input.thrust = 0;
  on_input.pitch = 0;
  on_input.roll = 0;
  on_input.yaw = -2048;
  on_input.ctrl = 0xF;
  on_input.chksum = on_input.thrust + on_input.pitch + on_input.roll
    + on_input.yaw + on_input.ctrl + 0xAAAA;

  //cast to char array
  char* on_array = (char*)&on_input;

  //send msg
  sstream.write(">*>di",5);
  sstream.write(on_array,sizeof(on_input));
}

main.cpp

 #include "definitions.h"
#include <SerialStream.h>
#include <string.h>
#include <stdio.h>
#include <iostream>
#include "Serial.h"

using namespace LibSerial;
using namespace std;

SerialStream* openport(){

 SerialStream sstream;
 sstream.SetBaudRate( SerialStreamBuf::BAUD_57600 );
 sstream.SetFlowControl( SerialStreamBuf::FLOW_CONTROL_NONE );
 sstream.SetCharSize( SerialStreamBuf::CHAR_SIZE_8 );
 sstream.SetNumOfStopBits(1);
 sstream.SetParity( SerialStreamBuf::PARITY_NONE );
 sstream.Open( "/dev/ttyUSB0" );

 if ( ! sstream.good() )
     {
         std::cerr << "[" << __FILE__ << ":" << __LINE__ << "] "
                   << "Error: Could not open serial port."
                   << std::endl ;
     }

 SerialStream* pointer = &sstream;

 return pointer;
}

//receives command struct and appends the necessary chars
char* input_array(char* structure, int size){

 char inicio[5] = {'>','*','>','d','i'};
 char input[size+5];
 strcpy(input, inicio);     // copy to destination
 strncat(input, structure+5,size); // append part of the second string

 puts(input);
 return input;
}

//sends the structure
int send_msg(SerialStream* ssp, char* msg, int buffersize)
{
 puts("enviando msje");
 (*ssp).write(msg , buffersize);
 puts("mensaje enviado");
 return 1;
}

int close_ports(SerialStream* ssp)
{
 ssp->Close();
 return 1;
}

int main()
{
 int sel = 1; // sel = 0 requests data, sel = 1 toggle motors

 if(sel == 0){
  //simple code to request data
  Serial* serialport = new Serial();
  bool cond = true;
  while(cond){
   serialport->request_IMUCalc();
   int size = 250; //size of data buffer
   char databuff[size];
   serialport->read(databuff);
   IMU_CALCDATA imudata = serialport->processIMU(databuff, size);
   serialport->displayData(imudata);
  }
  serialport->closeports();
 }

 else if (sel == 1){
  //simple code to start motors and stop them after 5 seconds
  Serial* serialport = new Serial();
  serialport->motors_on();
  std::cout << "motors on" << std::endl;
  sleep(5);
  serialport->motors_on(); //the same signal is needed to turn them off
  std::cout << "motors off" << std::endl;
  serialport->closeports();
 }

 return 0;
}





4 comentarios:

  1. Hi, do you have a version of this code in C? Thank you.

    ResponderEliminar
  2. No, sorry, but I believe the Asctec Highlevel SDK includes a C example of serial communication

    ResponderEliminar
  3. Ok. Let me try to explane my problem. I am writing a code in C (using Visual Studio) to control the position of a Pelican quad via Xbee using a Windows PC. But I have never implemented any serial communication before! So I am lost! Is your code only appropriate for Linux or is it possible to adapt it for Windows? Another question: I search for the SerialStream.h file in my computer and I did not find it. Where can I find this head file? Thank you very much.

    ResponderEliminar
    Respuestas
    1. SerialStream.h is part of the LibSerial library ( http://libserial.sourceforge.net/ ) This is used to open serial connections on Linux.
      You won't need it if you are using Visual Studio, there are specific libraries for windows included to manage serial connections.
      The rest of the code should be very similar and adaptable, just not the part where you open and configure the serial port.
      You can look for tutorials on serial communication using visual studio online, I recommend using C# or C++ for this.

      Eliminar