Go Down

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

mikesbaker

Got it. What you need to do is have a way to turn SBUS into PWM.

Let's talk at little bit more about SBUS and DSM2/DSMx serial. They are simply transmitting unsigned integers in raw binary over a UART. This means once you have these values you can do whatever you want with them. DSMx and SBUS have a 2048 resolution (2^11) whereas DSM2 has a 1024 (2^10) resolution. OK so the question now is how do we do something with this information.

First you map those values to a range you want to use:

Code: [Select]


void MapVar (float *x, float *y, float in_min, float in_max, float out_min, float out_max){
  *y = (*x - in_min) * (out_max - out_min) / (in_max - in_min) + out_min;
}
void MapVar (int16_t *x, float *y, float in_min, float in_max, float out_min, float out_max){
  *y = (*x - in_min) * (out_max - out_min) / (in_max - in_min) + out_min;
}

void MapVar (uint16_t *x, float *y, float in_min, float in_max, float out_min, float out_max){
  *y = (*x - in_min) * (out_max - out_min) / (in_max - in_min) + out_min;
}



In the above code I am using pointers. In addition to using pointers you could simple pass the variables or make the variables global. For the servo control you want to generate a PWM signal. Here is a great article about the difference between the two:

http://www.endurance-rc.com/ppmtut.php

Both PPM and PWM use square waves of varying width to convey information. Let's look at servos and ESC (electronic speed controllers). They are controlled by PWM signal with a frequency between 50 - 500Hz and a pulse width of around 1000 - 2000us. For this signal we are concerned with the pulse width and not the PWM's duty cycle. The center is 1500us, but many servos center at 1520. If you run a servo at too high a frequency it will break the servo. This is also for ESCs.

There are a number of ways that you can generate these signals with the arduino. The easiest is to use servo.h. It will allow you to make a lot of these signals on any pin you want. It also doesn't run all that fast. The maximum refresh rate is ~125Hz. A good flight controller runs the control loops very fast and updates the ESCs at ~400Hz. You want to use AVR C. I know that can be a big turn off for a lot of people here since it is considered difficult. And it is, but it is not too bad. If you do it right you only have to figure it out once. Here is another code sample to illustrate how to generate higher frequency ESC PWM signals. This code was written for the mega, but if you look at the datasheet for the 128 it will become clear to you as to how to do it. If it not don't be afraid to ask for assistance with a concept you are struggling with.

Code: [Select]


//motor defines
#define FREQ 400
#define PRESCALE 8
#define PERIOD ((F_CPU/PRESCALE/FREQ) - 1)

#define Motor1WriteMicros(x) OCR3B = x * 2//motor 1 is attached to pin2
#define Motor2WriteMicros(x) OCR3C = x * 2//motor 2 is attached to pin3
#define Motor3WriteMicros(x) OCR3A = x * 2//motor 3 is attached to pin5
#define Motor4WriteMicros(x) OCR4A = x * 2//motor 4 is attached to pin6

void setup(){
  MotorInit();
  //do other stuff
}

void loop(){
  Motor1WriteMicros(1250);
  Motor2WriteMicros(someVar);
  //do other stuff
}

void MotorInit(){
  DDRE |= B00111000;//set the ports as outputs
  DDRH |= B00001000;

 
  // Init PWM Timer 3                                       
  // WGMn1 WGMn2 WGMn3  = Mode 14 Fast PWM, TOP = ICRn ,Update of OCRnx at BOTOM
  TCCR3A = (1<<WGM31)|(1<<COM3A1)|(1<<COM3B1)|(1<<COM3C1);  // Clear OCnA/OCnB/OCnC on compare match, set OCnA/OCnB/OCnC at BOTTOM (non-inverting mode)
  TCCR3B = (1<<WGM33)|(1<<WGM32)|(1<<CS31);                 // Prescaler set to 8, that gives us a resolution of 0.5us
  ICR3 = PERIOD;                                // Clock_speed / ( Prescaler * desired_PWM_Frequency) #defined above. 

  TCCR4A = (1<<WGM41)|(1<<COM4A1);
  TCCR4B = (1<<WGM43)|(1<<WGM42)|(1<<CS41);
  ICR4 = PERIOD;
 
  Motor1WriteMicros(1000);//set the output compare value
  Motor2WriteMicros(1000);
  Motor3WriteMicros(1000);
  Motor4WriteMicros(1000);
 
}



This code will not run on the miniPro. This is meant to be an example as to how to set this all up. Furthermore, the 128 only has 1 16bit timer. The above code gives a 0.5us resolution. To use this method with the 128 it will give you two channels with 16bit resolution and two channels with 8 bit of resolution.

Hope that helps. Let me know if there is anything further I can clear up for you.

Tissy

Michael,

Thank you so much for your detailed response, its really appreciated.

I always seem to jump straight into the deep end.

Is there a reason the code will not run on a ATMega328 Pro Mini?

I have posted my code below to hopefully give you an idea of what I am trying to achieve.  Instead of using the normal output from my RC receiver, I would like to adapt the code to use the S-BUS instead and thereby freeing up at least one of the normal Rx channels.

You will see from the code below that if the PWM input is detected between certain frequencies, then it outputs accordingly, in this case utilising an IR Led.

In addition, I would like to use another S-Bus channel for the Arduino to switch on landing lights for example if again a switch on the Tx is 'on'.  Chances are I won't need it to control servos, just some auxiliary devices.

So my need is not to generate a PWM signal from the Arduino, but to interpret the S-BUS signal and have set routines depending on the channel input.

Hope that makes sense and thank you again for your assistance.

Steve
Code: [Select]

#include <multiCameraIrControl.h>

int x=50, y=300, z=100;
int ledPin = 13;
int IRPin = 2;
int RxInput = A0;
int pic_pause = 5000;
Sony Nex5(IRPin);

void setup()
{
Serial.begin(9600);      //
  pinMode (IRPin, OUTPUT);  // Output pin for IR LED
  pinMode (RxInput, INPUT);    // Input pin for PWM from Rx
}

void loop () {
z = pulseIn(RxInput, HIGH, 20000);
if (z<1400) {
  digitalWrite(IRPin, 0);
  Serial.println(z);
  delay(y); 
  }
//if (z>1401 && z<1599) {

  if (z>1155 && z<1170) {
    digitalWrite(IRPin, 1);
    Nex5.shutterNow();
  //  Nex5.shutterDelayed();
  //  delay(500);
    digitalWrite(IRPin, 0);
    Serial.println(z);
  //  delay(y);
    }
 
  if (z>1780 && z<1820) {
    digitalWrite(IRPin, 1);
    Nex5.shutterNow();
  //  delay(500);
    digitalWrite(IRPin, 0);
    Serial.println(z);
    delay(pic_pause);
    }

  if (z>1430 && z<1500) {
    digitalWrite(IRPin, 1);
    Nex5.toggleVideo();
    delay(500);
    digitalWrite(IRPin, 0);
    Serial.println(z);
  //  delay(y);
    }
 
  if (z>1600)   { 
    digitalWrite(IRPin, 1); delay (x); digitalWrite(IRPin, 0); delay (x);
    digitalWrite(IRPin, 1); delay (x); digitalWrite(IRPin, 0); delay (x);
    delay(y);
    Serial.println(z);
    }
  }

mikesbaker

You should be able to do that no problem. Just assign the variable Z to one of the channels off the SBUS. Use that map function to map max and min of the SBUS channel to whatever you want or modify the code to work with different ranges.

Tissy

Thanks Mike.

So should the code you have posted above still use the FUTABA_SBUS.h library?

I haven't used the map function before, so should the variables in_min & in_max be the expected PWM frequencies such as 1023 for example from the Rx?

I think I can work my code, I'm just trying to understand how the S_Bus channels are defined etc.

Could you expand a little more please :-)

Thanks again Mike,

Steve

ssozonoff

Hi Mike,

Great stuff you have done, have you had a chance to look at the SBUS2 protocol? It seems to be frames appended after the channel data is sent. I just received an FX32 so should be able to do some tests shortly. Would be interested in building a "base" sensor PCB around Arduino which people could extend to build their own sensors.

One thing I did read is that increasing the telemetry data rate has a direct impact on servo smoothness, which I guess makes perfect sense. Price to pay for sharing bandwidth I guess however not clear if there is any difference depending on amount of telemetry slots being used.

Best,
Serge

rharms427

I am attempting to test compile the example code and am having an issue.
Arduino IDE 1.5.2  Board is Mega
This is the line that fails
Serial<<sBus.channels[0]<<","<<sBus.channels[1]<<","<<sBus.channels[2]<<"\r\n";

With this error:
invalid operands of types "int" and 'const char[2]' to binary 'operator<<'
sbus_example.ino: In function 'void loop()':
sbus_example:19: error: invalid operands of types 'int' and 'const char [2]' to binary 'operator<<'

I am sorry to bother you, but I am at a loss.  While I have done some Arduino projects, I must confess that I am having a difficult time following  even the example  However, If I can get it to compile, then I will continue to plug away a figure out how this works.
Thank you for your time and efforts.
Rick Harms

rharms427

In regards to my previous reply, please ignore my question on the compile error.  I realized that for my project I do not need that ability.   For now, I replaced it with a series of print statements to verify that I can indeed read the sbus.  IT WORKED. I am ecstatic.  This will become the basis for an on board mixer that I am working on.  Thank you.

codeforge

Hi,

is there a way to make sbus signal with your library?

irony74


Soler

#24
Apr 14, 2014, 07:19 pm Last Edit: Apr 15, 2014, 12:38 pm by Soler Reason: 1
I am using the following code and I am not getting any output
Code: [Select]
#include <FUTABA_SBUS.h>
#include <Streaming.h>


FUTABA_SBUS sBus;
#include <Servo.h>

Servo myservo;  // create servo object to control a servo
               // a maximum of eight servo objects can be created

int pos = 0;    // variable to store the servo position



void setup(){
 sBus.begin();
 Serial.begin(115200);
  myservo.attach(9);  // attaches the servo on pin 9 to the servo object
}

void loop(){
 sBus.FeedLine();

 if (sBus.toChannels == 1){
   sBus.UpdateServos();
   sBus.UpdateChannels();
   sBus.toChannels = 0;
  Serial<<sBus.channels[0]<<","<<sBus.channels[1]<<","<<sBus.channels[2]<<"\r\n";

 }
 pos = sBus.channels[2];
 myservo.write(map(pos, 1000, 2000, 1, 180));

}

From what i see the if condition    sBus.toChannels == 1  is never reached.

I have tried 2 different Hex investors and on a Nano and Mega, the receiver I am using is the FR Sky TFR8SB and I have the Signal line from the S.Bus port connected to the input to the Hex inverter,  output of that pin to RX on Arduino.  The receiver is bound to the transmitter.

If I use Serial<<sBus.failsafe_status<<" Fail safe status \r\n";  I always receive a 0 from the status.

if someone can post some working code I would appreciate it,  Thanks

Soler

Please see the attached schematic that I have used for this.

mikesbaker

That all looks fine. Do you have access to an oscilloscope or logic analyzer? Are you getting anything on the serial port? Try echoing each byte received in hex. Is your receiver 3.3v logic levels?

Soler

Hi All,

I got this working now,  looks like i had a poor connection on my breadboard that was causing the issues.   

Thanks for the help.

One issues i am having is detecting the s.bus signal loss or failsafe,  it seams to detect for one pulse and stops,  should i have this within the " if (toChannels == 1){"   or out side this?

Soler

Does this have to be on the RX and TX pins or could this use other pins?   I see that people are using other pins to read and write PPM signals,  but does S.Bus need to be on the RX and TX?


Thanks

mikesbaker

#29
May 23, 2014, 04:48 pm Last Edit: May 24, 2014, 05:13 pm by mikesbaker Reason: 1
Yes it has to be the serial port.

I have an updated version of this code now. It is written for the mega. It will handle DSMx serial (no DSM2, but the change to DSM2 is trivial), sbus, or PWM RC signals. I may or may not get around to including a PPM decoder.

The only library required is streaming, but only because I find a bunch of printlns to be a pain to read. In addition to the obvious hardware you need a push button or toggle switch to go through the calibration process.

The way this works is you first center the sticks on your transmitter. It will output the raw values during this time.  Toggle the switch. It will output the center values. Toggle again and you are in the min max calibration section. Move all the switches and sticks through their entire range. Toggle the switch a few times and it will give you info regarding the calibration.

From there it just outputs the values scaled to +/- 1000 (great for servo PWM signal). The aileron, rudder, and elevator will be centered at 1500. The rest of the channels will have their minimum value be guaranteed to be 1000. Pretty important to make sure minimum stick on throttle is actually the minimum command! If you have the range on the transmitter set the same IE 100% both directions it will also center the values at 1500. The purpose of centering A,E,R to 1500 is for my quad copter project so I don't have to use any trims. Hope this helps someone and I'll try to be better about keeping on top of this thread.

Pastbin link because it is too long to post on the forum:

http://pastebin.com/LNHFg7Fq

Go Up