Verifying if the interrupt states have actually changed .

This is a R/C project and I wish to detect the physical disconnect of the R/C reciever from the Arduino ( Pro mini ).

I need help / guidance in determining wether the interrupt states have actually completed a HIGH - LOW cycle in x millisecs ... and in the event of this criteria not being met ... implementing a failsafe option .... preferably defaulting to a ZERO or NULL output state.

The code in it's present form shows a reading that is taken at the moment of a physical disconnect.

NOTE:- The following is NOT my work ...other than adding a 3rd channel and naming the channels..

#include <EnableInterrupt.h>

#define SERIAL_PORT_SPEED 115200
#define RC_NUM_CHANNELS  3

#define RC_CH1  0
#define RC_CH2  1
#define RC_CH3  2
#define RC_CH1_INPUT  2
#define RC_CH2_INPUT  3
#define RC_CH3_INPUT  4




word Aile;
word Ele;
word Rud;

uint32_t last_update_time = 0;
uint16_t rc_values[RC_NUM_CHANNELS];
uint32_t rc_start[RC_NUM_CHANNELS];
volatile uint16_t rc_shared[RC_NUM_CHANNELS];

void rc_read_values() {
  // read the values out of the shared, volatile array (which can change at any time)
  // and into an array that we only will only change when we call this function

  noInterrupts();
  memcpy(rc_values, (const void *)rc_shared, sizeof(rc_shared));
  interrupts();
}

void calc_input(uint8_t channel, uint8_t input_pin) {
  // This is the interrupt.  If the pin is high, we record the start
  // time of the pulse.  If the pin is low, we compare the start time
  // to the current time in order to get the length of the pulse.

  if (digitalRead(input_pin) == HIGH) {
    rc_start[channel] = micros();
  } else {
    uint16_t rc_compare = (uint16_t)(micros() - rc_start[channel]);
    rc_shared[channel] = rc_compare;
  }
}

void calc_ch1() { calc_input(RC_CH1, RC_CH1_INPUT); }
void calc_ch2() { calc_input(RC_CH2, RC_CH2_INPUT); } 
void calc_ch3() { calc_input(RC_CH3, RC_CH3_INPUT); } 

void setup() {
  Serial.begin(SERIAL_PORT_SPEED);

  pinMode(RC_CH1_INPUT, INPUT_PULLUP);
  pinMode(RC_CH2_INPUT, INPUT_PULLUP);
  pinMode(RC_CH3_INPUT, INPUT_PULLUP);

  // set up CHANGE interrupts, so that we get notified when
  // each signal goes high or low.
  enableInterrupt(RC_CH1_INPUT, calc_ch1, CHANGE);
  enableInterrupt(RC_CH2_INPUT, calc_ch2, CHANGE);
  enableInterrupt(RC_CH3_INPUT, calc_ch3, CHANGE);
}

void loop() {
  rc_read_values();
  Aile = (rc_values[RC_CH1]);
  Ele = (rc_values[RC_CH2]);
  Rud= (rc_values[RC_CH3]);

  Serial.print("CH1:"); Serial.println(Aile);
  Serial.print("CH2:"); Serial.println(Ele);
  Serial.print("CH3:"); Serial.println(Rud);

  delay(20);  // just so serial log doesn't flood
}

What do you mean "interrupt completed"?
After all an ISR should return much faster than milliseconds.

What is this "enableInterrupt" library supposed to do?

I wish to detect that an actual signal is being continuously read , and not...in the event of a physical disconnect/broken wire ... "stuck" at a random reading .

OK .. micro ... period of time taken to recognize and implement a failsafe condition.

Make sure your sensor routine always returns a value, or an "impossible" one when it detects the sensor is faulty.

woodygb:
This is a R/C project and I wish to detect the physical disconnect of the R/C reciever from the Arduino ( Pro mini ).

Do I have this right...
You want to monitor 3 servo channels which should have a period of 'x milliseconds'
During that period, the servo signal should go HIGH/LOW if the signal is still being received.

You say:

The code in it's present form shows a reading that is taken at the moment of a physical disconnect.

  • but I do not understand what you mean by this.

It seems that the current code measures the HIGH period of the signal causing the interrupt - I'm not sure why you are doing this (unless you want to check that the HIGH period is between specific values?)

To simply determine if the signals are still "wiggling", you only need to time a suitable period in loop() and have each interrupt routine simply increment a value. The fact that the interrupt was called means that the input signal has CHANGE'd. The simple counter tells you how many times it changes in the period you are timing.

I guess this is for Rx signal failure to be detected and to subtitute some failsafe servo settings.

I don't think you want to switch into "failsafe" mode if just the odd servo pulse is missing (in normal operation, especially near maximum range, lots of servo pulses get lost... that's why the servo signal repeats over and over.)

If your servo update rate is, say, 50Hz then you would expect 2 transitions every 20mS.
Using a simple incremented-in-interrupt value, you could test this every 100mS and say "it is probably still OK" when you get, for example, more than 6 transitions in that time.

Yours,
TonyWilk

Hi Tony,

Yes ...you have it right .

My reference

The code in it's present form shows a reading that is taken at the moment of a physical disconnect.

.... Would perhaps be better stated as ...
The code in it's present form shows a reading of whatever is stored in memory @ the time of a physical disconnect.

So as you say I need ...

To simply determine if the signals are still "wiggling",

...I have been trying to code this but the "simply" bit seems beyond my current ( limited ) knowledge.

Cheers Woody

woodygb:
I have been trying to code this but the "simply" bit seems beyond my current ( limited ) knowledge.

Ok, have a look at this:

// Variables to count on each interrupt
//
volatile word ch1_count=0;
volatile word ch2_count=0;
volatile word ch3_count=0;

// Interrupt routines to simply count number of pin changes
// - there should never be more than 10000, so we limit the count to that
//
void calc_ch1() 
{
 if( ch1_count < 10000 )
   ch1_count++;
}
void calc_ch2() 
{
 if( ch2_count < 10000 )
   ch2_count++;
} 
void calc_ch3() 
{
 if( ch3_count < 10000 )
   ch3_count++;
} 

unsigned long previousMillis = 0;    // will store last time pins were checked
const long interval = 1000;          // interval at which to check pin counts (milliseconds)

void loop()
{
 // use millis() time to check pin counts every 'interval'
 // - to see how this works, check out the example: 
 //     https://www.arduino.cc/en/Tutorial/BlinkWithoutDelay
 //
 unsigned long currentMillis = millis();

 if (currentMillis - previousMillis >= interval) {
   previousMillis = currentMillis;

   // get the counts from the interrupt routines and then reset them to zero
   noInterrupts();
     Aile= ch1_count;
     Ele= ch2_count;
     Rud= ch3_count;

     ch1_count= 0;
     ch2_count= 0;
     ch3_count= 0;
   interrupts();

   Serial.print("CH1:"); Serial.println(Aile);
   Serial.print("CH2:"); Serial.println(Ele);
   Serial.print("CH3:"); Serial.println(Rud);

   // Example test for input 'goodness':
   Serial.print("CH1 is ");
   if( Aile > 10 )
   {
      Serial.println("GOOD");
   }else{
      Serial.println("**BAD**");
   }

 }
}

Add in those variables, replace the calc_ch1(), calc_ch2() and calc_ch3() functions and replace loop()
The rest of your original sketch can stay as it is.

The timer 'interval' is currently set to 1000mS so the printout is slow.
This probably needs to be faster, say, 100mS.
See what the numbers are and , if you can, try moving the transmitter almost out of range and see when the numbers start reduce. You can then decide what number to check GOOD vs BAD with.

Yours,
TonyWilk

I would do this by getting the ISR to update a variable with the latest value of micros() -
for example

myISR () {
   latestISRmicros = micros();
   // other ISR code
}

and then in loop() have a check like this

noInterrupts();
  isrCheckMicros = latestISRmicros;
interrupts();
if (micros() - isrCheckMicros >= timeoutMicros) {
   // there has been no signal
}

...R

Robin2:
I would do this by getting the ISR to update a variable with the latest value of micros() -
snip

Yes, giving you a "no signal at all for 'timeoutMicros' ".

I was going for a measurement of "amount of signal during a period".

I don't know which is the more useful approach in this instance.

IF the RC link slowly degrades and loses more and more servo pulse periods, then detecting a threshold of "quality of service" might be useful. However, I have no experience with RC to know if this happens or not, or even if the servo pulses go 'quiet' when signal is lost.

Yours,
TonyWilk

P.S. well, no RC experience this century I should've said :slight_smile:

TonyWilk:
P.S. well, no RC experience this century I should've said :slight_smile:

Likewise.

Using a pair of nRF24L01+ modules for wireless control is a great deal simpler than detecting pulses intended for servos and has a lot more capability.

The systems I have built send a message about 10 times per second and if there are 10 consecutive missed messages the receiver shuts down the model train.

...R

Thank you both.

I'll give it a whirl later today.

Cheers Woody

@Tony,

Here is the sketch and the result of your code.

Many thanks.

#include <EnableInterrupt.h>

#define SERIAL_PORT_SPEED 115200
#define RC_NUM_CHANNELS  3

#define RC_CH1  0
#define RC_CH2  1
#define RC_CH3  2
#define RC_CH1_INPUT  2 // Pin number
#define RC_CH2_INPUT  3
#define RC_CH3_INPUT  4

word CH1;
word CH2;
word CH3;

word Aile;// Aileron
word Ele; // Elevator
word Rud; // Rudder


uint16_t rc_values[RC_NUM_CHANNELS];
uint32_t rc_start[RC_NUM_CHANNELS];
volatile uint16_t rc_shared[RC_NUM_CHANNELS];

// Variables to count on each interrupt
//
volatile word ch1_count = 0;
volatile word ch2_count = 0;
volatile word ch3_count = 0;

void rc_read_values() {
  // read the values out of the shared, volatile array (which can change at any time)
  // and into an array that we only will only change when we call this function

  noInterrupts();
  memcpy(rc_values, (const void *)rc_shared, sizeof(rc_shared));
  interrupts();
}

void calc_input(uint8_t channel, uint8_t input_pin) {
  // This is the interrupt.  If the pin is high, we record the start
  // time of the pulse.  If the pin is low, we compare the start time
  // to the current time in order to get the length of the pulse.
  // Transmitter Signal Range is ~ 1000 - 2000   microseconds with 1500 being neutral & 20 milliseconds between each transmission pulse.
  
  if (digitalRead(input_pin) == HIGH) {
    rc_start[channel] = micros();
    rc_start[0] = micros();
    rc_start[1] = micros();
    rc_start[2] = micros();
  } else {
    uint16_t rc_compare = (uint16_t)(micros() - rc_start[channel]);
    rc_shared[channel] = rc_compare;
  }
}
// Interrupt routines to simply count number of pin changes
// - there should never be more than 10000, so we limit the count to that
//

void calc_ch1() {
  {
    calc_input(RC_CH1, RC_CH1_INPUT);
  }
  if ( ch1_count < 10000 )
    ch1_count++;
}
void calc_ch2() {
  {
    calc_input(RC_CH2, RC_CH2_INPUT);
  }
  if ( ch2_count < 10000 )
    ch2_count++;
}
void calc_ch3() {
  {
    calc_input(RC_CH3, RC_CH3_INPUT);
  }
  if ( ch3_count < 10000 )
    ch3_count++;
}
unsigned long previousMillis = 0;    // will store last time pins were checked
const long interval = 25;          // interval at which to check pin counts (milliseconds)

void setup() {
  Serial.begin(SERIAL_PORT_SPEED);

  pinMode(RC_CH1_INPUT, INPUT_PULLUP);
  pinMode(RC_CH2_INPUT, INPUT_PULLUP);
  pinMode(RC_CH3_INPUT, INPUT_PULLUP);

  // set up CHANGE interrupts, so that we get notified when
  // each signal goes high or low.
  enableInterrupt(RC_CH1_INPUT, calc_ch1, CHANGE);
  enableInterrupt(RC_CH2_INPUT, calc_ch2, CHANGE);
  enableInterrupt(RC_CH3_INPUT, calc_ch3, CHANGE);
}

void loop() {
  // use millis() time to check pin counts every 'interval'
  // - to see how this works, check out the example:
  //     https://www.arduino.cc/en/Tutorial/BlinkWithoutDelay
  //
  unsigned long currentMillis = millis();

  if (currentMillis - previousMillis >= interval) {
    previousMillis = currentMillis;
  }
  // get the counts from the interrupt routines and then reset them to zero
  noInterrupts();
  CH1 = ch1_count;
  CH2 = ch2_count;
  CH3 = ch3_count;
  Serial.print("CH1 count:"); Serial.print(ch1_count); Serial.print(": :");// TEMP ADDITION TO CHECK OUTPUT OF VALUE CHANGE AGAINST TIME INTERVAL REDUCTION.
  Serial.print("CH2 count:"); Serial.print(ch2_count); Serial.print(": :");// TIME INTERVAL CURRENTLY SET @ 25 ms ... GIVES A COUNT BETWEEN 2 AND 4.
  Serial.print("CH3 count:"); Serial.print(ch3_count); Serial.print(": :");
  ch1_count = 0;
  ch2_count = 0;
  ch3_count = 0;
  interrupts();

  rc_read_values();
  if ( CH1 == 0 || CH2 == 0 || CH3 == 0 ) {

    Aile = 1500;//NEUTRAL or OFF SIGNAL
    Ele = 1500;
    Rud = 1500;
  }
  else {

    Aile = (rc_values[RC_CH1]);
    Ele = (rc_values[RC_CH2]);
    Rud = (rc_values[RC_CH3]);
  }
  Serial.print("CH1 Aile:"); Serial.print(Aile); Serial.print(": :");
  Serial.print("CH2 Ele:"); Serial.print(Ele); Serial.print(": :");
  Serial.print("CH3 Rud:"); Serial.print(Rud); Serial.print(": :");


  // Example test for input 'goodness':
  Serial.print("Signal is ");
  if ( CH1 == 0 || CH2 == 0 || CH3 == 0 )
  {
    Serial.println("**MISSING**");
  } else {
    Serial.println("PRESENT");
  }
  delay(20);  // just so serial log doesn't flood
}

woodygb:
Here is the sketch and the result of your code.

Good to see you got it done !

Had a look at your code and noticed the Serial.print()'s inside the noInterrupts() section. Although it might work in this case, it is bad practice to leave interrupts turned off for longer than necessary, and because Serial.print() actually needs interrupts back on to work.

So I'd suggest moving the print statements like this:

  // get the counts from the interrupt routines and then reset them to zero
  noInterrupts();
  CH1 = ch1_count;
  CH2 = ch2_count;
  CH3 = ch3_count;
  ch1_count = 0;
  ch2_count = 0;
  ch3_count = 0;
  interrupts();

  Serial.print("CH1 count:"); Serial.print( CH1 ); Serial.print(": :");// TEMP ADDITION TO CHECK OUTPUT OF VALUE CHANGE AGAINST TIME INTERVAL REDUCTION.
  Serial.print("CH2 count:"); Serial.print( CH2 ); Serial.print(": :");// TIME INTERVAL CURRENTLY SET @ 25 ms ... GIVES A COUNT BETWEEN 2 AND 4.
  Serial.print("CH3 count:"); Serial.print( CH3 ); Serial.print(": :");

Yours,
TonyWilk

Hi Tony,

A bit more investigation / "playing" shows that a proportion of your code is redundant to this sketch.

EDIT:- It would seem that the Delay at the end of the sketch is now my Time constant.... MORE thought and playing is obviously required.

#include <EnableInterrupt.h>

#define SERIAL_PORT_SPEED 115200
#define RC_NUM_CHANNELS  3

#define RC_CH1  0
#define RC_CH2  1
#define RC_CH3  2
#define RC_CH1_INPUT  2 // Pin number
#define RC_CH2_INPUT  3
#define RC_CH3_INPUT  4

word CH1;
word CH2;
word CH3;

word Aile;// Aileron
word Ele; // Elevator
word Rud; // Rudder


uint16_t rc_values[RC_NUM_CHANNELS];
uint32_t rc_start[RC_NUM_CHANNELS];
volatile uint16_t rc_shared[RC_NUM_CHANNELS];

// Variables to count on each interrupt
//
volatile word ch1_count = 0;
volatile word ch2_count = 0;
volatile word ch3_count = 0;

void rc_read_values() {
  // read the values out of the shared, volatile array (which can change at any time)
  // and into an array that we only will only change when we call this function

  noInterrupts();
  memcpy(rc_values, (const void *)rc_shared, sizeof(rc_shared));
  interrupts();
}

void calc_input(uint8_t channel, uint8_t input_pin) {
  // This is the interrupt.  If the pin is high, we record the start
  // time of the pulse.  If the pin is low, we compare the start time
  // to the current time in order to get the length of the pulse.
  // Transmitter Signal Range is ~ 1000 - 2000   microseconds with 1500 being neutral & 20 milliseconds between each transmission pulse.
  
  if (digitalRead(input_pin) == HIGH) {
    rc_start[channel] = micros();
    rc_start[0] = micros();
    rc_start[1] = micros();
    rc_start[2] = micros();
  } else {
    uint16_t rc_compare = (uint16_t)(micros() - rc_start[channel]);
    rc_shared[channel] = rc_compare;
  }
}
// Interrupt routines to simply count number of pin changes
// - there should never be more than 10, so we limit the count to that
//

void calc_ch1() {
  {
    calc_input(RC_CH1, RC_CH1_INPUT);
  }
  if ( ch1_count < 10)
    ch1_count++;
}
void calc_ch2() {
  {
    calc_input(RC_CH2, RC_CH2_INPUT);
  }
  if ( ch2_count < 10)
    ch2_count++;
}
void calc_ch3() {
  {
    calc_input(RC_CH3, RC_CH3_INPUT);
  }
  if ( ch3_count < 10)
    ch3_count++;
}

void setup() {
  Serial.begin(SERIAL_PORT_SPEED);

  pinMode(RC_CH1_INPUT, INPUT_PULLUP);
  pinMode(RC_CH2_INPUT, INPUT_PULLUP);
  pinMode(RC_CH3_INPUT, INPUT_PULLUP);

  // set up CHANGE interrupts, so that we get notified when
  // each signal goes high or low.
  enableInterrupt(RC_CH1_INPUT, calc_ch1, CHANGE);
  enableInterrupt(RC_CH2_INPUT, calc_ch2, CHANGE);
  enableInterrupt(RC_CH3_INPUT, calc_ch3, CHANGE);
}

void loop() {
  // use millis() time to check pin counts every 'interval'
  // - to see how this works, check out the example:
  //     https://www.arduino.cc/en/Tutorial/BlinkWithoutDelay
  // get the counts from the interrupt routines and then reset them to zero
  noInterrupts();
  CH1 = ch1_count;
  CH2 = ch2_count;
  CH3 = ch3_count;
  // Moved (-:
  ch1_count = 0;
  ch2_count = 0;
  ch3_count = 0;
  interrupts();
  Serial.print("CH1 count:"); Serial.print( CH1 ); Serial.print(": :");
  Serial.print("CH2 count:"); Serial.print( CH2 ); Serial.print(": :");
  Serial.print("CH3 count:"); Serial.print( CH3 ); Serial.print(": :");
  rc_read_values();
  if ( CH1 == 0 || CH2 == 0 || CH3 == 0 ) {

    Aile = 1500;//NEUTRAL or OFF SIGNAL
    Ele = 1500;
    Rud = 1500;
  }
  else {

    Aile = (rc_values[RC_CH1]);
    Ele = (rc_values[RC_CH2]);
    Rud = (rc_values[RC_CH3]);
  }
  Serial.print("CH1 Aile:"); Serial.print(Aile); Serial.print(": :");
  Serial.print("CH2 Ele:"); Serial.print(Ele); Serial.print(": :");
  Serial.print("CH3 Rud:"); Serial.print(Rud); Serial.print(": :");


  // Example test for input 'goodness':
  Serial.print("Signal is ");
  if ( CH1 == 0 || CH2 == 0 || CH3 == 0 )
  {
    Serial.println("**MISSING**");
  } else {
    Serial.println("PRESENT");
  }
  delay(20);  // just so serial log doesn't flood
}

Cheers Woody