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;
}





jueves, 4 de julio de 2013

Basic WiFi communication setup part 2


This is a continuation from the previous post: http://asctecbasics.blogspot.com/2013/06/basic-wifi-communication-setup-part-1.html

Testing everything is OK

First, let's check the previous steps are OK. Let's assume you assigned 192.168.0.15 to your computer and 192.168.0.16 to your quad.

From your computer you should be able to ping your quad, run in a terminal:

 ping 192.168.0.16  

The same should work  from your quad to your computer changing the IP address correspondingly.

Setting up SSH

This part is based on the instructions found here: http://teamprincipia.wordpress.com/2008/05/29/beginning-ssh-on-ubuntu/ 

You need to install SSH client and server on both machines (client usually comes with Ubuntu  but checking is always good):

 sudo apt-get install openssh-client openssh-server  

Mapping IP addresses to hostnames

This part is based on information found here: http://nootrix.com/2012/06/ros-networking/

The idea is to associate the IP address to a name, so it isn't necessary to write the IP address everytime you wish to login through SSH.

To do this, first edit the /etc/hosts files on the computer being used as groundstation (assuming you are using the same IPs mentioned before):

 sudo gedit /etc/hosts  

Then add the following line at the end of the file:

 192.168.0.16 quad

Do the same on the quadrotor, adding this line instead:

 192.168.0.15 groundstation

Now you should be able to open a terminal on the computer and ping the quadrotor using the following command:

 ping quad

This should have a similar result as doing a ping from the quadrotor from the computer. Check both pings are possible to be sure everything is ok.

Running ROS

After setting up ssh and mapping the names, you should be able to run ROS on the quadrotor using commands from the groundstation, and start nodes on the groundstation to communicate with the quadrotor.

To check this we will run a listener node on the quadrotor and try to communicate to it from the computer.

You should choose if the groundstation or the quadrotor will run the roscore service. In this case the quadrotor will be running roscore.

To do this, from the groundstation computer log in to the quadrotor. Assuming the username on the quadrotor is "user", open a terminal on your computer and type:

 ssh user@quad 

Your password will be required for this. Type it and now you should have a terminal logged into the quadrotor. In this terminal type:

 roscore 

Now repeat this process, openning a new terminal and logging into the quadrotor and start a listener node on it typing:
 
 rosrun rospy_tutorials listener.py

Now you should set up the groundstation. Open a new terminal on your computer and type:
 
 export ROS_MASTER_URI=http://quad:11311
 rosrun rospy_tutorials talker.py 

Now you should see the answer on the listener terminal, showing the communication is working between both.

What's next?

Now everything is set up, you should try to run the asctec_mav_framework package on the quadrotor and communicate to it from the groundstation.