Archive

Embedded Programming

It’s a lot of fun writing code on your Discovery board with its myriad of peripherals to explore and exposed headers for further experimentation. Nothing, however, beats the satisfaction to be had from creating and coding your own board and there comes a time when every red blooded geek must strike out on their own!

I’ve recently been presented with an opportunity to create a new board that required a lot more grunt than the PIC based boards I normally deal with. We needed a much faster processor, an FPU and some DSP capability. As a result I opted to use the STM32F405RG as the MCU. The next thing I wanted to do was to mash up a prototype but I needed a quick and easy way to program it. I remembered that the Discovery board has an ST-LINK section with an SWD interface broken out onto headers so I thought I’d give it a go.

STM32F4 Discovery ST-LINK Jumpers

Setting up your Discovery to use it as a programmer is a very straightforward affair. The ST-LINK section is the top third of the board and can be easily isolated by removing the two CN3 jumpers shown above.

You’re also clearly going to need to expose the SWD interface on your board so you have something to connect the ST-LINK to. The schematic below shows how I did it.

jtag

A couple of things to note about the circuit above. I’ve pulled BOOT0 low which means that I’m programming flash memory. If you want to program other areas of memory then you’ll want to be able to change the state of this and the BOOT1 pin which is multiplexed with PB2 on the STM32F40x parts. Details can be found in section 2.4 of the reference manual.

The other thing I did was to expose the additional JTAG interface pins also in order to give me several programming and debugging options. Finally, I connected the NRST pin on the ST-LINK to the NJRST pin on my STM32. That may be wrong, you might be better off connecting it to the NRST pin on the STM32. Perhaps someone out there can let me know in the comments below.

The SWD interface is connected as follows:

ST-LINK to STM32F405 Connections

I chose to expose the interface as a row of 0.1” pitch header and connected the two boards together with female to female patch leads.

The next step is to try and get some code running on the device. I’m using Atollic TrueSTUDIO Lite for this but I’m sure the process is pretty much the same for other IDEs too.

Project Creation Steps

To get up a program up and running on the new board you need to create a new project that targets the STM32F405 or whichever STM32 part you’re using. This will generate a new, compilable and runnable project. If you run the program up now in the debugger you should be able to step through the code, look at variable values and memory content. This shows that everything is working fine.

The final step is to sort out your clocks. TrueSTUDIO will create a default system_stm32f4xx.c file containing a clock configuration for you but unless you happen to be using a 25MHz crystal you’ll want to change it for one targeting your own board. Mine, for example, uses a 4MHz external clock.

STM32 Clock Configuration Tool

STM32 Clock Configuration Tool

The easiest way to do this (as far as I’m aware at least) is to use the clock configuration tool provided by STMicroelectronics for the purpose. You can download the tool and the accompanying notes from their website. I used the simple wizard mode, followed the instructions very carefully and where I wasn’t sure of a value, I looked it up in the original file created by TrueSTUDIO. Once the new system_stm32f4xx.c is generated, it needs to replace the one in your project. Simply re-build the project, make sure you can still launch and debug it and you’re up and running.

So I’ve been running with this setup for a couple of weeks now with very few problems. I did get a bit of a scare when I saw smoke pouring out of the Discovery’s USB port once but I think that was a pretty freak incident as it hasn’t happened since. I’ve had the odd unexplained debugger disconnection but given the amount of myopic prodding I tend to do with scope probes that could be more about what I’m doing than the what the debugger is doing. All in all I think it provides quite a good option to anyone wishing to experiment with creating their own STM32 boards without forking out for new kit.

LM60 circuit

Measuring the temperature of a PCB is quite a common thing to have to do in embedded systems and I have been working on a board that needs to do just that. There are dozens of ways to achieve this but for this project we chose to use the LM60 from Texas Instruments. The data sheet for this part can be found at http://www.ti.com/lit/ds/snis119d/snis119d.pdf. Note that for this project I’m using a PIC18 MCU and the CCS C compiler. If you’re using another development environment you’ll need to adapt the source code accordingly.

The LM60 isn’t exactly the most accurate temperature sensor in the world, accuracy in the data sheet is quoted at +-3.0-4.0% but in this application we were looking to provide PCB temperature over a CAN interface so that this can be monitored and correlated to any PCB failure that might occur in the field. The accuracy of the LM60 is good enough in this particular instance.

What this part does have going for it though is that it is small, it comes in SOT-23 as well as TO-92 packages, and it has a linear voltage output with respect to temperature which makes it pretty straightforward to convert to an absolute value in degrees Celsius using an ADC in a microcontroller. The LM60 is capable of measuring temperatures from -40 to +125 degrees and there is an automotive spec version, the LM60-Q1 which is fortunate as that was exactly what I needed.

The output voltage of the LM60 ranges from 174mV at -40ºC to 1205mV at 125ºC regardless of input voltage which can range from 2.7 to 10V. The output voltage with respect to temperature is calculated using the equation:

Vo = (+6.25mV/ºC x TºC) + 424mV

Translating that into the general form for a linear equation y = mx + c we can see that the two coefficients are:

Gradient m is 6.25
Offset c is 424

The next step is to figure out what values these voltages will be converted to by the ADC. The PIC18 I used in this project has 12 bit ADCs and the circuit runs at 3.6V. By dividing the ADC range (0-4095) by the voltage range (0-3600mV) we find that each 1mV at the ADC results in a value of 1.14… or it would if the value wasn’t an integer.

With that in mind we can convert the Vo to ºC equation into an ADC value to ºC equation by multiplying each of the coefficients by 1.14. This results in the new equation:

ADCin = (7.11 x TºC) + 482.3

Finally we need to move the equation around a bit as we’re not trying to calculate the ADCin value, we’re going to be getting that from the MCU. What we’re interested in is converting from the ADCin value to a temperature. Re-arranging the equation gives us:

TºC = (ADCin – 482.3) / 7.11

There’s just one other trick used in this driver and that is to avoid the use of floating point maths by multiplying up the values by a fixed amount and returning a result in milli-Celsius. Floating point maths, especially on an 8-bit PIC18, is an incredibly slow thing and best avoided where at all possible. To get around this I used a fairly unsophisticated technique of multiplying the input value and c-coefficient by 1,000,000 and the m-coefficient which will be the divisor in the equation by 1,000. By having the dividend an order of magnitude higher than the divisor and outputting in 1000ths of a degree, we can perform integer division without losing too much precision.

The final code for the driver is listed below. There are plenty of ways in which it could be extended and improved. For example, the code configures the ADC to use a VREF of VDD which is circuit dependent and affects the coefficients used in the equation. There are of course other options. You could for example fix VREF at a specific voltage if your PIC supports that. For example, the PIC18 I was using supports a fixed voltage reference (FVR) of 2.048V which would make better use of the 12-bit ADC and make it VDD independent (as long as VDD > 2.048V obviously!) Alternatively you could pass the value of VDD into the initialisation method and calculate the coefficients at that point.

lm60_driver.h

/*
 * File:   lm60_driver.h
 * Author: Jon Masters
 *
 * Created on 22 January 2014, 11:11
 *
 * Driver code for lm60 temperature sensing using a 10 or 12-bit ADC channel.
 * Assumes that the lm60 chip is being driven at VDD. All temperatures are
 * returned as degrees "milli" Celsius to avoid FP maths.
 *
 * PREREQUISITES:
 * ==============
 *
 * Code must include a #use delay.
 * Code should include either of the following:
 *
 * #device ADC=10
 * #device ADC=12
 *
 * If #device ADC=10 is used, include the following pre-processor definition:
 *
 * #define __LM60_10BIT_ADC
 *
 * Before using any ADC, CCS requires you to call setup_adc(). This driver
 * library will call this as part of the initialisation method. If you don't
 * want it to do that then include the following pre-processor directive:
 *
 * #define __LM60_INIT_NOSETUP
 *
 * You are then responsible for ensuring the call to setup_adc() is made.
 *
 * USAGE:
 * ======
 *
 * Start by calling the lm60_init method passing in the analog channel information
 * corresponding to the channel that the LM60 is connected to. Note that the method will,
 * by default initialise the ADC to use the internal clock. To override this
 * behaviour use __LM60_INIT_NOSETUP and call setup_adc() yourself.
 *
 * Once this is done you can call lm60_read_temp_mC() to read the temperature in
 * thousandths of a degree Celsius.
 */

#ifndef LM60_DRIVER_H
#define     LM60_DRIVER_H

#ifdef     __cplusplus
extern "C" {
#endif

/* Constants used to convert voltages to temperatures. */
#define lm60_y_intercept 482300000
#define lm60_y_grad 7110

/*
 * Holds the index of the ADC channel that the LM60 Vout is connected to.
 */
int8 lm60_adc_channel;

/*
 * Initialises the driver library.
 *
 * ARGS:
 *  sANx - the analog port that the LM60 Vout is connected to. Use the constants
 *          defined in the PIC header e.g. sAN0
 *  channel - the index of the analog channel that matches the sANx argument e.g.
 *          for sAN0, this will be the index 0, sAN25 will be 25.
 *
 * RETURNS:
 *  Nothing.
 */
void lm60_init(unsigned int16 sANx, int8 channel);

/*
 * Returns a temperature reading from the LM60 in milli-Celsius i.e. in 1/1000's
 * of a Celsius. E.g. 25C is returned as 25000mC. The means that FP maths isn't
 * required which should improve performance.
 *
 * ARGS:
 *  NONE.
 *
 * RETURNS:
 *  Temperature in mC.
 */
signed int32 lm60_read_temp_mC();

#ifdef     __cplusplus
}
#endif

#endif     /* LM60_DRIVER_H */

lm60_driver.c

/*
 * File:   lm60_driver.c
 * Author: Jon Masters
 *
 * Created on 22 January 2014, 11:05
 *
 * See header for pre-reqs, usage and function documentation.
 */

#include "lm60_driver.h"

void lm60_init(unsigned int16 sANx, int8 channel)
{
   
#ifndef __LM60_INIT_NOSETUP
    setup_adc(ADC_CLOCK_INTERNAL | ADC_TAD_MUL_16);
#endif //__LM60_INIT_NOSETUP

    setup_adc_ports(sANx, VSS_VDD);
    lm60_adc_channel = channel;
}

signed int32 lm60_read_temp_mC()
{
    set_adc_channel(lm60_adc_channel);
    delay_us(10);
    unsigned int16 r = read_adc();

#ifdef __LM60_10BIT_ADC
    r = r << 2;
#endif

    unsigned int32 adc_val = r;
    adc_val *= 1000000;

    // calculate result in milli-Celsius
    signed int32 rv = adc_val - lm60_y_intercept;
    rv /= lm60_y_grad;
    return rv;
}

As always I welcome your feedback. Please feel free to post comments / questions below, especially if I’ve made some horrendous gaff! Otherwise, I hope this has been in some way useful and I’ll see you next time.

PWM LEDS Breadboard

PWM LEDS Breadboard

In last few posts I looked at ways to chase a few LEDs and dim them using the PWM functionality of the PIC16F887 which is mounted on the PICkit2 demo board. I decided that I wanted to go a bit further with this though and to combine the two things together and see if I could get some smoother chase action going by using PWM to pulse the LEDs on and off rather than switch them on and off.

I had to go ditch the demo board to do this though as the LEDs on the demo board aren’t really connected to suitable inputs, unless of course you implement PWM in code using GPIO which is possible. I happen to have a PIC18F26K22 knocking about in a 28 pin DIP package, some breadboard and a bunch of other bits and bobs so I decided to build my own demo board for this project.

The first thing I did was to build a basic circuit that would allow me to power, program and run the PIC. I then checked the PIC data sheet to see where the CCP (capture, compare, PWM) pins are and added in a few LEDs along with current limiting resistors. Finally I dug out a 10K variable resistor and wired that up to one of the analog inputs of the PIC. The circuit diagram is shown below.

PWM LEDs Schematic

PWM LEDs Schematic

As the PIC18F26K22 is not supported by the PICkit 2 I also had to get hold of a PICkit 3 programmer. It’s time I just bought the programmer on its own rather than buy one with a demo board. After all, I’m creating my own board now right?

Next I had to figure out exactly what it was that I wanted the thing to do. I decided that I’d keep the chasing LEDs but use PWM to dim the brightness using a set of brightness values derived from a sine wave. Each of the three LEDs would start from a different point in the sine wave so that the first is at 0 degrees, the second at 45 degrees and the last one at 90 degrees. I decided to implement this using a look up table of byte values. The variable resistor, I decided, would be used to control speed.

Having moved to a new PIC, I had to generate a new set of config values. Once again I used the tool built into the MPLAB IDE to do this. I set it to use the internal oscillator and I switched LVP off as requested by the PICKit 3 programmer. Generating the code yielded the following which I added into the main.c code file.

// PIC18F26K22 Configuration Bit Settings

// CONFIG1H
#pragma config FOSC = INTIO67   // Oscillator Selection bits (Internal oscillator block)
#pragma config PLLCFG = OFF     // 4X PLL Enable (Oscillator used directly)
#pragma config PRICLKEN = ON    // Primary clock enable bit (Primary clock enabled)
#pragma config FCMEN = OFF      // Fail-Safe Clock Monitor Enable bit (Fail-Safe Clock Monitor disabled)
#pragma config IESO = OFF       // Internal/External Oscillator Switchover bit (Oscillator Switchover mode disabled)

// CONFIG2L
#pragma config PWRTEN = OFF     // Power-up Timer Enable bit (Power up timer disabled)
#pragma config BOREN = SBORDIS  // Brown-out Reset Enable bits (Brown-out Reset enabled in hardware only (SBOREN is disabled))
#pragma config BORV = 190       // Brown Out Reset Voltage bits (VBOR set to 1.90 V nominal)

// CONFIG2H
#pragma config WDTEN = ON       // Watchdog Timer Enable bits (WDT is always enabled. SWDTEN bit has no effect)
#pragma config WDTPS = 32768    // Watchdog Timer Postscale Select bits (1:32768)

// CONFIG3H
#pragma config CCP2MX = PORTC1  // CCP2 MUX bit (CCP2 input/output is multiplexed with RC1)
#pragma config PBADEN = ON      // PORTB A/D Enable bit (PORTB pins are configured as analog input channels on Reset)
#pragma config CCP3MX = PORTB5  // P3A/CCP3 Mux bit (P3A/CCP3 input/output is multiplexed with RB5)
#pragma config HFOFST = ON      // HFINTOSC Fast Start-up (HFINTOSC output and ready status are not delayed by the oscillator stable status)
#pragma config T3CMX = PORTC0   // Timer3 Clock input mux bit (T3CKI is on RC0)
#pragma config P2BMX = PORTB5   // ECCP2 B output mux bit (P2B is on RB5)
#pragma config MCLRE = EXTMCLR  // MCLR Pin Enable bit (MCLR pin enabled, RE3 input pin disabled)

// CONFIG4L
#pragma config STVREN = ON      // Stack Full/Underflow Reset Enable bit (Stack full/underflow will cause Reset)
#pragma config LVP = OFF        // Single-Supply ICSP Enable bit (Single-Supply ICSP disabled)
#pragma config XINST = OFF      // Extended Instruction Set Enable bit (Instruction set extension and Indexed Addressing mode disabled (Legacy mode))

// CONFIG5L
#pragma config CP0 = OFF        // Code Protection Block 0 (Block 0 (000800-003FFFh) not code-protected)
#pragma config CP1 = OFF        // Code Protection Block 1 (Block 1 (004000-007FFFh) not code-protected)
#pragma config CP2 = OFF        // Code Protection Block 2 (Block 2 (008000-00BFFFh) not code-protected)
#pragma config CP3 = OFF        // Code Protection Block 3 (Block 3 (00C000-00FFFFh) not code-protected)

// CONFIG5H
#pragma config CPB = OFF        // Boot Block Code Protection bit (Boot block (000000-0007FFh) not code-protected)
#pragma config CPD = OFF        // Data EEPROM Code Protection bit (Data EEPROM not code-protected)

// CONFIG6L
#pragma config WRT0 = OFF       // Write Protection Block 0 (Block 0 (000800-003FFFh) not write-protected)
#pragma config WRT1 = OFF       // Write Protection Block 1 (Block 1 (004000-007FFFh) not write-protected)
#pragma config WRT2 = OFF       // Write Protection Block 2 (Block 2 (008000-00BFFFh) not write-protected)
#pragma config WRT3 = OFF       // Write Protection Block 3 (Block 3 (00C000-00FFFFh) not write-protected)

// CONFIG6H
#pragma config WRTC = OFF       // Configuration Register Write Protection bit (Configuration registers (300000-3000FFh) not write-protected)
#pragma config WRTB = OFF       // Boot Block Write Protection bit (Boot Block (000000-0007FFh) not write-protected)
#pragma config WRTD = OFF       // Data EEPROM Write Protection bit (Data EEPROM not write-protected)

// CONFIG7L
#pragma config EBTR0 = OFF      // Table Read Protection Block 0 (Block 0 (000800-003FFFh) not protected from table reads executed in other blocks)
#pragma config EBTR1 = OFF      // Table Read Protection Block 1 (Block 1 (004000-007FFFh) not protected from table reads executed in other blocks)
#pragma config EBTR2 = OFF      // Table Read Protection Block 2 (Block 2 (008000-00BFFFh) not protected from table reads executed in other blocks)
#pragma config EBTR3 = OFF      // Table Read Protection Block 3 (Block 3 (00C000-00FFFFh) not protected from table reads executed in other blocks)

// CONFIG7H
#pragma config EBTRB = OFF      // Boot Block Table Read Protection bit (Boot Block (000000-0007FFh) not protected from table reads executed in other blocks)

I also set the clock frequency by setting the IRCF bits in the OSCCON register. This is the first statement in my main() function.

/*
 * The main function.
 */
void main()
{
    // set up the internal oscillator
    OSCCONbits.IRCF = 0b111;  // 16MHz

    while(1)
    {
    }
}

Knowing I was going to need a delay function later on, I defined _XTAL_FREQ at 16MHz and created a millisecond delay function based on the __delay_ms macro.

#define _XTAL_FREQ 16000000

/*
 * Implements a delay of a number of milliseconds.
 */
void delay(int ms)
{
    while(ms-- > 0)
    {
        __delay_ms(1);
    }
}

The approach I took to generating the sine table of brightness values was to calculate the values using a spreadsheet. I arbitrarily decided on 100 values and created a list of angles in 3.6 degree steps. In the column next to it I generate raw sine values for each angle which I multiplied by 128 and added 127. This gave me a range of values between 0 and 255 and I transferred those into an array of values in the program.

/*
 * Defines a list of values that are derived from a sine wave that
 * can be used to smoothly pulse the PWM duty cycle and the LEDs.
 */
const char brightness_values[] = {
    128, 136, 144, 152, 160, 168, 175, 182, 190, 197,
    203, 210, 216, 221, 227, 232, 236, 240, 244, 247,
    250, 252, 254, 255, 255, 255, 255, 255, 254, 252,
    250, 247, 244, 240, 236, 232, 227, 221, 216, 210,
    203, 197, 190, 182, 175, 168, 160, 152, 144, 136,
    128, 120, 112, 104,  96,  88,  81,  74,  66,  59,
     53,  46,  40,  35,  29,  24,  20,  16,  12,   9,
      6,   4,   2,   1,   0,   0,   0,   1,   2,   4,
      6,   9,  12,  16,  20,  24,  29,  35,  40,  46,
     53,  59,  66,  74,  81,  88,  96, 104, 112, 120
};

#define BRIGHTNESS_VALUES_COUNT 100

These values are going to act as my duty cycles to control the LED brightness.

Once I had done that I added in a method to initialise the PIC PWM on the three CCP modules I wanted to use to drive the LEDs. The configuration on the PIC18 is unsurprisingly slightly different to that of the PIC16 I was using before so, having used the previous trick of turning the steps in the data sheet into comments, I went back to the data sheet and worked through each step to make sure everything was properly configured.

I wanted to make sure my resolution was still 8 bits to match my brightness data so having selected a TMRx divider of 8 and a PRx value of 64, I added a new sheet to my ‘useful calculations’ spreadsheet to do the math and check it.

PWM Calculator

PWM Calculator

It turns out that the resolution is 8 bits with those settings and I have a period frequency of 7.7KHz which is great. The final code for the initPWM() method is below.

/*
 * Initialises PWM on the three PWM generators.
 */
void initPWM()
{
    // 1. Disable the CCPx pin output driver by setting the associated TRIS bit.
    TRISC = 0b00000110; // RC1 and RC2
    TRISBbits.RB5 = 1;

    // 2. Select the 8-bit TimerX resource, (Timer2, Timer4 or Timer6) to be
    //    used for PWM generation by setting the CxTSEL bits in the
    //    CCPTMRSx register.
    CCPTMRS0bits.C1TSEL = 0; // user timer 2

    // 3. Load the PRx register for the selected TimerX with the PWM period value.
    PR2 = 0x40; // decimal 64 period

    // 4. Configure the CCP module for the PWM mode by loading the CCPxCON
    //    register with the appropriate values.
    CCP1CONbits.CCP1M = 0b1100;
    CCP2CONbits.CCP2M = 0b1100;
    CCP3CONbits.CCP3M = 0b1100;

    // 5. Load the CCPRxL register and the DCxB bits of the CCPxCON register,
    //    with the PWM duty cycle value.
    CCPR1L = 128 >> 2;
    CCPR2L = 128 >> 2;
    CCPR3L = 128 >> 2;

    CCP1CONbits.DC1B = 128 & 0b00000011; // 128 is 50% duty cycle
    CCP2CONbits.DC2B = 128 & 0b00000011; // 128 is 50% duty cycle
    CCP3CONbits.DC3B = 128 & 0b00000011; // 128 is 50% duty cycle

    // 6. Configure and start the 8-bit TimerX resource:
    //    - Clear the TMRxIF interrupt flag bit of the
    //      PIR2 or PIR4 register. See Note 1 below.
    PIR1bits.TMR2IF = 0;

    //    - Configure the TxCKPS bits of the TxCON
    //      register with the Timer prescale value.
    T2CONbits.T2CKPS = 0b11; // prescale 8

    //    - Enable the Timer by setting the TMRxON
    //      bit of the TxCON register.
    T2CONbits.TMR2ON = 1;

    // 7. Enable PWM output pin:
    //    - Wait until the Timer overflows and the TMRxIF bit of the PIR2 or PIR4
    //      register is set. See Note 1 below.
    while(PIR1bits.TMR2IF == 0){}

    //    - Enable the CCPx pin output driver by clearing the associated TRIS bit.
    TRISC = 0;
    TRISBbits.RB5 = 0;
}

This method can then be called from the main() function prior to the while loop to initialise PWM across the three outputs. I also added a separate method to allow the duty cycles for each output to be set individually.

/*
 * Sets the PWM values for each of the three PWM controllers.
 */
void setPWM(char value1, char value2, char value3)
{
    CCPR1L = value1 >> 2;
    CCP1CONbits.DC1B = value1 & 0b00000011;

    CCPR2L = value2 >> 2;
    CCP2CONbits.DC2B = value2 & 0b00000011;

    CCPR3L = value3 >> 2;
    CCP3CONbits.DC3B = value3 & 0b00000011;
}

All this method does is replace the PWM duty cycle value in each of the CCPRxL and CCPxCON registers. The bit shifting and masking ensures that the LSBs end up in the DCxB bits of the CCPxCON register while the MSBs are written to the CCPRxL registers.

The next task was to add in the code to configure the ADC to read the data from the variable resistor. Again, I took the initialisation procedure from the data sheet and turned it into a set of comments that I could then take my time filling in. There are a couple of things that needed some thought. Firstly the ADC clock needed to be divided by 16 to bring it into the range of speeds that the ADC can cope with. This is all detailed in the data sheet in table 17-1 so there’s no mysterious voodoo in any of that. The other thing that needs to be calculated is the acquisition delay length. The ADC uses an sample and hold circuit based on an RC network and the acquisition time is the amount of time it’ll take to charge the capacitor before performing the conversion. Obviously you need to give it enough time to do that job and the way to do that is to set a period in TAD units in the ACQT bits of the ADCON2 register. Equation 17-1 is provided in the data sheet to help calculate the minimum TAD required. Again, I popped it into my ever growing spreadsheet of useful calculations and selected 12 as my value. This leaves plenty of time for signal capture in this application.

/*
 * Initialises the variable resistor ADC configuration.
 */
void initVR()
{
    // 1. Configure Port:
    //  - Disable pin output driver(See TRIS register)
    TRISAbits.RA0 = 1;

    //  - Configure pin as analog
    ANSELAbits.ANSA0 = 1;

    // 2. Configure the ADC module:
    //  - Select ADC conversion clock
    ADCON2bits.ADCS = 0b101; // Fosc/16

    //  - Configure voltage reference
    ADCON1bits.PVCFG = 0; // +ref is VDD
    ADCON1bits.NVCFG = 0; // -ref is VSS

    //  - Select ADC input channel
    ADCON0bits.CHS = 0; // connect AN0 to the ADC

    //  - Select result format
    ADCON2bits.ADFM = 0; // left justified

    //  - Select acquisition delay
    ADCON2bits.ACQT = 0b101; // 12 TAD

    //  - Turn on ADC module
    ADCON0bits.ADON = 1;
}

As with the previous programs I set the result alignment to be left aligned so that I could return a single byte value that worked across the whole of the potentiometer range. There’s plenty of resolution there for something this simple. Reference voltages again are VDD and VSS (GND) as that matches the potentiometer circuit and the analog input is configured to be taken from AN0 which is multiplexed with RA0.

I also added in a function to read and return the potentiometer value. This simply starts a conversion and waits for it to complete by set the GO bit in ADCON0 and waiting for it to clear. We could use interrupts for this which would be a neater solution but this will do for now. Finally the result is returned.

/*
 * Reads the value of the variable resistor into a single byte.
 */
char readVR()
{
    // start a conversion
    ADCON0bits.GO = 1;

    // wait for it to complete
    while(ADCON0bits.GO == 1){}

    // return the top 8 bits of the value
    return ADRESH;
}

Almost there now, the last thing to do was to update the main function to call of these methods and light up the LEDs.

/*
 * The main function.
 */
void main()
{
    // set up the internal oscillator
    OSCCONbits.IRCF = 0b111;  // 16MHz

    // init the PWM
    initPWM();

    // init the VR
    initVR();

    // brightness counter
    char val1 = 0;
    char val2 = 0;
    char val3 = 0;

    while(1)
    {
        // create a set of values at 45 degrees to each other in the table
        val1 = (val1 + 1) % 100;
        val2 = (val1 + 33) % 100;
        val3 = (val1 + 66) % 100;

        // set the PWM values
        setPWM(brightness_values[val1],
               brightness_values[val2],
               brightness_values[val3]);

        // create a delay proportional to the VR value
        delay((readVR() / 10) + 2);
    }
}

It’s all very straightforward. I created three values to hold individual indexes into the brightness values for each LED. They start at different points in the cycle and increment each time through the loop. The modulus operator resets the index back to the start of the look up table if they wrap around. Meanwhile the delay is set by the potentiometer reading from the AD conversion so that the speed of the chasing can be set. The delay value is manipulated a bit because I wanted to limit it from 2ms to roughly 30ms. Those values were determined through experimentation to see what felt about right.

The complete main.c code is included below.

#include <htc.h>
#include <pic18f26k22.h>


// PIC18F26K22 Configuration Bit Settings

// CONFIG1H
#pragma config FOSC = INTIO67   // Oscillator Selection bits (Internal oscillator block)
#pragma config PLLCFG = OFF     // 4X PLL Enable (Oscillator used directly)
#pragma config PRICLKEN = ON    // Primary clock enable bit (Primary clock enabled)
#pragma config FCMEN = OFF      // Fail-Safe Clock Monitor Enable bit (Fail-Safe Clock Monitor disabled)
#pragma config IESO = OFF       // Internal/External Oscillator Switchover bit (Oscillator Switchover mode disabled)

// CONFIG2L
#pragma config PWRTEN = OFF     // Power-up Timer Enable bit (Power up timer disabled)
#pragma config BOREN = SBORDIS  // Brown-out Reset Enable bits (Brown-out Reset enabled in hardware only (SBOREN is disabled))
#pragma config BORV = 190       // Brown Out Reset Voltage bits (VBOR set to 1.90 V nominal)

// CONFIG2H
#pragma config WDTEN = ON       // Watchdog Timer Enable bits (WDT is always enabled. SWDTEN bit has no effect)
#pragma config WDTPS = 32768    // Watchdog Timer Postscale Select bits (1:32768)

// CONFIG3H
#pragma config CCP2MX = PORTC1  // CCP2 MUX bit (CCP2 input/output is multiplexed with RC1)
#pragma config PBADEN = ON      // PORTB A/D Enable bit (PORTB pins are configured as analog input channels on Reset)
#pragma config CCP3MX = PORTB5  // P3A/CCP3 Mux bit (P3A/CCP3 input/output is multiplexed with RB5)
#pragma config HFOFST = ON      // HFINTOSC Fast Start-up (HFINTOSC output and ready status are not delayed by the oscillator stable status)
#pragma config T3CMX = PORTC0   // Timer3 Clock input mux bit (T3CKI is on RC0)
#pragma config P2BMX = PORTB5   // ECCP2 B output mux bit (P2B is on RB5)
#pragma config MCLRE = EXTMCLR  // MCLR Pin Enable bit (MCLR pin enabled, RE3 input pin disabled)

// CONFIG4L
#pragma config STVREN = ON      // Stack Full/Underflow Reset Enable bit (Stack full/underflow will cause Reset)
#pragma config LVP = OFF        // Single-Supply ICSP Enable bit (Single-Supply ICSP disabled)
#pragma config XINST = OFF      // Extended Instruction Set Enable bit (Instruction set extension and Indexed Addressing mode disabled (Legacy mode))

// CONFIG5L
#pragma config CP0 = OFF        // Code Protection Block 0 (Block 0 (000800-003FFFh) not code-protected)
#pragma config CP1 = OFF        // Code Protection Block 1 (Block 1 (004000-007FFFh) not code-protected)
#pragma config CP2 = OFF        // Code Protection Block 2 (Block 2 (008000-00BFFFh) not code-protected)
#pragma config CP3 = OFF        // Code Protection Block 3 (Block 3 (00C000-00FFFFh) not code-protected)

// CONFIG5H
#pragma config CPB = OFF        // Boot Block Code Protection bit (Boot block (000000-0007FFh) not code-protected)
#pragma config CPD = OFF        // Data EEPROM Code Protection bit (Data EEPROM not code-protected)

// CONFIG6L
#pragma config WRT0 = OFF       // Write Protection Block 0 (Block 0 (000800-003FFFh) not write-protected)
#pragma config WRT1 = OFF       // Write Protection Block 1 (Block 1 (004000-007FFFh) not write-protected)
#pragma config WRT2 = OFF       // Write Protection Block 2 (Block 2 (008000-00BFFFh) not write-protected)
#pragma config WRT3 = OFF       // Write Protection Block 3 (Block 3 (00C000-00FFFFh) not write-protected)

// CONFIG6H
#pragma config WRTC = OFF       // Configuration Register Write Protection bit (Configuration registers (300000-3000FFh) not write-protected)
#pragma config WRTB = OFF       // Boot Block Write Protection bit (Boot Block (000000-0007FFh) not write-protected)
#pragma config WRTD = OFF       // Data EEPROM Write Protection bit (Data EEPROM not write-protected)

// CONFIG7L
#pragma config EBTR0 = OFF      // Table Read Protection Block 0 (Block 0 (000800-003FFFh) not protected from table reads executed in other blocks)
#pragma config EBTR1 = OFF      // Table Read Protection Block 1 (Block 1 (004000-007FFFh) not protected from table reads executed in other blocks)
#pragma config EBTR2 = OFF      // Table Read Protection Block 2 (Block 2 (008000-00BFFFh) not protected from table reads executed in other blocks)
#pragma config EBTR3 = OFF      // Table Read Protection Block 3 (Block 3 (00C000-00FFFFh) not protected from table reads executed in other blocks)

// CONFIG7H
#pragma config EBTRB = OFF      // Boot Block Table Read Protection bit (Boot Block (000000-0007FFh) not protected from table reads executed in other blocks)

#define _XTAL_FREQ 16000000

/*
 * Defines a list of values that are derived from a sine wave that
 * can be used to smoothly pulse the PWM duty cycle and the LEDs.
 */
const char brightness_values[] = {
    128, 136, 144, 152, 160, 168, 175, 182, 190, 197,
    203, 210, 216, 221, 227, 232, 236, 240, 244, 247,
    250, 252, 254, 255, 255, 255, 255, 255, 254, 252,
    250, 247, 244, 240, 236, 232, 227, 221, 216, 210,
    203, 197, 190, 182, 175, 168, 160, 152, 144, 136,
    128, 120, 112, 104,  96,  88,  81,  74,  66,  59,
     53,  46,  40,  35,  29,  24,  20,  16,  12,   9,
      6,   4,   2,   1,   0,   0,   0,   1,   2,   4,
      6,   9,  12,  16,  20,  24,  29,  35,  40,  46,
     53,  59,  66,  74,  81,  88,  96, 104, 112, 120
};

#define BRIGHTNESS_VALUES_COUNT 100

/*
 * Implements a delay of a number of milliseconds.
 */
void delay(int ms)
{
    while(ms-- > 0)
    {
        __delay_ms(1);
    }
}

/*
 * Initialises the variable resistor ADC configuration.
 */
void initVR()
{
    // 1. Configure Port:
    //  - Disable pin output driver(See TRIS register)
    TRISAbits.RA0 = 1;

    //  - Configure pin as analog
    ANSELAbits.ANSA0 = 1;

    // 2. Configure the ADC module:
    //  - Select ADC conversion clock
    ADCON2bits.ADCS = 0b101; // Fosc/16

    //  - Configure voltage reference
    ADCON1bits.PVCFG = 0; // +ref is VDD
    ADCON1bits.NVCFG = 0; // -ref is VSS

    //  - Select ADC input channel
    ADCON0bits.CHS = 0; // connect AN0 to the ADC

    //  - Select result format
    ADCON2bits.ADFM = 0; // left justified

    //  - Select acquisition delay
    ADCON2bits.ACQT = 0b101; // 12 TAD

    //  - Turn on ADC module
    ADCON0bits.ADON = 1;
}

/*
 * Reads the value of the variable resistor into a single byte.
 */
char readVR()
{
    // start a conversion
    ADCON0bits.GO = 1;

    // wait for it to complete
    while(ADCON0bits.GO == 1)
    {
        delay(1);
    }

    // return the top 8 bits of the value
    return ADRESH;
}

/*
 * Initialises PWM on the three PWM generators.
 */
void initPWM()
{
    // 1. Disable the CCPx pin output driver by setting the associated TRIS bit.
    TRISC = 0b00000110; // RC1 and RC2
    TRISBbits.RB5 = 1;

    // 2. Select the 8-bit TimerX resource, (Timer2, Timer4 or Timer6) to be
    //    used for PWM generation by setting the CxTSEL bits in the
    //    CCPTMRSx register.
    CCPTMRS0bits.C1TSEL = 0; // user timer 2

    // 3. Load the PRx register for the selected TimerX with the PWM period value.
    PR2 = 0x40; // decimal 64 period

    // 4. Configure the CCP module for the PWM mode by loading the CCPxCON
    //    register with the appropriate values.
    CCP1CONbits.CCP1M = 0b1100;
    CCP2CONbits.CCP2M = 0b1100;
    CCP3CONbits.CCP3M = 0b1100;

    // 5. Load the CCPRxL register and the DCxB bits of the CCPxCON register,
    //    with the PWM duty cycle value.
    CCPR1L = 128 >> 2;
    CCPR2L = 128 >> 2;
    CCPR3L = 128 >> 2;

    CCP1CONbits.DC1B = 128 & 0b00000011; // 128 is 50% duty cycle
    CCP2CONbits.DC2B = 128 & 0b00000011; // 128 is 50% duty cycle
    CCP3CONbits.DC3B = 128 & 0b00000011; // 128 is 50% duty cycle

    // 6. Configure and start the 8-bit TimerX resource:
    //    - Clear the TMRxIF interrupt flag bit of the
    //      PIR2 or PIR4 register. See Note 1 below.
    PIR1bits.TMR2IF = 0;

    //    - Configure the TxCKPS bits of the TxCON
    //      register with the Timer prescale value.
    T2CONbits.T2CKPS = 0b11; // prescale 8

    //    - Enable the Timer by setting the TMRxON
    //      bit of the TxCON register.
    T2CONbits.TMR2ON = 1;

    // 7. Enable PWM output pin:
    //    - Wait until the Timer overflows and the TMRxIF bit of the PIR2 or PIR4
    //      register is set. See Note 1 below.
    while(PIR1bits.TMR2IF == 0){}

    //    - Enable the CCPx pin output driver by clearing the associated TRIS bit.
    TRISC = 0;
    TRISBbits.RB5 = 0;
}

/*
 * Sets the PWM values for each of the three PWM controllers.
 */
void setPWM(char value1, char value2, char value3)
{
    CCPR1L = value1 >> 2;
    CCP1CONbits.DC1B = value1 & 0b00000011;

    CCPR2L = value2 >> 2;
    CCP2CONbits.DC2B = value2 & 0b00000011;

    CCPR3L = value3 >> 2;
    CCP3CONbits.DC3B = value3 & 0b00000011;
}

/*
 * The main function.
 */
void main()
{
    // set up the internal oscillator
    OSCCONbits.IRCF = 0b111;  // 16MHz

    // init the PWM
    initPWM();

    // init the VR
    initVR();

    // brightness counter
    char val1 = 0;
    char val2 = 0;
    char val3 = 0;

    while(1)
    {
        // create a set of values at 45- to each other in the table
        val1 = (val1 + 1) % 100;
        val2 = (val1 + 33) % 100;
        val3 = (val1 + 66) % 100;

        // set the PWM values
        setPWM(brightness_values[val1],
               brightness_values[val2],
               brightness_values[val3]);

        // create a delay proportional to the VR value
        delay((readVR() / 10) + 2);
    }
}

So there it is, sinusoidally pulsed, PWM implemented, variable speed LED chasing. At some point I might look at driving a string of LEDs this way. Perhaps it could be a project for later in the year. In the meantime though I’m going to continue to explore some of the other features of the PIC18F26K22 and see what fun can be had.

Once again, please feel free to contact me with any comments or suggestions. I’m always happy to hear from you!

Getting Started with PICs

Getting Started with PICs

At the end of the last post I had managed to cobble something together that replicates the demo code that the PIC on the PICkit 2 demo board comes with complete with chasing LEDs, direction changing and speed control. Having done that it occurred to me that one thing I’d really like to try out is to see if I could get the LEDs to dim using the PIC’s onboard PWM control capabilities. I had a quick check of the data sheet and luckily enough, three of the four PWM outputs happen to be multiplexed with port D GPIO which means the demo board LEDs can be driven from them. Coincidence? Dunno but it made me happy anyway!

Okay so let’s back up a here and get a little perspective on this whole thing by tackling two fairly obvious questions that might pop up if you’re fairly new to this stuff. Firstly, what’s PWM and secondly why do you need it to dim LEDs?

Starting with the second question first, LED brightness can be controlled using variable voltages much like a normal tungsten bulb but in digital circuits signals are either on or off so in order to dim a LED in a digital circuit we need to find another way. This is where PWM (Pulse Width Modulation) come a long and gives us a big wet, sloppy kiss on the lips.

The way this works is basically to say that if you rapidly switch something on and off, say for example an electric motor, it’s a bit like running it at a lower voltage and how fast the motor will go depends on the amount of time it is switched on when compared to the amount of time it’s switched off. The terminology can be seen in the diagram below which comes from the PIC data sheet.

PWM Terminology

PWM Terminology

The total length of time for an on / off cycle is called the ‘period’ and this can also be expressed as a frequency which is 1/period. The amount of time that the circuit is switched on for during the period is called the ‘duty cycle’ and this is where the work gets done, motors get turned and LEDs light up. The stuff on there about timer 2 should become clearer later on.

So how is all this implemented in a PIC16F887 then? Well the PWM functionality comes as part of the ‘Capture Compare PWM’ (CCP) modules of which there are two in this particular PIC. These are cunningly named CCP1 and CCP2 and the one I chose to use was CCP1 because it’s the one that connects to the LEDs.

Configuring the PWM peripheral consists of setting the period, configuring how the duty cycle will be implemented (e.g. active high, active low and on which pins), setting the duty cycle and configuring the timer that’ll keep the whole thing going.

Easy peasy right? Err sort of!

Okay so the next thing to do was to fire up MPLAB and create a new project. After creating the project I grabbed the code from the previous PIC post as a starting point and deleted the button code. The ADC code I kept because I wanted to use it to control the LED dimming. I also deleted out the code that did the chasing so I ended up with an empty while loop. While I was in there I refactored the button initialisation code into a new function to get it out of the way. Basically all that was left in my main function was the oscillator configuration, a call to initialise the variable resistor ADC and an empty while loop.

Once I did that I used a little trick that I use a lot when I have a long and involved procedure or algorithm I need to work through and that is to add in all the comments first and add the code afterwards. The reason I sometimes do this is twofold. Firstly it means I can sketch out everything that needs to happen in comments before I get bogged down in how to actually get it to happen in that language on that platform which means I don’t forget anything. Secondly it allows me to tackle the code in any order I want to so i can start out by filling in the easy stuff and work through the complex stuff later on.

The data sheet helps us out a lot if we take that approach because it documents the exact initialisation procedure in chapter 11.5.7 Setup for PWM Operation. Hurrah! I copied the text out of the data sheet, pasted it into the code editor and converted each step into a comment. Now I just needed to fill in the blanks.

The first step is to temporarily set the pins we’re outputting to as inputs. The PWM output can go to four pins called P1A, P1B, P1C and P1D which are multiplexed with RC2, RD5, RD6 and RD7 respectively. Incidentally, the reason I elected to use CCP1 is because I needed to get output to the pins with LEDs attached and that can be done by using a trick called ‘Pulse Steering Mode’. Anyhow, as I had pins on both port B and port C and I’m not using them for anything else, I simply set them all to input.

    // Disable the PWM pin (CCPx) output drivers as an input by setting
    // the associated TRIS bit.
    TRISC = 0xff;  // blat the lot, we're not using them
    TRISD = 0xff;  // for anything else

The next job is to set the period. This is a bit of an awkward process because the period has dependencies not only on the period register (PR2) but also on the oscillator frequency and any pre-scaling on timer 2 that may or may not be set up. There is a full discussion including equations for calculating everything you need in the data sheet in section 11.5.1, 11.5.2 and 11.5.3. There are also a few examples in table 11-4 which I used as a starting point for subsequent tweakery.

    // Set the PWM period by loading the PR2 register.
    PR2 = 0x40; // this combined with a TMR2 prescale of 16 gives us
                // a PWM frequency of just under 2kHz

The value 0x40 is decimal 64 which is a quarter of 255, the maximum value for a single byte such as the one returned by my variable resistor reading function. If you check out equation 11-3 you’ll see that to get a fully on signal you need to supply a value for the duty cycle that is four times the value in the PR2 register. I ran through the calculations to see what my period frequency would end up as and it came out at just under 2KHz which seemed pretty good to me.

The next thing to do is to configure the CCP module so that it knows what kind of output you’re after. As I wanted to use pulse steering mode to direct the same output to RD5-7 I needed to use single output mode and that’s configured with the P1M bits in the CCP1CON register. I also set the extended CCP mode bits to tell that I wanted all output to be active high and finally set the pulse steering mode register PSTRCON to send output to the three LEDs.

    // Configure the CCP module for the PWM mode by loading the CCPxCON
    // register with the appropriate values.
    CCP1CONbits.P1M = 0b00;     // Single output mode
    CCP1CONbits.CCP1M = 0b1100; // ECCP Mode PWM P1A-D active high
    PSTRCON = 0b00011110;       // set up pulse steering mode on port D pins

Next I loaded up the duty cycle which is a 10bit value so it’s stored across a couple of registers. The least significant bits are held in the DC1B bits of the CCP1CON register if you’re using CCP1 and the most significant bits are written to CCPR1L.

    // Set the PWM duty cycle by loading the CCPRxL register and DCxB
    // bits of the CCPxCON register.
    CCP1CONbits.DC1B = 0x80 & 0b11;
    CCPR1L = 0x80 >> 2; // 0x80 is approx 50% duty cycle... I think!

In the code above I took an initial duty cycle value 0x80, masked off the two LSBs and wrote them to DC1B. I then shifted the same initial duty cycle value, shifted right by two places (they’re written to the other register) and wrote the result to the CCPR1L register.

The next job is to configure timer 2 which is going to drive the whole thing. The timer speed depends on the oscillator frequency and you can vary it by using pre-scalers and post-scalers. As mentioned above I used a pre-scaler value of 16 which is configured by setting the T2CKPS bits in the T2CON register to 0b10.

    // Configure and start Timer2:
    //  • Clear the TMR2IF interrupt flag bit of the PIR1 register.
    PIR1bits.TMR2IF = 0;

    //  • Set the Timer2 prescale value by loading the T2CKPS bits of the
    //    T2CON register.
    T2CONbits.T2CKPS = 0b10; // 16

    //  • Enable Timer2 by setting the TMR2ON bit of the T2CON register.
    T2CONbits.TMR2ON = 1;

Once all that is done the timer is switched on but we need to wait until a new PWM cycle has started before carrying on.

    // Enable PWM output after a new PWM cycle has started:
    //  • Wait until Timer2 overflows(TMR2IF bit of the PIR1 register is set).
    while(PIR1bits.TMR2IF == 0){}

Finally we let the output flow out of the pins.

    //  • Enable the CCPxp in output driver by clearing the associated
    //    TRIS bit.
    TRISD = 0;

One last thing I did was to set the LED adjacent to the dimmed ones to on so that I could compare brightness levels and get some idea of how well it was working.

    // switch on one of the adjacent LEDs for brightness comparison
    PORTD = 0b00010000;

Then of course you have your empty, infinite while loop.

If you run the code at this point the LEDs on RD5-7 display but are clearly dimmer than the one that is fully on which is great, it means the thing actually works. The next step though is to get that variable resistor into the mix so we can use that to set the amount of dimming.

Well we already have the code to get a byte value from the variable resistor and we already have the code to set the duty cycle. All we have to do is combine it together.

    char duty = 0;  // the duty cycle value derived from the VR value

    // loop forever
    while(1)
    {
        // read an 8-bit value from the VR
        duty = readVR();

        // set the new duty cycle
        CCP1CONbits.DC1B = duty & 0b11; // LSB
        CCPR1L = duty >> 2;             // MSB
    }

I then built the code, connected my PICKit 2 and demo board and gave it a spin. Sure enough it all seemed to work just fine. Turning the variable resistor allows a full range of dimming control from completely off to completely on.

#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

void initVR()
{
    // 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
}

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

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

    // init the ADC to read the VR
    initVR();

    // Disable the PWM pin (CCPx) output drivers as an input by setting
    // the associated TRIS bit.
    TRISC = 0xff;  // blat the lot, we're not using them
    TRISD = 0xff;  // for anything else

    // Set the PWM period by loading the PR2 register.
    PR2 = 0x40; // this combined with a TMR2 prescale of 16 gives us
                // a PWM frequency of just under 2kHz

    // Configure the CCP module for the PWM mode by loading the CCPxCON
    // register with the appropriate values.
    CCP1CONbits.P1M = 0b00;     // Single output mode
    CCP1CONbits.CCP1M = 0b1100; // ECCP Mode PWM P1A-D active high
    PSTRCON = 0b00011110;       // set up pulse steering mode on port D pins

    // Set the PWM duty cycle by loading the CCPRxL register and DCxB
    // bits of the CCPxCON register.
    CCP1CONbits.DC1B = 0x80 & 0b11;
    CCPR1L = 0x80 >> 2; // 0x80 is approx 50% duty cycle... I think!

    // Configure and start Timer2:
    //  • Clear the TMR2IF interrupt flag bit of the PIR1 register.
    PIR1bits.TMR2IF = 0;

    //  • Set the Timer2 prescale value by loading the T2CKPS bits of the
    //    T2CON register.
    T2CONbits.T2CKPS = 0b10; // 16

    //  • Enable Timer2 by setting the TMR2ON bit of the T2CON register.
    T2CONbits.TMR2ON = 1;

    // Enable PWM output after a new PWM cycle has started:
    //  • Wait until Timer2 overflows(TMR2IF bit of the PIR1 register is set).
    while(PIR1bits.TMR2IF == 0){}

    //  • Enable the CCPxp in output driver by clearing the associated
    //    TRIS bit.
    TRISD = 0;

    // switch on one of the adjacent LEDs for brightness comparison
    PORTD = 0b00010000;

    char duty = 0;  // the duty cycle value derived from the VR value

    // loop forever
    while(1)
    {
        // read an 8-bit value from the VR
        duty = readVR();

        // set the new duty cycle
        CCP1CONbits.DC1B = duty & 0b11; // LSB
        CCPR1L = duty >> 2;             // MSB
    }
}

The full, final code is above if you fancy picking through it (no pun intended). I hope there’s something vaguely useful in there. As always questions, comments and suggestions are more than welcome otherwise feel free just to say hi.

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.

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!

Getting Started with PICs

Getting Started with PICs

I’ve been spending a fair amount of time recently playing with micro controller boards but for some reason I’ve never had a play with PICs. I decided that it was about time to change all that so I finally got around to ordering a PIC programmer, downloaded a copy of Microchip MPLAB and gave it a go.

I bought the programmer from RS Online. It’s a PICkit2 Debug Express and it arrives with a 44-pin demo board complete with a PIC16F887 (not the PIC16F877 advertised!) and a few additional components for experimentation purposes which are LEDs, a push button and a variable resistor.

I’m working on a Mac so I was relieved to discover that the Microchip development environment, the MPLAB IDE, is now available on OS X and Linux as well as Windows. The software is available from the Microchip website and while I was there I also downloaded and installed the pk2cmd utility. You’ll need to scroll to the bottom of the page to find it.

The PIC on the demo board comes with a program already installed so the first thing I did after scanning through the various read me files was to try and get that running starting with the command line tool to make sure that this was all working correctly. I plugged the supplied USB cable into my Mac and the other end into the PICKit2. I then plugged the PICKit2 into the board making sure that the guide arrows lined up. I then ran the command line tool without parameters to get the help to display and had a look at what was available.

To get power to the board from the programmer you need to call the command line as follows:

pk2cmd -A3.3 -P -HK

The -A parameter sets the voltage to 3.3V, the -P parameter auto-detects the target device and the -HK keeps the command running until you hit a key.

What you then get is a program where the LEDs chase with the speed controlled by the variable resistor and the direction controlled by the push button.

Having got that going I fired up MPLAB to take a look and armed with the data sheets for the PIC and the demo board, I decided to try and get those LEDs to light up. It turned out to be pretty straightforward although it took a few goes to sort out the config settings and get them right for debugging. It wasn’t helped by the 887 vs 877 confusion!

I started by creating a new project using the category ‘Microchip Embedded’ and ‘Standalone Project’. After clicking ‘Next’ I chose the ‘Mid-Range 8-bit MCUs’ option and selected the ‘PIC16F887’ MCU. After clicking ‘Next’ I chose the PICKit2 device from under the PICKit2 category and clicked ‘Next’. I chose the XC8 tool chain, clicked ‘Next’ and gave the project the name ‘Simple LEDs’. I clicked ‘Finish’ and my project was created.

The project is created without any files so the next thing was to add a source file. I did this by right clicking on the ‘Source Files’ folder in the ‘Projects’ tab on the left and selecting ‘New’ followed by ‘C Source File’. From there I created a file called main.c.

Having done that I included the pic header.

#include <pic.h>

I then generated the configuration information for the project which is done using a series of #pragma directives in the source code. Fortunately there is a cunning tool in the IDE that generates the code for you so you can paste it into a source file. To open the tool, click on the ‘Window’ menu and select ‘PIC Memory Views’ and ‘Configuration Bits’. This pops up the screen below in the IDE.

PIC Configuration Settings

PIC Configuration Settings

I made a couple of changes to the default configuration by setting FOSC to INTRC_NOCLKOUT as I wanted to use the internal oscillator (there isn’t an external one on the demo board) and I switched off LVP because the first time I tried debugging a program MPLAB complained about it.

Generated Settings Code

Generated Settings Code

Once I’d done that I hit the ‘Generate Source Code to Output’ button and transferred the generated code to my main.c file 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)

The next thing to do was to add a main function which is defined as void with no parameters as follows.

void main()
{
   
}

Right, now for the interesting bit which is to get those LEDs lit up so time to open up those data sheets.

The first sheet I looked at was the one for the demo board because although I could sort of see the tracks on the board connecting them to port D, I wanted to be sure. The relevant part of the diagram is shown below and as you can see, the LEDs are most definitely connected to port D.

PIC Demo Board LEDs

PIC Demo Board LEDs

The next thing then was to grab the PIC16F887 data sheet and look at what we need to do to get port D working.

Well it turns out to be pretty easy. We simply need to configure the TRISD register to make all of the pins output pins and then write to the PORTD register to set the states.

The TRISD register is 8 bits wide and each bit represents one of the pins. Setting a bit to 0 makes it an output whereas setting it to 1 makes it an input. We want all of our pins to be outputs so that’s pretty easy, we just set the register to 0. To make all of the LEDs light up we need to write 1 to all of the pins so that’s pretty easy too, we just set the PORTD register to 0xff.

The last thing that needs to happen is to add in an infinite loop because we don’t want an embedded program to stop executing and that’s easily achieved by using an infinite while loop.

The final code for main.c then ends up looking like this:

#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)

void main()
{
    // configure port d as output
    TRISD = 0;

    // set the pins high to display the leds
    PORTD = 0xff;

    // loop forever
    while(1){}
}

Finally I made sure that the project was the ‘main’ project by right clicking on it in the ‘Projects’ view and selecting ‘Set as Main Project’ and then ran it by clicking on the ‘Debug’ menu and selecting ‘Debug Main Project’. A few moments later all eight of the LEDs on my board were alight. Success!

Having done that I thought it might be cool to try to replicate the demo program by writing from scratch using the data sheets for reference and this will be the subject of my next few posts.