4

I am designing my own quadcopter control algorithm, whereby I currently read 4 RC receiver channels using PulseIn on each loop in the following manner:

ch1_raw = pulseIn(rcPin1, HIGH, 25000);

In other words, ch1_raw contains the length of a HIGH pulse in microseconds.

I now want to avoid PulseIn in the interest of speed and instead achieve the same using interrupt.

I have looked at http://playground.arduino.cc/Main/PinChangeIntExample and I am aware that other similar questions have been asked (e.g. How to read RC receiver channels without using pulsein() command with arduino and Is PulseIn fast enough for Quadcopter?), but I don't understand how to implement interrupt to give me the correct ch1_raw value on each loop.

Can somebody help me? (I'm new to Arduino, so go easy on me please!) Thanks!


Edit: So far I have tried to implement the following:

#include <PinChangeInt.h>

unsigned long pulsestart, pulsewidth;

int rcPin1 = A0;

void setup(){

  pinMode(rcPin1, INPUT);

  Serial.begin(9600);

  PCintPort::attachInterrupt(rcPin1, counttime,RISING); 
  PCintPort::attachInterrupt(rcPin1, subtracttime,FALLING);

}


void loop(){

  Serial.print("  pulsewidth: ");
  Serial.println(pulsewidth);

}


void counttime()
{
  pulsestart = micros();
}

void subtracttime()
{
  pulsewidth = micros()- pulsestart;
}

Edit 2: (Somewhat) successful implementation so far, using EnableInterrupt.h instead. It seems that FALLING is being detected, but not RISING.

#include <EnableInterrupt.h>

#define rcPin1 10

volatile uint16_t pulsestart=0; 
volatile uint16_t pulsewidth=0;
//volatile unsigned long pulsestart=0; 
//volatile unsigned long pulsewidth=0;
volatile uint16_t risingcount=0;
volatile uint16_t fallingcount=0;

void counttime()
{
  pulsestart = micros();
  risingcount++;
}
void subtracttime()
{
  pulsewidth = micros()- pulsestart;
  fallingcount++;
}

void setup() {
  Serial.begin(9600);
  pinMode(rcPin1, INPUT_PULLUP);  
  enableInterrupt(rcPin1, counttime, RISING);
  enableInterrupt(rcPin1, subtracttime, FALLING);
}

void loop() {
  //delay(1000);
  Serial.print("risingcount: ");
  Serial.print(risingcount);  
  Serial.print(" fallingcount: ");
  Serial.println(fallingcount); 
  //Serial.print("pulsewidth: ");
  //Serial.println(pulsewidth);
}
  • For some reason, this code posted above does not work on the mega 2560 and only works on the uno. Please let me know if you can find out why. – Rohan Paleaj Jul 5 '16 at 20:01
5

Regarding the question, “Does PinChangeInt only work on digital pins?”, note that PCI's work on all the digital pins of an ATmega328, and on the first six analog pins. (32-pin '328s have eight analog pins, A0-A7; 28-pin '328s only six, A0-A5.)

Regarding the problem symptom that “pulsewidth just seems to increase continuously”, that could happen if for some reason rising edges aren't processed. To debug, add a couple more volative byte variables, say nrises and nfalls; increment each of them in the appropriate interrupt handler; and report their values from time to time.

Regarding your #include <PinChangeInt.h> statement, note that PinChangeInt.h is deprecated (as of 3 April 2015). Consider using EnableInterrupt.h instead.

It's possible you'll resolve your problem while changing libraries. If so, fine. If not, or if that library's performance isn't adequate, consider changing over to handling the pin change interrupts more directly. The ISR in the following sketch shown below runs several times faster than code that uses PinChangeInt or EnableInterrupt library calls.

[Edit: This edit encloses the ISR in a sketch that compiles. The code previously shown was missing two array length specifications. This code was tested with a rotary encoder attached to A2, A3, with A0 and A1 grounded; an output sample (using an Arduino Nano) follows the code.]

/*  rcTiming.ino -- JW, 30 November 2015 -- 
 * Uses pin-change interrupts on A0-A4 to time RC pulses
 *
 * Ref: http://arduino.stackexchange.com/questions/18183/read-rc-receiver-channels-using-interrupt-instead-of-pulsein
 *
 */
#include <Streaming.h>
static   byte rcOld;        // Prev. states of inputs
volatile unsigned long rcRises[4]; // times of prev. rising edges
volatile unsigned long rcTimes[4]; // recent pulse lengths
volatile unsigned int  rcChange=0; // Change-counter

// Be sure to call setup_rcTiming() from setup()
void setup_rcTiming() {
  rcOld = 0;
  pinMode(A0, INPUT);  // pin 14, A0, PC0, for pin-change interrupt
  pinMode(A1, INPUT);  // pin 15, A1, PC1, for pin-change interrupt
  pinMode(A2, INPUT);
  pinMode(A3, INPUT);
  PCMSK1 |= 0x0F;       // Four-bit mask for four channels
  PCIFR  |= 0x02;       // clear pin-change interrupts if any
  PCICR  |= 0x02;       // enable pin-change interrupts
}
// Define the service routine for PCI vector 1
ISR(PCINT1_vect) {
  byte rcNew = PINC & 15;   // Get low 4 bits, A0-A3
  byte changes = rcNew^rcOld;   // Notice changed bits
  byte channel = 0;
  unsigned long now = micros(); // micros() is ok in int routine
  while (changes) {
    if ((changes & 1)) {  // Did current channel change?
      if ((rcNew & (1<<channel))) { // Check rising edge
        rcRises[channel] = now;     // Is rising edge
      } else {              // Is falling edge
        rcTimes[channel] = now-rcRises[channel];
      }
    }
    changes >>= 1;      // shift out the done bit
    ++channel;
    ++rcChange;
  }
  rcOld = rcNew;        // Save new state
}

void setup() {
  Serial.begin(115200);
  Serial.println("Starting RC Timing Test");
  setup_rcTiming();
}

void loop() {
  unsigned long rcT[4]; // copy of recent pulse lengths
  unsigned int rcN;
  if (rcChange) {

    // Data is subject to races if interrupted, so off interrupts
    cli();          // Disable interrupts
    rcN = rcChange;
    rcChange = 0;       // Zero the change counter
    rcT[0] = rcTimes[0];
    rcT[1] = rcTimes[1];
    rcT[2] = rcTimes[2];
    rcT[3] = rcTimes[3];
    sei();          // reenable interrupts

    Serial << "t=" << millis() << " " << rcT[0] << " " << rcT[1]
       << " " << rcT[2] << " " << rcT[3] << " " << rcN << endl;
  }
  sei();            // reenable interrupts
}

The code shown above uses Streaming.h, a contributed library that adds some “syntactic sugar” to Arduino C. That is, at compile time it converts C++-like << Serial stream operators to Serial.print statements, without increasing code size. If you don't have Streaming.h installed, either install it via Streaming5.zip from arduiniana.org or manually translate the Serial << ... statement to Serial.print statements.

A sample of the sketch's output on serial monitor:

t=35773 0 0 14196 13456 4
t=35775 0 0 14196 13456 3
t=35778 0 0 14196 5748 4
t=35781 0 0 5244 5748 3
t=35783 0 0 5244 5748 4
t=35785 0 0 5244 5748 3
t=35786 0 0 5244 28 12
t=35788 0 0 3024 28 3
t=35789 0 0 3024 28 4
t=35792 0 0 3024 28 3
t=35796 0 0 3024 6396 4
t=35802 0 0 9316 6396 3
t=35809 0 0 9316 6396 4

In the output above, the two 0's are from A0, A1 being grounded and not changing. The next two numbers are from the DT and CLK lines of a KY-040 rotary encoder, which toggle alternately to encode step-direction. They should represent on-times in microseconds, and are always multiples of 4 because micros() reports multiples of 4.

The last number is the number of times the ISR's change-checking loop ran since loop()'s previous print. An A2 change will show a count of 3 or more, and an A3 change 4 or more. Cases where times are the same may represent contact bounces shorter than the ISR's service time; ie, several falling edges in a row might be detected, or several rising edges. A storage scope or logic analyzer probably is necessary for finding out exactly what happens.

  • Many thanks! So I have implemented nrises and nfalls as you suggested; and I have also tried to run the code here: playground.arduino.cc/Main/PinChangeIntExample - I have tried both on several different pins. The result is always the same: nrises and nfalls stay on 0, i.e. it seems that indeed the rising/falling edges are not being processed. – RLJ Nov 30 '15 at 14:13
  • I have also tried the code you provide above, which looks great for my application, since speed is of the essence. I am having some trouble implementing it though. Right now, I am getting a compiling error on "rcRises[channel] = now;": "invalid types 'volatile long unsigned int[byte]' for array subscript – RLJ Nov 30 '15 at 14:15
  • I have now been somewhat successful using EnableInterrupt.h instead (see edit 2 above). When attaching a simple switch between the rcPin1 and ground, I have managed to narrow down the problem to FALLING being detected (fallingcount increases each time I press/depress the switch) but not RISING (rising count stays at 0). This also occurs when attaching the RC receiver instead of the switch. – RLJ Nov 30 '15 at 15:47
  • Does anybody know why this would be? and how I can resolve this issue? Thanks! – RLJ Nov 30 '15 at 18:02
  • 2
    Brilliant! It works perfectly, and very fast. I am now able to run my complete quadcopter control loop (including inputs from gyro and accelerometer, outputs to ESCs, etc. not shown here) at ~200 Hz, whereas before (using PulseIn) the fastest I could achieve was ~12 Hz. BIG difference! Thanks so much. – RLJ Nov 30 '15 at 23:28
3

Interrupts don't obey, or even understand the concept of, the "loop" paradigm.

An interrupt fires when it needs to fire, and if you're reading a PWM signal then that means it's firing hundreds of times a second.

As long as your interrupt is updating a variable that can be read from within the loop, and you ensure that the variable doesn't get updated during your reading of it (Google: Critical Sections) then you will always have the most up to date value at your fingertips whenever you need it.

As for the interrupt itself, that's very straight forward - you just need to recognise if it's a rising or falling edge:

  • If rising edge: record the timestamp (micros()).
  • If falling edge: Subtract the recorded timestamp from the current micros() and store in the global variable.
  • 1
    Actually the repetition rate for a channel is traditionally around 50hz, meaning the loop will probably be running faster than the data source. However the channels are usually time multiplexed, so if downstream of a demux reading individual servo leads, we might say time striped. – Chris Stratton Nov 27 '15 at 16:34
  • 1
    50 Hz = 100 edges per second. That counts as hundreds to me ;) – Majenko Nov 27 '15 at 16:35
  • Many thanks! That's a lot more clear already. I have edited my question to show what I have tried to implement so far. However, it is not quite right yet. "pulsewidth" just seems to increase continuously and does not respond to any change in RC transmitter input. Can you see what I have done wrong? – RLJ Nov 27 '15 at 21:26
  • Pin A0 is an analogue pin. Does PinChangeInt only work on digital pins? – RLJ Nov 27 '15 at 21:30
  • Make your interrupt variables volatile. – Majenko Nov 27 '15 at 21:34

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.