How do I read PWM signals from Rx More Efficiently?

Hello,
I’ve been working on a project which involves reading a PWM signal from an RC hobby receiver. This is a part of the code I use, below. I’ve noticed that it slows down my Arduino’s clock speed quite a bit the more channels I read at a time (the code below reading only 1 channel), to the point where it is incapable of flying an airplane. Any ideas on how to make it faster?
Thanks so much,
Kevin

void setup() {
  Serial.begin(9600);
  pinMode(10, INPUT);
}
void loop() {
  int PWM = pulseIn(10, HIGH, 25000);
  Serial.print(PWM);
  delay(5);
}

akevkev:
Hello,
I've been working on a project which involves reading a PWM signal from an RC hobby receiver. This is a part of the code I use, below. I've noticed that it slows down my Arduino's clock speed quite a bit the more channels I read at a time (the code below reading only 1 channel), to the point where it is incapable of flying an airplane. Any ideas on how to make it faster?
Thanks so much,
Kevin

void setup() {

Serial.begin(9600);
  pinMode(10, INPUT);
}
void loop() {
  int PWM = pulseIn(10, HIGH, 25000);
  Serial.print(PWM);
  delay(5);
}

The function pulseIn() blocks which is bad.
delay() blocks which is bad.

You need to do multiple things at once and can't use blocking code.

How many channels do you want to read? Can you post your "whole" code?

Blackfin:
The function pulseIn() blocks which is bad.
delay() blocks which is bad.

In my full code (which is now below) I’ve removed all delay blocks (that was just there so my Arduino (a Teensy 3.2) didn’t overclock), but don’t know how to remove the pulseIn. How else should I read the PWM signal?

void PWMRead() {
  throttle = pulseIn(10, HIGH, 25000); //channel 1
  throttle = map(throttle, 1100, 1900, 0, 180);
  rollIn = pulseIn(3, HIGH, 25000); //channel 2
  rollIn = map(rollIn, 1100, 1900, 0, 180);
  pitchIn = pulseIn(4, HIGH, 25000); //channel 3
  pitchIn = map(pitchIn, 1100, 1900, 0, 180);
  mode = pulseIn(5, HIGH, 25000); //channel 4

  if (mode < 1500) {
    mode = 1;
  }
  else if (mode > 1500) {
    mode = 2;
  }
}

A search for "arduino read pwm from rc receiver" turns up many tutorials. Some should demonstrate non blocking methods for reading PWM.

You can try this 1st order effort. It uses polling; a relatively quick processor (72MHz) reading 1 to 2mS pulses should be able to do it reasonably well…

(If you try it, make sure to set your Serial Monitor to 115200…)

/*
 * Sketch:  sketch_feb24e
 * Target:  Teensy 3.2
 *
 */

#define NUM_CHANNELS    4
#define MODE_CHANNEL    3

const uint8_t   pinThrottle = 10;
const uint8_t   pinRollIn = 3;
const uint8_t   pinPitchIn = 4;
const uint8_t   pinModeIn = 5;

const uint8_t grPins[] = 
{
    pinThrottle,
    pinRollIn,
    pinPitchIn,
    pinModeIn
    
};

typedef struct structChannels
{
    uint8_t     pin;
    uint8_t     lastPin;
    uint32_t    tStart;    
    uint32_t    tWidth;
    uint8_t     degrees;
    
}sChannels_t;

sChannels_t Channels[NUM_CHANNELS] = {0};

uint32_t
    timeNow,
    timePrint;
uint8_t
    mode;

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

    for( uint8_t idx=0; idx<NUM_CHANNELS; idx++ )
    {
        Channels[idx].pin = grPins[idx];
        pinMode( Channels[idx].pin, INPUT_PULLUP );
        Channels[idx].lastPin = digitalRead( Channels[idx].pin );
        
    }//for
    
}//setup

void loop() 
{   
    uint8_t idx;

    //check channel inputs every loop
    PWMRead();

    //do other stuff here; nothing that blocks or waits or delays excessively
    //.
    //.
    //.
    
    //here I do a print every 100mS
    //is it time for a print?
    if( (timeNow - timePrint) >= 100ul )
    {
        timePrint = timeNow;
        
        idx = 0;       
        do
        {
            //print     "   CH:1 1500   CH:2 1460   CH:3 1670   CH:4 1790   MODE: 1\n" to SerMon
            Serial.print( "\tCH:" );      
            Serial.print( idx+1 );
            Serial.print( " " );
            Serial.print( Channels[idx].tWidth );            
            
            //each loop through printing check PWMs
            //so we lessen the effect of the relatively long
            //print time
            PWMRead();
            
        }while( ++idx < NUM_CHANNELS );

        Serial.print( "\tMODE: " );      
        Serial.println( mode );
        
    }//if
           
}//loop

void PWMRead() 
{
    static uint8_t
        channel=0;

    uint32_t timeNow = micros();

    //each pass we check one channel
    //read the state of the input
    uint8_t nowPin = digitalRead( Channels[channel].pin );
    //if now the same as last...
    if( nowPin != Channels[channel].lastPin )
    {
        //save as new last
        Channels[channel].lastPin = nowPin;

        //if high now...
        if( nowPin == HIGH )
        {
            //pin changed from low to high; log the uS count now
            Channels[channel].tStart = timeNow;
        }
        else
        {
            //pin changed from high to low; uS count now minus start is width
            //and map tWidth to 0 to 180
            Channels[channel].tWidth = (timeNow - Channels[channel].tStart) / 1000;
            Channels[channel].degrees = (uint8_t)map( Channels[channel].tWidth, 1100, 1900, 0, 180 ); 
           
        }//else
        
    }//if

    //set mode
    mode = (Channels[MODE_CHANNEL].tWidth <= 1500) ? 1 : 2;

    //bump to next channel
    channel++;
    if( channel >= NUM_CHANNELS )
        channel = 0;
    
}//PWMRead

Hello everyone,
I’m trying to make an RC airplane with a Teensy 3.2 which reads PWM signals from the receiver and outputs them to servos. However, even with a fast processor, the servo output is still very slow and jagged. I know that the pulseIn function, which reads the PWM signal slows it down, and most likely the servo library. I am wondering if:

  1. There is a simple way to read PWM signals without slowing the whole loop down
  2. There is a simple way to output servo signals to servos without slowing the program down
  3. There are any other parts of code that might cause everything to slow down.

Thank you so much,
Kevin

P.S does the Arduino.h or Wire.h library do anything that would lag the program?

//Libraries
#include <Arduino.h>
#include <TinyMPU6050.h>

#include <Wire.h>
#include <Servo.h>

//setup connection to MPU6050
MPU6050 mpu (Wire);

//servos
Servo LeftElevon;
Servo RightElevon;
Servo ESC;

//trim
int leftTrim = 0;
int rightTrim = 0;

int throttle; //rx input left stick y axis
int rollIn; //rx input right stickj x axis
int pitchIn; //rx input right stick y axis
int mode; //rx input for switch


int reOut;
int leOut;
int escOut;

void setup() {

  Serial.begin(9600); //serial output for debugging
  mpu.Initialize();//initalize the mpu6050

  //assign the outputs to pins
  ESC.attach(23, 1000, 2000); //servo 1
  LeftElevon.attach(22, 1000, 2000); //servo 2
  RightElevon.attach(21, 1000, 2000); //servo 3
  pinMode(7, OUTPUT); //LED pin
  pinMode(A2, INPUT); //arispeed

  //for the rx input
  pinMode(10, INPUT);
  pinMode(3, INPUT);
  pinMode(4, INPUT);
  pinMode(5, INPUT);

  //arm the ESC
  ESC.write(40);
  delay(1000);
  ESC.write(0);

  //startup LED 2 blinks
  digitalWrite(7, HIGH);
  delay(1000);
  digitalWrite(7, LOW);
  delay(1000);
  digitalWrite(7, HIGH);
  delay(1000);
  digitalWrite(7, LOW);

}

void loop() {

  RxDetect(); //detect pilot commands
  ServoOutput();
}





void RxDetect() {
  //pilot command
  throttle = pulseIn(10, HIGH, 25000); //channel 1
  throttle = map(throttle, 1100, 1900, 0, 180);
  rollIn = pulseIn(4, HIGH, 25000);
  rollIn = map(rollIn, 1100, 1900, 0, 180);
  pitchIn = pulseIn(5, HIGH, 25000);
  pitchIn = map(pitchIn, 1100, 1900, 0, 180);
  mode = pulseIn(3, HIGH, 25000); //channel 4

  if (mode < 1500) {
    mode = 1;
  }
  else if (mode > 1500) {
    mode = 2;
  }
}



void ServoOutput() {

  leOut; //left elevon output
  reOut; //right elevon output
  escOut = throttle; //ESC output

  //mixing this one is the one where roll plus pitch is equal to full actuation
  leOut = (rollIn / 2) + (pitchIn / 2) + leftTrim;
  reOut = (rollIn / 2) + ((180 - pitchIn) / 2) + rightTrim;

  //mixing this one is one axis maxes the servos out
  leOut = (rollIn - 45) + (pitchIn - 45) + leftTrim;
  reOut = (rollIn - 45) + (135 - pitchIn) + rightTrim;

  leOut = constrain(leOut, 0, 180);
  reOut = constrain(reOut, 0, 180);

  Serial.print(leOut);
  Serial.print(",");
  Serial.println(reOut);
  LeftElevon.write(leOut);
  RightElevon.write(reOut);

  ESC.write(escOut);
}

Try a search for "arduino read rc receiver non blocking". You may find a way to speed things up. Writing to a servo is pretty fast, but pulseIn does block.

Don't you already have a thread about this?

Did you try the code I posted in post #4 there?

Hello Blackfin,
Thank you so much for going through the effort of writing that piece of code for me. However, it does not output anything to the Serial monitor. I will continue trying and troubleshooting but am wondering if you intended for me to change or add anything, some of the concepts in your code I am not familiar with.
Kevin

doesn't pulseIn() block for the duration of the pulse? aren't RC pulses in the range from 1-2ms? and you call pulseIn() 4 times.

@akevkev

TOPIC MERGED. do NOT duplicate or start new topics for the same project.

Could you take a few moments to Learn How To Use The Forum.

Other general help and troubleshooting advice can be found here.
It will help you get the best out of the forum.

akevkev:
Hello Blackfin,
Thank you so much for going through the effort of writing that piece of code for me. However, it does not output anything to the Serial monitor. I will continue trying and troubleshooting but am wondering if you intended for me to change or add anything, some of the concepts in your code I am not familiar with.
Kevin

I added one line to get the millis count before checking the print interval. I see prints on the SerMon with this. Be sure you sure you set your serial monitor to 115200?

/*
 * Sketch:  sketch_feb28c
 * Target:  Teensy 3.2
 *
 */

#define NUM_CHANNELS    4
#define MODE_CHANNEL    3

const uint8_t   pinThrottle = 10;
const uint8_t   pinRollIn = 3;
const uint8_t   pinPitchIn = 4;
const uint8_t   pinModeIn = 5;

const uint8_t grPins[] =
{
    pinThrottle,
    pinRollIn,
    pinPitchIn,
    pinModeIn
   
};

typedef struct structChannels
{
    uint8_t     pin;
    uint8_t     lastPin;
    uint32_t    tStart;   
    uint32_t    tWidth;
    uint8_t     degrees;
   
}sChannels_t;

sChannels_t Channels[NUM_CHANNELS] = {0};

uint32_t
    timeNow,
    timePrint = 0;
uint8_t
    mode;

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

    for( uint8_t idx=0; idx<NUM_CHANNELS; idx++ )
    {
        Channels[idx].pin = grPins[idx];
        pinMode( Channels[idx].pin, INPUT_PULLUP );
        Channels[idx].lastPin = digitalRead( Channels[idx].pin );
       
    }//for
   
}//setup

void loop()
{   
    uint8_t idx;

    //check channel inputs every loop
    PWMRead();

    //do other stuff here; nothing that blocks or waits or delays excessively
    //.
    //.
    //.
   
    //here I do a print every 100mS
    //is it time for a print?
    timeNow = millis();
    if( (timeNow - timePrint) >= 100ul )
    {
        timePrint = timeNow;
       
        idx = 0;       
        do
        {
            //print     "   CH:1 1500   CH:2 1460   CH:3 1670   CH:4 1790   MODE: 1\n" to SerMon
            Serial.print( "\tCH:" );     
            Serial.print( idx+1 );
            Serial.print( " " );
            Serial.print( Channels[idx].tWidth );           
           
            //each loop through printing check PWMs
            //so we lessen the effect of the relatively long
            //print time
            PWMRead();
           
        }while( ++idx < NUM_CHANNELS );

        Serial.print( "\tMODE: " );     
        Serial.println( mode );
       
    }//if
           
}//loop

void PWMRead()
{
    static uint8_t
        channel=0;

    uint32_t timeNow = micros();

    //each pass we check one channel
    //read the state of the input
    uint8_t nowPin = digitalRead( Channels[channel].pin );
    //if now the same as last...
    if( nowPin != Channels[channel].lastPin )
    {
        //save as new last
        Channels[channel].lastPin = nowPin;

        //if high now...
        if( nowPin == HIGH )
        {
            //pin changed from low to high; log the uS count now
            Channels[channel].tStart = timeNow;
        }
        else
        {
            //pin changed from high to low; uS count now minus start is width
            //and map tWidth to 0 to 180
            Channels[channel].tWidth = (timeNow - Channels[channel].tStart) / 1000;
            Channels[channel].degrees = (uint8_t)map( Channels[channel].tWidth, 1100, 1900, 0, 180 );
           
        }//else
       
    }//if

    //set mode
    mode = (Channels[MODE_CHANNEL].tWidth <= 1500) ? 1 : 2;

    //bump to next channel
    channel++;
    if( channel >= NUM_CHANNELS )
        channel = 0;
   
}//PWMRead

When I upload the code to the Teensy 3.2 there is no option to select the serial baud rate, but now the code does run. However, it does not display the PWM values but rather the output below. When I use pulseIn with the same pins it does work so there's nothing wrong with connections or anything. One noticed is that when I toggle the Throttle Cut switch on my transmitter, setting the throttle PWM to below 1000, the CH:1 toggles between 1 and 0.
Finally, I also noticed that it isn't noticeably faster than the pulseIn command, have I been doing anything wrong?

	CH:1 0	CH:2 0	CH:3 0	CH:4 0	MODE: 1

akevkev:
When I upload the code to the Teensy 3.2 there is no option to select the serial baud rate, but now the code does run. However, it does not display the PWM values but rather the output below. When I use pulseIn with the same pins it does work so there's nothing wrong with connections or anything. One noticed is that when I toggle the Throttle Cut switch on my transmitter, setting the throttle PWM to below 1000, the CH:1 toggles between 1 and 0.
Finally, I also noticed that it isn't noticeably faster than the pulseIn command, have I been doing anything wrong?

	CH:1 0	CH:2 0	CH:3 0	CH:4 0	MODE: 1

It's probably something in my code. When I get a chance tomorrow I'll try to run it. I don't have a Teensy but I can check the basic function on an Uno or Mega here.

BTW you say it isn't faster than pulseIn(): Does that mean the servos move as they should (though just laggy or whatever)? Is it just not printing the right value to the serial monitor?

The speed which its printing to the serial monitor is about the same speed as the pulseIn function. I have not connected it to a servo yet.

akevkev:
The speed which its printing to the serial monitor is about the same speed as the pulseIn function. I have not connected it to a servo yet.

I'm updating the serial monitor only 10 times per second; in theory, the servo updates should be much faster. Should be able to do some testing on a Mega shortly...

OK, try this (dumb mistake on my part…):

/*
 * Sketch:  sketch_mar01b
 * Target:  Teensy 3.2
 *
 */

#define NUM_CHANNELS    4
#define MODE_CHANNEL    3

const uint8_t   pinThrottle = 10;
const uint8_t   pinRollIn = 3;
const uint8_t   pinPitchIn = 4;
const uint8_t   pinModeIn = 5;

const uint8_t grPins[] =
{
    pinThrottle,
    pinRollIn,
    pinPitchIn,
    pinModeIn
   
};

typedef struct structChannels
{
    uint8_t     pin;
    uint8_t     lastPin;
    uint32_t    tStart;   
    uint32_t    tWidth;
    uint8_t     degrees;
   
}sChannels_t;

sChannels_t Channels[NUM_CHANNELS] = {0};

uint32_t
    timeNow,
    timePrint = 0;
uint8_t
    mode;

void setup()
{
    Serial.begin( 115200 );
    
    for( uint8_t idx=0; idx<NUM_CHANNELS; idx++ )
    {
        Channels[idx].pin = grPins[idx];
        pinMode( Channels[idx].pin, INPUT_PULLUP );
        Channels[idx].lastPin = digitalRead( Channels[idx].pin );
       
    }//for
   
}//setup

void loop()
{       
    //check channel inputs every loop
    PWMRead();

    //do other stuff here; nothing that blocks or waits or delays excessively
    //.
    //.
    //.
           
}//loop

void PWMRead()
{
    static uint8_t
        channel=0;

    uint32_t timeNow = micros();

    //each pass we check one channel
    //read the state of the input
    uint8_t nowPin = digitalRead( Channels[channel].pin );
    //if now the same as last...
    if( nowPin != Channels[channel].lastPin )
    {        
        //save as new last
        Channels[channel].lastPin = nowPin;

        //if high now...
        if( nowPin == HIGH )
        {            
            //pin changed from low to high; log the uS count now
            Channels[channel].tStart = timeNow;
        }
        else
        {
            //pin changed from high to low; uS count now minus start is width
            //and map tWidth to 0 to 180
            Channels[channel].tWidth = timeNow - Channels[channel].tStart;            
            Channels[channel].degrees = (uint8_t)map( Channels[channel].tWidth, 1100, 1900, 0, 180 );

            Serial.print( "\tCH:" );     
            Serial.print( channel+1 );
            Serial.print( " " );
            Serial.println( Channels[channel].tWidth );                       
            
        }//else
       
    }//if

    //set mode
    mode = (Channels[MODE_CHANNEL].tWidth <= 1500) ? 1 : 2;

    //bump to next channel
    channel++;
    if( channel >= NUM_CHANNELS )
        channel = 0;
   
}//PWMRead

Blackfin:
It's probably something in my code. When I get a chance tomorrow I'll try to run it. I don't have a Teensy but I can check the basic function on an Uno or Mega here.

BTW you say it isn't faster than pulseIn(): Does that mean the servos move as they should (though just laggy or whatever)? Is it just not printing the right value to the serial monitor?

Hello Blackfin,
I've optimized my code a bit further, and although the servo movement is still a bit jagged and much slower than the Teensy's best operating clock speed it is flyable. However, I don't want to underappreciate your effort and willingness to write and test code for me, something which I recognize does take lots of effort. I've saved your code to study at a later date for when I need to read PWM with interrupts, and I've also learned a lot about how to use this forum.
Thank you so much,
Kevin

Have a look at this. It’s intended to use pin interrupts which may improve the performance of the servos. I don’t have a Teensy here so all I can say is it compiles without error but I haven’t been able to test it:

/*
 * Sketch:  sketch_mar02a
 * Target:  Teensy 3.2
 *
 */

#define NUM_CHANNELS    4

#define THROTTLE        0
#define ROLL            1
#define PITCH           2
#define MODE            3

//prototype
void isrThrottle();
void isrRoll();
void isrPitch();
void isrMode();

const uint8_t   pinThrottle = 10;
const uint8_t   pinRollIn = 3;
const uint8_t   pinPitchIn = 4;
const uint8_t   pinModeIn = 5;

typedef void (*pInterruptFunc)( void );

const uint8_t grPins[] =
{
    pinThrottle,
    pinRollIn,
    pinPitchIn,
    pinModeIn
   
};

void (*grpFuncs[NUM_CHANNELS])() = 
{
    &isrThrottle,
    &isrRoll,
    &isrPitch,
    &isrMode
};

typedef struct structChannels
{
    uint8_t         pin;
    pInterruptFunc  pFunc;        
    uint32_t        tStart;   
    uint32_t        tWidth;
    uint8_t         degrees;
   
}sChannels_t;

sChannels_t Channels[NUM_CHANNELS] = {0};

uint8_t
    mode;

void setup()
{
    Serial.begin( 115200 );
      
    for( uint8_t idx=0; idx<NUM_CHANNELS; idx++ )
    {
        Channels[idx].pin = grPins[idx];
        pinMode( Channels[idx].pin, INPUT_PULLUP );
        Channels[idx].pFunc = grpFuncs[idx];
        attachInterrupt( digitalPinToInterrupt( Channels[idx].pin ), Channels[idx].pFunc, CHANGE );                
       
    }//for
   
}//setup

void loop()
{       
    //check channel inputs every loop
    //PWMRead();

    //do other stuff here; nothing that blocks or waits or delays excessively
    //.
    //.
    //.
           
}//loop

void isrThrottle( void )
{
    uint32_t
        timeNow = micros();
        
    if( digitalRead( Channels[THROTTLE].pin ) == HIGH )
    {
        Channels[THROTTLE].tStart = timeNow;
        
    }//if
    else
    {
        Channels[THROTTLE].tWidth = timeNow - Channels[THROTTLE].tStart;           
        Channels[THROTTLE].degrees = (uint8_t)map( Channels[THROTTLE].tWidth, 1100, 1900, 0, 180 );
        
    }//else
    
}//isrThrottle

void isrRoll( void )
{    
    uint32_t
        timeNow = micros();
    
    if( digitalRead( Channels[ROLL].pin ) == HIGH )
    {
        Channels[ROLL].tStart = timeNow;
        
    }//if
    else
    {
        Channels[ROLL].tWidth = timeNow - Channels[ROLL].tStart;           
        Channels[ROLL].degrees = (uint8_t)map( Channels[ROLL].tWidth, 1100, 1900, 0, 180 );
        
    }//else
    
}//isrRoll

void isrPitch( void )
{ 
    uint32_t
        timeNow = micros();
        
    if( digitalRead( Channels[PITCH].pin ) == HIGH )
    {
        Channels[PITCH].tStart = timeNow;
        
    }//if
    else
    {
        Channels[PITCH].tWidth = timeNow - Channels[PITCH].tStart;           
        Channels[PITCH].degrees = (uint8_t)map( Channels[PITCH].tWidth, 1100, 1900, 0, 180 );
        
    }//else
       
}//isrPitch

void isrMode( void )
{
    uint32_t
        timeNow = micros();
        
    if( digitalRead( Channels[MODE].pin ) == HIGH )
    {
        Channels[MODE].tStart = timeNow;
        
    }//if
    else
    {
        Channels[MODE].tWidth = timeNow - Channels[MODE].tStart;           
        mode = (Channels[MODE].tWidth <= 1500) ? 1 : 2;
        
    }//else

}//isrMode