London
Offline
Faraday Member
Karma: 6
Posts: 6226
Have fun!
|
 |
« Reply #105 on: June 02, 2008, 08:04:57 am » |
1. By PWM - I mean using the internal timers (PWM or Otherwise) to build a signal train which matches the expected composite signal. This should be possible using a hardware timer - toggling the output pin in an overflow ISR, and setting up the timer for the next pulse length.
2. The linked "post 3" appears to solve the input capture and connection to the receiver output. - I am proposing the OTHER half of the equation - to reinject the signal train.
This could solve the Arduino challenge of a. limit of 2 servos from Hardware, or b. Partially Software driven servos with jitter.
Does anyone have recommendations (or source) on generating an arbitrary square wave using hardware timers and interrupts?
Ben Hi Ben, it's a long thread so perhaps not surprising that you didn't see it, but the 'other half' is posted started from reply #74: http://www.arduino.cc/cgi-bin/yabb2/YaBB.pl?num=1204020386/74#74It's a library called ServoTimer2 that uses interrupts from Timer2 to produce servo pulses for eight channels. Because you want to inject a composite pulse train, you would define a single pin for all of the channels. Let me know if its use is not clear or if you think its missing something necessary for your application.
|
|
|
|
|
Logged
|
|
|
|
|
0
Offline
Newbie
Karma: 0
Posts: 26
Arduino rocks
|
 |
« Reply #106 on: June 02, 2008, 10:10:58 am » |
Thanks, You're right. That code (#74) appears to sequentially pulse the Servos. (Does it include a reset Delay) I'll try it.
|
|
|
|
|
Logged
|
|
|
|
|
London
Offline
Faraday Member
Karma: 6
Posts: 6226
Have fun!
|
 |
« Reply #107 on: June 02, 2008, 10:27:52 am » |
Yes, it does do the delay.
With all 8 channels pulsing at 1.5ms (the default values for all channels) it will delay 8ms after the eighth channel before starting over on the first. If all channels are pulsing at 2ms the sync delay will be 4ms.
If you are going to use 8 channels and they all could be pulsed at the maximum value of 2.25ms, then increase the FRAME_SYNC_PERIOD constant from 20000 to 22000 so that there will be a minimum sync delay of 4ms even in the unlikely case where all channels are pulsed at 2.2ms. But if you are using 6 channels or less, there will always be at least this delay with the posted code.
Have fun and let me know how you get on.
|
|
|
|
|
Logged
|
|
|
|
|
0
Offline
Newbie
Karma: 0
Posts: 26
Arduino rocks
|
 |
« Reply #108 on: June 02, 2008, 09:40:49 pm » |
Mem, I co-mingled ServoTimer2 with the InputCapture Code you mentioned above. It compiled in any case, and appears to run, so I guess their are no hardware conflicts: I imagine incorporating both of these into a Library - do you feel input capture is within the scope of ServoTimer2? Also, I'd like to see a compatible storage scheme for the channel values. I tend to favor arrays - as they can better lead to self-learning/healing algorithms. Here's the co-mingled sketch #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) 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: #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
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 }
int 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
// 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; 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); 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
}
// 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(State == NOT_SYNCHED_state) Serial.println("The decoder has not detected a synch pulse "); else if ( State == ACQUIRING_state) Serial.println("The decoder has detected one synch pulse and has started filling channel data"); else if( State == 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 = GetChannelPulseWidth(i); Serial.println(pulsewidth); } delay(10); // update 10 times a second
delay(10); }
|
|
|
|
|
Logged
|
|
|
|
|
London
Offline
Faraday Member
Karma: 6
Posts: 6226
Have fun!
|
 |
« Reply #109 on: June 02, 2008, 10:45:26 pm » |
It would not be difficult to turn the input capture piece into a library, but to start, you could simply move the ISR and GetChannelPulseWidth function into a separate cpp or pde file in your sketch directory.
The underlying structure is already in the servoTimer2 library to do what you want, here are some ideas on making a new version of the servoTimer2 library so its more compatible with the input capture interface:
Add another constructor to initialize use of all channels on a single pin
ServoTimer2::ServoTimer2(int pin ) { ChannelCount = NBR_CHANNELS; pinMode( pin, OUTPUT) ; // set encoder pin to output for(uint8_t i=1; i <= NBR_CHANNELS; i++) { // channels start from 1 writeChan(i, DEFAULT_PULSE_WIDTH); // store default values servos.Pin.nbr = pin; servos.Pin.isActive = true; } }
writeChannel needs to be made a public member of the ServoTimer2 class to give your sketch direct access to the existing channel array.
You may want to modify attach() so it doesn't try to set a pin (passing a pin value of 0 would probably work as a hack), but attach still needs to start the ISR.
There is probably more tweaking that will be needed, but should not be difficult to do once you are clear on how the exisitng code works.
Note that your sketch would create one instance as follows: #define encoderPin 2 ServoTimer2 RCencoder = ServoTimer2(encoderPin);
// pulse channel 1 at 2ms RCencoder.writeChan(1,2000); // note that writeChan needs to be declared as a public method of the class for this to work !
|
|
|
|
« Last Edit: June 02, 2008, 10:49:12 pm by mem »
|
Logged
|
|
|
|
|
0
Offline
Newbie
Karma: 0
Posts: 26
Arduino rocks
|
 |
« Reply #110 on: June 03, 2008, 08:21:12 am » |
mem, Thank you for the code and the replies.
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: 1. #IfDef section to add the input capture to ServoTimer2 2. Public Access to the ServoArray 3. a recommended receiver with instructions for modification 4. a UAV shield with Receiver, Gyro, Accel, Gps, Alt, AirSpeed and pyrometer pins. (Existing Zigbee Shield for duplex)
Ben
|
|
|
|
|
Logged
|
|
|
|
|
London
Offline
Faraday Member
Karma: 6
Posts: 6226
Have fun!
|
 |
« Reply #111 on: June 03, 2008, 08:55:22 am » |
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#3It'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.
|
|
|
|
|
Logged
|
|
|
|
|
0
Offline
Newbie
Karma: 0
Posts: 26
Arduino rocks
|
 |
« Reply #112 on: June 03, 2008, 09:10:51 am » |
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.
|
|
|
|
|
Logged
|
|
|
|
|
London
Offline
Faraday Member
Karma: 6
Posts: 6226
Have fun!
|
 |
« Reply #113 on: June 03, 2008, 09:30:35 am » |
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.
|
|
|
|
|
Logged
|
|
|
|
|
0
Offline
Newbie
Karma: 0
Posts: 26
Arduino rocks
|
 |
« Reply #114 on: June 04, 2008, 07:29:19 am » |
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
|
|
|
|
|
Logged
|
|
|
|
|
0
Offline
Newbie
Karma: 0
Posts: 26
Arduino rocks
|
 |
« Reply #115 on: June 04, 2008, 07:30:16 am » |
Save as ServoInput2.cpp (in Library ServoInput2) 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; }
|
|
|
|
|
Logged
|
|
|
|
|
0
Offline
Newbie
Karma: 0
Posts: 26
Arduino rocks
|
 |
« Reply #116 on: June 04, 2008, 07:31:01 am » |
And Here 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 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
}
|
|
|
|
|
Logged
|
|
|
|
|
London
Offline
Faraday Member
Karma: 6
Posts: 6226
Have fun!
|
 |
« Reply #117 on: June 04, 2008, 07:39:40 am » |
I haven't had a chance to look closely at the code but this change should fix that compiler error
uint8_t ServoInput2::getState(); { return State; }
|
|
|
|
|
Logged
|
|
|
|
|
0
Offline
Newbie
Karma: 0
Posts: 26
Arduino rocks
|
 |
« Reply #118 on: June 04, 2008, 12:42:40 pm » |
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?
|
|
|
|
|
Logged
|
|
|
|
|
0
Offline
Newbie
Karma: 0
Posts: 26
Arduino rocks
|
 |
« Reply #119 on: June 04, 2008, 12:46:15 pm » |
Turns out I had tried declaring the class with a () at the end.
|
|
|
|
|
Logged
|
|
|
|
|
|