How to Program a PIC Microcontroller & Read an Encoder

Introduction: How to Program a PIC Microcontroller & Read an Encoder

Introduction: From electronic toys & games to microwave ovens, microcontrollers can be found just about anywhere these days! But how can we make these small computers work for our own electronics projects? That's where this tutorial comes in - with detailed instructions showing you what to do every step of the way. At the end of this project, your microcontroller will even be able to track the speed of a motor!

What to know: Before we get started though, there's some prior knowledge that will not be covered in this guide. If you'd like to brush up your knowledge on any of those topics (listed below), feel free to check out the links.


Voltage Measurement:


Oscilloscope Use:

Step 1: Materials

Note: Materials listed with an asterisk can be substituted depending on what you may have available. The components used for this tutorial that fall into that category are listed in parentheses. Please note that substitutions may render portions of this guide irrelevant (i.e. a portion of code may not work if a different microcontroller is used).


MPLAB X IDE (Free from:
MPLAB XC8 (Free from:


DC Power Supply
Banana Cables
PIC Programmer/Debugger* (MPLAB ICD 3)
RJ11 Connector [Included with MPLAB ICD 3]
USB 2.0 Cable [Included with MPLAB ICD 3]
PIC Microcontroller* (PIC18F4520) - Be sure to use a DIP microcontroller so that it can be put into a breadboard.
Device with a rotary encoder* (Pololu Item # 2269)
10Ohm Resistor
RJ-11 to Breadboard Adapter (DigiKey # H11394-ND)

Oscilloscope (Optional) - Allows you to see encoder signals
Oscilloscope Probes x 2 (Optional)
Multimeter (Optional) - Helpful for debugging issues

Step 2: Power Supply Setup

After acquiring the necessary equipment, our next task is to setup the breadboard & get power to the PIC microcontroller (MCU). That means it's time to hook up the DC power supply.

Power Supply: As you'll see in the picture of the Acopian DC power supply pictured in this step, each power supply channel has three colored connectors to which banana cables can be connected. For the purposes of this tutorial, we'll need +5V and +0V (ground) from the power supply. In order to supply +5V and +0V, connect the negative terminal and grounded terminal of the power supply together (the green and black terminals in the picture provided). Then attach a banana cable to both the positive (red) terminal and the grounded (green) terminal that will be connected to the breadboard. These terminals, found on the breadboard, are shown with connected banana cables in step 3.

Step 3: Breadboard Setup

Breadboard: It's time to setup the breadboard. After many wiring disasters of my own, I would suggest that you keep wire colors consistent. In this tutorial you'll see +5V wires in red and +0V wires in green with the exception of the wires in the RJ-11 to breadboard connector (which were wired by a company). Please also note that it is helpful to setup +5V and +0V on the breadboard's terminal strips.

Note: The pictures provided above show the same connections mentioned below and help in interpreting the instructions below.

Pin Numbers: Figuring out where to connect +5V and +0V (Ground) is as easy as opening up this link (containing the datasheet) and scrolling down to the fourth page: There you'll find that the following connections need to be made:

Pin 11 to +5V
Pin 12 to +0V
Pin 31 to +0V
Pin 32 to +5V
Pin 1 to one side of the 10Ohm Resistor (not mentioned in datasheet)

Additionally, the following connections need to be made with the RJ-11 to breadboard adapter (hereafter referred to as RJ-11 Adapter). The picture of the adapter associated with this step also shows what to connect.

RJ-11 Adapter Yellow Wire: To +5V
RJ-11 Adapter Blue Wire: To the other side of the 10Ohm Resistor
RJ-11 Adapter: Green Wire: To +0V
RJ-11 Adapter Red Wire: To Pin 40
RJ-11 Adapter Black Wire: To Pin 39
RJ-11 Adapter White Wire is NOT Connected

As a rule of thumb you can remember that integrated circuit chips start with pin 1 at the top left hand corner and finish by moving down the left side, then back up the highest numbered pin (top right corner). To tell the top from the bottom there is usually an indent or dot on the top side of the chip.

Also note that VDD = +5V and VSS = +0V (ground)

Step 4: Encoder Setup

Next, the motor with the encoder is added to the circuit and connected to the microcontroller. If you ordered the motor listed on the materials page, it came with a 6 pin adapter that can be used to plug the encoder into the breadboard (See picture 1).

Circuit Connection: The motor & encoder require the connections listed below. Three pictures, each with a different view on the circuit, are included to aid in the encoder connection process.

Red Wire: To +5V
Black Wire: To +0V
Green Wire: To +0V
Blue Wire: To +5V
Yellow Wire: To Pin 33 (on microcontroller)
White Wire: To Pin 34 (on microcontroller)

You may wish to connect the red wire only when testing the motor since the current configuration would keep the motor running constantly. Alternatively, a motor driver can be used to turn the motor on and off (not covered in this tutorial).

Step 5: Programming Setup

In Circuit Debugger: To complete the setup of hardware for this project, connect the ICD3 to your circuit using the RJ-11 cable. Finally, connect the ICD3 to your computer using the USB 2.0 Cable.

MPLAB X Configuration: At this point, we're ready to open MPLAB X and setup our project. See the steps below:

File>New Project>Standalone Project

Select Device: PIC18F4520

Select Hardware Tool: ICD3

Select Compiler: XC8

Name and save your project

Note: If you encounter any difficulty with these instructions selecting 'Quick Start' from the start page opens a webpage which will take you through the same steps with images. We are only concerned with the first five steps featured on that page for the purposes of this project.

Step 6: Programming

At this point, we'll be working exclusively with the MPLAB X IDE. To get started, notice the panel titled Projects on the left side of the MPLAB IDE window. From this panel, create a code:

Right-click on the 'Source Files' folder listed below your project>New>C Main File

Feel free to name it as you wish ('Main' is fine) You'll now see that MPLAB X has created a main file, visible in the center panel.

Code: For the purposes of this tutorial I will provide the necessary code, which you can copy and paste into that center window (after deleting all other text in the file) To give you an idea of what the code is doing, I've included a brief overview below as well as comments within the actual code itself (pasted below)

Overview: There are a couple of distinct portions of code for programming a PIC microcontroller, which are outlined below.

Define Statements: Define statements allow programmers to use words that may mean something else to the computer. For example, we could define the word OFF to mean a binary '0'. Thus while we see the word off, making the code easier to understand, the computer reads a binary '0'.

Include Statements: Include statements tell the compiler to include various header files. These external files may consist of files that define input/output functions (stdlib.h), or a variety of standard C functions (stdio.h), etc.

Pragmas: Pragmas are used to set the configuration bits on the microcontroller which control aspects such as oscillator selection, code protection, etc. Feel free to check out the configuration bits be selecting Window>PIC Memory Views>Configuration Bits

Functions: Functions are sections of the program that perform specific tasks. The main function is where the program starts execution.



How to program a PIC & read an encoder program

Last Revised: 1/3/14
Authors: Carson Miller
Written for: PIC18F4525 (Current Version)


#define INPUT 1
#define OUTPUT 0

#define _XTAL_FREQ 4000000 //Used by the XC8 delay_ms(x) macro

// PIC18F25K22 Configuration Bit Settings

#include <xc.h> //Includes PIC hardware mapping
#include "GenericTypeDefs.h" //Includes standard variable types

// #pragma config statements should precede project file includes.
// Use project enums instead of #define for ON and OFF.

#pragma config OSC = INTIO7 // Oscillator Selection bits (Internal oscillator block, CLKOUT function on RA6, port function on RA7)
#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)

#pragma config PWRT = OFF // Power-up Timer Enable bit (PWRT disabled)
#pragma config BOREN = SBORDIS // Brown-out Reset Enable bits (Brown-out Reset enabled in hardware only (SBOREN is disabled))
#pragma config BORV = 3 // Brown Out Reset Voltage bits (Minimum setting)

#pragma config WDT = OFF // Watchdog Timer Enable bit (WDT enabled)
#pragma config WDTPS = 32768 // Watchdog Timer Postscale Select bits (1:32768)

#pragma config CCP2MX = PORTC // CCP2 MUX bit (CCP2 input/output is multiplexed with RC1)
#pragma config PBADEN = ON // PORTB A/D Enable bit (PORTB<4:0> pins are configured as analog input channels on Reset)
#pragma config LPT1OSC = OFF // Low-Power Timer1 Oscillator Enable bit (Timer1 configured for higher power operation)
#pragma config MCLRE = ON // MCLR Pin Enable bit (MCLR pin enabled; RE3 input pin disabled)

#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 enabled)
#pragma config XINST = OFF // Extended Instruction Set Enable bit (Instruction set extension and Indexed Addressing mode disabled (Legacy mode))

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

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

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

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

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

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

//Dummy variable setup

UINT distance = 0;
CHAR direction = 0;
CHAR error = 0;

void configure(void)

//ADC Setup

ADCON1bits.PCFG = 1111; //Turns off all analog inputs (See datasheet p. 224)

//Oscillator Setup

OSCCONbits.IRCF = 110; //Sets oscillator to 4MHz

//Interrupt Setup

INTCONbits.GIE = 1; //Enables all unmasked or high priority interrupts (Depending on IPEN)
INTCONbits.PEIE = 1; //Enables all unmasked peripheral interrupts or low- priority interrupts (Depending on IPEN)
INTCONbits.INT0IF = 0; //Clears interrupt 0 flag bit (Must occur before enabling interrupt)
INTCONbits.INT0IE = 1; //Enables the INT0 external interrupt

INTCON2bits.INTEDG0 = 1; //Sets external interrupt 0 to interrupt on rising edge

RCONbits.IPEN = 0; //Disables priority levels on interrupts

TRISBbits.TRISB0 = INPUT; //Sets INT0 as input
TRISBbits.TRISB1 = INPUT; //Sets INT1 as input


void main()


//Program Loop

//Main interrupt service routine (ISR)
void interrupt ISR()
//Check to see if it is interrupt 0
if (INTCONbits.INT0IF == 1)
INTCONbits.INT0IF = 0; //Clears interrupt flag
error = 1;

Step 7: Running the Program & Debugging

Notes on using MPLABX IDE: After you've added the program, you can run the program buy pressing the debug project button. The debug project button looks like a torn piece of code with a play button next to it and is located in the upper toolbar (below the file toolbar). After pressing this you'll be able to pause and play the program using the buttons in the upper toolbar.

Checking the encoder value: To check the encoder value, you must add a variable 'watch' in the panel on the left side of the MPLAB IDE window. Simply click where it says 'Enter new watch' and type either direction or distance. You'll then be able to track those variables through that window. Note that the program must be paused to see the values in the variable watch change. Further, if the value in the variable watch shows up in an improper format, right click the incorrect value and hover over 'Display value column as' so that you can select the proper format.

Project Complete!

If you've reached the end and don't have a working project don't worry. There are a number of things to try:

Multimeter: Check to ensure that your microcontroller & motor/encoder are receiving +5V and +0V (ground) at the appropriate locations

Oscilloscope: Check to ensure that your encoder is sending signals like those shown in the oscilloscope image associated with this step. The encoder should put out two square waves, from the yellow and white wires, that are 90 degrees out of phase.

MPLAB X: Check to ensure that your microcontroller is setup properly by making sure that your special function registers have the proper binary values (as set in the code from step 6). To see the special function registers go to Window>PIC Memory Views>SFRs after pausing during a debugging session. From there you can check to see that the pin with INT0 (pin 33) is set as an input, for example, by holding your cursor over the TRISB register.

Comments: If you're unable to solve the problem or have questions/comments about this tutorial please leave a response in the comments section and I will happily respond.

Be the First to Share


    • Puzzles Speed Challenge

      Puzzles Speed Challenge
    • "Can't Touch This" Family Contest

      "Can't Touch This" Family Contest
    • CNC Contest 2020

      CNC Contest 2020

    10 Discussions


    6 years ago on Introduction

    I see where you increment your counter for going forward, but where do you decrement it when the encoder is turned backwards?


    Reply 6 years ago on Introduction

    Thank you for your comment. As soon as I have working code that supports a decreasing count during backward movement I will update the Instructable.


    Reply 2 years ago

    I know this is an old thread, but this may be useful to somebody who comes across it:


    if (IOCCFbits.IOCCF0 ==1) // Rotary encoder A leg has changed value


    if (PORTCbits.RC1==0) // If rotary encoder B leg is low


    RE_value++; // Increment rotary encoder value


    if (PORTCbits.RC1==1) // If rotary encoder B leg is high


    RE_value--; // Decrement rotary encoder value


    IOCCFbits.IOCCF0 = 0; // Clear RC1 change flag



    It is my interrupt routine that checks if the encoder A leg has changed and increments or decrements based on the relationship to the B leg at the time of change. Hope it helps somebody. :)


    3 years ago


    I ran across your excellent project. I wondered if you ever had a chance to implement code to decrement the count value? I am attempting to output values of direction and distance to an LCD display, however I am new to PIC programming and not sure how to make this work. If you have time to post the code I would be very grateful.


    Reply 3 years ago

    Hi! Thank you for your comment - it has been a while since I worked with this project. I believe that the other phase from the encoder would be used to determine the direction the motor is travelling. The interrupt routine would have to be modified to read the other phase and adjust your counting variable appropriately based on the direction sensed.


    4 years ago

    Can you share your project me ? I need to , cause i make a project like yours. But dont stabile

    Hi and thank-you for publishing this project, I followed your instructions for the code and used MPLAB X IDE and complier XC8 and it came with several errors when I attempted to build it,

    BUILD FAILED (exit value 2, total time: 1s).

    I am a newbie any help appreciated.


    Reply 5 years ago on Introduction

    Hi constructionjohn,

    I believe that you've identified a typo in my code.

    Where it previously said "#include //Includes PIC hardware mapping" it should have said "#include <xc.h> //Includes PIC hardware mapping"

    I've updated the code included in step 6 to reflect this change. Don't hesitate to write if you have any other questions. Thank you!


    6 years ago on Introduction

    Hello, I need to build a motor controller for a brushless motor to be used for my quadcopter. do you have any idea whether I can use an encoder to determine the position of the rotor and do the job for me?


    Reply 6 years ago on Introduction


    The encoder used in this project had a resolution of 48 counts per revolution. If that is precise enough for your application, the encoders could certainly track the number of revolutions per rotor to 1/48 precision.

    Did that answer your question?