Real-Time Graphical Representation | S.BUS Protocol

See the introduction page -> here or by following the breadcrumbs in the path above.

Real-Time Graphical Representation of the 16 Channels S.BUS Protocol

To simulate the results of the S.BUS we built  processing (www.processing.org) sketch to visualize the 16 channels of the S.BUS protocol in real time.

The processing.org sketch is Java based and  almost identical to the Arduino IDE sketch (Arduino IDE derived from Processing); so this was quite easy to convert. 

The graphics are rather rough at the moment; but good enough for what we want to use it for. 

Thanks goes to Futaba for making this S.BUS protocol so challenging for everyone to understand. I'm sure there are very good reasons and advantages for this - but at first glance we often wondered about "why make things simple if we can make things extremely difficult...!"

The processing sketch is available from our GIThub (also included below but that is not update)

https://github.com/robotmaker/Futaba-S-BUS-16-Channel-Simulation/

To get this Processing sketch to work, you'll need to have the S.BUS connected to the X8R or X4R receiver (or any other that support S.BUS) which in turn is connected via an Arduino or  FTDI USB to TTL serial converter thingy which connects to a USB connector on your PC.

As the S.BUS is an inverted signal (yes - to make life even more fun), you'll need to invert the signal using either:

The approach

The following video explains the concept and some detail of the approach we used.

S.BUS Protocol explained

The processing sketch is available from our GIThub (also included below but that is not update)

https://github.com/robotmaker/Futaba-S-BUS-16-Channel-Simulation/

It was not easy to understand the S.BUS protocol as we couldn't find any specification of the protocol from the manufacturers. But thanks to all those who documented and shared their understandings, we eventually also got our processing sketch and  Arduino sketch to work too. 

To help others, we have also shared our approach.  This webpage is a combination of  lessons-learned from all others and our own experiences. Please Note: that this is merely our own interpretation of the S.BUS which is probably not be 100% correct.  Our solution hasn't been stress tested or performance tested, so we don't recommend to use this method for your main control of a quadcopter. It's probably however  OK to be used for non-critical actuations e.g. turning on lights, buzzers, cameras  etc, and is not critical if something should go wrong.

Logic of S.BUS packet to channel conversions

The program starts by checking the serial port for data. If data arrives, it then does some validation of that data such as:

Conversion of S.BUS to create Channel 1:

Conversion of S.BUS to create Channel 2:   

Conversion of S.BUS to create Channel 3:

...and so on

Processing Sketch available for download on github

The processing sketch is available from our GIThub (also included below but that is not update)

https://github.com/robotmaker/Futaba-S-BUS-16-Channel-Simulation/

Processing Sketch

// Graphical represetentation of Futuba SBUS - also used in FrySky Receivers 

// Example by Colin Bacon

// ROBOTmaker

// www.robotmaker.eu

//To get this to work you'll need to have SBUS conneted receiver that is connected via an Arduiono or  FTDI USB thingy to your PC

//As the SBUS is an inverted signal (yes - to make life even more fun), you'll need to invert the signal using a simple transitor+2 restistors or if you have an FTDI there is a program to invert the signals automatically

//Further detail of how to do this are on our website

// References

//https://developer.mbed.org/users/Digixx/notebook/futaba-s-bus-controlled-by-mbed/

//http://www.clubamcl.fr/techniques/la-technologie-futaba-s-bus/

//Thanks goes to https://www.ordinoscope.net/index.php/Electronique/Protocoles/SBUS for the arduino code which I converted to processing

import processing.serial.*;

Serial myPort;  // The serial port

float x, y;

String    myString = null;

int []    buffer = new int [25];

int []    channels = new int [18];

int       errors = 0;

boolean   failsafe = false;

int       idx, inByte, lost, packetErrors;

long      last_refresh = 0;

float     ymag = 0;

float     newYmag = 0;

float     xmag = 0;

float     newXmag = 0; 

void setup() 

{

    size(1500, 600, FX2D); //give good dashboard resolutions

    // size(1500, 600, P3D); //used for drawing cubes

    //fullScreen(); //This sets it to full screen, but comment out the size commmand above

    pixelDensity(1);

    

    x = width * 0.3;

    y = height * 0.5;

    

    // Variable for drawing the cube

   

    

    // List all the available serial ports

    printArray(Serial.list());

    // Open the port you are using at the rate you want:

    myPort = new Serial(this, Serial.list()[0], 100000,'E',8,2);

    myString = myPort.readStringUntil(15); //clean the first buffer

    println(myPort); //Debug to show the port opened

}

void draw() 

{

  

 //Check for incoming Serial Port Data

 if (myPort.available() == 0){

   

   //If no incoming data then count lost packages

    packetErrors++; 

    textSize(14); //set text size

    text ("No Data at Serial Port", 400,20);

 }

 

 //When data is comming in then check for a start byte No.1=B11110000 and stop byte No.25=B00000000 

 while (myPort.available() > 0) {

     inByte = myPort.read();              //Read the Byte that just came in

    

    //if it's a new packet and the start byte is not  B00001111 (DEC15) then it's an error. 

    //Note that the SBUS byte data is MSB,  which causes some fun later

      if (idx == 0 && inByte != 0x0F) // error - wait for the start byte 

      {          

     text("Package Error", 100,100);

      

      } 

      // if it's a new packet and the start byte is  B00001111 (DEC15) then start reading the next 25 bytes.   

      else {

      buffer[idx++] = inByte;  // fill the buffer with 25 Bytes

    }

   

   // if the buffer of 25 Bytes is reached then start to decode   

      if (idx == 25) 

      {  

          idx = 0;  //reset the buffer count for the next cycle

          if (buffer[24] != 0x00) 

              {  //Check that the packet size is 25 bytes long with stop byte b00000000 as the last byte

              errors++;                //Count the number of errors

              println(errors);         //Print to the error totals to the dashboard

              } 

      else 

      { //Start decoding the bits and bytes

        

        //Buffer[0] contains the start byte value of 15,  so this is ignored

        //The Channels are 11 bits in length, so the bytes need to be split-up 

        //To make this conversion more 'interesting' as the bytes arrive as MSB i.e. the highest byte arrives first and the channels are 

        //assembled as little endian, which means the 1st 3 bits of the second byte need to comes before the 1st byte. 

        

        //--------------Channel 1 -------------------

        // The 1st channel = 1st byte + 3 bits from the 2nd byte

        // This channel packet of 11 bits is  then read backwards. 

        // As the inByte variable is already converted to integers we can move bits along as follows:

        // byte 1 = 00000000 00000011  (say the value is 3)

        // byte 2 = ‭01100100‬  (say the value of the 2nd byte is 100) 

        // but we are only interested in the last 3 bits which need to be moved infront of the 1st byte,...

        // byte 2 = ‭01100100 00000000 - ...So shift this byte along left by 8 and it looks like this 

        // byte 2 = | (or) the two bytes together and you get this

        // byte 1 =    00000000 00000011

        // byte 2 =    ‭01100100 00000000

        // Channel 1 = 01100100 00000011 <- the intermediate result

        // But we were only  interested in the 1st 3 bits of byte 2 so we need strip the 1st 5 bits by and-ing with 2047 (11 bits)

        // & (and) the two together and you get this....

        // Channel1 = 01100100 00000011 

        // 2047     = 00000111 11111111

        // Channel1 = 00000100 00000011 <- the final result

        //The bytes are litte Endian which means that to read the full

        

        channels[0]  = ((buffer[1]|buffer[2]<<8) & 0x07FF); //The first Channel

        

        //--------------Channel 2 -------------------

        // 2nd Channel = last 5 bit of byte2 and 8 bits of byte 3. Here we need to play around some more.

        // Remove the 1st 3 bits of byte2 by pushing them off to the right

        // byte 2   = 00000000 ‭01100100

        // byte 2   = 00000000 ‭00001100 moved 3 to the right

        // byte 3   = 00000000 ‭01010100 lets say that byte 3 is this

        // byte 3   = 00001010 10000000 moved 5 to the left to get the 5 bytes we need in place (remember this is MSB so bits in byte 3 comes before bits in byte 2)

        // | (or) byte 2 and 3 them together to get the 11 bits in place for channels2

        // byte 2   = 00000000 ‭00001100 

        // byte 3   = 00001010 10000000 

        // Channel2 = 00001010 10001100 < - result of| (or-ing) them together 

        // again we were only  interested in 11 bits so strip the package by and-ing with 2047 (11 bits)

        // & (and) the two together and you get this....

        // Channel2 = 00001010 10001100 < - interim result of| (or-ing) them together 

        // 2047     = 00000111 11111111 

        // Channel2 = 00000010 10001100 < - result of &  (and-ing) them together 

        

        channels[1]  = ((buffer[2]>>3|buffer[3]<<5)  & 0x07FF);

        

        //--------------Channel 3 -------------------

        // 3rd Channel = last 2 bit of byte3 and 8 bits of byte 4 and some bits from byte 5. 

        // Here we have even more to play around with. Someone with a sense of humour surely designed this. 

        // Talk about "why make things simple if we can make it very difficult..."

        // Remove the 1st 6 bits of byte3 by pushing them off to the right

        // byte 2   = 00000000 ‭00001100 

        // byte 3   = 00000000 ‭00000001 moved 6 to the right

        // byte 4   = 00000000 10101010 lets say that byte 4 is this

        // byte 4   = 00000010 10101000 moved 2 to the left to get the  bytes we need in place

        // | (or) byte 3 and 4 together to get the 11 bits in place for channels3

        // byte 3   = 00000000 ‭00000001

        // byte 4   = 00000010 10101000

        // Channel3 = 00000010 10101001 < - result of| (or-ing) them together 

        // byte 5   = 00000000 10110101 lets say that byte 5 is this

        // byte 5   = 11010100 00000000 moved 10 to the left to get the  bytes we need in place

        // Channel3 = 00000010 10101001 < - result of prrevious| (or-ing) them together 

        // Channel3 = 11010110 10101001 <- result of or-ing shift byte 5 and previous or-ing

        // | (or) byte 5  together with the current channel 3  to get the 11 bits in place for channels3

        // again we were only  interested in 11 bits so strip the package by and-ing with 2047 (11 bits)

        // & (and) the two together and you get this....

        // Channel3 = 11010110 10101001 <- result of or-ing shift byte 5 and previous or-ing

        // 2047     = 00000111 11111111 

        // Channel3 = 00000110 10101001 < - result of &  (and-ing) them together 

        // ...and so on

        channels[2]  = ((buffer[3]>>6 |buffer[4]<<2 |buffer[5]<<10)  & 0x07FF);

        channels[3]  = ((buffer[5]>>1 |buffer[6]<<7) & 0x07FF);

        channels[4]  = ((buffer[6]>>4 |buffer[7]<<4) & 0x07FF);

        channels[5]  = ((buffer[7]>>7 |buffer[8]<<1 |buffer[9]<<9)   & 0x07FF);

        channels[6]  = ((buffer[9]>>2 |buffer[10]<<6) & 0x07FF);

        channels[7]  = ((buffer[10]>>5|buffer[11]<<3) & 0x07FF);

        channels[8]  = ((buffer[12]   |buffer[13]<<8) & 0x07FF);

        channels[9]  = ((buffer[13]>>3|buffer[14]<<5)  & 0x07FF);

        channels[10] = ((buffer[14]>>6|buffer[15]<<2|buffer[16]<<10) & 0x07FF);

        channels[11] = ((buffer[16]>>1|buffer[17]<<7) & 0x07FF);

        channels[12] = ((buffer[17]>>4|buffer[18]<<4) & 0x07FF);

        channels[13] = ((buffer[18]>>7|buffer[19]<<1|buffer[20]<<9)  & 0x07FF);

        channels[14] = ((buffer[20]>>2|buffer[21]<<6) & 0x07FF);

        channels[15] = ((buffer[21]>>5|buffer[22]<<3) & 0x07FF);

        channels[16] = ((buffer[23]));

        //channels[16] = ((buffer[23])      & 0x0001) ? 2047 : 0;

        //channels[17] = ((buffer[23] >> 1) & 0x0001) ? 2047 : 0;

        //failsafe = ((buffer[23] >> 3) & 0x0001) ? 1 : 0;

        //if ((buffer[23] >> 2) & 0x0001) lost++;  

      }   

      } //For loop

      

 //pushMatrix();

      int pos1=0,channelText = 0;

      //Set the background colour of the bargraphs

      background(199);

      

 //Cycle through all channels and draw a bar chart for each channel value

      for(int c=0;c<16;c++){

        

       //Set the colour of the bar chart graphics

          noStroke(); //remove the graphic boarder lines

          fill(17,37,204); //set the colour of bar chart

       

       //Map Channel bar graph

          float z= float(channels[c]); //convert to a float

          float x1 = map(z,160,2000,0,width*0.95);

          int yoffSet = 50;

          pos1 = pos1 + 20; //y postion of bar on the canvas

          channelText = channelText + 1;

          rect(100, pos1+yoffSet , x1, 15);

          fill(0);

          text("Channel" + channelText + " = ", 5,pos1+10+yoffSet);

          text(channels[c],70,pos1+10 + yoffSet);

      

      //Draw the dashboard title and status messages

          textSize(26); 

          text("Open TX SBUS - Status Dashboard", width/2-150, 30); //Place the text in the centre of the dashboard

          textSize(10); //Reset text size

    

      //If the bits in byte23  are set, then there is an error. Display a warning 

          if (buffer[23] >= 12) {

              fill(226,72,47);

              ellipse(50,40,20,20); //Draw a red warning led

              fill(0); //colour the text

              textSize(14); //Reset text size

              text("Signal Lost",30,20);

              textSize(10); //Reset text size

            }

           else

            {

              fill(22,205,65);

              ellipse(50,40,20,20); //Draw a green warning led

              fill(0); //colour the text

              textSize(14); //Reset text size

              text("Signal OK",30,20);

             textSize(10); //Reset text size

            }

            

        //Place the error counts message

           textSize(14); //Reset text size

           text("Errors", 150,20);

           textSize(11); //Reset text size

           text("bytes23= ", 140,40);

           text(buffer[23],190,40);

           text("Count  = ", 140,55);

           text(errors, 190,55);

            

           //Place the lost package message

           textSize(14); //Reset text size

           text("Lost Packages", 220,20);

           textSize(11); //Reset text size

           text(packetErrors, 250,40);         

      

 

  } //if  

 } //While

   

 } 

References

FTDI

Keywords: