Go Down

Topic: Futaba SBUS reverse engineered to work with Arduino (Read 40187 times) previous topic - next topic

mikesbaker

Hi guys,

http://www.futaba-rc.com/sbus/index.html

I needed to use both regular servos and SBUS servos for a project I have going on right now. SBUS is an interesting thing. It uses a 100000 baud inverted UART with a 25 byte transmission. Each channel is transmitted using 11 bits of data. There is also a start an end byte. They also use 2 stop bits and even parity. Now is where the fun comes in. The signal is transmitted big endian, but each of the individual bytes are little endian. I had figured all this stuff out when I stumbled upon this gem:

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

Not only did what they have to say confirm my reverse engineering suspicions, but they had already made a great algorithm to bit bang the transmission into the correct format. A thanks is also due to user fat16lib for his excellent serial library which allows things like changing the parity and stop bits. I modified the code the guys over at MBED had done and used a hex inverter and the results are perfect. I wrote basically none of this code and those that did allow free redistribution so go nuts. IMO the ability to use SBUS is pretty big for arduino. If I feel motivated I'll turn this into a proper library. But for now it is what it is. If you can read code, the way this thing works is pretty self explanatory:

http://www.youtube.com/watch?v=_qeLBebtsdw

Code: [Select]
#include <SerialPort.h>

#define SBUS_SIGNAL_OK          0x00
#define SBUS_SIGNAL_LOST        0x01
#define SBUS_SIGNAL_FAILSAFE    0x03

SerialPort<0,25,25> port0;

uint8_t sbus_data[25] = {
  0x0f,0x01,0x04,0x20,0x00,0xff,0x07,0x40,0x00,0x02,0x10,0x80,0x2c,0x64,0x21,0x0b,0x59,0x08,0x40,0x00,0x02,0x10,0x80,0x00,0x00};
int16_t channels[18]  = {
  1023,1023,1023,1023,1023,1023,1023,1023,1023,1023,1023,1023,1023,1023,1023,1023,0,0};
int16_t servos[18]    = {
  1023,1023,1023,1023,1023,1023,1023,1023,1023,1023,1023,1023,1023,1023,1023,1023,0,0};
uint8_t  failsafe_status = SBUS_SIGNAL_FAILSAFE;
int sbus_passthrough = 1;
uint8_t byte_in_sbus;
uint8_t bit_in_sbus;
uint8_t ch;
uint8_t bit_in_channel;
uint8_t bit_in_servo;
uint8_t inBuffer[25];
int bufferIndex=0;
uint8_t inData;
int toChannels = 0;
uint32_t baud = 100000;
void setup(){
  //Serial.begin(100000);
  port0.begin(baud,  SP_2_STOP_BIT | SP_EVEN_PARITY | SP_8_BIT_CHAR);

}

void loop(){
  feedLine();
  if(toChannels==1){
    update_channels();
    update_servos();
    toChannels=0;
  } 
  //update_servos();
}





int16_t channel(uint8_t ch) {
  // Read channel data
  if ((ch>0)&&(ch<=16)){
    return channels[ch-1];
  }
  else{
    return 1023;
  }
}
uint8_t digichannel(uint8_t ch) {
  // Read digital channel data
  if ((ch>0) && (ch<=2)) {
    return channels[15+ch];
  }
  else{
    return 0;
  }
}
void servo(uint8_t ch, int16_t position) {
  // Set servo position
  if ((ch>0)&&(ch<=16)) {
    if (position>2048) {
      position=2048;
    }
    servos[ch-1] = position;
  }
}
void digiservo(uint8_t ch, uint8_t position) {
  // Set digital servo position
  if ((ch>0) && (ch<=2)) {
    if (position>1) {
      position=1;
    }
    servos[15+ch] = position;
  }
}
uint8_t failsafe(void) {
  return failsafe_status;
}

void passthroughSet(int mode) {
  // Set passtrough mode, if true, received channel data is send to servos
  sbus_passthrough = mode;
}

int passthroughRet(void) {
  // Return current passthrough mode
  return sbus_passthrough;
}
void update_servos(void) {
  // Send data to servos
  // Passtrough mode = false >> send own servo data
  // Passtrough mode = true >> send received channel data
  uint8_t i;
  if (sbus_passthrough==0) {
    // clear received channel data
    for (i=1; i<24; i++) {
      sbus_data[i] = 0;
    }

    // reset counters
    ch = 0;
    bit_in_servo = 0;
    byte_in_sbus = 1;
    bit_in_sbus = 0;

    // store servo data
    for (i=0; i<176; i++) {
      if (servos[ch] & (1<<bit_in_servo)) {
        sbus_data[byte_in_sbus] |= (1<<bit_in_sbus);
      }
      bit_in_sbus++;
      bit_in_servo++;

      if (bit_in_sbus == 8) {
        bit_in_sbus =0;
        byte_in_sbus++;
      }
      if (bit_in_servo == 11) {
        bit_in_servo =0;
        ch++;
      }
    }

    // DigiChannel 1
    if (channels[16] == 1) {
      sbus_data[23] |= (1<<0);
    }
    // DigiChannel 2
    if (channels[17] == 1) {
      sbus_data[23] |= (1<<1);
    }

    // Failsafe
    if (failsafe_status == SBUS_SIGNAL_LOST) {
      sbus_data[23] |= (1<<2);
    }

    if (failsafe_status == SBUS_SIGNAL_FAILSAFE) {
      sbus_data[23] |= (1<<2);
      sbus_data[23] |= (1<<3);
    }
  }
  // send data out
  //serialPort.write(sbus_data,25);
for (i=0;i<25;i++) {
    port0.write(sbus_data[i]);
  }
}
void update_channels(void) {
  uint8_t i;
  uint8_t sbus_pointer = 0;
  // clear channels[]
  for (i=0; i<16; i++) {
    channels[i] = 0;
  }

  // reset counters
  byte_in_sbus = 1;
  bit_in_sbus = 0;
  ch = 0;
  bit_in_channel = 0;

  // process actual sbus data
  for (i=0; i<176; i++) {
    if (sbus_data[byte_in_sbus] & (1<<bit_in_sbus)) {
      channels[ch] |= (1<<bit_in_channel);
    }
    bit_in_sbus++;
    bit_in_channel++;

    if (bit_in_sbus == 8) {
      bit_in_sbus =0;
      byte_in_sbus++;
    }
    if (bit_in_channel == 11) {
      bit_in_channel =0;
      ch++;
    }
  }
  // DigiChannel 1
  if (sbus_data[23] & (1<<0)) {
    channels[16] = 1;
  }
  else{
    channels[16] = 0;
  }
  // DigiChannel 2
  if (sbus_data[23] & (1<<1)) {
    channels[17] = 1;
  }
  else{
    channels[17] = 0;
  }
  // Failsafe
  failsafe_status = SBUS_SIGNAL_OK;
  if (sbus_data[23] & (1<<2)) {
    failsafe_status = SBUS_SIGNAL_LOST;
  }
  if (sbus_data[23] & (1<<3)) {
    failsafe_status = SBUS_SIGNAL_FAILSAFE;
  }

}
void feedLine(){
  while(port0.available()){
    inData = port0.read();
    if (inData == 0x0f){
      bufferIndex = 0;
      inBuffer[bufferIndex] = inData;
      inBuffer[24] = 0xff;
    }
    else{
      bufferIndex ++;
      inBuffer[bufferIndex] = inData;
    }
    if(inBuffer[0]==0x0f & inBuffer[24] == 0x00){

      memcpy(sbus_data,inBuffer,25);
      toChannels = 1;
      return;
    }
  }
}


Andy2No

It took me a while to find the fat16lib SerialPort library you've used (probably because I'm new at this), so here's the link:

http://arduino.cc/forum/index.php/topic,85207.0.html

april

Have you seen the price of these things?
I won't be using them anytime soon

http://www3.towerhobbies.com/cgi-bin/WTI0095P?FVSEARCH=sbus&search=Go

Graynomad

Quote
Have you seen the price of these things?

Yes but they have EASY PAY :)

_____
Rob
Rob Gray aka the GRAYnomad www.robgray.com

mikesbaker

I found a major bug in the code. The problem come from one of the channels transmitting a byte of 0x0F. I changed the code so that is not longer a problem. It also runs slightly faster:

Code: [Select]

#include <SerialPort.h>

#define SBUS_SIGNAL_OK          0x00
#define SBUS_SIGNAL_LOST        0x01
#define SBUS_SIGNAL_FAILSAFE    0x03



SerialPort<0,64,64> port0;


uint8_t sbus_data[25] = {
  0x0f,0x01,0x04,0x20,0x00,0xff,0x07,0x40,0x00,0x02,0x10,0x80,0x2c,0x64,0x21,0x0b,0x59,0x08,0x40,0x00,0x02,0x10,0x80,0x00,0x00};
int16_t channels[18]  = {
  1023,1023,1023,1023,1023,1023,1023,1023,1023,1023,1023,1023,1023,1023,1023,1023,0,0};
int16_t servos[18]    = {
  1023,1023,1023,1023,1023,1023,1023,1023,1023,1023,1023,1023,1023,1023,1023,1023,0,0};
uint8_t  failsafe_status = SBUS_SIGNAL_FAILSAFE;
int sbus_passthrough = 1;
uint8_t byte_in_sbus;
uint8_t bit_in_sbus;
uint8_t ch;
uint8_t bit_in_channel;
uint8_t bit_in_servo;
uint8_t inBuffer[25];
int bufferIndex=0;
uint8_t inData;
int toChannels = 0;
uint32_t baud = 99000;
int feedState = 0;


void setup(){
  port0.begin(baud,  SP_2_STOP_BIT | SP_EVEN_PARITY | SP_8_BIT_CHAR);

}

void loop(){

  feedLine();
 
  if(toChannels==1){
    update_channels();
    update_servos();
    toChannels=0;
  } 

}





int16_t channel(uint8_t ch) {
  // Read channel data
  if ((ch>0)&&(ch<=16)){
    return channels[ch-1];
  }
  else{
    return 1023;
  }
}
uint8_t digichannel(uint8_t ch) {
  // Read digital channel data
  if ((ch>0) && (ch<=2)) {
    return channels[15+ch];
  }
  else{
    return 0;
  }
}
void servo(uint8_t ch, int16_t position) {
  // Set servo position
  if ((ch>0)&&(ch<=16)) {
    if (position>2048) {
      position=2048;
    }
    servos[ch-1] = position;
  }
}
void digiservo(uint8_t ch, uint8_t position) {
  // Set digital servo position
  if ((ch>0) && (ch<=2)) {
    if (position>1) {
      position=1;
    }
    servos[15+ch] = position;
  }
}
uint8_t failsafe(void) {
  return failsafe_status;
}

void passthroughSet(int mode) {
  // Set passtrough mode, if true, received channel data is send to servos
  sbus_passthrough = mode;
}

int passthroughRet(void) {
  // Return current passthrough mode
  return sbus_passthrough;
}
void update_servos(void) {
  // Send data to servos
  // Passtrough mode = false >> send own servo data
  // Passtrough mode = true >> send received channel data
  uint8_t i;
  if (sbus_passthrough==0) {
    // clear received channel data
    for (i=1; i<24; i++) {
      sbus_data[i] = 0;
    }

    // reset counters
    ch = 0;
    bit_in_servo = 0;
    byte_in_sbus = 1;
    bit_in_sbus = 0;

    // store servo data
    for (i=0; i<176; i++) {
      if (servos[ch] & (1<<bit_in_servo)) {
        sbus_data[byte_in_sbus] |= (1<<bit_in_sbus);
      }
      bit_in_sbus++;
      bit_in_servo++;

      if (bit_in_sbus == 8) {
        bit_in_sbus =0;
        byte_in_sbus++;
      }
      if (bit_in_servo == 11) {
        bit_in_servo =0;
        ch++;
      }
    }

    // DigiChannel 1
    if (channels[16] == 1) {
      sbus_data[23] |= (1<<0);
    }
    // DigiChannel 2
    if (channels[17] == 1) {
      sbus_data[23] |= (1<<1);
    }

    // Failsafe
    if (failsafe_status == SBUS_SIGNAL_LOST) {
      sbus_data[23] |= (1<<2);
    }

    if (failsafe_status == SBUS_SIGNAL_FAILSAFE) {
      sbus_data[23] |= (1<<2);
      sbus_data[23] |= (1<<3);
    }
  }
  // send data out
  //serialPort.write(sbus_data,25);
  for (i=0;i<25;i++) {
    port0.write(sbus_data[i]);
  }
}
void update_channels(void) {
  uint8_t i;
  uint8_t sbus_pointer = 0;
  // clear channels[]
  for (i=0; i<16; i++) {
    channels[i] = 0;
  }

  // reset counters
  byte_in_sbus = 1;
  bit_in_sbus = 0;
  ch = 0;
  bit_in_channel = 0;

  // process actual sbus data
  for (i=0; i<176; i++) {
    if (sbus_data[byte_in_sbus] & (1<<bit_in_sbus)) {
      channels[ch] |= (1<<bit_in_channel);
    }
    bit_in_sbus++;
    bit_in_channel++;

    if (bit_in_sbus == 8) {
      bit_in_sbus =0;
      byte_in_sbus++;
    }
    if (bit_in_channel == 11) {
      bit_in_channel =0;
      ch++;
    }
  }
  // DigiChannel 1
  if (sbus_data[23] & (1<<0)) {
    channels[16] = 1;
  }
  else{
    channels[16] = 0;
  }
  // DigiChannel 2
  if (sbus_data[23] & (1<<1)) {
    channels[17] = 1;
  }
  else{
    channels[17] = 0;
  }
  // Failsafe
  failsafe_status = SBUS_SIGNAL_OK;
  if (sbus_data[23] & (1<<2)) {
    failsafe_status = SBUS_SIGNAL_LOST;
  }
  if (sbus_data[23] & (1<<3)) {
    failsafe_status = SBUS_SIGNAL_FAILSAFE;
  }

}
void feedLine(){
  if (port0.available() > 24){
    while(port0.available() > 0){
      inData = port0.read();
      switch (feedState){
      case 0:
        if (inData != 0x0f){
          while(port0.available() > 0){//read the contents of in buffer this should resync the transmission
            inData = port0.read();
          }
          return;
        }
        else{
          bufferIndex = 0;
          inBuffer[bufferIndex] = inData;
          inBuffer[24] = 0xff;
          feedState = 1;
        }
        break;
      case 1:
        bufferIndex ++;
        inBuffer[bufferIndex] = inData;
        if (bufferIndex < 24 && port0.available() == 0){
          feedState = 0;
        }
        if (bufferIndex == 24){
          feedState = 0;
          if (inBuffer[0]==0x0f && inBuffer[24] == 0x00){
            memcpy(sbus_data,inBuffer,25);
            toChannels = 1;
          }
        }
        break;
      }
    }
  }
}

mikesbaker

I have turned this into a library finally.

alekoy

Hi.
It would be very nice if you could include a small description of the hardware-side of this :)

mikesbaker


Hi.
It would be very nice if you could include a small description of the hardware-side of this :)


The output of the SBUS receiver goes into one of the inputs on an inverter (74HC14). The output from that input goes into the serial port. The output of the serial port you are using goes into the inverter and the output will drive SBUS servos. You must have a common ground to all the devices. It is best to run the servos from a different power supply than the arduino.

http://www.datasheetarchive.com/74HC14-datasheet.html

mikesbaker


mikesbaker

The library has been updated to no longer rely on the SerialPort.h library

techspy

Nice project. FYI, you can get cheap sbus receivers from Hobby King now. I have been looking for a similar solution. I want to convert the sbus protocol into a ppm signal. Does your project have that ability? If so, any idea of how much lag there would be?

Thanks

m_marc0

Sir
I am very interested by this project.
I use the Arduino 1.0 version.
I'm not an Arduino specialist and so, I doesn't compile This like the reply 4.
I have an error like "expected constructor,destructor,or type conversion before '<' token and the line "SerialPort<0,64,64> port0;" is in Yellow.
I have had file "SerialPort.h"  from "SerialPortBeta20120106.zip"
In fact, I have a lot of error :
sketch_jan07b.cpp:1:24: error: SerialPort.h: No such file or directory
sketch_jan07b:4: error: expected constructor, destructor, or type conversion before '<' token
sketch_jan07b.cpp: In function 'void setup()':
sketch_jan07b:22: error: 'port0' was not declared in this scope
sketch_jan07b:22: error: 'SP_2_STOP_BIT' was not declared in this scope
sketch_jan07b:22: error: 'SP_EVEN_PARITY' was not declared in this scope
sketch_jan07b:22: error: 'SP_8_BIT_CHAR' was not declared in this scope
sketch_jan07b.cpp: In function 'void update_servos()':
sketch_jan07b:86: error: 'sbus_data' was not declared in this scope
sketch_jan07b:96: error: 'sbus_data' was not declared in this scope
sketch_jan07b:111: error: 'sbus_data' was not declared in this scope
sketch_jan07b:115: error: 'sbus_data' was not declared in this scope
sketch_jan07b:119: error: 'sbus_data' was not declared in this scope
sketch_jan07b:121: error: 'sbus_data' was not declared in this scope
sketch_jan07b:128: error: 'port0' was not declared in this scope
sketch_jan07b:128: error: 'sbus_data' was not declared in this scope
sketch_jan07b.cpp: In function 'void update_channels()':
sketch_jan07b:145: error: 'sbus_data' was not declared in this scope
sketch_jan07b:160: error: 'sbus_data' was not declared in this scope
sketch_jan07b:167: error: 'sbus_data' was not declared in this scope
sketch_jan07b:175: error: 'sbus_data' was not declared in this scope
sketch_jan07b:178: error: 'sbus_data' was not declared in this scope
sketch_jan07b.cpp: In function 'void feedLine()':
sketch_jan07b:183: error: 'port0' was not declared in this scope
sketch_jan07b:210: error: 'sbus_data' was not declared in this scope
Could you hepl me ?
Best regards
Marc

Tissy

Fantastic work on this Mike  8)

I have a Futaba 14 channel transmitter (T8FG) with the R6208SB S-BUS receiver.

I would like to use the S-BUS channel to control 4 PPM outputs via the Arduino.  Just probably simple switched outputs not servos at all.

One appplication is two create a different output depending on the PMM input range from the transmitter.

Is it possible to use your code for this 4 channel application, or is it designed for just one channel.

Many thanks and great work again.

Steve

mikesbaker

Sorry for the slow response guys, I thought I was subscribed to this thread. In the future if someone needs some help with this library PM me if I don't respond fairly quickly.

m_marc0 - I have updated the library to no longer require SerialPort.h. It now uses the standard arduino conventions. The library is currently configured to run off of Serial1 on a mega. To change the edit the FUTABA_SBUS.h line 12

Code: [Select]

#define SBUS_SIGNAL_OK          0x00
#define SBUS_SIGNAL_LOST        0x01
#define SBUS_SIGNAL_FAILSAFE    0x03
#define BAUDRATE 100000
#define port Serial1  <---- this is the line to change


Tissy -I'm not quite clear on what you are trying to accomplish. You can create a PPM signal based off the different channels, but to what end? If you could describe your project in a little more detial hopefully I can give you a better answer. Also, are you sure you aren't confusing PPM with PWM?

Tissy

Thanks Mike.

Sorry, you're right, I wasn't very clear.

Basically I have a Futaba R6208SB S-BUS receiver which I am using on a multi-rotor.

I have one of the outputs from this receiver going to an Arduino MiniPro which is programmed to detect the PWM signal and use an IR LED to control a Sony NEX5 camera.  This works very will and I can switch the camera modes between video and stills and take manual or automatic interval pictures.

As this is connected to one of the main outputs of the Rx and I have a Futaba 14 channel Tx (T8FG), I would like to move auxiliary functions such as this to the S-Bus.

So the code on the MiniPro will read the S-BUS and perform a function (either servo based or logic based) on say 4 channels.

I know this device (FUTM4191 SBD-1 S.Bus Decoder) is available to decode the S-Bus signal to PWM, however I would still need the MiniPro to perform certain functions, so I was looking at cutting out the middle man if possible.

http://www.futaba-rc.com/sbus/

I would need to define the channel on the MiniPro which corresponds to the channel on the Tx, for example channels 9 - 12.

Does that make sense or have I made it worse :-D

Steve

Go Up