Playing with PICs : 3. Changing the Chase Speed

Getting Started with PICs

Getting Started with PICs

In my last PIC post I wrote about how to get the LEDs on the PICKit 2 demo board to chase by configuring the clock and using the delay macro to update the GPIO port. I also added some code to read the button and reverse the direction of the chasing mimicking the behaviour of the program on the PIC that is provided with the board. The other feature that program has though is for the speed of the chasing LEDs to be varied by turning the variable resistor.

PIC Demo Board Input

PIC Demo Board Input

This is a classic job for an Analog to Digital Converter (ADC) and these work by sampling a voltage and converting it to a number that can be used to represent some kind of quantity in the code. Before going ahead and getting the code going though, we need to go back to the demo board data sheet to find out where and how the variable resistor is connected to the PIC.

The pin we’re interested in is RA0 which is multiplexed with AN0, our analog input. Input from the variable resistor will range from +5V (VDD) to 0V (VCC) as I’m running the board at 5V. Next it’s off to the PIC16F887 data sheet to figure out how to get this thing done.

The first thing to do is to perform some configuration. We need to tell the PIC what the input range is going to be and how we’d like the 10 bit result laid out in the two 8 bit registers it’ll be written to.

    // configure RA0 as analog AN0
    ADCON1bits.ADFM = 0; // left justified result
    ADCON1bits.VCFG0 = 0; // use VDD as reference voltage
    ADCON1bits.VCFG1 = 0; // use VCC as reference gnd

I chose to left justify the result because I wanted to turn the value into a single byte that spanned the full range of the variable resistor. Using the highest 8 bits of the result gives me that and I can simply ignore the two least significant bits. The other two values tell the ADC that I’ll be using an input voltage range between VDD and VSS (GND). For details on the other options you’ll need to refer to the data sheet. This is probably a good point to remind anyone out there that while clearly I have no issue whatsoever with anyone pinching this code (or it wouldn’t be here!) this is code for the PIC16F887 and things can be different on other PICs. Learn to love those data sheets!

Next I set up the RA0 / AN0 pin so that it acts as an analogy input.

    TRISA = 1;
    ANSELbits.ANS0 = 1;

This is done by configuring the TRISA register so that bit 0, which maps to the pin, is set to input and then make sure it’s selected in the ANSEL register. It should be set anyway after a power on but this way we can be sure…

In the final part of the configuration I had to do some fiddling with the clock because I’m running at 8MHz and the ADC can’t go that fast. Dividing the clock by 32 puts us inside the recommended range of values.

    ADCON0bits.ADCS = 0b10; // clock / 32
    ADCON0bits.CHS = 0;     // AN0 selected
    ADCON0bits.ADON = 1;    // turn on the ad converter

Next I connect the AN0 channel to the ADC. I did this as part of the configuration stage because I’m only using one channel so there’s no need to keep changing it. Finally the ADC is switched on.

So that’s the setup work done, the next thing is to take the readings from the ADC. I created a separate function to do this so I can re-use it more easily.

char readVR()
{
    ADCON0bits.GO = 1;
    while(ADCON0bits.GO == 1){}
    return ADRESH;
}

To start a conversion all you have to do is to set the ‘GO’ bit in the ADCON register and wait until it gets cleared after the conversion is complete. The result is then stored as a 10 bit number across the ADRESH and ADRESL registers. All I need are the top 8 bits so I grab them from ADRESH and return the byte. That byte is then used in a delay in the chase code to provide a delay of between 0 and 255 milliseconds which is good enough for me.

The final code is listed below:

#include <pic.h>
#include <pic16f887.h>

// PIC16F887 Configuration Bit Settings

// CONFIG1
#pragma config FOSC = INTRC_NOCLKOUT// Oscillator Selection bits (INTOSCIO oscillator: I/O function on RA6/OSC2/CLKOUT pin, I/O function on RA7/OSC1/CLKIN)
#pragma config WDTE = OFF       // Watchdog Timer Enable bit (WDT disabled and can be enabled by SWDTEN bit of the WDTCON register)
#pragma config PWRTE = OFF      // Power-up Timer Enable bit (PWRT disabled)
#pragma config MCLRE = ON       // RE3/MCLR pin function select bit (RE3/MCLR pin function is MCLR)
#pragma config CP = OFF         // Code Protection bit (Program memory code protection is disabled)
#pragma config CPD = OFF        // Data Code Protection bit (Data memory code protection is disabled)
#pragma config BOREN = ON       // Brown Out Reset Selection bits (BOR enabled)
#pragma config IESO = ON        // Internal External Switchover bit (Internal/External Switchover mode is enabled)
#pragma config FCMEN = ON       // Fail-Safe Clock Monitor Enabled bit (Fail-Safe Clock Monitor is enabled)
#pragma config LVP = OFF        // Low Voltage Programming Enable bit (RB3 pin has digital I/O, HV on MCLR must be used for programming)

// CONFIG2
#pragma config BOR4V = BOR40V   // Brown-out Reset Selection bit (Brown-out Reset set to 4.0V)
#pragma config WRT = OFF        // Flash Program Memory Self Write Enable bits (Write protection off)

#define _XTAL_FREQ 8000000

int buttonPressed()
{
    // flag to tell us when the button is
    // being pressed
    static int buttonDown = 0;

    // is the button being pressed?
    if (PORTBbits.RB0 == 0)
    {
        // yes, set the flag
        buttonDown = 1;
    }

    // has the button been released?
    if (PORTBbits.RB0 == 1 && buttonDown == 1)
    {
        // yes, clear the flag
        buttonDown = 0;

        // tell the calling method
        return 1;
    }

    return 0;
}

char readVR()
{
    ADCON0bits.GO = 1;
    while(ADCON0bits.GO == 1){}
    return ADRESH;
}

void main()
{
    // set up the oscillator to run at 8MHz
    OSCCONbits.IRFC = 0b111;

    // configure port d as output
    TRISD = 0;

    // Configure bit 0 (AN12) on PORTB as a digital input
    PORTB = 1;
    ANSELHbits.ANS12 = 0;
    TRISB = 0b00000001;

    // configure RA0 as analog AN0
    ADCON1bits.ADFM = 0; // left justified result
    ADCON1bits.VCFG0 = 0; // use VDD as reference voltage
    ADCON1bits.VCFG1 = 0; // use VCC as reference gnd

    TRISA = 1;
    ANSELbits.ANS0 = 1;

    ADCON0bits.ADCS = 0b10; // clock / 32
    ADCON0bits.CHS = 0;     // AN0 selected
    ADCON0bits.ADON = 1;    // turn on the ad converter

    int d = 1;  // sets the direction of the chasing
    int i = 0;  // the index of the led that will be lit

    // loop forever
    while(1)
    {
        // if the button is pushed...
        if (buttonPressed() == 1)
        {
            // ...reverse the direction
            d = -d;
        }

        // select the next LED wrapping around
        // if required
        i = (i + d) % 8;

        // light up the correct LED
        PORTD = 1 << i;

        // read a delay value from the variable resistor
        // analog channel
        int del = readVR();

        // do the delay
        for (int l=0; l<del;l++)
        {
            __delay_ms(1);
        }
    }
}

So that’s that then, we have a version of the demo program that chases the LEDs, allows you to swap the direction of the chasing and also change the speed. I’m sure we can get more out of this board than that though but that can be a subject for another post.

As always I’d be really interested to hear your feedback. Feel free to post comments, suggestions, errors etc, they are gratefully received.

Advertisements
1 comment

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 )

Twitter picture

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

Facebook photo

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

Google+ photo

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

Connecting to %s

%d bloggers like this: