Introduction: 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.
PICkit 3 In-Circuit Debugger Breadboard and breadboard wires LEDs, buttons, potentiometers, or anything else you would like to connect to the PIC
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
- 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.
Step 1: 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.
Note: VDD = Positive Voltage and VSS = Ground.
- Connect the MCLR pin through a 1kΩ resistor to VDD.
- Connect a 0.1μF capacitor between every pair of adjoining VDD-VSS pairs or AVDD-AVSS pairs.
- Connect a 10μF capacitor between VCAP and Vss.
- Connect MCLR pin to pin 1 of the PICkit 3.
- Connect VDD to pin 2 of the PICkit 3.
- Connect VSS to pin 3 of the PICkit 3.
- Connect PGD pin to pin 4 of the PICkit 3.
- Connect PGC pin to pin 5 of the PICkit 3.
- Leave pin 6 of the PICkit 3 unconnected.
- Connect any analog inputs to pins with ANx functionality where x is a number.
- 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.
Step 2: 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.
- To get the latest version of the software visit Microchips website at http://www.microchip.com/pagehandler/en-us/family/mplabx/
- Select the software for your OS and follow the standard installation instructions.
- Once the software is installed, start MPLAB X
- In the menu bar select Tools->Options
- In the Options dialog select the Embedded tab and ensure XC8 is listed in the Toolchain list.
- If it is listed select OK and move on the next step.
- If it is not listed ensure that instillation has completed, and click Scan for Build Tools button.
- If still not listed, look on Microchips forum for help with your specific problem.
Step 3: Create New Project
In this step we will create a new project based on a template from Microchip.
- On the menu bar select File->New Project...
- In the new file dialog box expand Samples and select Microchip Embedded
- In the project box select PIC18 C Template
- Select Next
- Give the project any name you like
- Choose a location to save the project to in the Project Location box
- Leave the Project Folder as default options
- Check "Set as Main Project" box
- Select Finish
Step 4: Build Parameters
Before we can get started programming we need to set the build parameters.
Create Configuration Under Conf: [Default] select PICkit 3 Under Conf: [Default] select XC8 compiler 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.
Create Configuration
- Right click on the project name in the projects tool bar.
- In the Project Properties dialog select Manage Configurations...
- In the Configurations dialog select New
- In the New Configuration Name dialog enter Default and click OK
- In the Configurations dialog make sure Default is selected and click Set Active
- Click OK in the Configurations dialog
- In the Project Properties dialog select "Conf: [Default]" in the Categories list
- In the Device box type the name of the device you are using. In my case PIC18F26K80
- In the Hardware Tools list select PICkit3
- In the Compiler Toolchain select XC8 (v...) Where ... is the version you have installed.
- Select Apply
- For Option categories select Power
- Check "Power target circuit from PICkit3
- Select Apply.
- For Option categories select Optimizations
- Set "Optimization Set" to "none"
- Select Apply
Step 5: 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.
After all configuration bits have been set, click the "Generate Source Code to Output" button at the bottom of the panel. The panel will now switch to the Output tab. Select all the text in this tab and copy it to the clip board Paste it at the bottom of the configuration_bits.c file and pres save. Clean and build the project again by clicking the broom and hammer icon. 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.
- In the project explorer expand Source Files and open configuration_bits.c
- Remove all the text below the #endif line
- Notice a new tab has opened at the bottom of the screen
- 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:
- Extended Instruction Set -- Should be set to OFF when using template
- 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.
- 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.
- 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.
- Code/Table Write/Read protects -- Used to disable writing or reading to certain ranges of memory. Leave all of these disabled.
- If unsure about a setting, it is usually safe to leave it default.
Step 6: 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- In the datasheet find the section labeled Oscillator Configurations
- The first thing listed in this section is Oscillator Types. If you are using the internal oscillator then use the settings relating to INTIO1
- 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.
-
The next step is to program these settings to the MCU. This is done by setting registers. The first register to set is OSCCON.
- IDLEN -- used to control the action of the sleep command. Can be left as default.
- IRCF -- Oscillator selection. Since I am using HF-INTOSC/4 (4MHz) I will need to set this to a binary value of 101
- OSTS -- Read only bit
- HFIOFS -- Read only bit
- SCS -- clock select bits. Since I am using the internal oscillator, I will set to 1x where x can be 0 or 1
- The next register is OSCCON2; however, this register is mostly read only and not important at this point
- 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
- Return to MPLAB
- In the project explorer under Source Files open system.c
- At the bottom of this file is the function ConfigureOscillator. Remove the comments in that function.
- 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.
- 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.
- Set all the bits as determined above for the OSCCON register. Example: OSCCONbits.IRCF = 0b101;
- Do the same for all other needed oscillator registers. See below for an example of a finished ConfigureOscillator function.
- 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.
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.
- In system.c create a new function at the bottom of the file of type void wait_ms(uint16_t time)
- 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 }
- Open system.h in the Header Files folder in the project browser
- At the end add the line void wait_ms(uint16_t); to prototype the function.
- Change line 8 from 8000000L to 16000000L
- Build and check for errors/warnings
Step 8: 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.
The finished function should look like this:
- Open main.c in the project viewer under source files.
The finished function should look like this:
- Immediately above the while loop add the following code.
- Set the LED pin as output -- TRISAbits.TRISA0 = 0; // setting a TRIS bit to 0 sets as output, setting to 1 sets as input
- Inside the while loop add the following code
- Set the LED to OFF -- LATAbits.LATA0 = 0; // the LAT bits control the output of a pin. 0 = LOW, 1 = HIGH
- Wait for 1/2 second -- wait_ms(500);
- Set the LED to ON -- LATAbits.LATA0 = 1;
- 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 } }
- Build the program and check for errors or warnings
- Ensure the PICkit is connected correctly to the PIC and the computer
- Click the make and program device button (the button to the right of the clean and build button)
- If prompted select PICkit 3 and click OK
- When the warning shows double check you have the correct PIC in the circuit and click OK
- 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.
- In the project browser open user.c under Source Files
-
Above the InitApp function create a new function
void init_adc(void)
- 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}
-
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 conversionwhile(ADCON0bits.DONE);// wait for the conversion to finishreturn(ADRESH<<8)|ADRESL;// return the result}
-
In the InitApp function add the line
init_adc()
-
In the file user.h add the prototype
uint16_t adc_convert(uint8_t);
- 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 outputwhile(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}}
- 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.
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.
-
To set a pin as an input, write a 1 to that pins TRIS register bit -
TRISAbits.TRISA2 = 1;
- If a pin share analog features, it may be necessary to set it to digital by clearing the appropriate bit in ANCONx register
-
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;
- 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 digitalwhile(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.
21 Comments
Question 1 year ago
CAN I PROGRAM A PIC WITHOUT A PICKIT
1 year ago
Just wanted to say a thankyou to the poster of this instructable. It's been over 20 years since I learnt how to program PIC microcontrollers at university. I used a PICDEM.net 2 board with a PIC18F97J60 microcontroller and managed to get the code to work with some modifications. A very useful re-introduction to the world of microcontroller programming.
I only wish I had paid more attention when they were teaching us C programming as it is a very different world to programming in assembler!
Reply 1 year ago
I'm glad you found this useful. I had completely forgotten about even making this tutorial as a class project in college 8 years ago until I got an email notification of your comment. I am a bit surprised that it still works and is relevant so many years later.
4 years ago
It would be great if you could replicate these steps using Free and Open source Software and Hardware. For example I have prepared a list of FOSS here:
https://www.reddit.com/r/electronics/comments/ap6m45/curated_list_of_awesome_free_and_open_source/
and maybe using RISC-V architecture with HiFive1 or LoFive development boards from SiFive.
6 years ago
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
7 years ago
wow
7 years ago
hi, what is the maximum input voltage is required for the pic controller and is able to convert ac voltage into digital signal
7 years ago
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.
7 years ago
I WANT C CODE FOR OSILLATOR CONFIGURATION OF PIC32MX675F256H....... WITH 16MHZ CRYSTAL
8 years ago on Step 6
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!
Reply 8 years ago on Introduction
I'm using the 18F24k20 and the manual says:
determined by configuration of the FOSC<2:0>
bits in the CONFIG1H Configuration register.
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).
8 years ago on Step 9
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.
8 years ago on Step 1
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.
8 years ago on Introduction
CAN I USE PIC 18F4520 FOR REALTIME VIDEO PROCESSING, HOW?
8 years ago on Introduction
HOW TO INTERFACE MATLAB PROGRAM TO PIC18F4520,
8 years ago on Step 9
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);
}
9 years ago on Step 2
What is the difference between MPLAB IDE and MPLAB X IDE?
10 years ago on Step 7
I see that you have amended the code with the semicolon
10 years ago on Step 7
i found my errors thanks anyway
the for statement needs to end in a semicolon which is missing here
10 years ago on Step 7
Hi noob here, im getting errors at this point unable to resolve identifiers time & time1
should these be defined somewhere else? thanks in advance