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. 


Graphical Representation of the SBUS protocol

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:


  1. Using a simple transistor+2 resistors to invert the signal ..or...
  2. If you have an FTDI USB to TLL serial converter there is a program  (called FT_Prog) on the ftdiChip.com webpage which allows you to configure signal inversions automatically. See this website for examples of how to connect up and make a cable....or...
  3. If you have an X4R you can hack the receiver to obtain non-inverted data directly; without the need to invert the signal by fixing a connector to available position show here (I soldered  directly to inverter pin as I wanted to add a connector later). 
Inverted S.BUS connection to X4R

The approach

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

Connecting Processing Sketch to the S.BUS




FrySky / CCD3 Interfaces to 360 Degree IR Sensor

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/


Real Time Graphical Representation of the 16 channel SBUS protocol


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.

  • Inverted signal: Which means the Arduino can't read this straight out-the-box without being inverted. We just used a simple transistor + resistor circuit to invert the signal. Others have used invertorsUnfortunately it's not possible to read inverted signals on the Arduino UART, but on some other microcontroller this is possible (e..g certain Microchip PIC controllers can be configured to read inverted signals)
  • Baud Rate: 100000 (Yes - that is a non-standard  baud rate and rather fast for some micro-controller UARTs' to keep up with.)
  • Partity: Even
  • StopBits: 2
  • Packet size: 25 Bytes in one packet- which contains all 16 channels and fail-safe information
    • Channel Bits: The Channels are 11 bits in length, so the bytes need to be split-up (explained below) 
    • Start Byte:  is B1111000 (240) which arrives as MSB (i.e. highest bit arrives first). We decided not to convert this to MSB and so we searched for B00001111 (15) as the start byte instead.
    • Stop Byte: B0000000
    • The bytes arrive in MSB, but are assembled as little Endian, which means some bytes from the subdequent byte need to  comes before the 1st byte (confused? - you are not alone, but this is explained more below)

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:
  • Is this a start byte?
  • Is the byte counter at 1? or are we in the middle of a packet?
  • Is the 25th byte a '0'? - then we have a good packet, other wise the packet has been lost some bytes

Conversion of S.BUS to create Channel 1:

  • Buffer[0] contains the start byte value of 15,  so this is ignored. The first byte appears in Buffer[1]
  • The Channels are 11 bits in length, so we use the 1st bytes (8 bits) + the 1st 3 bits of the second byte. As this little Endian we actual need the last 3 bits of the second byte which is in fact the first bytes. Pictures normally help here.
  • 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 in front 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[1]  = ((buffer[1]|buffer[2]<<8) & 0x07FF); //The first Channel

Conversion of S.BUS to create 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[2]  = ((buffer[2]>>3|buffer[3]<<5)  & 0x07FF);

Conversion of S.BUS to create 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

Processing Sketch available for download on github

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


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:

  • Drone, UAV, Quads, Quadcopeter
  • Unmanned Aerial Vehicle
  • Processing.org, processing, P5, Sketch
  • Arduino, Sketch
  • 360 Degree Proximity Sensor
  • S.Bus, S-Bus, SBUS
  • Telemetry
Comments