One neat feature of the current design is that servos can be added either to the composite feedback, or forwarded to a pin, so that even more servos could be added in addition to the capacity of the receiver.
I'm thinking perhaps:
#IfDef section to add the input capture to ServoTimer2
Public Access to the ServoArray
a recommended receiver with instructions for modification
a UAV shield with Receiver, Gyro, Accel, Gps, Alt, AirSpeed and pyrometer pins. (Existing Zigbee Shield for duplex)
The current design would need to be modified slightly if more than 8 servos (actual or composite) are used in order to ensure that interframe delay was at least 3 or 4 ms. If you want to try it with more than 8 channels I can help you make the change.
I would not recommend combining the servoTimer2 output code (the decoder) and the input code (the encoder) into the same library. I can't think of any advantage in doing so and it creates extra baggage for anyone that just needs to use half the functionality. Making a new library for the input side is not difficult and although some of the constants in the header files would be similar (i.e. min and max pulse widths and frame period), they don't need to be the same for all applications so better to have separate headers for the constants. If you need help creating the header file for the input libray, I will post something to get you started, but I won't have time to write and test a new library, but will be happy to advise from the sidelines
The only receiver I have experimented with integrating with the arduino is the Hitec hf-05s I mentioned it earlier in this thread http://www.arduino.cc/cgi-bin/yabb2/YaBB.pl?num=1204020386/3#3
It's small, inexpensive and works well, but its not easy to make the connections because of the tiny size of its PCB so its recommended only to those with experience working on small SMT boards.
mem,
To your point of "extra baggage" I thought an #Ifdef section to turn on the decoder section would save any memory that would otherwise be unnecessary. I think the header info, the structures, and the constants - in addition the need to maintain conflict-free operation might weight in favor of a single lib. - We'll see - if the shield is ever realized - it would justify a suite of supporting libs.
Because servoTimer2 uses an 8 bit timer and the input capture uses the 16 bit timer, the underlying data structures are actually somewhat different.
The 8 bit counter uses two separate bytes (a counter and a remainder) to keep track of the pulse width. The 16 bit input code is simpler because it uses a 16 bit integer to hold the pulse width.
I certainly don't want to stop you if you feel that combining them is better for your application, but I feel for general use, keeping the input and output separate libraries is more flexible in use and easier to maintain.
Here's the Library Attempt - Sadly it doesn't Compile
ERROR OUTPUT:
In function 'void loop()':
error: request for member 'getState' in 'servoInput', which is of non-class type 'ServoInput2 ()()'
//***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
#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
volatile unsigned int Pulses[ MAX_CHANNELS + 1]; // array holding channel pulses width value in microseconds
volatile uint8_t Channel; // number of channels detected so far in the frame (first channel is 1)
volatile uint8_t State; // this will be one of the following states:
class ServoInput2
{
public:
ServoInput2(); //Constructor
int GetChannelPulseWidth(uint8_t); // this is the access function for channel data
void attach(); //Initialize
uint8_t getState(); //State Function
private:
};
#endif
extern "C" {
// AVR LibC Includes
#include <inttypes.h>
#include <avr/interrupt.h>
#include "WConstants.h"
}
#include "ServoInput2.h"
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 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;
}
// 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();
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
}
Good catch, I left off the class:: prefix; however, that had no effect on the error message - somehow the object "ServoInput2" is being interpreted as a type rather than a class?
I think there's a bit of a problem accessing the Pulses array from the ISR, the Lib, and the sketch - is there a scope modifier I need?
Ben
The Error Says:
o: In function incPulse': multiple definition of Pulses'hardware\libraries\ServoInput2\ServoInput2.o:D:\arduino-0011/hardware\libraries\ServoInput2\ServoInput2.cpp:11: first defined here
o: In function incPulse': o: In function incPulse':
in the cpp file, after #include "ServoInput2.h" add: #include <wiring.h> #include <avr/interrupt.h>
move the declarations of the following into the cpp file and make them static as follows
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:
remove the semicolon from the end of
uint8_t ServoInput2::getState()
at the bottom of the file, make an instance of the library for the user. There can only be one instance (there is support for only a single input capture in the arduino chip) so lets make it a little easer for the user
ServoInput2 ServoInput = ServoInput2() ;
in the header file add #include <inttypes.h>
to the top of the header file
and add
extern ServoInput2 ServoInput; // make an instance for the user
to the bottom , just before the endif.
you will need to remove the following from the sketch
ServoInput2 servoInput(); // remove this because its declared in the source file.
I think you will need to fix some inconsistent capitalization between the sketch and the source files but these changes should get you close.
Mem,
Great
That compiles. appears to run - I'll need to connect it to a receiver next.
(the creation of a user instance didn't work - but I remmed, and everything else appears ok.)
o: In function loop': undefined reference to ServoTimer2::writeChan(unsigned char, int)'
Which is quite surprising - where does "char" come from?
Here's the h:
class ServoTimer2
{
public:
// constructor:
ServoTimer2();
uint8_t attach(int); // attach the given pin to the next free channel, sets pinMode, returns channel number or 0 if failure
// the attached servo is pulsed with the current pulse width value, (see the write method)
void detach();
void write(int); // store the pulse width in microseconds (between MIN_PULSE_WIDTH and MAX_PULSE_WIDTH)for this channel
void writeChan(uint8_t,int); // (Channel,Pulsewidth) store the pulse width in microseconds (between MIN_PULSE_WIDTH and MAX_PULSE_WIDTH)for the
given //channel
int read(); // returns current pulse width in microseconds for this servo
boolean attached(); // return true if this servo is attached
private:
uint8_t chanIndex; // index into the channel data for this servo
};
And the method code:
static void writeChan(uint8_t chan, int pulsewidth);
static void ServoTimer2::writeChan(uint8_t chan, int pulsewidth)
{
// calculate and store the values for the given channel
... etc
So far you've lived up to your tagline...
Any ideas here?
Ben
The compiler is complaining because
static void writeChan(uint8_t chan, int pulsewidth)
is a static function, only accessible to code in the servoTimer2.cpp source file and not a member of the servoTimer2 class.
Making it a member method would be odd because it would allow any instance to change any other instances data. I suggest you create an array of servo instances and use that to access the data like this example sketch
#include <ServoTimer2.h>
#define NBR_SERVOS 8 // this must be between 1 and NBR_CHANNELS as defined in ServoTimer2.H
byte servoPins[NBR_SERVOS] = {2,3,4,5,6,7,8,9}; // pins our servos are attached to
ServoTimer2 Servos[NBR_SERVOS] ;
int direction[NBR_SERVOS]; // 1 is forward, -1 is reverse
void setup() {
for( int i =0; i < NBR_SERVOS; i++){
direction[i] = 1; // forward
Servos[i].attach(servoPins[i]);
Servos[i].write( 800 + (i * 200)); // write some test data
}
}
void loop() {
int pulseWidth;
//sweep all the servos back and forth
for( int i=0; i < NBR_SERVOS; 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);
}
uint8_t is another name for an unsigned char, it's short for u(unsigned)int(integer)8(eight bits)_t(it's a type). I tend to use byte instead of uint8_t these days but either more clearly express their purpose than 'a character without a sign'. All three forms are the same to the compiler
Good to hear your making progress, looking forward to seeing more
0 is a real Arduino pin so if you attach a servo assigned to pin 0 it will write to that pin. Pin 0 is usually used by the serial port on the Arduino so don't actually attach an instance using pin 0 if your board has a serial interface/USB.
Your sketch could check and not call the attach method if the pin is 0 (or 1)
void setup() {
for( int i =0; i < NBR_SERVOS; i++){
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
}
}
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...
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...
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.