PIC Microcontrollers Input/Output IO Ports GPIOs Tutorial

Previous Tutorial Previous Tutorial Tutorial 4 Next Tutorial Next Tutorial
Input/Output IO Ports – GPIO Pins
PIC Microcontrollers Course Home Page 🏠

 

in this tutorial, we’ll address the Input/Output Ports in the Microchip PIC microcontrollers. You’ll learn much about the digital input/output pins GPIOs in microcontrollers, how they work and how to drive/interface them properly. We’ll also flash some LEDs for testing out these concepts. With MPLAB x IDE, XC8 Compiler, and PICKit2 or 3. There is also a video guide by the end of this tutorial that you should check out.


   Required Components   

Qty. Component Name Buy On Amazon.com
1 PIC16F877A Add
1 BreadBoard Add
3 LEDs Add    Add
2 Push Buttons Add
1 Resistors Kit Add    Add
1 Capacitors Kit Add    Add
1 Jumper Wires Pack Add    Add
1 LM7805 Voltage Regulator (5v) Add
1 Crystal Oscillator Add
1 PICkit2 or 3 Programmer Add
1 9v Battery or DC Power Supply Add    Add    Add

The Prototyping Board Setup

Prototyping Board - Embedded Systems Tutorials With PIC MCUs


   What are GPIO pins?   

 

GPIO stands for “General Purpose Input/Output pins“. In fact, most of the pins in a typical microcontroller are GPIO pins except for some special pins. The special (non-GPIO) pins are usually the following ones:

  • Power Supply pins: Vdd & Vss
  • OSCillator pins: OSC1 & OSC2. Those couple of pins are used to provide the MCU with the oscillator clock input it needs.
  • MCLR: the master clear reset pin. Used for restarting the MCU on purpose.
  • VUSB: the external USB power source.

Except for the special function pins, all the other pins are GPIOs. As shown below from the PIC16F877A datasheet, the GPIO pins are clustered in PORTs.
PIC16F877A Pinout Diagram

As you may have noticed that the pins [2 to 7] are named [RA0 to RA5] and that’s why this is called “PORTA“. In our MCU there are 5 digital Input/output ports [A, B, C, D, and E]. It’s common in 8-Bit microcontrollers to have I/O ports each of which has up to 8-pins. However, some I/O ports may have less than 8-pins as in our PIC16F877A for examples. As you can see

  • PORTA: has 6 pins [2 to 7]
  • PORTB: has 8 pins [33 to 40]
  • PORTC: has 8 pins
  • PORTD: has 8 pins
  • PORTE: has 3 pins [8 to 10]
Note

The pins of every single port may not be in consecutive order physically on the chip. Which is the case for PORTC & PORTD as you may have noticed


   How does a digital input/output port work?   

 

Generally speaking, the I/O ports are operating similarly which mean that you only need to investigate one of them. Then the other ones will be using the exact same concept and working in a similar way. For this tutorial, we’ll be using PORTB for our Demo. & Lab.

There are basically a couple of registers dedicated to each I/O port to control its operation.

  • TRISx Register
  • PORTx Register

The registers dedicated to PORTA pins are (TRISA & PORTA), and the registers dedicated to PORTB pins are (TRISB & PORTB), and so on.

Input_Output Ports For PIC MCUs

Controlling or driving the I/O port is a pretty easy process. It involves only two consecutive steps.

  1. Configure The Pin, determine the data direction, whether it’s an Input or Output
  2. Read / Write. If the pin is configured to be an input pin, then you’ll be reading (polling) its state several times within your program. If the pin is configured to be an output pin, then you’ll be driving its state (High / Low) within your program.

 

       1. Configure i/o pins      

Whenever you wish to use an I/O pin, you must first configure it to be an output or input pin. Here the function of TRISx register comes in, as the value we write to that register specifies the direction of the corresponding pins (input/output). The x here stands for any port [A, B, C, D or E].

  • Writing 1 (High) to a single bit of the TRISx register configures the pin corresponding to this bit to be an input pin.
  • Writing 0 (Low) to a single bit of the TRISx register configures the pin corresponding to this bit to be an output pin.

Which means that writing the following values to these bits will cause the following effects

Value Bit Effect
1 TRISB0 RB0 pin becomes an input pin
0 TRISB1 RB1 pin becomes an output pin
1 TRISC4 RC4 pin becomes an input pin
0 TRISD3 RD3 pin becomes an output pin
And so on…

However, it’s possible to configure the 8-pin of any port all at once instead of bit-by-bit changes as follows

Generally speaking, in C-Programming, writing to bits/registers could be done in several ways from which I’ll discuss the most common 3 methods.

 

I- Register overwriting method

or equivalently

Pro Tip

This method is a non-friendly method as it overwrites all the values of the 8-Bits in the register, not only the low 3-Bits. Which we may not be interested in changing them. In the above LOC (line of code), we’re interested in affecting only 3-Bits of the TRISB register. However, doing “Register overwriting” has affected all of the 8-Bits in this register!

 

II- Bit-Masking method

pic.

Note

This method is a preferred one and considered to be a friendly way to set or clear specific bits within a register.

 

III- Bit-fields method

This method is the easiest and safest one of all. However, it’s not a common practice for non-PIC dudes. But as long as we’re using the XC8, we should take advantage of this pre-built structures. This enhances almost everything developing, debugging, portability, readability, etc.

or (equivalently)

In case you’re curious. How Bit-fields method works?

Click here to view answer

Well, do you remember the step in which we wrote the name of our MCU PIC16F877A while creating our project?

This actually automatically includes for you (behind the scene) a pre-written header file called pic16f877a.h which has around 3 KLOC (3000 LOC)! I’ll show you how to view it and illustrate a snippet of this code.

write the following LOC at the beginning of your code file

then hover your mouse over the <pic16f877a.h> then right-click it & choose to navigate to the file definition. This will open up the whole file in a new tab. Here is a snippet of this file.

Using Bit-Fields In MPLAB IDE
 Bit-Fields For Memory Registers Manipulations

As you can see, they’ve created bit-fields of each register @ the same memory address in the RAM as in the datasheet. The fields’ width is specified to be 1-Bit for each field, using this operator [  :1  ] for each field. The fields’ names are usually the same as in the datasheet.

Typically a bit-fields for a specific register follows the convention shown below.

Xbits

PCBgogo Ad

Where X is the name of any specific register as found in the datasheet. Which means that accessing any bit is easily done by writing the following.

Xbits.

This will list down all the bits of this register (fields) for you to choose from. Try it yourself!

Bit-Fields For Memory Registers Accessing

Notice: In fact, other than pic16f877a.h file, there are many other header files that contain register maps and bit-fields pre-defined and pre-written for all PIC microcontrollers within the XC8. Which is a free open-source code that you should definitely take advantage of.

 

       2. Read/Write digital i/o pins      

After configuring any i/o pin, you’ll be able to read its state (if it’s an input pin) or to change its logical state (if it’s an output pin). Here the function of PORTx register comes in, as the value we write to that register specifies the logical state of the corresponding pins (High 1/ Low 0).

  • Writing 1 (High) to a single bit of the PORTx register sets the corresponding pin to be High.
  • Writing 0 (Low) to a single bit of the PORTx register sets the corresponding pin to be Low.

Which means that writing the following values to these bits will bring us the following results

Value Bit Effect
1 RB2 RB2 pin is driven High (logic 1), but it must first be configured as an output pin
0 RB3 RB3 pin is driven Low (logic 0), but it must first be configured as an output pin
1 RC5 RC5 pin is driven High (logic 1), but it must first be configured as an output pin
0 RD7 RD7 pin is driven Low (logic 0), but it must first be configured as an output pin
And so on…

1. Reading digital Inputs

 

Reading-in the logical state of an input pin requires additional hardware connections in order to guarantee a stable system behavior. A typical pin can be driven to High, Low or left Floating. However, you shouldn’t leave any i/o pin floating in the air as long as you’re using it as an input for your system.

The floating state must be avoided under any circumstances, as it’s not deterministic consistent input. It totally depends on how noisy the environment is. For this reason, input pins are generally pulled-down or pulled-up.

  •   Input Pins Pull-Up  
    In this configuration, the pin is always pulled-up to High (logic 1) until an event occurs to drive it Low (to 0). Mostly all you need to have is a 10kohm resistor connected between the input pin and the Vdd (+5v) as shown below.
    Digital Input Pins Pull-Up - Embedded Systems Tutorials With PIC MCUs
    The logical state of the pin is always 1 until the button is pressed, then it’s short-circuited with ground and becomes 0. This is called Negative Logic input, as the action of the user (pressing button) transfers the digital pin state from High to Low.

 

  •   Input Pins Pull-Down  
    In this configuration, the pin is always pulled-down to Low (logic 0) until an event occurs to drive it High (to 1). Mostly all you need to have is a 10kohm resistor connected between the input pin and the Vss (0v or Gd) as shown below.

    Digital Input Pins Pull-Down - Embedded Systems Tutorials With PIC MCUs
    The logical state of the pin is always 0 until the button is pressed, then it’s short-circuited with Vdd (+5v) and becomes 1. This is called Positive Logic input, as the action of the user (pressing button) transfers the digital pin state from Low to High.
Note

Some pins have internal Pull-Up resistors built-in the microcontroller itself (e.g. PORTB pins). However, in most cases, you’ll have to choose & create one of the 2 configurations above which are identical in function but reversed in logic handling mechanisms.

 

Let’s take a simple example for reading a digital input. Say we’ve got an i/o pin called RD4 which is configured to be an input pin. This pin is connected to a Pull-Down resistor with a small push button. We’re willing to do something when the user presses the button. The C-Code for this is shown below.

or equivalently
or equivalently

Take the time to get familiar with these concepts and play around to make sure that everything you understand is actually behaving as it should be.

Hint

Checking the input pins’ states using control flow logic (e.g. if, else, switch, while, etc.) is called Polling and it’s not the best choice for handling critical input signals to the system specifically those which require an instantaneous response from the system (Real-Time). This will be discussed later in The Interrupts Tutorial.

For now, we’re done with reading digital inputs and let’s move on to see how we can write to the digital output pins in our microcontroller.

 

2. Writing Digital Outputs

 

Writing to the PORTx register will change the logical state of i/o pins (only if they are configured as output pins). The output pins’ logical states 0, 1 corresponds to 0v, +5v respectively. Which means that a typical output pin could possibly be sourcing current to other devices or it could be sinking current instead.

  •   Output Pins Current Sourcing  
    In this configuration, the output pin is used as a (+5v) source that delivers current to drive very small loads (e.g. LEDs, Transistors, Relays, Optocouplers, etc.). A typical pin in our microcontroller can source up to 25 mA for each i/o pin and this value varies from an MCU chip to another. This is the most commonly used configuration and it should be clear in the diagram below.
    Input-Output Pins Current Sourcing - Embedded Systems Tutorials With PIC MCUs
  •   Output Pins Current Sinking  
    In this configuration, the output pin is used as a (0v or Ground) that sinks current from small loads (e.g. LEDs, Transistors, Relays, Optocouplers, etc.). A typical pin in our microcontroller can sink up to 25 mA for each i/o pin. This configuration is shown in the diagram below.

Check the datasheet for PIC16F877A, chapter 17.0 (Electrical Characteristics), page 173

io pins max current

Note

You should never exceed the current limit for i/o pins at any cost. Otherwise, it’ll cost you the entire chip itself or at least some pins if you’re lucky enough. Loads like DC motors, Stepper motors, Large LEDs array, etc. all of them could be easily interface with a small transistor biasing circuit. You’ll be safe and so do your MCU as well.

 

Let’s take a simple example for writing a digital output. Say we’ve got an i/o pin called RC5 which is configured to be an output pin. This pin is connected to a 330 resistor with a small LED. We’re willing to turn it on for 1 second and turn it back off again (Flash!). The C-Code for this is shown below.

 

And now we’re done with the demo part of this tutorial and let’s move on to the practical LABs.


   Flashing some LEDs – LAB   

 

Lab Name Flashing LEDs (Digital Output)
Lab Number 1
Lab Level Beginner
Lab Objectives Learn how to use GPIO pins for generating a digital output signal and use it for flashing some LEDs.
Note

All the LABs in this course are performed in 3 successive steps:

1- Coding     2- Simulation     3- Prototyping

 

       1. Coding       

 

Open the MPLAB IDE and create a new project name it “Flashing LEDs”. If you have some issues doing so, you can always refer to the previous tutorial using the link below.

Create New Project With MPLAB IDE

Set the configuration bits to match the generic setting which we’ve stated earlier. And if you also find troubles creating this file, you can always refer to the previous tutorial using the link below.

Now, open the main.c file and let’s start developing the firmware for our project.

Tip

Never forget to include the configurations header file within the main.c file of your project.

First of all, we’ve got three LEDs which will be hooked to 3 of our GPIO pins at PORTB. This means that we’ll have to set the data direction for 3 of the PORTB pins to be output pins. Let’s use the following pins for our LAB [RB0, RB1, and RB2].

Setting the direction (input or output) of these pins is achieved via the TRISB register as follows.

After configuring the GPIO pins which we’ll be using, it’s time to move to our main loop. In which we’ll flash the LEDs in order. Each of them will be ON for 50ms and OFF for another 50ms.
Note

Using the __delay_ms() macro requires that the pre-defined name _XTAL_FREQ to be holding your actual oscillator’s frequency value. For our example, we’re using a 4MHz Crystal Oscillator. Which means that you must include the following LOC at the beginning of your code.

Cross your fingers. Hit the compile button! And we’re done with the firmware.

Building MPLAB Project - PIC Tutorials

 

       2. Simulation       

 

The schematic for this LAB is shown below. Just create a similar one in your simulator.

Microchip-PIC-Embedded-Systems-Tutorials-GPIO-LAB1-Schematic

After connecting everything up in your simulator, you’ll have to double-click the MCU chip and point to the firmware code .hex file. The path will be as follows

ProjectName.X > dist > default > production > FirmwareFile.hex

Make sure to simulate your project at the same oscillator frequency used in the code. Otherwise, the timing diagram will be messed-up. Which means that the 50ms time delays in the code, will NOT be a 50ms time delay in the simulation if you set OSC freq. to a value other than 4MHz!

Note

While using some simulators, you won’t be in need to connect the power supply 5v, crystal oscillator nor the pull-up resistor for the MCLR pin on your own. Which is the case for those who are using Proteus ISIS software. However, any real-life misconnection for any of these three will bring you into a real nightmare! There is so little chance of discovering that your Chip is insanely restarting all the time due to a dodgy MCLR pin or not being clocked at all due to OSC issues, so please, be careful.

 

       3. Prototyping       

 

You should have created the basic prototyping setup which we’ve prepared previously. If you’ve any troubles doing so, you can refer to that previous tutorial.

Prototyping Board - Embedded Systems Tutorials With PIC MCUs

Connect the RB0, RB1 and RB2 pins to the 1st, 2nd and 3rd LEDs Cathode leads. Then hook 3 resistors 330 between the LEDs’ anodes and the Ground line.

Plug the power supply et voila! you’ve accomplished your first (Hello World) embedded project. Congratulations!

The final running project in case you’re curious.

Play Video On YouTube

   Reading Digital Inputs – LAB   

 

Lab Name Reading Digital Inputs (Digital Input)
Lab Number 2
Lab Level Beginner
Lab Objectives Learn how to use GPIO pins for reading a digital input signal and using it to control the flashing of an LED.

 

       1. Coding       

 

Open the MPLAB IDE and create a new project name it “Digital Input”. If you have some issues doing so, you can always refer to the previous tutorial using the link below.

Set the configuration bits to match the generic setting which we’ve stated earlier. And if you also find troubles creating this file, you can always refer to the previous tutorial using the link below.

Now, open the main.c file and let’s start developing the firmware for our project.

We’ll use a couple of GPIO pins:

  1. The first one will operate as a digital input pin and will be hooked to the push button to read its logical state (high/low).
  2. The second pin will operate as a digital output pin and will be hooked to the LED in order to flash it twice per second.

Let’s set the RB0 pin to be an input pin and RB1 to be an output. To configure these pins we’ll write the following code.

After configuring the GPIO pins which we’ll be using, it’s time to move to our main loop. In which we’ll read the state of the button hooked to pin RB0. And if it’s pressed down, we’ll flash the LED hooked to RB1 pin.

Note

The bit-fields could be treated as if they are boolean variables. Which means that the RB0 checking (polling) could have been done with the following LOC without equality check (==1).

Cross your fingers. Hit the compile button! And we’re done with the firmware.

 

       2. Simulation       

 

The schematic for this LAB is shown below. Just create a similar one in your simulator.

PIC Microcontrollers Programming Tutorials LAB2 Schematic

After connecting everything up in your simulator, you’ll have to double-click the MCU chip and point to the firmware code .hex file, make sure to simulate your project at the same oscillator frequency used in the code. Otherwise, the timing diagram will be messed-up.

 

       3. Prototyping       

 

You should have created the basic prototyping setup which we’ve demonstrated previously. If you’ve any troubles doing so, you can refer to that previous tutorial.

Prototyping Board - Embedded Systems Tutorials With PIC MCUs

Connect the RB0 pin to the button which is connected in pull-down mode. Connect the RB1 pin to the LED’s cathode. Then hook resistors 330 between the LED’s anode and the Ground line.

Plug the power supply and test this out!

The final running project in case you’re curious.

Play Video On YouTube

   The power of #define directive   

 

When you’re dealing with Input/Output post, whatever platform is being used, it becomes fundamentally much more efficient to use the pre-processor #define instead of writing directly to bits or registers. Let’s consider the following LOC for example.

What this directive actually does is telling the compiler before assembling code to replace every single occurrence of LED1 in your code with RB0. Which means that you can switch the LED hooked to RB0 pin ON and OFF in the following manner.

This practice is highly recommended not only for debugging purposes but also for portability, readability and the like. It’s ultimately the best way to deal with i/o ports, and it’s also substantial for creating libraries that are easily portable. Let’s take just one concrete example to see the real power of the so-called #define pre-processor. Consider the following code

This code obviously toggling the logical state of the RC1 pin for 100ms, 200ms, and 300ms. Well, let’s suppose for whatever reason you’re forced to leave the RC1 pin and move the (e.g. LED) and hook it to another i/o pin. Let’s say you’ll move it to RD1 pin. Now, you’ll go to the code and make some modifications at every occurrence for RC1 to replace it with RD1. Which is obviously an unnecessary additional overhead, and the final results will never be consistent nor efficient. However, let’s do those modifications.

The decision of moving an i/o pin has resulted in roughly 6 lines that needed correction. It’s not always the case of course and you may be in a situation in which this issue could leave you with a couple of hundreds LOC waiting to be corrected! Just consider instead if you’re using the #define in the following manner.

This code is typically the exact same equivalent of the first example. Which toggles the RC1 pin for varying time intervals. However, when a change has to be made for the i/o pin we are using, and let’s say we’re migrating the LED from RC1 to the RD1 pin. The ONLY change that must be made is just the first LOC as follows.

Pro Tip

Always pick relevant names for the #define pre-processor. Nothing is more painful than irrelevant naming for i/o, variables, functions or whatever. So please, pay attention and invest a couple of seconds of your time thinking about a relevant name that exactly describes your identifier.

 


   Checkout My PIC MCU Programming Video Tutorial   

 

 

PIC Microcontrollers Course Home Page 🏠
Previous Tutorial Previous Tutorial Tutorial 4 Next Tutorial Next Tutorial
Share This Page With Your Network!

Khaled Magdy

I'm an embedded systems engineer doing both Software & Hardware. I'm an EE guy who studied Computer Engineering, But I'm also passionate about Computer Science. I love reading, writing, creating projects and Technical training. A reader by day a writer by night, it's my lifestyle. You can view my profile or follow me via contacts.

You may also like...

16 Responses

  1. Lee says:

    I think there is a typo on section 2 (Read/Write digital i/o pins). In the table on passing 0 to RB3, it says that RB2 pin is driven low. Should that be RB3?

  2. QT says:

    I think the schematic is incorrect for lab 2. The 10k should be pulled down not up, as shown clearly in your video. I tried to implement the schematic and I got reverse logic. Press button = OFF, Not pressed = on.

    • Khaled Magdy says:

      That’s right.. i misconnected that button as you’ve mentioned. It’ll be edited right now. Thanks for your feedback ^^

  3. Ahmad says:

    that was pretty simple and relieving

  4. Adam Tamimi says:

    The preprocessor directive [#include “config.h”] has to be removed, otherwise the program does not compile properly, giving these error messages. I am using mplab-x IDE ver 5.2 & the compiler XC8 ver 2.05
    ————————————————————————————————————————————
    Firmware_Code.c:9:10: fatal error: ‘config.h’ file not found
    #include “config.h”
    ^~~~~~~~~~
    1 error generated.
    (908) exit status = 1
    nbproject/Makefile-default.mk:106: recipe for target ‘build/default/production/Firmware_Code.p1’ failed
    make[2]: Leaving directory ‘C:/Users/otami/OneDrive/Desktop/Test_Project’
    nbproject/Makefile-default.mk:90: recipe for target ‘.build-conf’ failed
    make[1]: Leaving directory ‘C:/Users/otami/OneDrive/Desktop/Test_Project’
    nbproject/Makefile-impl.mk:39: recipe for target ‘.build-impl’ failed
    make[2]: *** [build/default/production/Firmware_Code.p1] Error 1
    make[1]: *** [.build-conf] Error 2
    make: *** [.build-impl] Error 2

    BUILD FAILED (exit value 2, total time: 658ms)
    —————————————————————————————————————

    • Khaled Magdy says:

      Try creating a new project and create the config.h header on your own. It should be generated and saved in the right directory which may resolve your issue

  5. saideh says:

    how use hall effect of Arduino?

  6. Mostafa saleh says:

    very good
    please continue

    mostafa saleh

  7. AKshay says:

    Why don’t you use LAT register as it is more reliable, because IF we set the TRIS bit as output and the same port bit to high, due to some noise or shorting the PORT value will change and cannot be referable. But LAT once a bit is set or cleared, it stays that way
    Right?
    Please clarify my curiosity…
    Thanks

  8. Mohamad says:

    Thank you Mr Khaled for your great tutorial
    Unfortunately non the download links is working.

  9. Hugo V. says:

    How did you connect the PIC3 to flash the chip?

    • Luca says:

      That’s my thought exacly. I think he forgot to mention this fundamental aspect. Have a look at the pickit 3 user manual to understand how to connect it properly to the PIC.

      • Khaled Magdy says:

        Yea! I did really miss to mention this as it would vary from user to another. All pickit programmers do share the same 5 pinout in the same order. And should be easily connected to our prototyping board.
        I did also the exact same thing step by step in the course’s videos on youtube which is unfortunately not in English.

  10. Mostafa says:

    I think there is a typo here
    #define RB0 LED1

    shouldn’t it be
    #define LED1 RB0
    ?

    • Khaled Magdy says:

      Of course! Thanks for pointing this out. I should check this and edit it. However, I believe that it’s written correctly in the downloadable project files. And it also needs to be checked. Thanks, Mostafa!

Leave a Reply

%d bloggers like this: