Arduino based S-Bus decoder with CPPM and Servo

Electronic projects that are either related to the firmwares for the 9x, or simply great for radio control applications.
Helle
Posts: 577
Joined: Sat Jul 21, 2012 7:08 am
Country: -

Arduino based S-Bus decoder with CPPM and Servo

Post by Helle »

Hy,

my S-Bus Decoder with Arduino pro micro is running.

input is S-Bus form X8R at RX-pin
output is PPM at Pin 9 or pin 10 with free selectible Timing
PPM-Timing 100 to 500us, 10000us to 22500 to 40000us ch1 to ch 16
positiv or negativ
and has 8 Kanal Servo output Ch9 to Ch16 Pin 2,3,4,5,6,7,8,16 or others
and Serial to PC via USB for test and control S-Bus data and Servo Data

here you will find the software, the libraries and the shematic
http://fpv-community.de/showthread.php? ... ods/page12

and Page #80, #113, #115, #116, #119. #132

Software is adapted from some big Librarys with lots of options but only some funktions I need
Most librarys are for Mega 128 or 2560, so I had to change some details for AT32u4

from Futaba S-bus decoder Library
from PPM encoder but with Timer 3 so Timer 1 is free!
from RC Library the ServoOut Function
SerialOut via USB to PC from Serial Streaming library
Some Funktions from Arduino

Its quick and dirty programmed at one evening, I know, but its running
and works fine on the table and pinboard
all signals are testetd with Oszi

Helle

AlanGP
Posts: 27
Joined: Sun Feb 19, 2012 6:51 pm
Country: -
Location: Paderborn, Germany

Re: Arduino based S-Bus decoder with CPPM and Servo

Post by AlanGP »

Well done! There are a lot of Taranis owners looking for something like this. I will build one this weekend.

Vielen Dank

Alan
Anything can be made to fly - wings are just an option
User avatar
jhsa
Posts: 19480
Joined: Tue Dec 27, 2011 5:13 pm
Country: Germany

Re: Arduino based S-Bus decoder with CPPM and Servo

Post by jhsa »

Helle wrote: and has 8 Kanal Servo output Ch9 to Ch16 Pin 2,3,4,5,6,7,8,16 or others
and Serial to PC via USB for test and control S-Bus data and Servo Data
Hi Is this also possible to have channels 1 to 8?
here you will find the software, the libraries and the shematic
http://fpv-community.de/showthread.php? ... ods/page12

and Page #80, #113, #115, #116, #119. #132

Software is adapted from some big Librarys with lots of options but only some funktions I need
Most librarys are for Mega 128 or 2560, so I had to change some details for AT32u4
Could this be adapted to work with an arduino Pro Mini, Nano, Uno, etc?

Thanks,

João
My er9x/Ersky9x/eepskye Video Tutorials
https://www.youtube.com/playlist?list=PL5uJhoD7sAKidZmkhMpYpp_qcuIqJXhb9

Donate to Er9x/Ersky9x:
https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=YHX43JR3J7XGW
mstrens
Posts: 1435
Joined: Fri Dec 27, 2013 7:49 pm
Country: -

Re: Arduino based S-Bus decoder with CPPM and Servo

Post by mstrens »

jhsa wrote:
Helle wrote: and has 8 Kanal Servo output Ch9 to Ch16 Pin 2,3,4,5,6,7,8,16 or others
and Serial to PC via USB for test and control S-Bus data and Servo Data
Hi Is this also possible to have channels 1 to 8?
here you will find the software, the libraries and the shematic
http://fpv-community.de/showthread.php? ... ods/page12

and Page #80, #113, #115, #116, #119. #132

Software is adapted from some big Librarys with lots of options but only some funktions I need
Most librarys are for Mega 128 or 2560, so I had to change some details for AT32u4
Could this be adapted to work with an arduino Pro Mini, Nano, Uno, etc?

Thanks,

João
I just developped a similar poject this week.
My target was to expand an X4R receiver.
It uses an arduino pro mini at 16 mhz.
It uses no big library (except for debugging).
I think that it does not require an inverter on the SBSUS even if such an inverter is better (at least to protect the arduino).
It decodes currently 6 channels but it could adapted in order to decode more channels (probably up to 12 on top of the 4 already provided by X4R)
It is possible to set up in the firmware the channels that have to be decoded.
Debugging uses the SPI interface and another arduino pro mini.
If you want I can send you the alpha code.
I also plan to design a small board having the same size as the arduino pro mini in order to support all servo connectors (otherwise it is possible to connect servo cables directly to the arduino.
User avatar
jhsa
Posts: 19480
Joined: Tue Dec 27, 2011 5:13 pm
Country: Germany

Re: Arduino based S-Bus decoder with CPPM and Servo

Post by jhsa »

That is what I'm looking for. Connecting the servos directly to the arduino with frsky s.bus as input.

Thanks

João
My er9x/Ersky9x/eepskye Video Tutorials
https://www.youtube.com/playlist?list=PL5uJhoD7sAKidZmkhMpYpp_qcuIqJXhb9

Donate to Er9x/Ersky9x:
https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=YHX43JR3J7XGW

mstrens
Posts: 1435
Joined: Fri Dec 27, 2013 7:49 pm
Country: -

Re: Arduino based S-Bus decoder with CPPM and Servo

Post by mstrens »

jhsa wrote:That is what I'm looking for. Connecting the servos directly to the arduino with frsky s.bus as input.

Thanks

João
I propose to make some more tests and then to publish the code and some explanations how to use it.
I expect it should be OK in less than 1 week
User avatar
Kilrah
Posts: 11108
Joined: Sat Feb 18, 2012 6:56 pm
Country: Switzerland

Re: Arduino based S-Bus decoder with CPPM and Servo

Post by Kilrah »

If you don't want to bother, there's a neat little Chinese device with an ARM processor that already has 16 servo connectors, converts Sbus, PPM and servo signals to and from each other in all directions, and costs a mere $13. Hardware generation on all signals too, so no jitter and other issues.
rdeanchurch
Posts: 750
Joined: Tue Dec 27, 2011 11:22 pm
Country: United States
Location: Carson City, Nv

Re: Arduino based S-Bus decoder with CPPM and Servo

Post by rdeanchurch »

Kilrah wrote:If you don't want to bother, there's a neat little Chinese device with an ARM processor that already has 16 servo connectors, converts Sbus, PPM and servo signals to and from each other in all directions, and costs a mere $13. Hardware generation on all signals too, so no jitter and other issues.
Got a link?
TIA
Dean
OldDmbThms: 1. Takeoff, 2. Crash, 3. Repair, GOTO 1
User avatar
jhsa
Posts: 19480
Joined: Tue Dec 27, 2011 5:13 pm
Country: Germany

Re: Arduino based S-Bus decoder with CPPM and Servo

Post by jhsa »

Kilrah wrote:If you don't want to bother, there's a neat little Chinese device with an ARM processor that already has 16 servo connectors, converts Sbus, PPM and servo signals to and from each other in all directions, and costs a mere $13. Hardware generation on all signals too, so no jitter and other issues.
Well, I would like to bother ;)
I like to build my own stuff. Radios, sensors, planes, etc :) But that do sound interesting, only because it has 16 available pins :)

João
My er9x/Ersky9x/eepskye Video Tutorials
https://www.youtube.com/playlist?list=PL5uJhoD7sAKidZmkhMpYpp_qcuIqJXhb9

Donate to Er9x/Ersky9x:
https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=YHX43JR3J7XGW
rdeanchurch
Posts: 750
Joined: Tue Dec 27, 2011 11:22 pm
Country: United States
Location: Carson City, Nv

Re: Arduino based S-Bus decoder with CPPM and Servo

Post by rdeanchurch »

So do I except for the mechanics of the connectors.
This shield would do to end that.
http://www.ebay.com/itm/F08019-16-Chann ... 41878ec866
But it is also $13
Dean
OldDmbThms: 1. Takeoff, 2. Crash, 3. Repair, GOTO 1
User avatar
jhsa
Posts: 19480
Joined: Tue Dec 27, 2011 5:13 pm
Country: Germany

Re: Arduino based S-Bus decoder with CPPM and Servo

Post by jhsa »

The reason I want to build the S.Bus to PWM decoder is to work with this project here..

viewtopic.php?f=91&t=5476

Eepskye can now output 16 channels in the S.bus format. we Already have the possibility of decoding to PPM for the 9x users, thanks to Mike, but having the arduino decoding the SBus signal to PWM would also be nice as it would give more possibilities..

João
My er9x/Ersky9x/eepskye Video Tutorials
https://www.youtube.com/playlist?list=PL5uJhoD7sAKidZmkhMpYpp_qcuIqJXhb9

Donate to Er9x/Ersky9x:
https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=YHX43JR3J7XGW
Erni
Posts: 27
Joined: Wed Jun 04, 2014 3:41 pm
Country: -

Re: Arduino based S-Bus decoder with CPPM and Servo

Post by Erni »

If you use the Sbus and servo librarys with the Arduino it becomes quite simple.
I have used this sketch just to see if it worked.
You just feed the sbus signal from the receiver through an inverter to rx on the Arduino
You can have 12 servos connected with a Arduino mini.
I used a transistor as inverter.

#include <FUTABA_SBUS.h>
#include <Servo.h>

FUTABA_SBUS sBus;
Servo myservo;

void setup(){
sBus.begin();
myservo.attach(14);
}

void loop(){
sBus.FeedLine();
if (sBus.toChannels == 1){
sBus.UpdateServos();
sBus.UpdateChannels();
sBus.toChannels = 0;
myservo.writeMicroseconds(sBus.channels[6]+800);
}
}
mstrens
Posts: 1435
Joined: Fri Dec 27, 2013 7:49 pm
Country: -

Re: Arduino based S-Bus decoder with CPPM and Servo

Post by mstrens »

Please find here the code I made in order to extend the X4R receiver (or other X serie receiver) with 6 additional PPM channels.
The PPM signals are generated every 18 ms so it should work with all kind of servos.
All explanations are at the beginning of the code.
It does not use complex libraries (except if you want to debug).
Even the standard arduino interrupts are disabled.
I tested it and it seems me OK (e.g. no jitter at all)

Comments are welcome.

Code: Select all

// This poject uses Arduino pro mini 5 volt 16 mhz in order to decode Futaba SBUS.
// This version decodes the 16 channels available on SBUS but generates only 6 PPM signals.
// The 6 PPM signals are available on arduino pins 2 up to 7.
// The 6 PPM signals are based on 6 consecutive channels.
// By default, pin 2 deliver the first channel on traditional receiver and pin 7 deliver the channel 6. 
// Still, you can select the fist channel changing the value "1" in the line "#define FIRSTCHANNEL 1" here under.
// E.g. if you want that PPM channel on arduino pin 2 is channel 4 from a traditional the line would be would be "#define FIRSTCHANNEL 4"
//      In this case, pin 3 will generate channel 5, pin 4 will generate channel 6 ...
// The PPM signals are generates once per 18 msec. This refresh rate is supported by all servos. 
//
// The SBUS signal has to be inverted before connecting it to arduino pin Rx. There are 2 ways to invert the SBUS signal
// 1 : Best is to invert the SBUS signal from the Receiver with a transistor and 2 resistors (see the web for a schema or look at a commecial product at e.g. Hobbyking).
// 2: It is also possible to let Arduino invert the SBUS signal but this takes some delay and some SBUS frame are lost. Still servos seems to react fast.
//    In this case, the original (non inverted) SBUS signal has to be applied on arduino digital pin 8.
//    The inverted signal is then available on arduino digital pin 9. Pin 9 has then to be connected to arduino pin Rx.
//
// As long as Arduino did not get a valid Sbus frame after power on, it wil not generate PPM signals.
// As soon as a valid Sbus frame is received, the 6 PPM signal wil be generated.
// Unvalid sbus frames are discarded and arduino generates PPM signals based on last correct frame received.
// Please note that if no new (valid) frames are received, the latest valid frame will continue to be used to generate the PPM signals (so servos will keep their last position). 
//
// Arduino has a green led. This led will work as follow:
// If arduino never got a valid Sbus (e.g. Tx not powered on, no bind with Rx), led is off.
// Once a valid Sbus frame is received, the led will start blinking.
// When led blink fast (about 1 per sec) , it means that there are no Sbus frame error (normal case).
// If Arduino get at one invalid frame, the led wil be on for about 3 sec.
// If during the 3 sec, there are no other Sbus frame error, it will continue with fast binking rate.
// If there is at least one new frame error within the 3 sec, the led will be on for the next 3 seconds
// So, if there are many frame errors, it could be that the led is always on.
//
// Debugging : 
// It is not possible to debug this project with serial.print command because the hardware serial is already used to read the Sbus (and a sofware serial would be to slow).
// Still, it is possible to debug this project using a second arduino connected to this one via the SPI interface.
// The second arduino has then to be loaded with a "slave" program.
// More informations at this link : http://www.utopiamechanicus.com/article/spi-debug/
//
// Note about loading the Arduino pro mini with this firmware.
// Programming the Arduino pro mini is normally done using a FTDI cable (or board).
// This FTDI connects on Pc side with the USB. On arduino side, it uses the RX and TX pins.
// In this project Arduino Rx pin is normally connected to the (inverted Sbus signal).
// Please take care not to connect the arduino to the Rx (Sbus port) while programming it (otherwise programming will fail).


// this line selects the first channels being generated; this version will then generate this channel and the next 5 channels;
#define FIRSTCHANNEL 1 


#define PPMCHANNEL (FIRSTCHANNEL - 1) 

#include <avr/io.h>
#include <avr/interrupt.h>    // Needed to use interrupts    


#include <SPI.h>
#include "pins_arduino.h"

// conditional debugging
// uncomment to debug using SPI interface
//#define DEBUG 


#ifdef DEBUG 

  #define BEGIN_DEBUG   do { SPI.begin (); SPI.setClockDivider(SPI_CLOCK_DIV8); } while (0)
  #define TRACE(x)      SPIdebug.print   (x)
  #define TRACE2(x,y)   SPIdebug.print   (x,y)
  #define TRACELN(x)    SPIdebug.println (x)
  #define TRACELN2(x,y) SPIdebug.println (x,y)
  
  class tSPIdebug : public Print
  {
  public:
    virtual size_t write (const byte c)  { 
      digitalWrite(SS, LOW); 
      SPI.transfer (c); 
      digitalWrite(SS, HIGH); 
    }  // end of tSPIdebug::write
  }; // end of tSPIdebug
      
  // an instance of the SPIdebug object
  tSPIdebug SPIdebug;
  
#else
  #define BEGIN_DEBUG   ((void) 0)
  #define TRACE(x)      ((void) 0)
  #define TRACE2(x,y)   ((void) 0)
  #define TRACELN(x)    ((void) 0)
  #define TRACELN2(x,y) ((void) 0)
#endif // DEBUG

#define PIN_LED            13  // The Signal LED (default=13=onboard LED)
#define FRAME_DURATION 36000  // 36000 = 18000 usec * 2 because frequency is 16Mz and prescaler is 8
#define ZEROCYCLE 3000 // 1500 usec * 2
#define SBUS_MAXCHAR 25
#define MAX_CHANNELS 16

//  Measured values with Futaba FX-30/R6108SB:
//    -+100% on TX:  PCM 1.100/1.520/1.950ms -> SBus raw values: 350/1024/1700  (100% ATV)
//    -+140% on TX:  PCM 0.930/1.520/2.112ms -> SBus raw values:  78/1024/1964  (140% ATV)  
//    -+152% on TX:  PCM 0.884/1.520/2.160ms -> SBus raw values:   1/1024/2047  (140% ATV plus dirty tricks)

// define range mapping here, -+100% -> 1000..2000 
#define SBUS_RANGE_MIN 200.0f
#define SBUS_RANGE_MAX 1800.0f

#define SBUS_TARGET_MIN 2000.0f // 2000 cycles = 1000 usec (because prescaler = 1/8 and Mhz = 16) 
#define SBUS_TARGET_MAX 4000.0f // 4000 cycles = 2000 usec (because prescaler = 1/8 and Mhz = 16) 

// pre-calculate the floating point stuff as far as possible at compile time 
#define SBUS_SCALE_FACTOR ((SBUS_TARGET_MAX - SBUS_TARGET_MIN) / (SBUS_RANGE_MAX - SBUS_RANGE_MIN))
#define SBUS_SCALE_OFFSET (int)(SBUS_TARGET_MIN - (SBUS_SCALE_FACTOR * SBUS_RANGE_MIN + 0.5f))


uint32_t counter = 1;
uint32_t debugCounter ;
static boolean sbusStarted = false;
static boolean channelToLoad = false;
static uint8_t servoIndex ;
static uint8_t servoPin[] =             { 0 , 0 , 0x04 , 0x04 , 0x08 , 0x08 , 0x10 , 0x10  , 0x20  ,  0x20 , 0x40  ,  0x40 ,  0x80 ,  0x80} ; // each value will toggle a pin
volatile static uint16_t servoCycle[] = { 0 , 0 , 3000 , 5000 , 8000 , 10000, 13000, 15000 , 18000 , 20000 , 23000 , 25000 , 28000 ,  40000 } ; // !!!! the last value must be higher than FRAME_DURATION in order to let CompB generates an interrupt
static uint8_t UART_error ;
static uint8_t timer0 ;
static uint8_t bufferIndex ;
static uint8_t buffer[SBUS_MAXCHAR] ;
//static uint16_t channels[ MAX_CHANNELS ] ;
static uint16_t channelsMicrosec[ MAX_CHANNELS ] ;
unsigned char readOneChar ;
unsigned char c ;
uint16_t countUART_Error ;
uint16_t countSBUS_TimeToBigError ;
uint16_t countSBUS_TimeToSmallError ;
uint16_t countSBUS_StartError ;
uint16_t countSBUS_EndError ;
uint16_t countSBUS_Frame ;
uint16_t countSBUS_Error ;

uint8_t led ; 

//************************************ Init*************************************

void init() // this function is replace the standard Arduino function
{
  // Timer1
  TIMSK1 &= ~( 1<< OCIE1A ) ; // Disable interupt on timer 1 for compA
  TIMSK1 &= ~( 1<< OCIE1B ) ; // Disable interupt on timer 1 for compB
  TCCR1A = 0x00 ;    //Init.
  TCCR1B = 0xC1 ;    // I/p noise cancel, rising edge, Clock/1

	// the bootloader connects pins 0 and 1 to the USART; disconnect them
	// here so they can be used as normal digital i/o; they will be
	// reconnected in Serial.begin()
	UCSR0B = 0;
	sei();  //allow interrupt in general
}

void initPinChange()
{
    DDRB &= ~(1 << DDB0);         // Clear the PB0 pin (= arduino digital pin 8)
    // PB0 (used by PCINT0 pin) is now an input
    DDRB |= (1 << DDB1);         // Set the PB1 pin (= arduino digital pin 9)
    // PB1 is now an output


    PORTB |= (1 << PORTB0)  ;        // turn On the Pull-up
//    // PB0 is now an input with pull-up enabled

    PCICR |= (1 << PCIE0);     // set PCIE0 to enable PCMSK0 scan
    PCMSK0 |= (1 << PCINT0);   // set PCINT0 to trigger an interrupt on state change 

    sei();                     // turn on interrupts
}



#define MYUBRR 9 // FOSC/16/BAUD-1 = 16000000/16/100000 -1
#define SBUS_STARTBYTE 0x0F
#define SBUS_ENDBYTE 0x00


void USART_Init( unsigned int ubrr){
    //Set baud rate 
    UBRR0H = (unsigned char)(ubrr>>8);
    UBRR0L = (unsigned char)ubrr;
    // Enable receiver only  
    UCSR0B = 0x10 ;  //no interrupt, Rx enabled, TX disabled, only 8 bits
    /* Set frame format: 8data, 2stop bit */
    UCSR0C = 0x2E ; // mode normal, even parity , 2 stop bit, 8 bits, no polarity for asynchrone
}

void initTimer0(){    // timer0 is used to check if there are at least 3 msec without received char before starting a frame.
    TCCR0A = 0x00;             // no use of O0A and B and normal mode of operation (increment of TCNT0) 
    TCCR0B = 0x05 ;          // initialise prescaler to 1024 ; so timer is active and 1 count = 1024/16 usec = 64 usec)    

   
    OCR0A =  46 ; // 3000 usec / 64 usec = 46
    TCNT0 = 0;              // set the timer count to 0
    
    TIFR0 |= _BV(OCF0A) ;     // clear any pending interrupts on CompA 
    TIMSK0 = 0  ; // do not enable interrupt
}

void initTimer1(){
    TCCR1A = 0;             // no output based on compA/compB 
    TCCR1B = 0x0 ;          // initialise prescaler to 0 ; so timer is not active   
//    TCCR1B = 0x02 ;         // set prescaler of 8 (binary = 0000 0010 (only CS11 = 1) 
   
    OCR1A = servoCycle[2] ; 
    OCR1B = FRAME_DURATION ;
    TCNT1 = 0;              // clear the timer count 
    
    TIFR1 |= _BV(OCF1A) | _BV(OCF1B);     // clear any pending interrupts on CompA and compB 
    TIMSK1 |=  _BV(OCIE1A) ; // enable the output compare interrupt on CompA 
    TIMSK1 |=  _BV(OCIE1B) ; // enable the output compare interrupt on CompB
}

void startTimer1(){
    TCCR1B = 0x02 ;         // set prescaler of 8 (binary = 0000 0010 (only CS11 = 1) 
   
    OCR1A = servoCycle[2] ;
    PORTD = 0x04 ; // set PD2 high to start the first frame

    TCNT1 = 0;              // clear the timer count 
    
    TIFR1 |= _BV(OCF1A) | _BV(OCF1B);     // clear any pending interrupts on CompA and CompB; 
    TIMSK1 |=  _BV(OCIE1A) ; // enable the output compare interrupt 
    TIMSK1 |=  _BV(OCIE1B) ; // enable the output compare interrupt on CompB
}

// *************************   ISR vector ****************************

ISR (PCINT0_vect){ // invert digital pin 9 compare to digital pin 8
  if( (PINB & (1 << PINB0)) )
    {
      // HIGH to LOW pin change
        PORTB &= ~(1 << PORTB1) ; 
    }
    else
    {
       // Low to HIGH pin1 change
       PORTB |= (1 << PORTB1) ; 
    }
}


// this function is called when timer1 = compA
ISR(TIMER1_COMPA_vect) {
    //  PORTD &= ~(1 << servoIndex) ;
    servoIndex++ ;
    OCR1A = servoCycle[servoIndex] ;
    PIND = servoPin[servoIndex] ;
    //  PORTD |= (1 << (servoIndex )) ;// toggle pin of servoIndex and servoIndex-1 
}  

// this function is called when timer1 = compB (at the end of refresh
ISR(TIMER1_COMPB_vect){
   TCNT1 = 0;
   servoIndex = 2 ;
   OCR1A =  servoCycle[2] ;
   PORTD = 0x04 ;// toggle pin of first servo (on PD2)
}  



//************************** Other functions 
inline void ledOn() {
  PORTB |= (1 << PORTB5) ; 
}
inline void ledOff() {
  PORTB &= (~(1 << PORTB5)) ;
}
inline void togleLed() {
  PORTB ^= (1 << PORTB5) ;
}


void convertBufferToChannelMicrosec() { // put unused channel as comment in order to reduce execution time for main loop.
#if (PPMCHANNEL == 0) 
        channelsMicrosec[0]  = (uint16_t)(((buffer[1]    |buffer[2]<<8)                 & 0x07FF) * SBUS_SCALE_FACTOR +.5f) + SBUS_SCALE_OFFSET;
#endif
#if (PPMCHANNEL == 0) || (PPMCHANNEL == 1)  
        channelsMicrosec[1]  = (uint16_t)(((buffer[2]>>3 |buffer[3]<<5)                 & 0x07FF) * SBUS_SCALE_FACTOR +.5f) + SBUS_SCALE_OFFSET;
#endif
#if (PPMCHANNEL == 0) || (PPMCHANNEL == 1) || (PPMCHANNEL == 2)  
        channelsMicrosec[2]  = (uint16_t)(((buffer[3]>>6 |buffer[4]<<2 |buffer[5]<<10)  & 0x07FF) * SBUS_SCALE_FACTOR +.5f) + SBUS_SCALE_OFFSET;
#endif
#if (PPMCHANNEL == 0) || (PPMCHANNEL == 1) || (PPMCHANNEL == 2) || (PPMCHANNEL == 3)  
        channelsMicrosec[3]  = (uint16_t)(((buffer[5]>>1 |buffer[6]<<7)                 & 0x07FF) * SBUS_SCALE_FACTOR +.5f) + SBUS_SCALE_OFFSET;
#endif
#if (PPMCHANNEL == 0) || (PPMCHANNEL == 1) || (PPMCHANNEL == 2) || (PPMCHANNEL == 3) || (PPMCHANNEL == 4) 
        channelsMicrosec[4]  = (uint16_t)(((buffer[6]>>4 |buffer[7]<<4)                 & 0x07FF) * SBUS_SCALE_FACTOR +.5f) + SBUS_SCALE_OFFSET;
#endif
#if (PPMCHANNEL == 0) || (PPMCHANNEL == 1) || (PPMCHANNEL == 2) || (PPMCHANNEL == 3) || (PPMCHANNEL == 4) || (PPMCHANNEL == 5)
        channelsMicrosec[5]  = (uint16_t)(((buffer[7]>>7 |buffer[8]<<1 |buffer[9]<<9)   & 0x07FF) * SBUS_SCALE_FACTOR +.5f) + SBUS_SCALE_OFFSET;
#endif
#if  (PPMCHANNEL == 1) || (PPMCHANNEL == 2) || (PPMCHANNEL == 3) || (PPMCHANNEL == 4) || (PPMCHANNEL == 5) || (PPMCHANNEL == 6)
        channelsMicrosec[6]  = (uint16_t)(((buffer[9]>>2 |buffer[10]<<6)                & 0x07FF) * SBUS_SCALE_FACTOR +.5f) + SBUS_SCALE_OFFSET;
#endif
#if  (PPMCHANNEL == 2) || (PPMCHANNEL == 3) || (PPMCHANNEL == 4) || (PPMCHANNEL == 5) || (PPMCHANNEL == 6) || (PPMCHANNEL == 7)
        channelsMicrosec[7]  = (uint16_t)(((buffer[10]>>5|buffer[11]<<3)                & 0x07FF) * SBUS_SCALE_FACTOR +.5f) + SBUS_SCALE_OFFSET;
#endif
#if  (PPMCHANNEL == 3) || (PPMCHANNEL == 4) || (PPMCHANNEL == 5) || (PPMCHANNEL == 6) || (PPMCHANNEL == 7) || (PPMCHANNEL == 8)
        channelsMicrosec[8]  = (uint16_t)(((buffer[12]   |buffer[13]<<8)                & 0x07FF) * SBUS_SCALE_FACTOR +.5f) + SBUS_SCALE_OFFSET;
#endif
#if  (PPMCHANNEL == 4) || (PPMCHANNEL == 5) || (PPMCHANNEL == 6) || (PPMCHANNEL == 7) || (PPMCHANNEL == 8) || (PPMCHANNEL == 9)
        channelsMicrosec[9]  = (uint16_t)(((buffer[13]>>3|buffer[14]<<5)                & 0x07FF) * SBUS_SCALE_FACTOR +.5f) + SBUS_SCALE_OFFSET;
#endif
#if  (PPMCHANNEL == 5) || (PPMCHANNEL == 6) || (PPMCHANNEL == 7) || (PPMCHANNEL == 8) || (PPMCHANNEL == 9)  || (PPMCHANNEL == 10)
        channelsMicrosec[10]  = (uint16_t)(((buffer[14]>>6|buffer[15]<<2|buffer[16]<<10) & 0x07FF) * SBUS_SCALE_FACTOR +.5f) + SBUS_SCALE_OFFSET;
#endif
#if  (PPMCHANNEL == 6) || (PPMCHANNEL == 7) || (PPMCHANNEL == 8) || (PPMCHANNEL == 9) || (PPMCHANNEL == 10) || (PPMCHANNEL == 11)
        channelsMicrosec[11]  = (uint16_t)(((buffer[16]>>1|buffer[17]<<7)                & 0x07FF) * SBUS_SCALE_FACTOR +.5f) + SBUS_SCALE_OFFSET;
#endif
#if  (PPMCHANNEL == 7) || (PPMCHANNEL == 8) || (PPMCHANNEL == 9)  || (PPMCHANNEL == 10) || (PPMCHANNEL == 11) || (PPMCHANNEL == 12)
        channelsMicrosec[12]  = (uint16_t)(((buffer[17]>>4|buffer[18]<<4)                & 0x07FF) * SBUS_SCALE_FACTOR +.5f) + SBUS_SCALE_OFFSET;
#endif
#if  (PPMCHANNEL == 8) || (PPMCHANNEL == 9)  || (PPMCHANNEL == 10) || (PPMCHANNEL == 11) || (PPMCHANNEL == 12) || (PPMCHANNEL == 13)
        channelsMicrosec[13]  = (uint16_t)(((buffer[18]>>7|buffer[19]<<1|buffer[20]<<9)  & 0x07FF) * SBUS_SCALE_FACTOR +.5f) + SBUS_SCALE_OFFSET;
#endif
#if  (PPMCHANNEL == 9)  || (PPMCHANNEL == 10) || (PPMCHANNEL == 11) || (PPMCHANNEL == 12) || (PPMCHANNEL == 13) || (PPMCHANNEL == 14)
        channelsMicrosec[14]  = (uint16_t)(((buffer[20]>>2|buffer[21]<<6)                & 0x07FF) * SBUS_SCALE_FACTOR +.5f) + SBUS_SCALE_OFFSET;
#endif
#if  (PPMCHANNEL == 10) || (PPMCHANNEL == 11) || (PPMCHANNEL == 12) || (PPMCHANNEL == 13) || (PPMCHANNEL == 14) || (PPMCHANNEL == 15)
        channelsMicrosec[15]  = (uint16_t)(((buffer[21]>>5|buffer[22]<<3)                & 0x07FF) * SBUS_SCALE_FACTOR +.5f) + SBUS_SCALE_OFFSET;
#endif
}  


void setup() {
//  start = micros ();

  BEGIN_DEBUG;
  TRACELN ("Commenced device-under-test debugging!");
  initPinChange() ;
  servoIndex = 2 ; 
  
  initTimer1() ;
  pinMode(PIN_LED, OUTPUT); // The signal LED is used for testing the pin change (in interrupt)
  PORTD = 0x0 ; 
  DDRD =  0xFC ; // 1111 1100 set the pin 2 to 7 as output
  PORTD = 0x0 ;
//  startTimer1() ;
  USART_Init(MYUBRR) ;
  initTimer0() ; 
}  // end of setup
 
void loop() 
{  

    if ( (UCSR0A & (1<<RXC0)) ) { // check if a byte is ready to be read
      UART_error = UCSR0A & 0x1C ; // save error
      c =  UDR0;  // save char (it clear automatically the RXC0 flag)
      timer0 = TIFR0 & (1 << OCF0A ) ; // save the flag that says if there is more than 3 ms since previous char
      TCNT0 = 0 ; //reset the timer
      TIFR0 |= (1 << OCF0A ) ; // reset the flag in timer 0 saying that it reach OCR0A value
      if ( UART_error ) { // if error in UART
          bufferIndex = 0 ;
          countUART_Error++ ;
          countSBUS_Error++ ;
          TRACE ("UART: ");
          TRACELN2 (UART_error, HEX );

      } else { 
          if ( bufferIndex == 0 ) {     
               if  ( timer0 == 0 )  {
 //                  bufferIndex = 0 ;
                   countSBUS_TimeToSmallError++ ;
                   countSBUS_Error++ ;
               } else if ( c != SBUS_STARTBYTE ) {
//                   bufferIndex = 0 ;
                   countSBUS_StartError++ ;
                   countSBUS_Error++ ;
               } else {  
                   buffer[bufferIndex] = c ;
                   bufferIndex++ ;
               }
          } else {
              if ( timer0 ) {
                  bufferIndex = 0 ;
                  countSBUS_TimeToBigError++ ;
                  countSBUS_Error++ ;
              } else {
                   buffer[bufferIndex] = c ;
                   bufferIndex++ ;
                   if ( bufferIndex == SBUS_MAXCHAR ) {
                       if (  c == SBUS_ENDBYTE ) {
                           convertBufferToChannelMicrosec() ;
                           channelToLoad = true ;
                           countSBUS_Frame++ ; 
                       } else {
                       countSBUS_EndError++ ;
                       countSBUS_Error++ ;
                       }  
                       bufferIndex = 0 ;
                       
                   }
              }
          }
      } // end else UART error     
    } // end if ( (UCSR0A & (1<<RXC0)) ) 
  if( channelToLoad) {
      if ( (OCR1A - TCNT1) > 200 ) {  // avoid to load new SBUS channel when an interrupt will occurs in order not to delay the interrupt
          cli() ; 
          servoCycle[2] = channelsMicrosec[PPMCHANNEL] ;
          sei() ;
          cli() ;
          servoCycle[4] = channelsMicrosec[PPMCHANNEL + 1] + 5000;
          sei() ;
          cli() ;
          servoCycle[6] = channelsMicrosec[PPMCHANNEL + 2] + 10000;
          sei() ;
          cli() ;
          servoCycle[8] = channelsMicrosec[PPMCHANNEL + 3] + 15000;
          sei() ;
          cli() ;
          servoCycle[10] = channelsMicrosec[PPMCHANNEL + 4] + 20000;
          sei() ;
          cli() ;
          servoCycle[12] = channelsMicrosec[PPMCHANNEL + 5] + 25000;
          sei() ;
          channelToLoad = false ;
          if ( sbusStarted == false ) {
                startTimer1() ;
                sbusStarted = true ;
                counter = 1 ;
          }    
      }    
  }   

  if (sbusStarted) { // led is blinking as soon as a Sbus has been received, Led is on for a long time (at least 3 X normal time each time there is an Sbus error)
    if (countSBUS_Error > 0) {
        counter = 500000 ;
        ledOn();
        countSBUS_Error = 0 ;
    }
    counter--;
    if (counter == 0) {
      counter = 100000 ;
//        ledOn();
//          digitalWrite(13,HIGH) ;
      togleLed() ;
    }  
  }
  
#ifdef DEBUG    
    debugCounter++;
    if (debugCounter == 1000000)
    {
//        ledOff() ; // Led pin is used by SPI too. So here we put it to 0 before 
        TRACE ("SBUS_Error: ");
        TRACE (countSBUS_Error);
        TRACE (" ch0: ");
        TRACE (channelsMicrosec[0]);
        TRACE ("Servo1: ");
        TRACELN (servoCycle[2]);
  //      TRACE ("UART: ");
  //      TRACE (countUART_Error);
  //      TRACE ("  Time_to_small:") ;
  //      TRACE (countSBUS_TimeToSmallError) ;
  //      TRACE ("  Time_to_big:") ;
  //      TRACE (countSBUS_TimeToBigError) ;
  //      TRACE ("  Start:") ;
  //      TRACE (countSBUS_StartError) ;
  //      TRACE ("  End:") ;
  //      TRACE (countSBUS_EndError) ;
  //      TRACE ("  Frames:") ;
  //      TRACELN (countSBUS_Frame) ;   
      debugCounter = 0;
    }  // end of if
#endif  
}  // end of loop


unsigned long millis()
{
}

unsigned long micros() {
}

void delay(unsigned long ms)
{
}

/* Delay for the given number of microseconds.  Assumes a 8 or 16 MHz clock. */
void delayMicroseconds(unsigned int us)
{
}





User avatar
MikeB
9x Developer
Posts: 17990
Joined: Tue Dec 27, 2011 1:24 pm
Country: -
Location: Poole, Dorset, UK

Re: Arduino based S-Bus decoder with CPPM and Servo

Post by MikeB »

Couple of things here.
1. Do the two commented lines in:
ISR(TIMER1_COMPA_vect)
like:
// PORTD &= ~(1 << servoIndex) ;
need to be uncommented?

2. You have the sequence:
sei();
cli();
in loop().

This does not enable interrupts as one instruction after sei() is executed before any interrupt is taken and you disable interrupts in that instruction.

Mike.
erskyTx/er9x developer
The difficult we do immediately,
The impossible takes a little longer!
mstrens
Posts: 1435
Joined: Fri Dec 27, 2013 7:49 pm
Country: -

Re: Arduino based S-Bus decoder with CPPM and Servo

Post by mstrens »

MikeB wrote:Couple of things here.
1. Do the two commented lines in:
ISR(TIMER1_COMPA_vect)
like:
// PORTD &= ~(1 << servoIndex) ;
need to be uncommented?

2. You have the sequence:
sei();
cli();
in loop().

This does not enable interrupts as one instruction after sei() is executed before any interrupt is taken and you disable interrupts in that instruction.

Mike.
Mike,
Thanks for comments.
About point 1, the 2 lines commented can in fact be totally removed.
In a previous version, I used them but finally I replaced them by the line with PIND. This instruction makes an XOR on one of the pin. So depending on the index it sets or it clears a bit.
I made it in this way in order to run faster (I hoped inverting the SBUS signal with a pin change interrupt but it is not 100% ok) and in order to avoid setting the next pin at the same time as the previous is cleard. Now the pin are set always at a fixed interval. This allows having always 18ms between the rising edge on each pin.

About point 2.
Initially I had only one cli() at the beginning of the block and one sei() at the end of the block.
I added those cli() sei() sequence in between hoping getting less Sbus frame error when the Sbus signal is inverted by the arduino.
Still It did not work. Problably you just found the reason.
I will try adding a dummy instruction (1 cycle) between the cli() and the sei() and see if it solves the sbus frame errors.
I presume I can use a second cli() just after the first one as dummy instruction.
User avatar
jhsa
Posts: 19480
Joined: Tue Dec 27, 2011 5:13 pm
Country: Germany

Re: Arduino based S-Bus decoder with CPPM and Servo

Post by jhsa »

Is it not possible to have channels 1 to 8 or 9 to 16? :(
My er9x/Ersky9x/eepskye Video Tutorials
https://www.youtube.com/playlist?list=PL5uJhoD7sAKidZmkhMpYpp_qcuIqJXhb9

Donate to Er9x/Ersky9x:
https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=YHX43JR3J7XGW
mstrens
Posts: 1435
Joined: Fri Dec 27, 2013 7:49 pm
Country: -

Re: Arduino based S-Bus decoder with CPPM and Servo

Post by mstrens »

jhsa wrote:Is it not possible to have channels 1 to 8 or 9 to 16? :(
Not really with this code.
Currently, it provides only 6 consecutive channels starting from the one you want (e.g. you can get channels 4 to 9 or 9 to 14).
I made it in this way because I planned to use a pcb having the same size as the arduino pro mini.
It would be very easy to add one channel (so having a total of 7 channels) without impacting the performance and having the possibility of using the arduino to invert the Sbus signal.

With more rework, it would be possible to add even more channels. E.g. it would be possible to use the analog pins A0 up to A5 (so 6 channels) and, if required, even 3 others pins in order to get a total of 16 channels but in this case, it will for sure require implementing an hardware inverter on the Sbus signal.
User avatar
jhsa
Posts: 19480
Joined: Tue Dec 27, 2011 5:13 pm
Country: Germany

Re: Arduino based S-Bus decoder with CPPM and Servo

Post by jhsa »

Ok, Thank you anyway. But It's not really what I'm looking for at the moment :)

João
My er9x/Ersky9x/eepskye Video Tutorials
https://www.youtube.com/playlist?list=PL5uJhoD7sAKidZmkhMpYpp_qcuIqJXhb9

Donate to Er9x/Ersky9x:
https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=YHX43JR3J7XGW
mstrens
Posts: 1435
Joined: Fri Dec 27, 2013 7:49 pm
Country: -

Re: Arduino based S-Bus decoder with CPPM and Servo

Post by mstrens »

jhsa wrote:Ok, Thank you anyway.But It's not really what I'm looking for at the moment :)

João
If you want getting more than 6 channels, you can also connect several modules (arduino) in parallel on the same bus. Each module can provide 6 different channels (or 7 with a minor software change)
User avatar
MikeB
9x Developer
Posts: 17990
Joined: Tue Dec 27, 2011 1:24 pm
Country: -
Location: Poole, Dorset, UK

Re: Arduino based S-Bus decoder with CPPM and Servo

Post by MikeB »

I've got some code that generates PPM outputs, 4 at a time using just timer1. It sorts the 4 pulses into length order, shortest first. Then it starts them 10 uS apart, using timer1 COMPA interrupt, and ends them in the same way. The end times MUST differ by at least 10uS as well as they are ordered on length. This avoids the interrupts being too close together.

With this, it should be possible to receive the SBUS data using a software serial input, then generate 4 pulses followed by 4 more. Then wait for the SBUS frame again, then if required, generate two more sets of 4 pulses for the other 8 channels. The software serial input should therefore be able to handle the inverted data.

Mike.
erskyTx/er9x developer
The difficult we do immediately,
The impossible takes a little longer!
mstrens
Posts: 1435
Joined: Fri Dec 27, 2013 7:49 pm
Country: -

Re: Arduino based S-Bus decoder with CPPM and Servo

Post by mstrens »

MikeB wrote:I've got some code that generates PPM outputs, 4 at a time using just timer1. It sorts the 4 pulses into length order, shortest first. Then it starts them 10 uS apart, using timer1 COMPA interrupt, and ends them in the same way. The end times MUST differ by at least 10uS as well as they are ordered on length. This avoids the interrupts being too close together.

With this, it should be possible to receive the SBUS data using a software serial input, then generate 4 pulses followed by 4 more. Then wait for the SBUS frame again, then if required, generate two more sets of 4 pulses for the other 8 channels. The software serial input should therefore be able to handle the inverted data.

Mike.
Thanks Mike.
In fact I have no need for more than 6 channels (on top of the 3 or 4 already deliverd by the X4R).
I plan to make a board having the same size as the arduino pro mini. This allows just to put 6 (or max 7) servo connector pin headers.
This board is also useful in order to add one resistor for each channel in order to better protect the arduino pin (to the servo).
I think it will be easy to put the inverter (one transistor + 2 resistors) on this PCB too. This will also be safier in order to protect the arduino pin Rx.
mstrens
Posts: 1435
Joined: Fri Dec 27, 2013 7:49 pm
Country: -

Re: Arduino based S-Bus decoder with CPPM and Servo

Post by mstrens »

FYI,
I am currently working on a version that should deliver the 16 channels.
This version will require that the Sbus signal is inverted (e.g. using 1 transistor and 2 resistors).

I hope it will be OK this PM
mstrens
Posts: 1435
Joined: Fri Dec 27, 2013 7:49 pm
Country: -

Re: Arduino based S-Bus decoder with CPPM and Servo

Post by mstrens »

Here a version that read the SBUS and generates 16 PPM pulse on 16 Arduino pins from an arduino pro mini.

Read more explanations at the beginning of the code

Code: Select all

// This poject uses Arduino pro mini 5 volt 16 mhz in order to decode Futaba SBUS.
// This version decodes the 16 channels available on SBUS and generates only 16 PPM signals.
// The 16 PPM signals are available on arduino pins 2 up to 9, A0 up to A5 , Tx and 12 (in this sequence)
// The PPM signals are generates once per 20 msec. This refresh rate is supported by all servos. 
//
// The SBUS signal has to be inverted before connecting it to arduino pin Rx.
// So it requires to invert the SBUS signal from the Receiver with a transistor and 2 resistors (see the web for a schema or look at a commecial product at e.g. Hobbyking).
//
// As long as Arduino did not get a valid Sbus frame after power on, it wil not generate PPM signals.
// As soon as a valid Sbus frame is received, the 16 PPM signals wil be generated.
// Unvalid sbus frames are discarded and arduino generates PPM signals based on last correct frame received.
// Please note that if no new (valid) frames are received, the latest valid frame will continue to be used to generate the PPM signals (so servos will keep their last position). 
//
// Arduino has a green led. This led will work as follow:
// If arduino never got a valid Sbus (e.g. Tx not powered on, no bind with Rx), led is off.
// Once a valid Sbus frame is received, the led will start blinking.
// When led blink fast (about 1 per sec) , it means that there are no Sbus frame error (normal case).
// If Arduino get at one invalid frame, the led wil be on for about 3 sec.
// If during the 3 sec, there are no other Sbus frame error, it will continue with fast binking rate.
// If there is at least one new frame error within the 3 sec, the led will be on for the next 3 seconds
// So, if there are many frame errors, it could be that the led is always on.
//
// Debugging : 
// It is not possible to debug this project with serial.print command because the hardware serial is already used to read the Sbus (and a sofware serial would be to slow).
// Still, it is possible to debug this project using a second arduino connected to this one via the SPI interface.
// The second arduino has then to be loaded with a "slave" program.
// More informations at this link : http://www.utopiamechanicus.com/article/spi-debug/
//
// Note about loading the Arduino pro mini with this firmware.
// Programming the Arduino pro mini is normally done using a FTDI cable (or board).
// This FTDI connects on Pc side with the USB. On arduino side, it uses the RX and TX pins.
// In this project Arduino Rx pin is normally connected to the (inverted Sbus signal).
// Please take care not to connect the arduino to the Rx (Sbus port) while programming it (otherwise programming will fail).


// this line selects the first channels being generated; this version will then generate this channel and the next 5 channels;

#include <avr/io.h>
#include <avr/interrupt.h>    // Needed to use interrupts    


#include <SPI.h>
#include "pins_arduino.h"

// conditional debugging
// uncomment to debug using SPI interface
//#define DEBUG 


#ifdef DEBUG 

  #define BEGIN_DEBUG   do { SPI.begin (); SPI.setClockDivider(SPI_CLOCK_DIV8); } while (0)
  #define TRACE(x)      SPIdebug.print   (x)
  #define TRACE2(x,y)   SPIdebug.print   (x,y)
  #define TRACELN(x)    SPIdebug.println (x)
  #define TRACELN2(x,y) SPIdebug.println (x,y)
  
  class tSPIdebug : public Print
  {
  public:
    virtual size_t write (const byte c)  { 
      digitalWrite(SS, LOW); 
      SPI.transfer (c); 
      digitalWrite(SS, HIGH); 
    }  // end of tSPIdebug::write
  }; // end of tSPIdebug
      
  // an instance of the SPIdebug object
  tSPIdebug SPIdebug;
  
#endif // DEBUG

#define PIN_LED            13  // The Signal LED (default=13=onboard LED)
//#define FRAME_DURATION 36000  // 36000 = 18000 usec * 2 because frequency is 16Mz and prescaler is 8
//#define ZEROCYCLE 3000 // 1500 usec * 2
#define TIMER1_ZERO 25536   // in order to get an overflow every 20 ms (delay between each frame), timer 1 start at 25536 = 65536 - 40000 
#define SBUS_MAXCHAR 25
#define MAX_SBUSCHANNELS 16

//  Measured values with Futaba FX-30/R6108SB:
//    -+100% on TX:  PCM 1.100/1.520/1.950ms -> SBus raw values: 350/1024/1700  (100% ATV)
//    -+140% on TX:  PCM 0.930/1.520/2.112ms -> SBus raw values:  78/1024/1964  (140% ATV)  
//    -+152% on TX:  PCM 0.884/1.520/2.160ms -> SBus raw values:   1/1024/2047  (140% ATV plus dirty tricks)

// define range mapping here, -+100% -> 1000..2000 
#define SBUS_RANGE_MIN 200.0f
#define SBUS_RANGE_MAX 1800.0f

#define SBUS_TARGET_MIN 2000.0f // 2000 cycles = 1000 usec (because prescaler = 1/8 and Mhz = 16) 
#define SBUS_TARGET_MAX 4000.0f // 4000 cycles = 2000 usec (because prescaler = 1/8 and Mhz = 16) 

// pre-calculate the floating point stuff as far as possible at compile time 
#define SBUS_SCALE_FACTOR ((SBUS_TARGET_MAX - SBUS_TARGET_MIN) / (SBUS_RANGE_MAX - SBUS_RANGE_MIN))
#define SBUS_SCALE_OFFSET (int)(SBUS_TARGET_MIN - (SBUS_SCALE_FACTOR * SBUS_RANGE_MIN + 0.5f))


uint32_t counter = 1;
uint32_t debugCounter ;
static boolean sbusStarted = false;
static boolean channelToLoad_1 = false;
static boolean channelToLoad_2 = false;
static uint8_t servoIndex1 ;
static uint8_t servoIndex2 ;


// sequence for frame 1 (arduino pin=PORT)      2=D2            3=D3            4=D4            5=D5            6=D6             7=D7         8=B0          9=B1   
static uint8_t            servoPin1B[] = {  0x00 ,  0x00 ,  0x00 ,  0x00 ,  0x00 ,  0x00 ,  0x00 ,  0x00 ,  0x00 ,  0x00 ,  0x00 ,  0x00 ,  0x01 ,  0x01,   0x02  , 0x02 } ; // each value will toggle a pin
static uint8_t            servoPin1C[] = {  0x00  , 0x00 ,  0x00 ,  0x00 ,  0x00 ,  0x00 ,  0x00 ,  0x00 ,  0x00 ,  0x00 ,  0x00 ,  0x00 ,  0x00 ,  0x00 ,  0x00 ,  0x00 } ; // each value will toggle a pin
static uint8_t            servoPin1D[] = {  0x04 ,  0x04 ,  0x08 ,  0x08 ,  0x10 ,  0x10 ,  0x20 ,  0x20 ,  0x40 ,  0x40 ,  0x80 ,  0x80 ,  0x00 ,  0x00 ,  0x00 ,  0x00 } ; // each value will toggle a pin
volatile static uint16_t servoCycle1[] = { 25700 , 28700 , 30200 , 33200 , 34700 , 37700 , 39200 , 42200 , 43700 , 46700 , 48200 , 51200 , 52700 , 55700 , 57200 , 60200 , 25700 } ; 
// sequence for frame 2 (arduino pin=PORT)      A0=C0           A1=C1           A2=C2           A3=C3           A4=C4            A5=C5         Tx=D1          12=B4   
static uint8_t servoPin2B[] =            {  0x00  , 0x00 ,  0x00 ,  0x00 ,  0x00 ,  0x00 ,  0x00 ,  0x00 ,  0x00 ,  0x00 ,  0x00 ,  0x00 ,  0x00 ,  0x00 ,  0x10 ,  0x10 } ; // each value will toggle a pin
static uint8_t servoPin2C[] =            {  0x01  , 0x01 ,  0x02 ,  0x02 ,  0x04 ,  0x04 ,  0x08 ,  0x08 ,  0x10 ,  0x10 ,  0x20 ,  0x20 ,  0x00 ,  0x00 ,  0x00 ,  0x00 } ; // each value will toggle a pin
static uint8_t servoPin2D[] =            {  0x00  , 0x00 ,  0x00 ,  0x00 ,  0x00 ,  0x00 ,  0x00 ,  0x00 ,  0x00 ,  0x00 ,  0x00 ,  0x00 ,  0x02 ,  0x02 ,  0x00 ,  0x00 } ; // each value will toggle a pin
volatile static uint16_t servoCycle2[] = { 25750 , 28750 , 30250 , 33250 , 34750 , 37750 , 39250 , 42250 , 43750 , 46750 , 48250 , 51250 , 52750 , 55750 , 57250 , 60250 , 25750  } ; // !!!! the last value must be higher than FRAME_DURATION in order to let CompB generates an interrupt


static uint8_t UART_error ;
static uint8_t timer0 ;
static uint8_t bufferIndex ;
static uint8_t buffer[ SBUS_MAXCHAR ] ;
// static uint16_t channels[ MAX_SBUSCHANNELS ] ;
static uint16_t channelsMicrosec[ MAX_SBUSCHANNELS ] ;
unsigned char readOneChar ;
unsigned char c ;
uint16_t countUART_Error ;
uint16_t countSBUS_TimeToBigError ;
uint16_t countSBUS_TimeToSmallError ;
uint16_t countSBUS_StartError ;
uint16_t countSBUS_EndError ;
uint16_t countSBUS_Frame ;
uint16_t countSBUS_Error ;

uint8_t led ; 

//************************************ Init*************************************

void init() // this function replaces the standard init Arduino function
{
  // Timer1
  TIMSK1 &= ~( 1<< OCIE1A ) ; // Disable interupt on timer 1 for compA
  TIMSK1 &= ~( 1<< OCIE1B ) ; // Disable interupt on timer 1 for compB
  TCCR1A = 0x00 ;    //Init.
  TCCR1B = 0xC1 ;    // I/p noise cancel, rising edge, Clock/1

	// the bootloader connects pins 0 and 1 to the USART; disconnect them
	// here so they can be used as normal digital i/o; they will be
	// reconnected in Serial.begin()
	UCSR0B = 0;
	sei();  //allow interrupt in general
}



#define MYUBRR 9 // FOSC/16/BAUD-1 = 16000000/16/100000 -1
#define SBUS_STARTBYTE 0x0F
#define SBUS_ENDBYTE 0x00


void USART_Init( unsigned int ubrr){
    //Set baud rate 
    UBRR0H = (unsigned char)(ubrr>>8);
    UBRR0L = (unsigned char)ubrr;
    // Enable receiver only  
    UCSR0B = 0x10 ;  //no interrupt, Rx enabled, TX disabled, only 8 bits
    /* Set frame format: 8data, 2stop bit */
    UCSR0C = 0x2E ; // mode normal, even parity , 2 stop bit, 8 bits, no polarity for asynchrone
}

void initTimer0(){    // timer0 is used to check if there are at least 3 msec without received char before starting a frame.
    TCCR0A = 0x00;             // no use of O0A and B and normal mode of operation (increment of TCNT0) 
    TCCR0B = 0x05 ;          // initialise prescaler to 1024 ; so timer is active and 1 count = 1024/16 usec = 64 usec)    

   
    OCR0A =  46 ; // 3000 usec / 64 usec = 46
    TCNT0 = 0;              // set the timer count to 0
    
    TIFR0 |= _BV(OCF0A) ;     // clear any pending interrupts on CompA 
    TIMSK0 = 0  ; // do not enable interrupt
}

void initTimer1(){
    TCCR1A = 0;             // no output based on compA/compB 
    TCCR1B = 0x0 ;          // initialise prescaler to 0 ; so timer is not active   
//    TCCR1B = 0x02 ;         // set prescaler of 8 (binary = 0000 0010 (only CS11 = 1) 
   
    OCR1A = servoCycle1[0] ; 
    OCR1B = servoCycle2[0] ;
    TCNT1 = TIMER1_ZERO;              // clear the timer count 
    
    TIFR1 |= _BV(OCF1A) | _BV(OCF1B) | _BV( TOV1);     // clear any pending interrupts on CompA and compB and overflow
 //   TIMSK1 |=  _BV(OCIE1A) ; // enable the output compare interrupt on CompA 
 //   TIMSK1 |=  _BV(OCIE1B) ; // enable the output compare interrupt on CompB
 //   TIMSK1 |=  _BV(  TOIE1); // enable the overflow interrupt on timer1
}

void startTimer1(){
    TCCR1B = 0x02 ;         // set prescaler of 8 (binary = 0000 0010 (only CS11 = 1) 
   servoIndex1 = 0 ;
   servoIndex2 = 0 ;
    OCR1A = servoCycle1[0] ; // initialize compA with first value (to set the first pin)
    OCR1B = servoCycle2[0] ; // initialize compB with first value (to set the first pin)
   PORTB &= 0b11101100 ;// set PB0, PB1, PB4 to 0
   PORTC &= 0b11100000 ;// set PC0 to PC5 to 0
   PORTD &= 0b00000001 ;// set PD1 to PD7 to 0

    TCNT1 = TIMER1_ZERO;              // set the timer count in order to get 20ms before overflow
    
    TIFR1 |= _BV(OCF1A) | _BV(OCF1B)  | _BV( TOV1) ;     // clear any pending interrupts on CompA and CompB; 
    TIMSK1 |=  _BV(OCIE1A) ; // enable the output compare interrupt 
    TIMSK1 |=  _BV(OCIE1B) ; // enable the output compare interrupt on CompB
    TIMSK1 |=  _BV(  TOIE1); // enable the overflow interrupt on timer1
}

// *************************   ISR vector ****************************
// this function is called when timer1 = compA  and is used to generate the frame 1
ISR(TIMER1_COMPA_vect) {
  PINB = servoPin1B[servoIndex1] ; 
  PINC = servoPin1C[servoIndex1] ; 
  PIND = servoPin1D[servoIndex1] ; 
  servoIndex1++ ;
    OCR1A = servoCycle1[servoIndex1] ;
}  
// this function is called when timer1 = compB and is used to generate the frame 2
ISR(TIMER1_COMPB_vect) {
  PINB = servoPin2B[servoIndex2] ; 
  PINC = servoPin2C[servoIndex2] ; 
  PIND = servoPin2D[servoIndex2] ; 
  servoIndex2++ ;
    OCR1B = servoCycle2[servoIndex2] ;
}  

// this function is called when timer1 overflow (at the end of the 20 msec)
ISR(TIMER1_OVF_vect){
    TCNT1 = TIMER1_ZERO;  // set the timer count in order to get 20ms before overflow
    servoIndex1 = 0 ;     // reset the index
    servoIndex2 = 0 ;
   PORTB &= 0b11101100 ;// set PB0, PB1, PB4 to 0
   PORTC &= 0b11100000 ;// set PC0 to PC5 to 0
   PORTD &= 0b00000001 ;// set PD1 to PD7 to 0
}  



//************************** Other functions 
inline void ledOn() {
  PORTB |= (1 << PORTB5) ; 
}
inline void ledOff() {
  PORTB &= (~(1 << PORTB5)) ;
}
inline void togleLed() {
  PORTB ^= (1 << PORTB5) ;
}


void convertBufferToChannelMicrosec() { // put unused channel as comment in order to reduce execution time for main loop.
        channelsMicrosec[0]  = (uint16_t)(((buffer[1]    |buffer[2]<<8)                 & 0x07FF) * SBUS_SCALE_FACTOR +.5f) + SBUS_SCALE_OFFSET;
        channelsMicrosec[1]  = (uint16_t)(((buffer[2]>>3 |buffer[3]<<5)                 & 0x07FF) * SBUS_SCALE_FACTOR +.5f) + SBUS_SCALE_OFFSET;
        channelsMicrosec[2]  = (uint16_t)(((buffer[3]>>6 |buffer[4]<<2 |buffer[5]<<10)  & 0x07FF) * SBUS_SCALE_FACTOR +.5f) + SBUS_SCALE_OFFSET;
        channelsMicrosec[3]  = (uint16_t)(((buffer[5]>>1 |buffer[6]<<7)                 & 0x07FF) * SBUS_SCALE_FACTOR +.5f) + SBUS_SCALE_OFFSET;
        channelsMicrosec[4]  = (uint16_t)(((buffer[6]>>4 |buffer[7]<<4)                 & 0x07FF) * SBUS_SCALE_FACTOR +.5f) + SBUS_SCALE_OFFSET;
        channelsMicrosec[5]  = (uint16_t)(((buffer[7]>>7 |buffer[8]<<1 |buffer[9]<<9)   & 0x07FF) * SBUS_SCALE_FACTOR +.5f) + SBUS_SCALE_OFFSET;
        channelsMicrosec[6]  = (uint16_t)(((buffer[9]>>2 |buffer[10]<<6)                & 0x07FF) * SBUS_SCALE_FACTOR +.5f) + SBUS_SCALE_OFFSET;
        channelsMicrosec[7]  = (uint16_t)(((buffer[10]>>5|buffer[11]<<3)                & 0x07FF) * SBUS_SCALE_FACTOR +.5f) + SBUS_SCALE_OFFSET;
        channelsMicrosec[8]  = (uint16_t)(((buffer[12]   |buffer[13]<<8)                & 0x07FF) * SBUS_SCALE_FACTOR +.5f) + SBUS_SCALE_OFFSET;
        channelsMicrosec[9]  = (uint16_t)(((buffer[13]>>3|buffer[14]<<5)                & 0x07FF) * SBUS_SCALE_FACTOR +.5f) + SBUS_SCALE_OFFSET;
        channelsMicrosec[10]  = (uint16_t)(((buffer[14]>>6|buffer[15]<<2|buffer[16]<<10) & 0x07FF) * SBUS_SCALE_FACTOR +.5f) + SBUS_SCALE_OFFSET;
        channelsMicrosec[11]  = (uint16_t)(((buffer[16]>>1|buffer[17]<<7)                & 0x07FF) * SBUS_SCALE_FACTOR +.5f) + SBUS_SCALE_OFFSET;
        channelsMicrosec[12]  = (uint16_t)(((buffer[17]>>4|buffer[18]<<4)                & 0x07FF) * SBUS_SCALE_FACTOR +.5f) + SBUS_SCALE_OFFSET;
        channelsMicrosec[13]  = (uint16_t)(((buffer[18]>>7|buffer[19]<<1|buffer[20]<<9)  & 0x07FF) * SBUS_SCALE_FACTOR +.5f) + SBUS_SCALE_OFFSET;
        channelsMicrosec[14]  = (uint16_t)(((buffer[20]>>2|buffer[21]<<6)                & 0x07FF) * SBUS_SCALE_FACTOR +.5f) + SBUS_SCALE_OFFSET;
        channelsMicrosec[15]  = (uint16_t)(((buffer[21]>>5|buffer[22]<<3)                & 0x07FF) * SBUS_SCALE_FACTOR +.5f) + SBUS_SCALE_OFFSET;
}  


void setup() {
//  start = micros ();
#ifdef DEBUG
  BEGIN_DEBUG;
  TRACELN ("Commenced device-under-test debugging!");
#endif  
  servoIndex1 = 0 ; 
  servoIndex2 = 0 ; 
  initTimer1() ;
  pinMode(PIN_LED, OUTPUT); // The signal LED is used for testing the pin change (in interrupt)
//  startTimer1() ;
  USART_Init(MYUBRR) ;

  
  DDRB |= 0b00010011 ;// set PB0, PB1, PB4 as output
  DDRC |= 0b00011111 ;// set PC0 to PC5 as output
  DDRD |= 0b11111110 ;// set PD1 to PD7 to 0


  PORTB &= 0b11101100 ;// set PB0, PB1, PB4 to 0
  PORTC &= 0b11100000 ;// set PC0 to PC5 to 0
  PORTD &= 0b00000001 ;// set PD1 to PD7 to 0
  initTimer0() ; 
}  // end of setup
 
void loop() 
{  

    if ( (UCSR0A & (1<<RXC0)) ) { // check if a byte is ready to be read
      UART_error = UCSR0A & 0x1C ; // save error
      c =  UDR0;  // save char (it clear automatically the RXC0 flag)
      timer0 = TIFR0 & (1 << OCF0A ) ; // save the flag that says if there is more than 3 ms since previous char
      TCNT0 = 0 ; //reset the timer
      TIFR0 |= (1 << OCF0A ) ; // reset the flag in timer 0 saying that it reach OCR0A value
      if ( UART_error ) { // if error in UART
          bufferIndex = 0 ;
          countUART_Error++ ;
          countSBUS_Error++ ;
#ifdef DEBUG          
          TRACE ("UART: ");
          TRACELN2 (UART_error, HEX );
#endif
      } else { 
          if ( bufferIndex == 0 ) {     
               if  ( timer0 == 0 )  {
 //                  bufferIndex = 0 ;
                   countSBUS_TimeToSmallError++ ;
                   countSBUS_Error++ ;
               } else if ( c != SBUS_STARTBYTE ) {
//                   bufferIndex = 0 ;
                   countSBUS_StartError++ ;
                   countSBUS_Error++ ;
               } else {  
                   buffer[bufferIndex] = c ;
                   bufferIndex++ ;
               }
          } else {
              if ( timer0 ) {
                  bufferIndex = 0 ;
                  countSBUS_TimeToBigError++ ;
                  countSBUS_Error++ ;
              } else {
                   buffer[bufferIndex] = c ;
                   bufferIndex++ ;
                   if ( bufferIndex == SBUS_MAXCHAR ) {
                       if (  c == SBUS_ENDBYTE ) {
                           convertBufferToChannelMicrosec() ;
                           channelToLoad_1 = true ;
                           channelToLoad_2 = true ;
                           countSBUS_Frame++ ; 
                       } else {
                       countSBUS_EndError++ ;
                       countSBUS_Error++ ;
                       }  
                       bufferIndex = 0 ;
                       
                   }
              }
          }
      } // end else UART error     
    } // end if ( (UCSR0A & (1<<RXC0)) ) 
  if( channelToLoad_1) {
      if ( (OCR1A > TCNT1) && ((OCR1A - TCNT1) > 200 )) {  // avoid to load new SBUS channel when an interrupt of compA will occurs in order not to delay the interrupt
 
          cli() ; 
          servoCycle1[1] = channelsMicrosec[0] + servoCycle1[0];
          servoCycle1[3] = channelsMicrosec[1] + servoCycle1[2];
          servoCycle1[5] = channelsMicrosec[2] + servoCycle1[4];
          servoCycle1[7] = channelsMicrosec[3] + servoCycle1[6];
          servoCycle1[9] = channelsMicrosec[4] + servoCycle1[8];
          servoCycle1[11] = channelsMicrosec[5] + servoCycle1[10];
          servoCycle1[13] = channelsMicrosec[6] + servoCycle1[12];
          servoCycle1[15] = channelsMicrosec[7] + servoCycle1[14];
          sei() ;
 
          channelToLoad_1 = false ;
          if ( sbusStarted == false ) {
                startTimer1() ;
                sbusStarted = true ;
                counter = 1 ;
          }    
      }    
  }   

  if( channelToLoad_2) {
      if ( (OCR1B > TCNT1) && ((OCR1B - TCNT1) > 200 )) {  // avoid to load new SBUS channel when an interrupt of compB will occurs in order not to delay the interrupt
 
          cli() ; 
          servoCycle2[1] = channelsMicrosec[8] + servoCycle2[0];
          servoCycle2[3] = channelsMicrosec[9] + servoCycle2[2];
          servoCycle2[5] = channelsMicrosec[10] + servoCycle2[4];
          servoCycle2[7] = channelsMicrosec[11] + servoCycle2[6];
          servoCycle2[9] = channelsMicrosec[12] + servoCycle2[8];
          servoCycle2[11] = channelsMicrosec[13] + servoCycle2[10];
          servoCycle2[13] = channelsMicrosec[14] + servoCycle2[12];
          servoCycle2[15] = channelsMicrosec[15] + servoCycle2[14];
          sei() ;
 
          channelToLoad_2 = false ;
          if ( sbusStarted == false ) {
                startTimer1() ;
                sbusStarted = true ;
                counter = 1 ;
          }    
      }    
  }   



  if (sbusStarted) { // led is blinking as soon as a Sbus has been received, Led is on for a long time (at least 3 X normal time each time there is an Sbus error)
    if (countSBUS_Error > 0) {
        counter = 500000 ;
        ledOn();
        countSBUS_Error = 0 ;
    }
    counter--;
    if (counter == 0) {
      counter = 100000 ;
      togleLed() ;
    }  
  }
  
#ifdef DEBUG    
    debugCounter++;
    if (debugCounter == 1000000)
    {
//        ledOff() ; // Led pin is used by SPI too. So here we put it to 0 before 
        TRACE ("SBUS_Error: ");
        TRACE (countSBUS_Error);
        TRACE (" ch0: ");
        TRACE (channelsMicrosec[0]);
        TRACE ("Servo1: ");
        TRACELN (servoCycle1[2]);
  //      TRACE ("UART: ");
  //      TRACE (countUART_Error);
  //      TRACE ("  Time_to_small:") ;
  //      TRACE (countSBUS_TimeToSmallError) ;
  //      TRACE ("  Time_to_big:") ;
  //      TRACE (countSBUS_TimeToBigError) ;
  //      TRACE ("  Start:") ;
  //      TRACE (countSBUS_StartError) ;
  //      TRACE ("  End:") ;
  //      TRACE (countSBUS_EndError) ;
  //      TRACE ("  Frames:") ;
  //      TRACELN (countSBUS_Frame) ;   
      debugCounter = 0;
    }  // end of if
#endif  
}  // end of loop


unsigned long millis()
{
}

unsigned long micros() {
}

void delay(unsigned long ms)
{
}

/* Delay for the given number of microseconds.  Assumes a 8 or 16 MHz clock. */
void delayMicroseconds(unsigned int us)
{
}





User avatar
jhsa
Posts: 19480
Joined: Tue Dec 27, 2011 5:13 pm
Country: Germany

Re: Arduino based S-Bus decoder with CPPM and Servo

Post by jhsa »

Thanks..will see if it works with eepskye.. :)

João
My er9x/Ersky9x/eepskye Video Tutorials
https://www.youtube.com/playlist?list=PL5uJhoD7sAKidZmkhMpYpp_qcuIqJXhb9

Donate to Er9x/Ersky9x:
https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=YHX43JR3J7XGW
User avatar
jhsa
Posts: 19480
Joined: Tue Dec 27, 2011 5:13 pm
Country: Germany

Re: Arduino based S-Bus decoder with CPPM and Servo

Post by jhsa »

mstrens wrote: // This poject uses Arduino pro mini 5 volt 16 mhz in order to decode Futaba SBUS.
// This version decodes the 16 channels available on SBUS and generates only 16 PPM signals.
// The 16 PPM signals are available on arduino pins 2 up to 9, A0 up to A5 , Tx and 12 (in this sequence)
// The PPM signals are generates once per 20 msec. This refresh rate is supported by all servos.
//
Allow me to share my opinion on a subject that might lead to some confusion.
Your code is generating 16 PWM channels, not PPM? ;)

João
My er9x/Ersky9x/eepskye Video Tutorials
https://www.youtube.com/playlist?list=PL5uJhoD7sAKidZmkhMpYpp_qcuIqJXhb9

Donate to Er9x/Ersky9x:
https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=YHX43JR3J7XGW
User avatar
jhsa
Posts: 19480
Joined: Tue Dec 27, 2011 5:13 pm
Country: Germany

Re: Arduino based S-Bus decoder with CPPM and Servo

Post by jhsa »

Hmm it isn't working for me.. But maybe because eepskye is sending a signal which might be a bit different. well, not eepskye directly but the other arduino code..
Thanks anyway

João
My er9x/Ersky9x/eepskye Video Tutorials
https://www.youtube.com/playlist?list=PL5uJhoD7sAKidZmkhMpYpp_qcuIqJXhb9

Donate to Er9x/Ersky9x:
https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=YHX43JR3J7XGW
User avatar
MikeB
9x Developer
Posts: 17990
Joined: Tue Dec 27, 2011 1:24 pm
Country: -
Location: Poole, Dorset, UK

Re: Arduino based S-Bus decoder with CPPM and Servo

Post by MikeB »

Ok João, try this one:
SbusToPpm.zip
8 channels out only, 14 Oct 2014 17:27
First test version!
(4.83 KiB) Downloaded 762 times
I'm using a software serial, non-inverted input at 57600 baud, compatible with eepskye output. The serial input is on the Rx pin. Just got it working, so not much testing yet.
The plan is to auto detect the incoming baudrate, and possibly whether it is inverted or not.
I will also extend it to more outputs, probably 16.

Mike.
erskyTx/er9x developer
The difficult we do immediately,
The impossible takes a little longer!
mstrens
Posts: 1435
Joined: Fri Dec 27, 2013 7:49 pm
Country: -

Re: Arduino based S-Bus decoder with CPPM and Servo

Post by mstrens »

jhsa wrote:Hmm it isn't working for me.. But maybe because eepskye is sending a signal which might be a bit different. well, not eepskye directly but the other arduino code..
Thanks anyway

João
I tested it using the Sbus output from a X8R.
In my case it worked.

Still as explained, when using a "normal" sbus it requires to use an harware inverter (using e.g. 1 NPN transistor and 2 resistors. I presume that you know how to build such an inverter.
User avatar
jhsa
Posts: 19480
Joined: Tue Dec 27, 2011 5:13 pm
Country: Germany

Re: Arduino based S-Bus decoder with CPPM and Servo

Post by jhsa »

Yes Mstrens, thank you.. I did build one.. But as Mike said above the signal from eepskye is a bit different.
it's good to give the people with the X receivers the option to have 16 channels though. thank you.

Mike, on your other arduino code, aren't you sending a normal sbus inverted signal at 100000 baud on pin 11? I had the idea that you did, therefore I thought it would work with this code :)
will try your code now, thanks

João
My er9x/Ersky9x/eepskye Video Tutorials
https://www.youtube.com/playlist?list=PL5uJhoD7sAKidZmkhMpYpp_qcuIqJXhb9

Donate to Er9x/Ersky9x:
https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=YHX43JR3J7XGW

Post Reply

Return to “General RC Electronic Projects and Discussion”