Playing with PICs : 2. Chasing LEDs

Getting Started with PICs

Getting Started with PICs

In the last PIC post I described how I got started with my PICKit 2 and MPLAB toolchain and got something running on the demo board. The next thing I wanted to do was to replicate the program that the demo board PIC comes with. This program makes the LEDs chase with the speed determined by the variable resistor and the direction determined by the push button. I decided to start with the chasing LEDs as this is a simple GPIO exercise.

PIC Demo Board LEDs

PIC Demo Board LEDs

Before I could get started with this job however, I realised that I was going to need a delay function of some kind. If you include pic.h then one is available to you. Well two actually, you have __delay_ms() and __delay_us(). Both of these take an argument which is cast to an unsigned long with __delay_ms() giving you a delay measured in milliseconds and __delay_us() giving you a delay in microseconds. For these to work properly though you need to define a value called _XTAL_FREQ which is the speed of your clock. This means you need to know your clock speed which in turn means you need to tell the PIC what you want the speed to be.

To keep things simple I’ve used the internal PIC clock. On the PIC16F887 the internal clock can run at several different speeds. These are described in the data sheet in the oscillator overview section and range from 31KHz through to 8MHz. While there is nothing about this application that really warrants it, I decided to go for broke and run the thing at the full 8MHz. You do this from inside the program by changing the three IRFC bits in the OSCCON register. The options are documented in the data sheet in figure 4.1. Using the OSCCONbits structure defined in PIC16f887.h makes this very easy.

// set up the oscillator to run at 8MHz
    OSCCONbits.IRFC = 0b111;

This gives us the basic tools to get the chasing working, the only other thing is to find a neat way to get the LEDs to light up in turn. To do this I simple held an LED index that is incremented in the main loop and use that to shift a bit through the port register. I used an additional variable (d) to hold the amount to increment the index (i) with rather than just using the constant value 1. The reason for this was to set up the code ready to chase in either direction later on.

The simple chasing code is as below:

#include <pic.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

void main()
{

    // set up the oscillator to run at 8MHz
    OSCCONbits.IRFC = 0b111;

    // configure port d as output
    TRISD = 0;
    
    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)
    {
        // select the next LED wrapping around
        // if required
        i = (i + d) % 8;

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

        __delay_ms(100);
    }
}

The next thing to do was to add in the code to deal with the button press. When the user presses the button the LED chasing needs to switch direction. This is also a pretty simple GPIO job with the difference being that this time we use the GPIO as an input rather than an output.

PIC Demo Board Button

PIC Demo Board Button

As can be seen in the circuit diagram above taken from the demo board data sheet, the button is connected to RB0. When the switch is open, the pin is held high by the 10K resistor R2. Pushing the switch connects it to ground which makes the pin go low. The sequence of events for a button press then is a transition from high to low then from low back to high again. This is seen by the program running on the PIC as a change in state on RB0 from 1 to 0 then back to 1 again.

Ok so we know what the sequence of events is that we’re looking for, the next thing is to find out how to get input from a pin in a PIC program so back to the data sheet, this time section 3.3 where there is a description of port B and how to use it.

First off, we write 1 to PORTB so that RB0 initialises high. The next thing to do is to set up the input as a digital rather than an analog input. The pin RB0 doubles up as AN12 and configuration for AN12 is done using the ANSELH register. To set this pin as a digital input we’ll need to set the AN12 bit to 0. The final thing that needs to happen is that we need to configure RB0 as an input.This is done by writing 1 to the TRISB register. Note that this configuration procedure comes directly from the data sheet. If you’re working with a different PIC make sure you’re going back to the correct data sheet for whatever PIC you’re using and make sure that you know what you need to do.

Once this configuration is complete we’re ready to receive digital input from the push button and this is a simple case of looking for the 1 0 1 sequence. Now there’s something really important to understand here that I’ve seen people new to programming come unstuck on many times. What needs to be implemented here is not a sequence of lines of code that goes something along the lines of:

// wrong approach!!
if (PORTBbits.RB0 == 1) {
     if (PORTBbits.RB0 == 0) {
          If (PORTBbits.RB0 == 1) {
               // do something!
          }
     }
}

If you do that the chances of getting to the ‘do something’ code are very slim indeed! The reason is that the code is running at 8MHz and you’d be mighty lucky to time it just right to get the sequence bang on time to make that code work. You also don’t want to hold up the program while the user has their finger on the button. What you need is to be able to allow the user to hold down and release the button at any time which could be many thousands of times around the ‘while’ loop.

The way to solve this is to think of the program in terms of a state machine. The program can be in one of three states:

1. Initialising which is basically everything before the start of the ‘while’ loop;
2. The user is not pressing the button;
3. The user is pressing the button.

We track the state by using a static state flag called buttonDown and the event that we need to identify is the transition from buttonDown to ‘not’ buttonDown. When the transition is detected, the variable ‘d’ which changes the index of the active LED is negated so it changes from 1 to -1 or -1 to 1 causing the chasing to switch direction.

#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;
}

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;

    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;

        __delay_ms(100);
    }
}

So that’s the chasing working. In the next post I’ll add in the variable resistor so that we can control the speed as well as the direction.

In the meantime, if you have any comments or suggestions please feel free to post below. Feedback is always welcome!

Advertisements

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: