PIC LED Controller – PWM capture

The LED controller for my quadcopter will be controlled by by the remote control on one of the PWM channels. The last step of preparation before writing the LED controller itself is to understand how to listen on a remote control (RC) channel and detect the PWM “value” that is transmitted on the channel.

I decided to connect my cheap Minima receiver to the circuit and write a small program to display the PWM value, that I’lll transmit to it, on my 3-digit, 7-segment display. The Minima is sold by Hobbyking.com here. It is compatible with my Hitec transmitter and the price is just 19.73 euros, which is cheap compared to the original Hitec Optima receiver.

I had to come up with a method for communicating between the two PIC16F1825 controllers because the application controller has to transmit the PWM value to the 7-segment controller. I thought of using the USART serial interface but I decided to use the DAC (Digital to Analog Conversion) feature of the PIC16F1825. In this mode, the chip can output 32 voltage levels on pin RA0 – the DACOUT pin. This pin is connected to input pin RA2 on the 7-segment controller. The 7-segment controller displays a number between 0 and 99 that corresponds to the voltage on its input pin so it will display a number that corresponds to the PWM value. I decided to use this method because accuracy is not important in this project.

Set up

The circuit is shown in the picture below.

PWMTest_Setup

The RC transmitter at the top is my Hitec Aurora 9. On the right side, outside the breadboard is the Minina receiver. Its red LED is on indicating that it is connected to the transmitter. The receiver is connected with 3 cables from its channel 3 output to the breadboard:

  • Red cable connected to +5V
  • Black cable connected to GND
  • White/Yellow cable – the signal cable (white on the Minima side and yellow on the breadboard side) connected to pin RA5 of the application controller.

There are 2 PIC16F1825 controllers on the board:

  • Application controller (top) – runs the PWM capture function
  • 7-Segment controller (bottom) – drives the 7-segment display.

Application controller

The application controller is connected to 4 LEDs that are not part of this project. They are there because I don’t want to take them out for every picture that I take of the breadboard and eventually they will be part of the LED controller.

The application controller is also connected to the PICKit3 data cable that connects to RA0, RA1, RA3 as I described in a previous post.

The two connections relevant to this project are RA5 – the PWM input, and RA0 – the output of the analog signal to the 7-Segment controller. RA0 functions as the DACOUT pin (Digital to Analog Converter Out).

7-Segment controller

The 7-segment controller is described in a previous post. It receives an input voltage, between 0V and +5V) on pin RA2 and displays a number between 0 and 99 that corresponds to the input. The input pin, RA2 is connected to the DACOUT pin on the application controller (RA0).

Code

The code is quite simple. I use the IOC (interrupt on change) feature that calls the ISR whenever a rising or falling edge is detected on an input pin. I also use Timer 1 to time the PWM duty cycle – the time the line is high.

The I use the DAC (digital to analog converter) to pass a value to the 7-segment display controller.

/*
* File: main.c
* Author: Eli Gurvitz – Quadcopter Blog
*
* Created on February 26, 2015, 10:59 PM
*/

#include <xc.h>

The rising and falling edge #defines are used in the ISR. The LOWPWM is the value of the lowest PWM reading. I found it by using the debugger.

#define LOWPWM 8800
#define RISING_EDGE 0
#define FALLING_EDGE 1

Skipping the settings of the configuration bytes which usually goes here.

unsigned int pwmvalue = 0;

/****************************************
*
****************************************/
void interrupt ISR()
{

The state variable indicates if we are dealing with a rising edge or a falling edge.

    static int state = RISING_EDGE;
    unsigned int newvalue;

    if (IOCAF5 == 1) {

If the ISR is called for a rising edge then we reset timer 1 and change the IOC setting to interrupt on a falling edge (the IOCAP and IOCAN registers).

        if (state == 0) { // rising edge
            TMR1L = 0;
            TMR1H = 0;
            IOCAP5 = 0;
            IOCAN5 = 1;
            state = FALLING_EDGE;
        }
        else { // falling edge

If the ISR is called for a falling edge then we read the value of timer 1. If the value is different from the previous value then we update the PWM value. Then we set IOC to be called on a rising edge.
            newvalue = (TMR1H << 8) + TMR1L;
            if (newvalue != pwmvalue) {
                pwmvalue = newvalue;
            }
            state = RISING_EDGE;
            IOCAP5 = 1;
            IOCAN5 = 0;
        }
        IOCAF5 = 0;
    }
}

/****************************************
*
****************************************/
void main()
{
    int i;
    int j;

    OSCCON = 0xF0; // set internal osc to 32Mhz

Timer1 settings

    TMR1ON = 1;
    TMR1GE = 0;

Setting RA0 for output and RA5 for input.

    TRISA0 = 0;
    TRISA5 = 1;

Setting the IOC for pin 5. The initial setting is for detecting a rising edge.

    IOCAP5 = 1;
    IOCAN5 = 0;
    IOCAF5 = 0;
    IOCIE = 1;

GIE must be enabled in order to receive the IOC interrupts.

    GIE = 1;

DAC settings – enable DAC and enable the DACOUT pin.

    DACEN = 1;
    DACOE = 1;

Main loop – I decided to update the 7-segment display only periodically (on a count to 10000). This

    while(1) {
        if (i > 10000) {

Initially, I tried to calculate the DACOUT value (which is set by writing to the 5 LSBits of DACCON1) by a simple function (see below) but it didn’t work. Therefore I compare the PWM value to each possible value from the lowest  (LOWPWM) and up to 32 increments (of 200 in this case) and set DACOUT to the index of the increment. This is good enough for the purpose of this sample.

            for (j = 0; j < 32; j++) {
                if (pwmvalue < (LOWPWM + (j * 200))) {
                    DACCON1 = j;
                    break;
                }
            }
            i = 0;
        }
        i++;
    };
}

Running

 Issues that I still need to understand

  1. I tried using the PWM capture feature of the PIC16F1825 but it didn’t work. It never stopped in the ISR (Interrupt Service Routine).
  2. At first I tried to calculate the output value in the following way but it never worked. The DACOUT value varied between 3 and 8 and never corresponded to the value of the function described below.
    1. Divide the PWM input range into 32 groups. Each group has a size of (PWMHIGH – PWMLOW) / 32
    2. Normalise the PWM input by subtracting the min value from it.
    3. Divide the normalised PWM input by the group size.
    4. Or, in other words: DACOUT = (pwmvalue – PWMLOW) / group size = (pwmvalue-PWMLOW) * 32 / (PWMHIGH – PWMLOW).

 

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Google photo

You are commenting using your Google account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s