Introduction: Programming PIC Microcontrollers

Picture of Programming PIC Microcontrollers
PIC microcontrollers are a very useful and versatile tool for use in many electronic projects. They are very inexpensive and easy to find. They are also very powerful and many are capable of speeds up to 64 MIPS using the internal oscillator block, about 16 times faster than most comparable AVR microcontrollers. PICs are also easy to program, however getting the project set up can some times be tricky. These instructions will walk through the process of setting up the software, creating a new project, and programming some very simple functions to test the configuration and ensure everything is working. They are designed to be very open ended; after the project is created and the basics are finished the reader is encouraged to explore all the features and extras not covered in these instructions. Also you will find that these instructions will start out walking through step by step, but as the instructions near the end the reader is encouraged to explore other ways of accomplishing the tasks and make the project their own.
What you will need To build a project with a PIC microcontroller only requires a few items.
  • PIC microcontroller
    • These instructions are for programming a PIC18F series MCU, although others are similar.
    • Obtained from Microchips website.
    • Microchip allows students with valid .edu email addresses sample PIC's for free!
    • The PIC I am using to create these instructions is a PIC18F22K80
  • PICkit 3 In-Circuit Debugger
    • Available from Microchip.
    • Costs $45 for general public, and #34 with student discount if you have an .edu email address.
    • There are also other programmers which will work just as well; however, this is the best one for starting out.
  • Breadboard and breadboard wires
  • LEDs, buttons, potentiometers, or anything else you would like to connect to the PIC

Step 1: Build Hardware

Picture of Build Hardware
Before doing any programming the first step is to build the hardware. Although the PIC18F portfolio is very large, many of the chips have several commonalities. For more detailed information see the "Guidelines for Getting Started with PIC18Fxxxx Microcontrollers" section in your devices datasheet. For detailed pin-outs of the PIC microcontroller see the "Pin Diagram" section in your devices datasheet.

Note: VDD = Positive Voltage and VSS = Ground.
  1. Connect the MCLR pin through a 1kΩ resistor to VDD.
  2. Connect a 0.1μF capacitor between every pair of adjoining VDD-VSS pairs or AVDD-AVSS pairs.
  3. Connect a 10μF capacitor between VCAP and Vss.
  4. Connect MCLR pin to pin 1 of the PICkit 3.
  5. Connect VDD to pin 2 of the PICkit 3.
  6. Connect VSS to pin 3 of the PICkit 3.
  7. Connect PGD pin to pin 4 of the PICkit 3.
  8. Connect PGC pin to pin 5 of the PICkit 3.
  9. Leave pin 6 of the PICkit 3 unconnected.
  10. Connect any analog inputs to pins with ANx functionality where x is a number.
  11. Connect any digital inputs or outputs to pins with Rxy functionality where x is a letter identifying the port, and y is a number identifying the bit.
For my example I have an LED connected between RA0 and ground, the wiper of a potentiometer connected to AN1, and a DPST switch connected to RA2. You may find it easier to program the PIC if you have sketched down a schematic of your circuit.

Step 2: Get Software

Picture of Get Software
These instructions will use XC8 compiler and MPLAB X IDE by Microchip. This step will explain how to get these tools and ensure they have been installed correctly.
  1. To get the latest version of the software visit Microchips website at http://www.microchip.com/pagehandler/en-us/family/mplabx/
  2. Select the software for your OS and follow the standard installation instructions.
Note: If you are using Windows 8 you may need to run the installers in compatibility mode for Windows 7.
  1. Once the software is installed, start MPLAB X
  2. In the menu bar select Tools->Options
  3. In the Options dialog select the Embedded tab and ensure XC8 is listed in the Toolchain list.
  4. If it is listed select OK and move on the next step.
  5. If it is not listed ensure that instillation has completed, and click Scan for Build Tools button.
  6. If still not listed, look on Microchips forum for help with your specific problem.

Step 3: Create New Project

Picture of Create New Project
In this step we will create a new project based on a template from Microchip.
  1. On the menu bar select File->New Project...
  2. In the new file dialog box expand Samples and select Microchip Embedded
  3. In the project box select PIC18 C Template
  4. Select Next
  5. Give the project any name you like
  6. Choose a location to save the project to in the Project Location box
  7. Leave the Project Folder as default options
  8. Check "Set as Main Project" box
  9. Select Finish
The project will now show up in Project Explore on the left hand side of the screen.

Step 4: Build Parameters

Picture of Build Parameters
Before we can get started programming we need to set the build parameters.
Create Configuration
  1. Right click on the project name in the projects tool bar.
  2. In the Project Properties dialog select Manage Configurations...
  3. In the Configurations dialog select New
  4. In the New Configuration Name dialog enter Default and click OK
  5. In the Configurations dialog make sure Default is selected and click Set Active
  6. Click OK in the Configurations dialog
Set Configuration Properties
  1. In the Project Properties dialog select "Conf: [Default]" in the Categories list
    1. In the Device box type the name of the device you are using. In my case PIC18F26K80
    2. In the Hardware Tools list select PICkit3
    3. In the Compiler Toolchain select XC8 (v...) Where ... is the version you have installed.
    4. Select Apply
  2. Under Conf: [Default] select PICkit 3
    1. For Option categories select Power
    2. Check "Power target circuit from PICkit3
    3. Select Apply.
  3. Under Conf: [Default] select XC8 compiler
    1. For Option categories select Optimizations
    2. Set "Optimization Set" to "none"
    3. Select Apply
  4. Click OK to close the dialog box
Test the Configuration To test the configuration click the clean and build button (the one with the hammer and broom). Text will start scrolling in the output window at the bottom of the page. If everything is successful the this text will say BUILD SUCCESSFUL (total time: ...). If you get an error, go back through this step making sure that you did not miss anything, and that everything was applied.

Step 5: Set Configuration Bits

Picture of Set Configuration Bits
The next step is setting the configuration bits. The configuration bits tell the MCU its initial conditions for when it turns on. They are used to set the clock source and speed, watchdog time configuration, and other similar features. Configuration bits are device dependent, so check the data sheet for the chip you are using for more information.
  1. In the project explorer expand Source Files and open configuration_bits.c
  2. Remove all the text below the #endif line
  3. Notice a new tab has opened at the bottom of the screen
  4. Set the bits as needed for your project. Since these are chip dependent, check the data sheet for more information about what each does. Some common settings follow:
    1. Extended Instruction Set -- Should be set to OFF when using template
    2. Oscillator -- Used to select the processor. Unless you are using an external crystal, leave set as Internal RC oscillator. See data sheet for other oscillator configurations. Note: CLKOUT will allow for easier debugging, and should be turned on if available.
    3. PLL Enable -- Will allow for future use of the PLL. Note: this will not turn on the PLL, it will only enable it. It is recommended to enable it.
    4. Watchdog Timer -- The watch dog timer is used to ensure the processor will not lock up. It however makes it much harder to debug. It is recommended to disable it while initially programming, and only enable it after the project is nearly done.
    5. Code/Table Write/Read protects -- Used to disable writing or reading to certain ranges of memory. Leave all of these disabled.
    6. If unsure about a setting, it is usually safe to leave it default.
  5. After all configuration bits have been set, click the "Generate Source Code to Output" button at the bottom of the panel.
  6. The panel will now switch to the Output tab. Select all the text in this tab and copy it to the clip board
  7. Paste it at the bottom of the configuration_bits.c file and pres save.
  8. Clean and build the project again by clicking the broom and hammer icon.
  9. Ensure the build was successful. Also check to make sure there was no errors in the output
If everything has worked move on to the next step. If there are errors or warnings fix them before moving on.

Step 6: Configure Oscillator

Picture of Configure Oscillator
The next step is to start programming; however, before we get to the application code we must program the system code. The system code are the low level functions such as configuring the oscillator and basic delay functions.

Determining Settings

Before we can program the settings, we must choose what speed we would like to run at. For this example I will use 16MHz as most PIC's can run at this speed. For my configuration I will use the 4MHz postscaller from the HF-INTOSC, and the 4x PLL giving an output frequency of 4MHz*4x=16MHz
  1. In the datasheet find the section labeled Oscillator Configurations
  2. The first thing listed in this section is Oscillator Types. If you are using the internal oscillator then use the settings relating to INTIO1
  3. On the next page or two you will find a schematic drawing of the oscillator similar to the one shown. It is helpful to trace the signal on this drawing to ensure the correct speed is being selected.
  4. The next step is to program these settings to the MCU. This is done by setting registers. The first register to set is OSCCON.
    1. IDLEN -- used to control the action of the sleep command. Can be left as default.
    2. IRCF -- Oscillator selection. Since I am using HF-INTOSC/4 (4MHz) I will need to set this to a binary value of 101
    3. OSTS -- Read only bit
    4. HFIOFS -- Read only bit
    5. SCS -- clock select bits. Since I am using the internal oscillator, I will set to 1x where x can be 0 or 1
  5. The next register is OSCCON2; however, this register is mostly read only and not important at this point
  6. The last oscillator configuration register is OSCTUNE. We will not tune the frequency for this project, however we must use this register to turn on the PLL using the PLLEN bit.

Applying Settings

  1. Return to MPLAB
  2. In the project explorer under Source Files open system.c
  3. At the bottom of this file is the function ConfigureOscillator. Remove the comments in that function.
  4. To set the bits of a register type in all caps the register name, followed by the lowercase word bits and then a period and the bit name.
  5. To set the bits follow that with an equal sign. To use binary type 0bXXXX where XXXX is the binary number. Lastly end the line with a semi-colon.
  6. Set all the bits as determined above for the OSCCON register. Example: OSCCONbits.IRCF = 0b101;
  7. Do the same for all other needed oscillator registers. See below for an example of a finished ConfigureOscillator function.
  8. When finished build and check for warnings/errors

/** * Configure the clock source and speed */void ConfigureOscillator(void){
    OSCCONbits.IRCF     =0b101;
    OSCCONbits.SCS      =0b00;
    OSCTUNEbits.PLLEN   =0b1;}

Step 7: Wait Milli-Second Function

One of the most useful functions is wait_ms. This however is not a function in the standard library, and will need to be programmed by you. For this implementation there will be a loop which will hold the processor until the given time has passed.

PIC18F microcontrollers need 4 clock cycles to execute one line of assembly code. Therefore with a clock of 16MHz, lines will be executed at 4 million lines per second = 4000 lines per milli-second. Since a for loop will take one instruction each time for the comparison, and two for the operation one for the body of the loop, it will work perfectly. We just need the for loop to loop 1000 time per milli-second.
  1. In system.c create a new function at the bottom of the file of type void wait_ms(uint16_t time)
  2. Below is the completed function
/**
 * Wait for a given number of milli-seconds using busy waiting scheme.
 * @param time - time in ms to wait.
 */
void wait_ms(uint16_t time)
{
    static long timel = 0;
    timel = time * 1000l;
    for( ; timel; timel--);// no initial condition, while time is >0, decrement time each loop
}
  1. Open system.h in the Header Files folder in the project browser
  2. At the end add the line void wait_ms(uint16_t); to prototype the function.
  3. Change line 8 from 8000000L to 16000000L
  4. Build and check for errors/warnings

Step 8: Blink an LED

Picture of Blink an LED
The best way to test that everything is set up correctly is to blink an LED light. If the light blinks at the expected rate then everything has been configured correctly. In this example the LED is connected to PORT A, Pin 0 (RA0 on the datasheet). If you have your LED connected to a different pin, use the appropriate registers and bits.
  1. Open main.c in the project viewer under source files.
The function void main(void) is the main entry point of the program. When the MCU first powers on it will enter into this function. The first line calls the ConfigureOscillator function you filled in to set the clock source and speed. The next line calls InitApp, a function that we will fill in shortly, and finally it enters an infinite loop. Since there is no operating system for the function to return to, there is no return call at the end.
The finished function should look like this:
  1. Immediately above the while loop add the following code.
    1. Set the LED pin as output -- TRISAbits.TRISA0 = 0; // setting a TRIS bit to 0 sets as output, setting to 1 sets as input
  2. Inside the while loop add the following code
    1. Set the LED to OFF -- LATAbits.LATA0 = 0; // the LAT bits control the output of a pin. 0 = LOW, 1 = HIGH
    2. Wait for 1/2 second -- wait_ms(500);
    3. Set the LED to ON -- LATAbits.LATA0 = 1;
    4. Wait for 1/2 second -- wait_ms(500);
void main(void)
{
    /* Configure the oscillator for the device */
    ConfigureOscillator();

    /* Initialize I/O and Peripherals for application */
    InitApp();
   
    TRISAbits.TRISA0 = 0; // set pin as output
    while(1)
    {
        LATAbits.LATA0 = 0; // set pin LOW
        wait_ms(500);       // wait 0.5 seconds
        LATAbits.LATA0 = 1; // set pin HIGH
        wait_ms(500);       // wait 0.5 seconds
    }
}
  1. Build the program and check for errors or warnings
  2. Ensure the PICkit is connected correctly to the PIC and the computer
  3. Click the make and program device button (the button to the right of the clean and build button)
  4. If prompted select PICkit 3 and click OK
  5. When the warning shows double check you have the correct PIC in the circuit and click OK
  6. If a warning shows about Target Device ID click OK to ignore it

Step 9: Reading an Analog Value

So far the program can blink an LED. Next lets give it some user input. We will use a potentiometer to create an analog signal which will change the speed of the LED. The ADC takes an analog voltage, and outputs a digital value.
  1. In the project browser open user.c under Source Files
  2. Above the InitApp function create a new function void init_adc(void)
  3. Enter the following code to initialize the ADC module
/** * Initialize the Analog to Digital Converter. */void init_adc(void){
    TRISAbits.TRISA1    =0b1;  // set pin as input
    ANCON0bits.ANSEL1   =0b1;  // set pin as analog
    ADCON1bits.VCFG     =0b00;// set v+ reference to Vdd
    ADCON1bits.VNCFG    =0b0;  // set v- reference to GND
    ADCON1bits.CHSN     =0b000;// set negative input to GND
    ADCON2bits.ADFM     =0b1;  // right justify the output
    ADCON2bits.ACQT     =0b110;// 16 TAD
    ADCON2bits.ADCS     =0b101;// use Fosc/16 for clock source
    ADCON0bits.ADON     =0b1;  // turn on the ADC}
  1. Next create another function immediately after called uint16_t adc_convert(uint8_t channel)
/** * Preform an analog to digital conversion. * @param channel The ADC input channel to use. * @return The value of the conversion. */
uint16_t adc_convert(uint8_t channel){
    ADCON0bits.CHS      = channel;  // select the given channel
    ADCON0bits.GO       =0b1;      // start the conversion
    while(ADCON0bits.DONE);         // wait for the conversion to finish
    return(ADRESH<<8)|ADRESL;      // return the result}
  1. In the InitApp function add the line init_adc()
  2. In the file user.h add the prototype uint16_t adc_convert(uint8_t);
  3. Change main to match the following:
voidmain(void){
    uint16_t adc_value;                 // variable to hold ADC conversion result in

    /* Configure the oscillator for the device */
    ConfigureOscillator();

    /* Initialize I/O and Peripherals for application */
    InitApp();

    TRISAbits.TRISA0 =0;               // set pin as output
    while(1)
    {
        LATAbits.LATA0 =0;             // set pin LOW
        adc_value = adc_convert(1);     // preform A/D conversion on channel 1
        wait_ms(adc_value>>2);          // wait 0.5 seconds
        LATAbits.LATA0 =1;             // set pin HIGH
        adc_value = adc_convert(1);     // preform A/D conversion on channel 1
        wait_ms(adc_value>>2);          // wait 0.5 seconds
    }}
  1. Build and download the code. As you turn the POT the speed the LED blinks should change

Step 10: Read a Digital Value

Next lets get digital input from the switch. When the switch is off we will have the program do what it has been doing all along, and when the switch is on the program will light up the LED solid until the switch is turned off again.
  1. To set a pin as an input, write a 1 to that pins TRIS register bit - TRISAbits.TRISA2 = 1;
  2. If a pin share analog features, it may be necessary to set it to digital by clearing the appropriate bit in ANCONx register
  3. When writing a value to a pin use the LAT register; however, when reading a value from a pin use the PORT register - value = PORTAbits.RA2;
  4. Change main to the following:
voidmain(void){
    uint16_t adc_value;                 // variable to hold ADC conversion result in

    /* Configure the oscillator for the device */
    ConfigureOscillator();

    /* Initialize I/O and Peripherals for application */
    InitApp();

    TRISAbits.TRISA0 =0;               // set pin as output
    TRISAbits.TRISA2 =1;               // set pin as input
    ANCON0bits.ANSEL2=0;               // set pin as digital
    while(1)
    {
        if(PORTAbits.RA2)               // if the pin is high
        {
            LATAbits.LATA0 =1;         // set the pin as high
        }
        else                            // if the pin is low
        {                               // blink the LED
            LATAbits.LATA0 =0;         // set pin LOW
            adc_value = adc_convert(1);// preform A/D conversion on channel 1
            wait_ms(adc_value>>2);      // wait some time
            LATAbits.LATA0 =1;         // set pin HIGH
            adc_value = adc_convert(1);// preform A/D conversion on channel 1
            wait_ms(adc_value>>2);      // wait some time
        }
    }}

That is it! You now have the basic knowledge of how to set up a new project, read and write to digital pins, and how to read from analog pins. These three features will allow you to do 90% of the projects using PICs on the Internet. Also, as you continue your exploration into PIC microcontrollers you will find that most other features require very similar steps to configure peripherals, and read and right to registers.

Comments

RyanBone (author)2017-03-06

Hi, I've been following along with a pic 18F45K20 and I am at the blink LED part. Everything works fine except that the LED blinks every 2 seconds. I believe I am running at 16 MHz. I could not find the PLL so I just configured my OSCONbits.IRFC = 0b111 (16 MHz according to data sheet). I am using the INTIO67 clock, Which I also can't seem to find on the data sheet. I'm new to this and not sure what I am missing here. Anything helps and here is the link to the data sheet I am looking at, starts at page 26. Thank you for any help. http://ww1.microchip.com/downloads/en/DeviceDoc/40001303H.pdf

Meet P1 (author)2016-09-07

wow

calcium3000 made it! (author)2016-07-15

Great writeup! Got through step 8 using a 12F508.

alaopgj (author)2016-03-11

hi, what is the maximum input voltage is required for the pic controller and is able to convert ac voltage into digital signal

grtyvr (author)2015-12-20

This is an awesome ible! As I was working along I noticed that when I opened the configuration_bits.c file the window did not open in the status area of the IDE. The window can be opened from Window-->PIC Memory Views-->Configuration Bits.

VANDJADHAV (author)2015-11-20

I WANT C CODE FOR OSILLATOR CONFIGURATION OF PIC32MX675F256H....... WITH 16MHZ CRYSTAL

mr.frob (author)2015-03-29

On Step 6, you say because you're using the internal oscillator you're setting the SCS bit to 1x (where x is 0 or 1), but in the sample code you have it set to 0b00. This seems a bit confusing.

I am working on a PIC16F687 and my datasheet says this bit should be set to 1 for internal, and 0 for external, so I'm setting mine to 0b1. Build was successful, we'll see how it turns out in the end. Great -able btw!

FloridaJo (author)mr.frob2015-06-07

I'm using the 18F24k20 and the manual says:


  • When SCS<1:0>=00,the system clock source is
    determined by configuration of the FOSC<2:0>
    bits in the CONFIG1H Configuration register.
  • WhenSCS<1:0>=01,the system clock source is
    the 32.768 kHz secondary oscillator shared with
    Timer1.

So, setting OSCCONbits.SCS = 0b00;
determines the internal oscillator block will be used and at what frequency.

In your case of the PIC16F687 (SCS is just bit Zero) the manual states:

When SCS = 0, system clock source is determined by FOSC<2:0> bits.

When SCS = 1, system clock source is frequency determined by IRCF <2:0>
After a reset, SCS is always cleared (set to 0).

Cuauhetematzin (author)2015-04-17

I was looking for an instructable that could tell me how to use inputs and outputs, and this lived up to my expectations. Thank you very much.

uCHobby (author)2015-02-27

Should you have a series resistor on your LED? I've not read all the specifications on every PIC but you would typically need to limit the current to protect the digital output there.

M.Elamparithi (author)2015-02-05

CAN I USE PIC 18F4520 FOR REALTIME VIDEO PROCESSING, HOW?

M.Elamparithi (author)2015-02-05

HOW TO INTERFACE MATLAB PROGRAM TO PIC18F4520,

MarcelP (author)2015-01-28

Hi,

I've got a huge problem with my PIC18F4520..... I can't read inputs. The Outputs are working fine. The port state gets only recognised if the uC gets reset.

I need help.....

#if defined(__XC)
#include <xc.h> /* XC8 General Include File */
#elif defined(HI_TECH_C)
#include <htc.h> /* HiTech General Include File */
#elif defined(__18CXX)
#include <p18cxxx.h> /* C18 General Include File */
#endif

#if defined(__XC) || defined(HI_TECH_C)

#include <stdint.h> /* For uint8_t definition */
#include <stdbool.h> /* For true/false definition */

#endif

#include "system.h" /* System funct/params, like osc/peripheral config */
#include "user.h" /* User funct/params, such as InitApp */

/******************************************************************************/
/* User Global Variable Declaration */
/******************************************************************************/

// 7 segment display 1
#define BCD1bit1 LATAbits.LATA5
#define BCD1bit2 LATEbits.LATE2
#define BCD1bit3 LATEbits.LATE1
#define BCD1bit4 LATEbits.LATE0

// 7 segment display 2
#define BCD2bit1 LATAbits.LATA7
#define BCD2bit2 LATCbits.LATC1
#define BCD2bit3 LATCbits.LATC0
#define BCD2bit4 LATAbits.LATA6

// 7 segment display 3
#define BCD3bit1 LATCbits.LATC2
#define BCD3bit2 LATDbits.LATD1
#define BCD3bit3 LATDbits.LATD0
#define BCD3bit4 LATCbits.LATC3


// 9bit GRAY code
#define GRAYbit1 PORTDbits.RD7
#define GRAYbit2 PORTDbits.RD6
#define GRAYbit3 PORTDbits.RD5
#define GRAYbit4 PORTDbits.RD4
#define GRAYbit5 PORTCbits.RC7
#define GRAYbit6 PORTCbits.RC6
#define GRAYbit7 PORTCbits.RC5
#define GRAYbit8 PORTCbits.RC4
#define GRAYbit9 PORTDbits.RD3


/* i.e. uint8_t <variable_name>; */

/******************************************************************************/
/* Main Program */
/******************************************************************************/

void main(void)
{
/* Configure the oscillator for the device */
ConfigureOscillator();

/* Initialize I/O and Peripherals for application */
InitApp();

/* TODO <INSERT USER APPLICATION CODE HERE> */

ADCON1 = 0x0F;
ADCON1bits.PCFG = 0xFFFF;
CMCON = 7;

// setting ports input or output
TRISA = 0b00011111;
TRISB = 0b11111111;
TRISC = 0b11110000;
TRISD = 0b11111100;
TRISE = 0b11111000;

BCD1bit1 = 0;
BCD1bit2 = 0;
BCD1bit3 = 0;
BCD1bit4 = 0;

BCD2bit1 = 0;
BCD2bit2 = 0;
BCD2bit3 = 0;
BCD2bit4 = 0;

BCD3bit1 = 0;
BCD3bit2 = 0;
BCD3bit3 = 0;
BCD3bit4 = 0;



while(1)
{

if(GRAYbit1 == 1)
{
BCD1bit1 = 1;
BCD1bit2 = 0;
BCD1bit3 = 0;
BCD1bit4 = 0;
}
else if(GRAYbit1 == 0)
{
BCD1bit1 = 0;
BCD1bit2 = 0;
BCD1bit3 = 1;
BCD1bit4 = 0;
}
wait_ms(200);

}

Electrical Technology made it! (author)2014-10-29

The above one method is little complicated...here is a simple one, want to share with you guys,,,,

How to Program PIC18 Microcontroller in C. Step by Step Tutorial (Pictorial Views)

Link: http://electricaltechnology.org/2014/10/programmi...

juanyi (author)2014-05-12

What is the difference between MPLAB IDE and MPLAB X IDE?

shandybrother (author)2013-09-22

I see that you have amended the code with the semicolon

shandybrother (author)2013-09-19

i found my errors thanks anyway
the for statement needs to end in a semicolon which is missing here

shandybrother (author)2013-09-18

Hi noob here, im getting errors at this point unable to resolve identifiers time & time1
should these be defined somewhere else? thanks in advance

vricsi94 (author)2013-05-20

I'm just starting with PIC microcontrollers, and this was a great help for me. I used a PIC18F14K22. Your instructions were clear, so I was able to find everything I needed in the datasheet, to modify your code. Thanks for this great instructable! :D

About This Instructable

417,621views

160favorites

License:

More by vensengineer:Programming PIC Microcontrollers
Add instructable to: