Using all 24 pin change interrupts on an arduino mega for reading PWM

I’m making a quadcopter with redundant flight control systems. The quad has 8 motors (2 on each arm) and 3 flight controllers. I’m trying to use the Arduino mega as a “voter” to determine if the flight controllers agree and which signals to send to the motors. Therefore, I have to interpret 24 PWM signals.

I found this code which uses pin change interrupts to interpret and print 4 PWM signals from a receiver: Reading PWM Receiver Signal Using Arduino with Interrupt - Part 2 - YouTube

and I extended it to make this:

The code uses pin change interrupts, and I need all 24, but pin change interrupt pins 11-15 don’t correspond to any pins on the Arduino (based on the pinout diagram for the ATmega2560); they’re pins on the microcontroller that don’t have any breakout points. I solved that problem by connecting the individual microcontroller pins to some unused digital pins’ breakout points and scraping away the traces to those breakout points.

It seems to work fine when I remove channels 16-24, but the data are just crazy when I try to use all the channels. All the pulse lengths should be about 1000us when the motors aren’t spinning (2000us for 100% rpm), but when the quad is disarmed, these are the data I’m getting:


(In the image, channel 9 has a bad connection and that's why it's all zeroes)

The project "Read PWM, Decode RC Receiver Input, and Apply Fail-Safe" by Joop Brokking indicates that the ISRs take more time when you use more channels. The trend he described suggests that each ISR would take around 31us when using all 8 channels. Is that too much?

I suspect that the Arduino just isn’t fast enough to be up to the task, but I can’t figure out how to prove that, and I’m really at a loss for ideas.

If anyone has any insight into my problem it would be greatly appreciated. I’m a high school student working on a difficult science fair project.

At a quick glance, the interrupt service routines look quite "heavy". What level of accuracy do you require in the delivered result ?
What frequency is the PWM signal ? Around 50Hz ?

Your code:

unsigned long timer[25];
byte last_channel[24];
int input[24];


void setup() {
  PCICR |= (1 << PCIE0);
  PCICR |= (1 << PCIE1);
  PCICR |= (1 << PCIE2);
  PCMSK0 |= (1 << PCINT0);//pin 53 - B0 - Rx Channel 1
  PCMSK0 |= (1 << PCINT1);//pin 52 - B1 - Rx Channel 2
  PCMSK0 |= (1 << PCINT2);//pin 51 - B2 - Rx Channel 3
  PCMSK0 |= (1 << PCINT3);//pin 50 - B3 - Rx Channel 4
  PCMSK0 |= (1 << PCINT4);//pin 10 - B4 - Rx Channel 5
  PCMSK0 |= (1 << PCINT5);//pin 11 - B5 - Rx Channel 6
  PCMSK0 |= (1 << PCINT6);//pin 12 - B6 - Rx Channel 7
  PCMSK0 |= (1 << PCINT7);//pin 13 - B7 - Rx Channel 8

  PCMSK1 |= (1 << PCINT8);//pin 0  - E0 - Rx Channel 9
  PCMSK1 |= (1 << PCINT9);//pin 15 - J0 - Rx Channel 10
  PCMSK1 |= (1 << PCINT10);//pin 14 - J1 - Rx Channel 11
  PCMSK1 |= (1 << PCINT11);//pin 22 - J6 - Rx Channel 12
  PCMSK1 |= (1 << PCINT12);//pin 24 - J5 - Rx Channel 13
  PCMSK1 |= (1 << PCINT13);//pin 26 - J4 - Rx Channel 14
  PCMSK1 |= (1 << PCINT14);//pin 28 - J3 - Rx Channel 15
  PCMSK1 |= (1 << PCINT15);//pin 30 - J2 - Rx Channel 16

  PCMSK2 |= (1 << PCINT16);//pin A8 - K0 - Rx Channel 17
  PCMSK2 |= (1 << PCINT17);//pin A9 - K1 - Rx Channel 18
  PCMSK2 |= (1 << PCINT18);//pin A10 - K2 - Rx Channel 19
  PCMSK2 |= (1 << PCINT19);//pin A11 - K3 - Rx Channel 20
  PCMSK2 |= (1 << PCINT20);//pin A12 - K4 - Rx Channel 21
  PCMSK2 |= (1 << PCINT21);//pin A13 - K5 - Rx Channel 22
  PCMSK2 |= (1 << PCINT22);//pin A14 - K6 - Rx Channel 23
  PCMSK2 |= (1 << PCINT23);//pin A15 - K7 - Rx Channel 24
  Serial.begin(9600);
}

void loop() {
  for (int i = 0; i < 24; i = i + 1) {
    Serial.print(input[i]);
    Serial.print(" - ");
  }
  Serial.println();
}


ISR(PCINT0_vect) {
  timer[0] = micros();
  // channel 1 ---------------
  if (last_channel[0] == 0 && PINB & B00000001 ) {
    last_channel[0] = 1;
    timer[1] = timer[0];
  }
  else if (last_channel[0] == 1 && !(PINB & B00000001) ) {
    last_channel[0] = 0;
    input[0] = timer[0] - timer[1];
  }

  // channel 2 ---------------
  if (last_channel[1] == 0 && PINB & B00000010 ) {
    last_channel[1] = 1;
    timer[2] = timer[0];
  }
  else if (last_channel[1] == 1 && !(PINB & B00000010) ) {
    last_channel[1] = 0;
    input[1] = timer[0] - timer[2];
  }

  // channel 3 ---------------
  if (last_channel[2] == 0 && PINB & B00000100 ) {
    last_channel[2] = 1;
    timer[3] = timer[0];
  }
  else if (last_channel[2] == 1 && !(PINB & B00000100) ) {
    last_channel[2] = 0;
    input[2] = timer[0] - timer[3];
  }

  // channel 4 ---------------
  if (last_channel[3] == 0 && PINB & B00001000 ) {
    last_channel[3] = 1;
    timer[4] = timer[0];
  }
  else if (last_channel[3] == 1 && !(PINB & B00001000) ) {
    last_channel[3] = 0;
    input[3] = timer[0] - timer[4];
  }

  // channel 5 ---------------
  if (last_channel[4] == 0 && PINB & B00010000 ) {
    last_channel[4] = 1;
    timer[5] = timer[0];
  }
  else if (last_channel[4] == 1 && !(PINB & B00010000) ) {
    last_channel[4] = 0;
    input[4] = timer[0] - timer[5];
  }

  // channel 6 ---------------
  if (last_channel[5] == 0 && PINB & B00100000 ) {
    last_channel[5] = 1;
    timer[6] = timer[0];
  }
  else if (last_channel[5] == 1 && !(PINB & B00100000) ) {
    last_channel[5] = 0;
    input[5] = timer[0] - timer[6];
  }

  // channel 7 ---------------
  if (last_channel[6] == 0 && PINB & B01000000 ) {
    last_channel[6] = 1;
    timer[7] = timer[0];
  }
  else if (last_channel[6] == 1 && !(PINB & B01000000) ) {
    last_channel[6] = 0;
    input[6] = timer[0] - timer[7];
  }

  // channel 8 ---------------
  if (last_channel[7] == 0 && PINB & B10000000 ) {
    last_channel[7] = 1;
    timer[8] = timer[0];
  }
  else if (last_channel[7] == 1 && !(PINB & B10000000) ) {
    last_channel[7] = 0;
    input[7] = timer[0] - timer[8];
  }
}


ISR(PCINT1_vect) {
  timer[0] = micros();
  // channel 9 ---------------
  if (last_channel[8] == 0 && PINE & B00000001 ) {
    last_channel[8] = 1;
    timer[9] = timer[0];
  }
  else if (last_channel[8] == 1 && !(PINE & B00000001) ) {
    last_channel[8] = 0;
    input[8] = timer[0] - timer[9];
  }

  // channel 10 ---------------
  if (last_channel[9] == 0 && PINJ & B00000001 ) {
    last_channel[9] = 1;
    timer[10] = timer[0];
  }
  else if (last_channel[9] == 1 && !(PINJ & B00000001) ) {
    last_channel[9] = 0;
    input[9] = timer[0] - timer[10];
  }

  // channel 11 ---------------
  if (last_channel[10] == 0 && PINJ & B00000010 ) {
    last_channel[10] = 1;
    timer[11] = timer[0];
  }
  else if (last_channel[10] == 1 && !(PINJ & B00000010) ) {
    last_channel[10] = 0;
    input[10] = timer[0] - timer[11];
  }

  // channel 12 ---------------
  if (last_channel[11] == 0 && PINJ & B01000000 ) {
    last_channel[11] = 1;
    timer[12] = timer[0];
  }
  else if (last_channel[11] == 1 && !(PINJ & B01000000) ) {
    last_channel[11] = 0;
    input[11] = timer[0] - timer[12];
  }

  // channel 13 ---------------
  if (last_channel[12] == 0 && PINJ & B00100000 ) {
    last_channel[12] = 1;
    timer[13] = timer[0];
  }
  else if (last_channel[12] == 1 && !(PINJ & B00100000) ) {
    last_channel[12] = 0;
    input[12] = timer[0] - timer[13];
  }

  // channel 14 ---------------
  if (last_channel[13] == 0 && PINJ & B00010000 ) {
    last_channel[13] = 1;
    timer[14] = timer[0];
  }
  else if (last_channel[13] == 1 && !(PINJ & B00010000) ) {
    last_channel[13] = 0;
    input[13] = timer[0] - timer[14];
  }

  // channel 15 ---------------
  if (last_channel[14] == 0 && PINJ & B00001000 ) {
    last_channel[14] = 1;
    timer[15] = timer[0];
  }
  else if (last_channel[14] == 1 && !(PINJ & B00001000) ) {
    last_channel[14] = 0;
    input[14] = timer[0] - timer[15];
  }

  // channel 16 ---------------
  if (last_channel[15] == 0 && PINJ & B00000100 ) {
    last_channel[15] = 1;
    timer[16] = timer[0];
  }
  else if (last_channel[15] == 1 && !(PINJ & B00000100) ) {
    last_channel[15] = 0;
    input[15] = timer[0] - timer[16];
  }
}


ISR(PCINT2_vect) {
  timer[0] = micros();
  // channel 17 ---------------
  if (last_channel[16] == 0 && PINK & B00000001 ) {
    last_channel[16] = 1;
    timer[17] = timer[0];
  }
  else if (last_channel[16] == 1 && !(PINK & B00000001) ) {
    last_channel[16] = 0;
    input[16] = timer[0] - timer[17];
  }

  // channel 18 ---------------
  if (last_channel[17] == 0 && PINK & B00000010 ) {
    last_channel[17] = 1;
    timer[18] = timer[0];
  }
  else if (last_channel[17] == 1 && !(PINK & B00000010) ) {
    last_channel[17] = 0;
    input[17] = timer[0] - timer[18];
  }

  // channel 19 ---------------
  if (last_channel[18] == 0 && PINK & B00000100 ) {
    last_channel[18] = 1;
    timer[19] = timer[0];
  }
  else if (last_channel[18] == 1 && !(PINK & B00000100) ) {
    last_channel[18] = 0;
    input[18] = timer[0] - timer[19];
  }

  // channel 20 ---------------
  if (last_channel[19] == 0 && PINK & B00001000 ) {
    last_channel[19] = 1;
    timer[20] = timer[0];
  }
  else if (last_channel[19] == 1 && !(PINK & B00001000) ) {
    last_channel[19] = 0;
    input[19] = timer[0] - timer[20];
  }

  // channel 21 ---------------
  if (last_channel[20] == 0 && PINK & B00010000 ) {
    last_channel[20] = 1;
    timer[21] = timer[0];
  }
  else if (last_channel[20] == 1 && !(PINK & B00010000) ) {
    last_channel[20] = 0;
    input[20] = timer[0] - timer[21];
  }

  // channel 22 ---------------
  if (last_channel[21] == 0 && PINK & B00100000 ) {
    last_channel[21] = 1;
    timer[22] = timer[0];
  }
  else if (last_channel[21] == 1 && !(PINK & B00100000) ) {
    last_channel[21] = 0;
    input[21] = timer[0] - timer[22];
  }

  // channel 23 ---------------
  if (last_channel[22] == 0 && PINK & B01000000 ) {
    last_channel[22] = 1;
    timer[23] = timer[0];
  }
  else if (last_channel[22] == 1 && !(PINK & B01000000) ) {
    last_channel[22] = 0;
    input[22] = timer[0] - timer[23];
  }

  // channel 24 ---------------
  if (last_channel[23] == 0 && PINK & B10000000 ) {
    last_channel[23] = 1;
    timer[24] = timer[0];
  }
  else if (last_channel[23] == 1 && !(PINK & B10000000) ) {
    last_channel[23] = 0;
    input[23] = timer[0] - timer[24];
  }
}

The frequency is 50hz and I think an accuracy of about 4us seems reasonable -- it doesn't need to be perfect because the Arduino's job is just to determine if the signals are diverging too much.

Also, I just graphed the seemingly random data I posted and found some patterns that may or may not be at all significant: PWM data graphed - Google Sheets

At least in the first 8 channels, the trend in the data appears to be a somewhat regular triangle (maybe somewhat sawtooth) wave. I think that this is a symptom of the error accumulating. I think that the Arduino is getting further and further behind the point at which the signal goes low until it misses that pulse's fall and triggers after the next pulse's fall.

Is this evidence that the Arduino isn't fast enough for the job? What alternatives might I be able to implement if I can't use a Mega?

Your printing is relatively slow and not synchronized with anything. That is, you are seeing snapshots at loop speed and the loop is being constantly interrupted by the incoming data. Further, as far as I see, you are not clearing out the old timer values so, if a channel does not receive new data, you will continue to display the old data.

However, you also have the potential problem that you are reading 4 byte timers which may be updated during the time you are reading them. The Mega is an 8 bit processor so its “atomicity” is units of one byte. The usual solution is to suspend interrupts, make a copy of the variable, restore interrupts, then process, in your case print, the variable. Here, you have to be careful not to suspend them for too long otherwise you will miss seeing changes in the data.

If you do start altering the timer variables in the loop(), say to reset them, you will also have to declare these as volatile.

A 32bit processor may be better in this case, however, it is not clear that the problem is also one of processor capacity.

This is all very good advice and I really appreciate it. I'm new to Arduino coding so my implementation of your ideas might be terrible. Anyway, tried to implement all of your suggestions in this code:

another_try_at_printing_24_channels.ino (9.4 KB)

I couldn't figure out a way to suspend the interrupts, but I made them conditionally not do anything depending on the value of the variable "enableinterrupts." I don't know if that achieves the effect you were intending.

Writing the lines:

PCICR |= (0 << PCIE0);
PCICR |= (0 << PCIE0);
PCICR |= (0 << PCIE0);

didn't disable the interrupts either.

Sorry for the late response I was camping for the last few days.