3 axis auto stabilized platform

Foiled for tonight.
I have a Hitec NFS-04MG 4 channel receiver; the output stage is a 4015 shift register. The composite signal appears on one topside trace, but it appears not to be separable. (It's not a straight shot into the shift - since the reset RC timer is all tangled up in the composite line.

I think i shouldn't waste more time on this receiver - it's old school - I'd like to "crack" a receiver which is more widely available. I've got a nice GWS nano 6 chan I think I'll break open next.
In order to use this model - it might work to add a reset line on a separate pin, but that misses the point...

Ben

Mem,
I think you mentioned having a Hitec Micro O5S

  • do you think it possible to cut the composite trace and get a small wire on both the PIC input pin, and the radio signal out?
    I have some other receivers - but I don't have a clearly disambiguated composite trace isolated.
    that receiver is $23 at Tower hobby and quite small. It would be an excellent candidate generally...

Ben

The 05S should be suitable, you can find info on the composite signal locations in a post early in this thread. I tested the input capture code using the composit signal output from this receiver. I didn't try to inject a signal but I think you may be able to lift the pins of the SMT PIC chip if you have a small enough soldering iron. The traces are tiny so it will need a steady hand and good eyesight.

Hi to all!! Im new arduino fun, its one month i try to understand PPM/PWN and R/C transmission. I read MEM & Axeil post to know more about it, also i found an oscilloscope to investigate on my R/C Hitec Focus 4.
My idea is to create a UAV (QuadCopter) like other project on Internet :wink: but im not a good elettronic/programmer... so i follow the new post from Bgalliand as i can understand hw wants make the same goal of me.
At the moment my progress are:

  1. Found the pin on receiver (Hitec Focus4) where arrived the PPM signal (4 Channel)
  2. i used the old code from MEM/Axeil to read PPM without multiplex only attached to pin 8, but i can read all 4 CH without dirty and wrong order capture.

My idea is to use 4 Brushless motor to power the QuadCopter and an Accelorometer to correct the fly, perhaps the future is to have much more but at the moment is enough.
I ask for an help from where i need to move now, I can read the PPM and pulse the Brushless (with ESC controller) without other component? is need a multiplexer to have a good PPM signal?
Many thanks for your great post!!!

hi DiMiz, axileon needed the multiplexer because he wanted to use a receiver that did not output the channels in sequence. If your hitec 4 channel receiver works like my hitec 5 channel one then you can skip the multiplexer and use the recent decoder code, I think this is the latest:

ServoInput.h

//*****************ServoInput2.h*************************************
#ifndef ServoInput2_H
#define ServoInput2_H

#include <inttypes.h>

#define icpPin            8   // this interrupt handler must use pin 8
#define TICKS_PER_uS      2   // number of timer ticks per microsecond
#define MAX_CHANNELS    8   // maximum number of channels we can store  
#define SYNC_GAP_LEN      (3000 * TICKS_PER_uS) // we assume a space at least 3000us is sync (note clock counts in 0.5 us ticks)
#define NOT_SYNCHED_state  0    // the system is not synched so the data is random
#define ACQUIRING_state  1      // one sync pulse detected but not all channels have been received 
#define READY_state     2       // synched and all channel data is valid 

class ServoInput2
{
  public:
      ServoInput2(); //Constructor
      int GetChannelPulseWidth(uint8_t);  // this is the access function for channel data
      uint8_t getState(); //State Function

 private:
};

#endif

servoInput.cpp

//******** servoInput.cpp *********

#include "ServoInput2.h"
#include <wiring.h>
#include <avr/interrupt.h>


static volatile unsigned int Pulses[ MAX_CHANNELS + 1]; // array holding channel pulses width value in microseconds 
static volatile uint8_t  Channel;      // number of channels detected so far in the frame (first channel is 1)
static volatile uint8_t State;         // this will be one of the following states:



ISR(TIMER1_CAPT_vect)
{
   if(! bit_is_set(TCCR1B ,ICES1)){       // was falling edge detected ?   
       TCNT1 = 0;               // reset the counter      
       if(Channel <= MAX_CHANNELS) {
           Pulses[Channel++] = ICR1 / TICKS_PER_uS;  // store pulse length as microsoeconds
        }      
   }
   else {                          // rising  edge was detected   
        TCNT1 = 0;               // reset the counter      
        if(ICR1 >= SYNC_GAP_LEN){   // is the space between pulses big enough to be the SYNC
            Channel = 1;       // if so, reset the channel counter to 1       
              if(State == NOT_SYNCHED_state)
                  State = ACQUIRING_state;        // this is the first sync pulse, we need one more to fill the channel data array
              else if( State == ACQUIRING_state)      
                   State = READY_state;           // this is the second sync so flag that channel data is valid
        }    
   }      
   TCCR1B ^= _BV(ICES1);                 // toggle bit value to trigger on the other edge
}


ServoInput2::ServoInput2()
{
  pinMode(icpPin,INPUT);
  Channel = 1;             
  State = NOT_SYNCHED_state;
  TCCR1A = 0x00;         // COM1A1=0, COM1A0=0 => Disconnect Pin OC1 from Timer/Counter 1 -- PWM11=0,PWM10=0 => PWM Operation disabled 
  TCCR1B = 0x02;         // 16MHz clock with prescaler means TCNT1 increments every .5 uS (cs11 bit set
  TIMSK1 = _BV(ICIE1);   // enable input capture interrupt for timer 1
}


uint8_t ServoInput2::getState()  
{
  return State;
}


int ServoInput2::GetChannelPulseWidth( uint8_t channel)
{
  // this is the access function for channel data
  int result;  
  if( (State == READY_state)  && (channel > 0) && (channel <=  MAX_CHANNELS)  ) {
    cli();             //disable interrupts
    result =  Pulses[channel] ;
    sei();             // enable interrupts
  }
  else
    result = 0;        // return 0 if no valid pulse is available  

  return result;
}

Hi MEM!! Many thanks for your fast reply to my help, I try this code this evening. As i can see this is function and simple Arduino skecth, how can i use it? Also i can debug with Serial output the PulseWidth?
Many thanks i will report my test.
Sincerelly

Reply #116 has a sketch that sends the pulse width to the serial port. If you can try to post an outline of the functionality you want for your sketch, I or others here can help you more with the code.

Thanks Mem i will try the sketch from post 116, i think is what i need now to test R/C transmission and PPM conversion.
My generic outline for what i want is:
1- Grab R/C command from transmitter
2- Receive PPM signal decode it and match Accelorometer XYZ variation to stabilized the QuadCopter
3- Pulse 4 Brushless motor using the ESC circuit (The ESc is already done :-))

At the moment if can decode without problem the PPM signal i think is great step :slight_smile:
Many thanks this evening i will report my improvements, also you never made a Helicopter like my project?

Ok is very late in Italy, but im very excited with my project so i describe what i have reached...not too much:)
I copy and paste your Libray in arduino-0011/hardware/libraries/ServoInput2
ServoInput2.h

//*****************ServoInput2.h*************************************
#ifndef ServoInput2_H
#define ServoInput2_H

#include <inttypes.h>

#define icpPin          8   // this interrupt handler must use pin 8
#define TICKS_PER_uS    2   // number of timer ticks per microsecond
#define MAX_CHANNELS    8   // maximum number of channels we can store  
#define SYNC_GAP_LEN    (3000 * TICKS_PER_uS) // we assume a space at least 3000us is sync (note clock counts in 0.5 us ticks)
#define NOT_SYNCHED_state  0    // the system is not synched so the data is random
#define ACQUIRING_state  1      // one sync pulse detected but not all channels have been received 
#define READY_state     2        // synched and all channel data is valid 

class ServoInput2
{
  public:
        ServoInput2(); //Constructor
        int GetChannelPulseWidth(uint8_t);  // this is the access function for channel data
        uint8_t getState(); //State Function

 private:
};

extern ServoInput2 ServoInput;  // make an instance for the user 
#endif

and ServoInput2.cpp

//******** servoInput.cpp *********

extern "C" {
        // AVR LibC Includes
#include <inttypes.h>
#include <avr/interrupt.h>
#include "WConstants.h"
        }

#include "ServoInput2.h"
#include <wiring.h>
//#include <avr/interrupt.h>


static volatile unsigned int Pulses[ MAX_CHANNELS + 1]; // array holding channel pulses width value in microseconds 
static volatile uint8_t  Channel;      // number of channels detected so far in the frame (first channel is 1)
static volatile uint8_t State;         // this will be one of the following states:



ISR(TIMER1_CAPT_vect)
{
   if(! bit_is_set(TCCR1B ,ICES1)){       // was falling edge detected ?   
       TCNT1 = 0;               // reset the counter      
       if(Channel <= MAX_CHANNELS) {
           Pulses[Channel++] = ICR1 / TICKS_PER_uS;  // store pulse length as microsoeconds
        }      
   }
   else {                          // rising  edge was detected   
        TCNT1 = 0;               // reset the counter      
        if(ICR1 >= SYNC_GAP_LEN){   // is the space between pulses big enough to be the SYNC
            Channel = 1;       // if so, reset the channel counter to 1       
              if(State == NOT_SYNCHED_state)
                  State = ACQUIRING_state;        // this is the first sync pulse, we need one more to fill the channel data array
              else if( State == ACQUIRING_state)      
                   State = READY_state;           // this is the second sync so flag that channel data is valid
        }    
   }      
   TCCR1B ^= _BV(ICES1);                 // toggle bit value to trigger on the other edge
}


ServoInput2::ServoInput2()
{
  pinMode(icpPin,INPUT);
  Channel = 1;             
  State = NOT_SYNCHED_state;
  TCCR1A = 0x00;         // COM1A1=0, COM1A0=0 => Disconnect Pin OC1 from Timer/Counter 1 -- PWM11=0,PWM10=0 => PWM Operation disabled 
  TCCR1B = 0x02;         // 16MHz clock with prescaler means TCNT1 increments every .5 uS (cs11 bit set
  TIMSK1 = _BV(ICIE1);   // enable input capture interrupt for timer 1
}


uint8_t ServoInput2::getState()  
{
  return State;
}


int ServoInput2::GetChannelPulseWidth( uint8_t channel)
{
  // this is the access function for channel data
  int result;  
  if( (State == READY_state)  && (channel > 0) && (channel <=  MAX_CHANNELS)  ) {
    cli();             //disable interrupts
    result =  Pulses[channel] ;
    sei();             // enable interrupts
  }
  else
    result = 0;        // return 0 if no valid pulse is available  

  return result;
}

ServoInput2 ServoInput = ServoInput2() ;

As you can see i have made some mod using your post #121 with bGatti because i found some undefined error when compile
and my sketch is
follow in the next post...

ok my sketch is

// this sketch cycles three servos at different rates 

#include <ServoTimer2.h>  // the servo library
#include <ServoInput2.h>


// define the pins for the servos
#define rollPin  13
#define pitchPin 13
#define yawPin   13

ServoTimer2 servoRoll;    // declare variables for up to eight servos
ServoTimer2 servoPitch; 
ServoTimer2 servoYaw; 
// ServoInput2 ServoInput();  // remove this because its declared in the source file.

void setup() {
  servoRoll.attach(rollPin);     // attach a pin to the servos and they will start pulsing
  servoPitch.attach(pitchPin); 
  servoYaw.attach(yawPin); 


  Serial.begin(9600);   

  
}


// this function just increments a value until it reaches a maximum 
int incPulse(int val, int inc){
   if( val + inc  > 2000 )
      return 1000 ;
   else
       return val + inc;  
}

void loop()
{ 
 int val;
  
   val = incPulse( servoRoll.read(), 1);
   servoRoll.write(val);

   val =  incPulse( servoPitch.read(), 2);
   servoPitch.write(val);
   
   val = incPulse(servoYaw.read(), 4);
   servoYaw.write(val);
 
 
 int pulsewidth;

   // print the decoder state
   if(ServoInput.getState() == NOT_SYNCHED_state)
       Serial.println("The decoder has not detected a synch pulse ");   
   else if (ServoInput.getState() == ACQUIRING_state)
       Serial.println("The decoder has detected one synch pulse and has started filling channel data");  
   else if(ServoInput.getState() == READY_state)
      Serial.println("The decoder is synched and the channel data is valid");  
   else
      Serial.println("Unknown decoder state, this should never happen!"); 
   

  // now print the channel pulse widths
  // they should be 0 if the state is not ready 
  for ( int i =1; i <=4; i++ ){ // print the status of the first four channels
      Serial.print("Channel ");
      Serial.print(i);
      Serial.print(" has width ");
      pulsewidth = ServoInput.GetChannelPulseWidth(i);
      Serial.println(pulsewidth);
  }
   delay(10); // update 10 times a second        

   
}

but my debug is:

The decoder has not detected a synch pulse

Channel 1 has width 0

Channel 2 has width 0

Channel 3 has width 0

Channel 4 has width 0

The decoder has not detected a synch pulse


About my R/C Signal:
1- i Have found from oscilloscope the PPM frame and seems of 10ms not the usually 20ms
2- the sync frame seems 440/450us not 3000 as you defined in ServoInput2.h, i have try to mod but not seems to affect nothing...

Many thanks for possible help, if you need other info please ask me!!

Also some image of my PPM signal
and my Arduino & R/C receiver attached...

I hope my info is usefull
Thanks Dimi

DiMaz,

I notice in your Oscope that your signal is an inversion of my signal.
This is not to say yours is upside down, only something to consider.

I think you might benefit from inverting the interupt code.

For reference, this version compiled:

//***Save As ServoInput2.h ************************************ServoInput2*************************************
//This add-on to ServoTimer2 provides for the reading of typical RC composite servo inputs
//To save the extra memory used by this add-on library include the line:  (sharp)define omitServoInput2

#ifndef omitServoInput2
#define omitServoInput2

#include <wiring.h>
#include <avr/interrupt.h> 
#include <inttypes.h> 

#define NOT_SYNCHED_state  0    // the system is not synched so the data is random
#define ACQUIRING_state  1      // one sync pulse detected but not all channels have been received 
#define READY_state     2       // synched and all channel data is valid 


class ServoInput2
{
  public:
      ServoInput2(); //Constructor

      uint8_t getState(); //State Function
      int GetChannelPulseWidth(uint8_t);  // this is the access function for channel data
      void attach(); //Initialize

 private:


};

//extern ServoInput2 ServoInput;  // make an instance for the user 

#endif
extern "C" {
  // AVR LibC Includes
  #include <inttypes.h>
  #include <avr/interrupt.h>
  #include "WConstants.h"
}


#include "ServoInput2.h"

#define icpPin            8         // this interrupt handler must use pin 8
#define TICKS_PER_uS      2          // number of timer ticks per microsecond
#define MAX_CHANNELS    8         // maximum number of channels we can store  
#define SYNC_GAP_LEN      (3000 * TICKS_PER_uS) // we assume a space at least 3000us is sync (note clock counts in 0.5 us ticks)


static volatile unsigned int Pulses[ MAX_CHANNELS + 1]; // array holding channel pulses width value in microseconds  
static volatile uint8_t  Channel;      // number of channels detected so far in the frame (first channel is 1)
static volatile uint8_t State;         // this will be one of the following states: 




ISR(TIMER1_CAPT_vect)
{
   if(! bit_is_set(TCCR1B ,ICES1)){       // was falling edge detected ?   
       TCNT1 = 0;               // reset the counter      
       if(Channel <= MAX_CHANNELS) {
           Pulses[Channel++] = ICR1 / TICKS_PER_uS;  // store pulse length as microsoeconds
        }      
   }
   else {                          // rising  edge was detected   
         digitalWrite( 13,HIGH); //prove detect on high

        TCNT1 = 0;               // reset the counter      
        if(ICR1 >= SYNC_GAP_LEN){   // is the space between pulses big enough to be the SYNC
            Channel = 1;       // if so, reset the channel counter to 1       
              if(State == NOT_SYNCHED_state)
                  State = ACQUIRING_state;        // this is the first sync pulse, we need one more to fill the channel data array
              else if( State == ACQUIRING_state)     
                   State = READY_state;           // this is the second sync so flag that channel data is valid
        }    
   }     
   TCCR1B ^= _BV(ICES1);                 // toggle bit value to trigger on the other edge
}


ServoInput2::ServoInput2()
{
  pinMode(icpPin,INPUT);
  Channel = 1;             
  State = NOT_SYNCHED_state;
  TCCR1A = 0x00;         // COM1A1=0, COM1A0=0 => Disconnect Pin OC1 from Timer/Counter 1 -- PWM11=0,PWM10=0 => PWM Operation disabled 
  TCCR1B = 0x02;         // 16MHz clock with prescaler means TCNT1 increments every .5 uS (cs11 bit set
  TIMSK1 = _BV(ICIE1);   // enable input capture interrupt for timer 1
}



uint8_t ServoInput2::getState()
{
   return State;
} 



int ServoInput2::GetChannelPulseWidth( uint8_t channel)
{
  // this is the access function for channel data
  int result;  
  if( (State == READY_state)  && (channel > 0) && (channel <=  MAX_CHANNELS)  ) {
     cli();             //disable interrupts
     result =  Pulses[channel] ;
     sei();             // enable interrupts
  }
  else
     result = 0;        // return 0 if no valid pulse is available  

  return result;
}
 
//ServoInput2 ServoInput = ServoInput2() ;

And the Sketch:

// this sketch cycles three servos at different rates 

#include <ServoTimer2.h>  // the servo library
#include <ServoInput2.h>

// define the pins for the servos
#define rollPin 13
#define NBR_SERVOS 8   // this must be between 1 and NBR_CHANNELS as defined in ServoTimer2.H
byte servoPins[NBR_SERVOS] = {9,9,9,9,0,0,9,9};  // pins our servos are attached to
ServoTimer2 Servos[NBR_SERVOS]  ; 
ServoInput2 ServoInput;

int direction[NBR_SERVOS];   // 1 is forward, -1 is reverse

void setup() {
  for( int i =0; i < NBR_SERVOS; i++){
        direction[i] = 1;  //  forward
          if(servoPins[i] >1)
            Servos[i].attach(servoPins[i]); // this will start pulsing the pin!

//        Servos[i].write( 800 + (i * 200));  // writing to an unattched pin is ok

//       Servos[i].write( 800 + (i * 200));  // write some test data       
  Serial.begin(9600);  
    }    
}
  

void loop()
{ 
  
  // print the decoder state
   if(ServoInput.getState() == NOT_SYNCHED_state)
       Serial.println("The decoder has not detected a synch pulse ");   
   else if (ServoInput.getState() == ACQUIRING_state)
       Serial.println("The decoder has detected one synch pulse and has started filling channel data");  
   else if(ServoInput.getState() == READY_state)
     Serial.println("The decoder is synched and the channel data is valid");  
   else
     Serial.println("Unknown decoder state, this should never happen!"); 

  // now print the channel pulse widths
  // they should be 0 if the state is not ready
  int pulseWidth;
 
    //sweep all the servos back and forth
    for( int i=0; i <  NBR

Looks like the Sketch was truncated:

Here again - the Sketch:

// this sketch cycles three servos at different rates 

#include <ServoTimer2.h>  // the servo library
#include <ServoInput2.h>

// define the pins for the servos
#define rollPin 13
#define NBR_SERVOS 8   // this must be between 1 and NBR_CHANNELS as defined in ServoTimer2.H
byte servoPins[NBR_SERVOS] = {9,9,9,9,0,0,9,9};  // pins our servos are attached to
ServoTimer2 Servos[NBR_SERVOS]  ; 
ServoInput2 ServoInput;

int direction[NBR_SERVOS];   // 1 is forward, -1 is reverse

void setup() {
  for( int i =0; i < NBR_SERVOS; i++){
        direction[i] = 1;  //  forward
          if(servoPins[i] >1)
            Servos[i].attach(servoPins[i]); // this will start pulsing the pin!

//        Servos[i].write( 800 + (i * 200));  // writing to an unattched pin is ok

//       Servos[i].write( 800 + (i * 200));  // write some test data       
  Serial.begin(9600);  
    }    
}
  

void loop()
{ 
  
  // print the decoder state
   if(ServoInput.getState() == NOT_SYNCHED_state)
       Serial.println("The decoder has not detected a synch pulse ");   
   else if (ServoInput.getState() == ACQUIRING_state)
       Serial.println("The decoder has detected one synch pulse and has started filling channel data");  
   else if(ServoInput.getState() == READY_state)
     Serial.println("The decoder is synched and the channel data is valid");  
   else
     Serial.println("Unknown decoder state, this should never happen!"); 

  // now print the channel pulse widths
  // they should be 0 if the state is not ready
  int pulseWidth;
 
    //sweep all the servos back and forth
    for( int i=0; i <  NBR_SERVOS; i++){ 
          pulseWidth = ServoInput.GetChannelPulseWidth(i);
        //pulseWidth = Servos[i].read();
         
        if(pulseWidth >= MAX_PULSE_WIDTH){
            direction[i] = -1;
        }
        if (pulseWidth <= MIN_PULSE_WIDTH) {
             direction[i] = 1;     
        }
       Servos[i].write(pulseWidth + direction[i]);       
    }
   delay(10); // update 10 times a second        
}

End Code

Hi bGatti thanks for your reply, about my Oscope signal probably i invert the signal function :wink: im not sure last was very late so probably i have made some error on using my Oscope...but i will try your code to test my R/C receive.
Are try to create a QuadCopter or something similar? If im able to capture my R/C command my second step is use a accelorometer to stabilized the copter, have made some work on it?
Many thanks
Ps: Using your posted code are you able to Capture the signal without problem?

Hi DiMiz, looking at your scope trace I think the version below may work for you. Try it and let us know if if finds the sync and what output pulse widths are reported on the serial port

ISR(TIMER1_CAPT_vect)
{
  if(! bit_is_set(TCCR1B ,ICES1)){       // was falling edge detected ?   
    TCNT1 = 0;               // reset the counter      
    if(ICR1 >= SYNC_GAP_LEN){   // is the space between pulses big enough to be the SYNC
      Channel = 1;       // if so, reset the channel counter to 1       
      if(State == NOT_SYNCHED_state)
        State = ACQUIRING_state;        // this is the first sync pulse, we need one more to fill the channel data array
      else if( State == ACQUIRING_state)      
        State = READY_state;           // this is the second sync so flag that channel data is valid
    }    
    else if(Channel <= MAX_CHANNELS) {  // else its channel pulse so save it 
      Pulses[Channel++] = ICR1 / TICKS_PER_uS;  // store pulse length as microsoeconds
    }      
  }
  else {                          // rising  edge was detected   
    TCNT1 = 0;               // reset the counter      

  }      
  TCCR1B ^= _BV(ICES1);                 // toggle bit value to trigger on the other edge
}

This code is functionally similar to the above but I hope it is easier to understand and customize.

#define PULSE_STARTS_ON_RISING_EDGE   // un-comment this if the output goes positive for duration of the pulse
//#define PULSE_STARTS_ON_FALLING_EDGE   // un-comment this if the output goes to 0 for duration of the pulse


ISR(TIMER1_CAPT_vect)
{
 // we want to measure the time to the end of the pulse 
#if defined PULSE_STARTS_ON_RISING_EDGE
  if(bit_is_clear(TCCR1B ,ICES1)){       // was falling edge detected ?   
#else  
  if( bit_is_set(TCCR1B ,ICES1)){       // was rising edge detected ? 
#endif  
    TCNT1 = 0;               // reset the counter      
    if(ICR1 >= SYNC_GAP_LEN){   // is the space between pulses big enough to be the SYNC
      Channel = 1;       // if so, reset the channel counter to 1       
      if(State == NOT_SYNCHED_state)
        State = ACQUIRING_state;        // this is the first sync pulse, we need one more to fill the channel data array
      else if( State == ACQUIRING_state)      
        State = READY_state;           // this is the second sync so flag that channel data is valid
    }    
    else if(Channel <= MAX_CHANNELS) {  // else its channel pulse so save it 
      Pulses[Channel++] = ICR1 / TICKS_PER_uS;  // store pulse length as microsoeconds
    }      
  }
  else {        // the start of the pulse was detected   
    TCNT1 = 0;        // so reset the counter to start timeing 

  }      
  TCCR1B ^= _BV(ICES1);                 // toggle bit value to trigger on the other edge
}

I have not run the code in this form so please report if you have problems.

Many thanks Mem I will test it and report my progress this night, thanks so much!!!

Mem,
I noticed a shift in the pulse direction.
Do you think we could add "auto-shift-detection"? That's a pretty common feature of modern receivers.
I'm thinking measure each input change and set the shift bit based on the longest state.

Ben

DiMiz,
Yes, I made good progress on 3 Axis Accel. I used the cheapest 3 Axis part from Sparkfun (~$19 on breakout board). I provided Sketch and hookup.

Also, your signal is probably inverted (not your scope). You'll need to use mem's invertible code.

Ben

Hi bGatti what is the part number of Accelometer, so i try to find a reseller in Italy?
Many thanks for help i test Mem code this night