0

I am trying to read several PWM signals from an RC receiver into an ATMega 2560. I am having trouble understanding how the ICRn pin functions as it appears to be used for all three compare registers.

The RC PWM signal has a period of 20ms with a HIGH pulse of 2ms being a valid upper value and 1ms being a valid lower value. So the value will sweep from 1000us to 2000us. The period should begin at the rising edge of the pulse.

I have prescaled the 16MHz clock by 8 to have a 2MHz timer an thus should be able to measure the signal to 0.5us accuracy which is sufficient for my requirements.

Please note that I am having not problems with PWM output and this question is specifically about PWM input.

My code thus far is attached below. I know that I will have to use ICR3 and an ISR to measure the PWM values but I am unsure as to the best procedure for doing this. I also do not know how to check if the value measured is from PE3, PE4, or PE5. Is this code right and how do I get the value that I am looking for?

Any help would be greatly appreciated.

// Set pins as inputs
DDRE |= ( 0 << PE3 ) | ( 0 << PE4 ) | ( 0 << PE5 );

// Configure Timers for CTC mode
TCCR3A |=   ( 1 << WGM31 ) | ( 1 << WGM30 ); // Set on compare match
TCCR3B |=   ( 1 << WGM33 ) | ( 1 << WGM32 ) | ( 1 << CS31); // Set on compare match, prescale_clk/8

TCCR3B |=   ( 1 << ICES5 ) // Use rising edge as trigger

// 16 bit register - set TOP value
OCR3A = 40000 - 1;
OCR3B = 40000 - 1;
OCR3C = 40000 - 1;

TIMSK3 |= ( 1 << ICIE3 );
deanshanahan
  • 318
  • 5
  • 14

2 Answers2

1

I had forgotten to post my solution a few months ago so here it is...

I used a PPM receiver in the end so this code can easily edited to read a simple PWM.

In my header file I made a structure for a 6 channel receiver that I was using for my project. This can be changed as required for receivers with more or less channels.

#ifndef _PPM_H_
#define _PPM_H_

// Libraries included
#include <stdint.h>
#include <avr/interrupt.h>

struct orangeRX_ppm {
    uint16_t ch[6];
    };
volatile unsigned char ch_index;
struct orangeRX_ppm ppm;

/* Functions */
void ppm_input_init(void); // Initialise the PPM Input to CTC mode
ISR( TIMER5_CAPT_vect ); // Use ISR to handle CTC interrupt and decode PPM

#endif /* _PPM_H_ */

I then had the following in my .c file.

// Libraries included
#include <avr/io.h>
#include <stdint.h>
#include "ppm.h"

/*      PPM INPUT
 *      ---
 *      ICP5    Pin48 on Arduino Mega
 */
void ppm_input_init(void)
{
    DDRL |= ( 0 << PL1 ); // set ICP5 as an input

    TCCR5A = 0x00; // none
    TCCR5B = ( 1 << ICES5 ) | ( 1 << CS51); // use rising edge as trigger, prescale_clk/8
    TIMSK5 = ( 1 << ICIE5 ); // allow input capture interrupts

    // Clear timer 5
    TCNT5H = 0x00;
    TCNT5L = 0x00;
}

// Interrupt service routine for reading PPM values from the radio receiver.
ISR( TIMER5_CAPT_vect )
{
    // Count duration of the high pulse
    uint16_t high_cnt; 
    high_cnt = (unsigned int)ICR5L;
    high_cnt += (unsigned int)ICR5H * 256;

    /* If the duration is greater than 5000 counts then this is the end of the PPM signal 
     * and the next signal being addressed will be Ch0
     */
    if ( high_cnt < 5000 )
    {
        // Added for security of the array
        if ( ch_index > 5 )
        {
            ch_index = 5;
        }

        ppm.ch[ch_index] = high_cnt; // Write channel value to array

        ch_index++; // increment channel index
    }
    else
    {
        ch_index = 0; // reset channel index
    }

    // Reset counter
    TCNT5H = 0;
    TCNT5L = 0;

    TIFR5 = ( 1 << ICF5 ); // clear input capture flag
}

This code will use an trigger an ISR every time ICP5 goes from low to high. In this ISR the 16bit ICR5 register "ICR5H<<8|ICR5L" holds the number of pre-scaled clock pulses that have elapsed since the last change from low to high. This count is typically less than 2000 us. I have said that if the count is greater than 2500us (5000 counts) then the input is invalid and the next input should be ppm.ch[0].

I have attached an image of PPM as seen on my oscilloscope.

Image of 6 Channel PPM

This method of reading PPM is quite efficient as we do not need to keep polling pins to check their logic level.

Don't forget to enable interrupts using the sei() command. Otherwise the ISR will never run.

Mogsdad
  • 44,709
  • 21
  • 151
  • 275
deanshanahan
  • 318
  • 5
  • 14
0

Let's say you want to do the following (I'm not saying this will allow you to accurately measure the PWM signals but it might serve as example on how to set the registers)

Three timers running, which reset every 20 ms. This can be done by setting them in CTC mode for OCRnA: wgm3..0 = 0b0100.

//timer 1
TCCR4A = 0;
TCCR1B = (1<<CS11) | (1<<WGM12);
OCR1A = 40000 - 1;
//timer 3 (there's no ICP2)
TCCR3A = 0;
TCCR3B = (1<<CS31) | (1<<WGM32);
OCR3A = 40000 - 1;
//timer 4
TCCR4A = 0;
TCCR4B = (1<<CS41) | (1<<WGM42);
OCR4A = 40000 - 1;

Now connect each of the three pwm signals to their own ICPn pin (where n = timer). Check the datasheet for the locations of the different ICPn pins (i'm pretty sure it's not PE3, 4, 5)

Assuming the pwm signals start high at t=0 and go low after their high-time for the remainder of the period. You want to measure the high-time so we trigger an interrupt for each when a falling edge occurs on the ICPn pin.

bit ICESn in the TCCRnB register set to 0 will select the falling edge (this is already done in the previous code block).

To trigger the interrupts, set the corresponding interrupt enable bits:

TIMSK1 |= (1<<ICIE1);
TIMSK3 |= (1<<ICIE3);
TIMSK4 |= (1<<ICIE4);
sei();

Now each time an interrupt is triggered for ICn you can grab the ICRn register to see the time (in clockperiods/8) at which the falling edge occurred.

PenguMC
  • 86
  • 4