hm.. actually i tried the pulseIn with the multiplexer today.... the code got stucked at the 1st pulseIn
i really have no idea why... i'm very puzzled on that..
sorry to bother you. but how do you actually change your code to read 3 channels, 1 after another? i have been looking at your code but i still dont understand the ISR part..
maybe.. if possible can you explain it further to me? so that I can make any further changes I want if possible..
for your alternative solution, you're saying we call pulseIn 3 times in the loop? and then go to do something else then loop again?
Here is a modification of the interrupt sketch for the multiplexer logic (I have indicated where you need to add the code that will write the two pins gating the multiplexer to select the current channel.
A difficulty with the output of your receiver is that there is no way of knowing when the next channels pulse will start. We overcome this by waiting until we detect the end of the previous frames pulse before capturing the pulse width and moving on to the next channel.
This is implemented using a 'Ready' flag that is toggled on an off on successive pulses and ensuring that the measurement only happens every other trailing edge. Another flag is used to indicate that enough pulses have been detected to ensure that all channel data is correct.
here is the logic:
In setup, channel is set to 1 and Ready flag and DataAvailable flags to set to false
When the first falling edge is detected Ready will be toggled so it will be set to true
When the rising edge is detected and Ready is true then the measurement proper starts
When a falling edge is detected and the Ready flag is true then the measurement ends,
the channel is incremented. If the channel count after incrementing is greater than the number of channels then the DataAvailable flag is set true and pulse data can be accessed
The ready flag will be reset back to false because it is toggled on every trailing edge.
I hope that is clear enough for you to think about a PulseIn version if you prefer that route.
#define icpPin 8 // this interrupt handler must use pin 8
#define TICKS_PER_uS 2 // number of timer ticks per microsecond
#define MAX_CHANNELS 3 // maximum number of channels we want
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)
boolean DataAvailable; // set to true when we have received data for all channels
boolean Ready; // true when we are ready to detect the leading edge of a channel
// here is the logic of how this code works :
// to start, channel is set to 1 and ready to false and the DataAvailable flag is false
// when a negative edge is detected ready is set to true
// when a positive edge is detected and ready is true then the measurement starts
// when a negative edge is detected and the ready flag is true then the measurement ends, the ready flag is reset back to false
// and the channel is incremented. If the channel count after incrementing is greater than the number of channels then
// the DataAvailable flag is set true and pulse data can be accessed
ISR(TIMER1_CAPT_vect){
if( !bit_is_set(TCCR1B ,ICES1)){ // was falling edge detected ?
if(Ready ) {
Pulses[Channel] = ICR1 / TICKS_PER_uS; // store pulse length as microsoeconds
if(++Channel > MAX_CHANNELS)
Channel = 1; //reset the channel counter to 1
// Add code here to gate the multiplexer to the current channel
}
Ready = !Ready; //toggle the ready flag
}
else { // rising edge was detected
TCNT1 = 0; // reset the counter
}
TCCR1B ^= _BV(ICES1); // toggle bit value to trigger on the other edge
}
void setup() // run once, when the sketch starts
{
Serial.begin(9600);
pinMode(icpPin,INPUT);
Channel = 1;
Ready = false;
DataAvailable = false;
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
}
int GetChannelPulseWidth( uint8_t channel) {
// this is the access function for channel data
int result;
if( DataAvailable && (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;
}
void loop() // run over and over again
{
int pulsewidth;
// print the decoder state
if(DataAvailable == false)
Serial.println("The decoder has not detected all channels ");
else
Serial.println("The the channel data is valid");
// 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(100); // update 10 times a second
}
i tested the signal(to digital pin 8) on the scope. the signal looks fine but when i connect it to the arduino... nothing happens. do you know what's the problem?
You could test it with just one receiver channel connected directly to pin 8 to verify that the interrupt logic works. If you get output pulse data between 1 and 2 ms and it follows the transmitter control then the problem may on the multiplexer side.
Have you tried a sketch without the interrupt code, just your multiplexer code and a simple digitlaRead on pin 8. You could connect resistors to provide high or low signals to the inputs to check that the multiplexer is working as you expect.
One comment on the multiplexer code, you should gate the multiplexer after the digitalWrites to the select inputs, otherwise you will get spurious pulses that will confuse the logic.
If g is the gate and abc are the inputs:
digitalWrite(a,LOW);
digitalWrite(b,LOW);
digitalWrite(c,LOW);
digitalWrite(g,LOW); // enable the gate after selecting inputs
Try it with the following modification to the interrupt handler:
if(++Channel > MAX_CHANNELS)
{ // add the braces
Channel = 1; //reset the channel counter to 1
// Add code here to gate the multiplexer to the current channel
DataAvailable = true; // this line is needed to indicate that the channel data is valid!!!!
}
but i do have a question. right now i'm having the correct output with only 1 channel. but the thing is when i connect 2/3 channels, they all output the same thing as the 1st channel..
i do think the problem lies with the multiplexer. actually my question is, does the variable channel means the channel number?so its ranges 1 to 3 right?
i tested the if(channel == 1) code... its not working.... i cannot configure the multiplexer according to the channel number.
The Channel variable is a channel number, but because there is nothing in this verison to indicate which channel is channel 1 then it will indicate channel 1 as the first channel it happens to detect. But the three channels should have different values if you are sending different values on your transmitter.
edit: I just reread what I wrote above and its not correct. The multiplexer will select the correct channel if its gated correctly.
Could you post the sketch that includes the multiplexer code.
hi i changed my code. i managed to get it working right now... just that there appear to be another problem.
i have connected the receiver in sequence 0,1,2
however the debug screen prints
chn 1 as receiver chn 2
chn 2 as receiver chn 0
chn 3 as receiver chn 1
Pulses[Channel] = ICR1 / TICKS_PER_uS; // store pulse length as microsoeconds
if(Channel == 1)
{
digitalWrite(a,LOW);
digitalWrite(b,LOW);
digitalWrite(c,LOW);
digitalWrite(g,LOW);
}
else if(Channel == 2)
{
digitalWrite(a,HIGH);
digitalWrite(b,LOW);
digitalWrite(c,LOW);
digitalWrite(g,LOW);
}
else if(Channel == 3)
{
digitalWrite(a,LOW);
digitalWrite(b,HIGH);
digitalWrite(c,LOW);
digitalWrite(g,LOW);
}
if(++Channel > MAX_CHANNELS)
{
Channel = 1; //reset the channel counter to 1
DataAvailable = true; // this line is needed to indicate that the channel data is valid!!!!
}
also.. i'm using the serial servo program to update the servos.. but the gap in in between pulses is too long... do you know of a better way to pulse the servos accurately?
I composed the following while you where posting the above. Have a read of this while I read your post:
modify the code as below so that the multiplexer channel selection is outside the brackets. I have moved it to a separate function of convenience. This code also turns the output of the multiplexer off when selecting.
if(++Channel > MAX_CHANNELS)
{
Channel = 1; //reset the channel counter to 1
DataAvailable = true; // this line is needed to indicate that the channel data is valid!!!!
}
SelectChannel( Channel)
void SelectChannel( int channel){
digitalWrite(g,HIGH); // turn the output off while twiddling select bits
if(Channel == 1)
{
digitalWrite(a,LOW);
digitalWrite(b,LOW);
}
else if(Channel == 2)
{
digitalWrite(a,HIGH);
digitalWrite(b,LOW);
}
else if(Channel == 3)
{
digitalWrite(a,LOW);
digitalWrite(b,HIGH);
}
digitalWrite(c,LOW); // c is always low
digitalWrite(g,LOW); // turn output on
}
edit: looks like you made the fix to move the Channel Selection outside the brackts. But you should add the code to gate the multiplexer off when writing the selection pins.
If you post your servo code will see if I can help. But in principle you want to update the servo every 20 milliseconds using whatever data is in the channel array after the DataAvailable flag is set to true
Note that the channel array is updated only every 40 ms or so but the servos should be pulsed every 20ms.
void PulseServo( int channel, int pulsewidth ){
// your code here to pulse the servo for the given channel with the given pulse width
// you should check that pulsewidth is within the valid range for your servos
}
void loop() // run over and over again
{
if(DataAvailable == false)
Serial.println("The decoder has not detected all channels ");
else{
PulseServo(1,GetChannelPulseWidth(1);
delay(6);
PulseServo(2,GetChannelPulseWidth(2);
delay(6);
PulseServo(3,GetChannelPulseWidth(3);
delay(6);
}
}
hi... thanks for your help so far.. i managed to get it working already.
void PulseServo(int channel,int pulsewidth)
{
// your code here to pulse the servo for the given channel with the given pulse width
// you should check that pulsewidth is within the valid range for your servos
int pulse = 0;
int pin_num = 0;
pin_num = channel + 1;
pulse = pulsewidth;
if ((pulse <= 2200) && (pulse >= 600))
{
digitalWrite(pin_num, HIGH);
delayMicroseconds(pulse);
digitalWrite(pin_num, LOW);
}
}
however... the servo is a litter jerky..... i check the pulse on the scope.... when everything is stationary... the PWM seems a bit unstable.. the pulses keeps moving left right right left... though the time between pulses is still ok..
do you have any idea what is causing the pulses to be moving?
You could send the pulse width to the serial port so you can inspect what is being sent to the servo.
Something like this would do the trick
void PulseServo(int channel,int pulsewidth)
{
// note that pulse variable is not needed and has been removed
int pin_num = 0;
pin_num = channel + 1;
if ((PulseWidth <= 2200) && (PulseWidth >= 600)) // 600 is a little low, try it with 800
{
digitalWrite(pin_num, HIGH);
delayMicroseconds(PulseWidth);
digitalWrite(pin_num, LOW);
}
else {
; you may want to add code here to blink a LED if Pulse WIdth is out of range
}
Serial.print("chan ");
Serial.print(Channel);
Serial.print("=");
Serial.print(PulseWidth);
Serial.print(", ");
}
hey. thanks.. actually i'm using timer 0 and timer 2 for do a PWM for the servos.... timer 1 will be used for the ICP.....
the code is as follow...
#include <avr/io.h>
#include <avr/interrupt.h>
int servoRoll = 6;
int servoPitch = 5;
int servoYaw = 11;
void setup()
{
Serial.begin(9600);
pinMode(servoRoll, OUTPUT);
pinMode(servoPitch, OUTPUT);
pinMode(servoYaw, OUTPUT);
/* setup for timer 0 */
TCCR0A = _BV(COM0A1) | _BV(COM0B1) | _BV(WGM01) | _BV(WGM00); // clear both A n B on compare match, set fast PWM mode.
TCCR0B = _BV(CS02) | _BV(CS00); // set prescaler of 1024 for 8 bit timer - 0 to 255.
TIMSK0 = _BV(OCIE0B) | _BV(OCIE0A) | _BV(TOIE0) ; // enables the interrupt for the
OCR0A = OCR0B = 0x18; // 0x18 = 24 = 1.5 ms
/* end of setup for timer 0 */
/* setup for timer 2 */
TCCR2A = _BV(COM2A1) | _BV(WGM21) | _BV(WGM20); // clear both A on compare match, set fast PWM mode.
TCCR2B = _BV(CS22) | _BV(CS21) | _BV(CS20); // set prescaler of 1024 for 8 bit timer - 0 to 255.
TIMSK2 = _BV(OCIE2A) | _BV(TOIE2) ; // enables the interrupt for the
OCR2A = 0x18; // 0x18 = 24 = 1.5 ms
/* end of setup for timer 2 */
sei();
}
void loop()
{
cli();
OCR2A= 16;
sei();
delay(17);
cli();
OCR2A = 32;
sei();
delay(17);
}
i discovered that i can only use 1 of the timers for this purpose.. not two. if i use two and one of them is correct, the other has something wrong with the pulse.
actually we cant keep changing the OCRXX values right? but my counter will overflow after 16 ms right? that means I should be able to vary the pulse for the motor right?
The resolution is pretty low using the 8 bit counters and that may explain why the servos are not smooth.
Any reason you can't use a simple delay instead of the timers for driving the servos similar to reply #33 above? You have at least 14 milliseconds to do any calculations between pulsing the servos and because your data is changing every two frames you can spread this over 28 milliseconds if you need to.
If an 8 bit timer count of 24 is 1.5ms then each step is over 60 microseconds. It should be within a microsecond if you use delayMicrosecond. Have you tried it using the delay instead of the timer?