Skip to main content
Arduino

Return to Answer

+ example code.
Source Link
Edgar Bonet
  • 45.1k
  • 4
  • 42
  • 81

Edit: Here is what I had in mind when suggesting the use of the pin change interrupt. Notice that the pin numbers are "hardwired" into the code and cannot be changed easily. This is because:

  1. Pins 2 – 7 share a single pin change interrupt request (PCINT2_vect), thus a single ISR is needed to time the six channels.
  2. They also share a port (port D), meaning they can all be read by the ISR in a single instruction.

The other possible choices having these nice properties would be pins 8 – 13 (PCINT0_vect, port B) or A0 – A5 (PCINT1_vect, port C).

/*
 * Read 6 PWM signals from an RC receiver.
 *
 * Channels 0-5 are on digital pins 2-7 (i.e. PD2-PD7 = PCINT18-23).
 */
// This always contains the last length measured on each channel.
volatile uint16_t last_pulse_lengths[6];
// Interrupt triggered on every change in pins 2-7.
ISR(PCINT2_vect)
{
 static uint8_t last_pin_states;
 static uint16_t start_times[6]; // start time for each channel
 uint8_t pin_states = PIND >> 2; // shifted to bits 0-5
 uint16_t now = micros();
 // Check every changed bit of the port.
 for (int i = 0; i < 6; i++) {
 uint8_t mask = _BV(i);
 // Record start time on rising edge.
 if ((pin_states & mask) && !(last_pin_states & mask))
 start_times[i] = now;
 // Record pulse length on falling edge.
 if (!(pin_states & mask) && (last_pin_states & mask))
 last_pulse_lengths[i] = now - start_times[i];
 }
 last_pin_states = pin_states;
}
// Return the pulse lengths avoiding race conditions.
static void get_pulse_lengths(uint16_t lengths[6])
{
 for (int i = 0; i < 6; i++) {
 noInterrupts();
 uint16_t tmp = last_pulse_lengths[i];
 interrupts();
 lengths[i] = tmp;
 }
}
void setup()
{
 // Setup the pin change interrupt.
 PCIFR = _BV(PCIF2); // clear pin change interrupt flag
 PCICR = _BV(PCIE2); // enable pin change interrupt
 PCMSK2 = 0xfc; // sense changes on pins 2-7 (i.e. PD2-PD7)
 Serial.begin(9600);
}
void loop()
{
 // Report the pulse lengths on the serial console.
 uint16_t pulse_lengths[6];
 get_pulse_lengths(pulse_lengths);
 for (int i = 0; i < 6; i++) {
 Serial.print(pulse_lengths[i]);
 if (i < 5) Serial.print(", ");
 else Serial.println();
 }
 delay(1000);
}

Edit: Here is what I had in mind when suggesting the use of the pin change interrupt. Notice that the pin numbers are "hardwired" into the code and cannot be changed easily. This is because:

  1. Pins 2 – 7 share a single pin change interrupt request (PCINT2_vect), thus a single ISR is needed to time the six channels.
  2. They also share a port (port D), meaning they can all be read by the ISR in a single instruction.

The other possible choices having these nice properties would be pins 8 – 13 (PCINT0_vect, port B) or A0 – A5 (PCINT1_vect, port C).

/*
 * Read 6 PWM signals from an RC receiver.
 *
 * Channels 0-5 are on digital pins 2-7 (i.e. PD2-PD7 = PCINT18-23).
 */
// This always contains the last length measured on each channel.
volatile uint16_t last_pulse_lengths[6];
// Interrupt triggered on every change in pins 2-7.
ISR(PCINT2_vect)
{
 static uint8_t last_pin_states;
 static uint16_t start_times[6]; // start time for each channel
 uint8_t pin_states = PIND >> 2; // shifted to bits 0-5
 uint16_t now = micros();
 // Check every changed bit of the port.
 for (int i = 0; i < 6; i++) {
 uint8_t mask = _BV(i);
 // Record start time on rising edge.
 if ((pin_states & mask) && !(last_pin_states & mask))
 start_times[i] = now;
 // Record pulse length on falling edge.
 if (!(pin_states & mask) && (last_pin_states & mask))
 last_pulse_lengths[i] = now - start_times[i];
 }
 last_pin_states = pin_states;
}
// Return the pulse lengths avoiding race conditions.
static void get_pulse_lengths(uint16_t lengths[6])
{
 for (int i = 0; i < 6; i++) {
 noInterrupts();
 uint16_t tmp = last_pulse_lengths[i];
 interrupts();
 lengths[i] = tmp;
 }
}
void setup()
{
 // Setup the pin change interrupt.
 PCIFR = _BV(PCIF2); // clear pin change interrupt flag
 PCICR = _BV(PCIE2); // enable pin change interrupt
 PCMSK2 = 0xfc; // sense changes on pins 2-7 (i.e. PD2-PD7)
 Serial.begin(9600);
}
void loop()
{
 // Report the pulse lengths on the serial console.
 uint16_t pulse_lengths[6];
 get_pulse_lengths(pulse_lengths);
 for (int i = 0; i < 6; i++) {
 Serial.print(pulse_lengths[i]);
 if (i < 5) Serial.print(", ");
 else Serial.println();
 }
 delay(1000);
}
Source Link
Edgar Bonet
  • 45.1k
  • 4
  • 42
  • 81

pulseIn() is a blocking function: it waits until it gets a complete pulse, or until it times out. While it's waiting, the Arduino cannot do anything else, and it will miss pulses on other channels.

The RC receiver sends the pulses sequentially. Then, if you try to read them in the right sequence, it may work. If you read them out of sequence, you will miss some pulses, and the corresponding pulseIn() calls can timeout, returning zeros.

For this kind of job I would use the pin change interrupt on pins 2 – 7. This should allow reading the six pulses continuously without blocking the whole program. But it's low-level (AVR-level) programming, probably not doable with the standard Arduino libraries.

lang-cpp

AltStyle によって変換されたページ (->オリジナル) /