Decoding a PWM Signal Using the Input Capture Module

The input capture module is present on all PICs in differing numbers, anywhere from only a single module up to 8 or 12 channels. Its function is to capture the value of its associated timer on either the rising or falling edge of a transition, or on both. It is also possible to capture every 4th or 16th rising edge, acting as a clock divider.

We intend to use it to generate an interrupt on both the rising and falling edges. The rising edge interrupt marks the start of our PWM pulse, meaning the timer is reset at this point. The falling edge interrupt is then used to capture the value of the timer, which will represent the length of the pulse, divided by a constant scale factor determined by the timer configuration.

Setting up the input capture module

As with any module, the first step is to setup its input and output pins. If your chip uses a peripheral select module, as mine does, you need to set this up first.

#include <stdio>
#include <p33Fj128GP202>           //Insert your appropiate header file here
#define  PPSUnlock                   __builtin_write_OSCCONL(OSCCON &amp; 0xbf)
#define  PPSLock                     __builtin_write_OSCCONL(OSCCON | 0x40)
RPINR7bits.IC1R = 0b1010; //Tie IC1 to RP10
TRISBbits.TRISB10 = 1;

The first step is to unlock the peripheral pin select lock. This exists to prevent accidental pin routing changes that could catastrophically damage the system, so although you can leave it unlocked to make coding easier I would recommend locking it again once you are done configuring it. The next step is to choose which pin you want the input IC1 to be connected to. I have chosen RP10, hence the binary value 0b1010. It is also necessary to check that the appropiate TRIS register is configured as an input by setting the TRISB10 bit in this case.

Next we are going to configure the IC1 module itself using the IC1CON register.

IC1CONbits.ICSIDL = 0; //Continue in idle mode
IC1CONbits.ICTMR = 1; //Use Timer2
IC1CONbits.ICI = 0b00; //Interrupt on every capture
IC1CONbits.ICM = 0b001; //Every edge

ICSIDL controls the modules operation in idle mode. By clearing this bit the module will keep running.

ICTMR selects which timer module is to be used, with clearing the bit equalling timer3, and setting the bit equalling timer3. Timers 1 4 and 5 cannot be used with this module.

ICI controls how many capture events are required to cause an interrupt. We set this to 0 to interrupt on every capture. A value of 01 captures every 2nd event, 10 every 3rd, and 11 every 4th.

ICM is the most important register, as it controls what type of event will trigger a capture. From the microchip input capture manual:

111  = Input capture functions as interrupt pin only when device is in Sleep or Idle mode (rising edge
detect only, all other control bits are not applicable)
110  // Unused (Input Capture module disabled)
101  // Capture mode, every 16th rising edge
100  // Capture mode, every 4th rising edge
011  // Capture mode, every rising edge
010  // Capture mode, every falling edge
001  // Capture mode, every edge – rising and falling (the ICI&lt;1:0&gt; bits do not control interrupt
generation for this mode)
000  // Input capture module turned off

The final stage of setting up the module is to configure the related interrupt registers

IEC0bits.IC1IE = 1; //Enable interrupt
IPC0bits.IC1IP = 0b110; //Priority 6

This simply enables the IC1 interrupt, and gives it a priority level 6.

Using the input capture module

The next step is to actually tell the program how to handle the IC1 interrupt events.

void _ISR _IC1Interrupt(void)
   if(PORTBbits.RB10 == 1)
      trash = IC1BUF;
   else if(PORTBbits.RB10 == 0)
      captured_value = IC1BUF;
   IFS0bits.IC1IF = 0; //Clear flag

This function is called every time the interrupt flag is read as 1. The first if statement is true when a rising edge has been captured, which in our case marks the start of a PWM pulse we wish to time. Timer2 is restarted to time this pulse, and the value captured during the rising edge event is cleared out of the IC1 buffer by reading it to a trash variable.

Given that our PWM signal is now high, the next event to be captured will be the falling edge of the signal. When this happens, RB10 will read as 0, triggering the else if statement. This reads out the captured timer value into the variable captured_value, which by multiplying by the timers period we can obtain the high time of the pulse in seconds. Finally the IC1 interrupt flag is cleared and the loop is free to repeat again.

This code is then copied for each input capture module, allowing a number of PWM channels to be decoded simultaneously. Now, you may be thinking "Surely you would need to use a different timer for each PWM channel to avoid any overlapping?". Luckily, as the PWM data must be transmitted over a single channel, time division multiplexing is used, meaning each channel has its own portion of the signal. This means when the signal is demultiplexed, each signal still occupies its own period in the total 20ms signal, meaning there is no overlap between channels, allowing one timer to be used. Please note, this is just what I have observed from my Hobbyking 6ch receiver, so other brands may work differently.


Comments (1) Trackbacks (0)
  1. Hi,

    I am using PIC24FJ128GP510A controller for my PWM input capture based application development. I need to do input capture on 5 channels simultaneously. I am using Timer 3 in free running mode. I am able to capture values but they are not stable. Input capture values are varying continously. Any idea what i am missing. I have kept capture mode on rising edge and in interrupt changing mode to falling edge as i need to caculate duty cycle as well.

    Any help…what i am missing..

Leave a comment

Trackbacks are disabled.