How PIC I/O ports work
If you are new to microcontroller programming you might be wondering how a program running inside a chip can read inputs from external circuits, or control devices attached to a chip’s output pins. All microcontrollers do this a little differently, but the overall concepts are very similar. Continue reading to learn how I/O (Input and Output) ports work in the different types of PIC microcontrollers used in mirobo projects, and how the use of #define directives in a header file can simplify pin naming in your programs.
I/O ports and pins
PIC microcontroller I/O pins are typically organized into groups of up to eight pins referred to as ports. The PIC16F1459 microcontroller used in CHRP4 and UBMP4 has three 8-bit ports named PORTA, PORTB, and PORTC. Since this microcontroller has only twenty pins, not all ports have the full set of eight I/O pins. In fact, only PORTC has all eight I/O pins brought out of the chip for I/O – PORTA has five I/O pins, and PORTB has just four. Take a look at the schematic diagram of CHRP4 or UBMP4 to see which port pins are connected to each circuit’s external I/O devices.
The 8-pin PIC12F1840 microcontroller used in the PIANO2 circuit has its six I/O pins arranged into one 8-bit port, named PORTA. Some of Microchip’s other small microcontrollers have a single I/O port named GPIO instead of using the PORTA name in common with Microchip’s larger microcontrollers.
Within each lettered port, individual I/O pins are identified by their pin numbers, with these pin numbers being assigned by their bit position within the 8-bit port. This means that the least-significant bit, which represents the right-most digit of a binary number, is bit 0 (or, pin 0) of the port.
So, when looking at the PIC16F1459 microcontroller symbol on the CHRP4 or UBMP4 schematic, individual port pins will be labelled with an R, then the port’s letter identifier (A, B, C, etc.), and finally the bit number, such as RA4, or RC0 for example. Why R? Microchip refers to internal RAM locations as registers – the I/O ports are mapped into the internal RAM – so RA4 is the register name used to identify bit 4 of register PORTA. Likewise, RC0 refers to bit 0 of the PORTC register.
Ports, latches, tri-states, and more…
While simple, short names such as RA4 are used to label the external microcontroller pins, the C compiler requires the use of longer register names when referring to the same I/O pins. The reason that the compiler doesn’t simply use the pin name is partly due to the fact that newer microcontrollers have more complex and fully-featured I/O ports than older ones did, including separate input and output registers. So, one external microcontroller pin may be connected to multiple internal RAM registers.
In more modern PIC microcontrollers, such as the PIC16F1459 and PIC12F1840, the PORTA register is one of two RAM registers responsible for input and output named PORTA and LATA (short for Latch A), respectively. Three additional registers, TRISA, ANSELA, and WPUA are also connected with the operation of PORTA and control additional features available on some of the PORTA pins. Here is a description of what each of the registers associated with PORTA are responsible for:
PORTA is the input register associated with the RA(0-5) pins. Reading PORTA by itself will read the state of all input devices connected to the port. Reading a single pin, such as pin RA4, can be done using the name RA4, but is more safely done by adding the bits specifier after the port name and appending the hardware bit name. For example, hardware pin RA4 is referred to PORTAbits.RA4 in a C program.
LATA is the output latch register for the RA(0-5) pins. Writing to the LATA register stores 8 bits of output data in the (available) individual latches connected to the output pins. Writing to a single output pin, such as LATA4 works, as does writing to the pin by using the long name created by adding the bits specifier and the latch bit name. For example, the statement LATAbits.LATA4 = 1; will output a high voltage on hardware pin RA4.
TRISA is the tri-state control register for the port A I/O pins. Tri-state circuits can connect or disconnect their outputs from their inputs, and one or more tri-state circuits can be used in combination to determine which port pins will be used as inputs and which port pins will be used as outputs. This is important because any output latch that could not be disconnected from its input pin would have its output signal interfere with an external circuit’s input signal to the pin, making reading the actual value of the pin unreliable. To prevent this, a tri-state register is used to control the input and output configurations of every port pin.
Writing the value 0 into a TRISA tri-state control bit causes its corresponding pin to be an output, and writing 1 into a TRISA bit makes the associated pin an input – think of 0 as being like the letter ‘o’ in output, and as 1 being like the letter ‘i’ in input to help you remember the direction of the TRISA bits. By default, and for safer start-up operation, all microcontroller I/O pins are configured as inputs when a microcontroller is first powered on. To use I/O pins as outputs, your program has to write zeros to the TRISA bits corresponding to the output pins. The statement TRISAbits.TRISA4 = 0; can be used to configure RA4 as an output, for example.ANSELA is the analogue select register and determines which PORTA pins will be enabled for analogue input instead of digital input. Writing 1 into an ANSELA bit causes the corresponding pin to become an analog input, and writing a 0 into an ANSELA bit makes the pin a digital input instead. So, configuring pin RA4 as a digital input can be done using the statement: ANSELAbits.ANSA4 = 0;
WPUA is the weak-pull-up resistor register for any PORTA pins configured as digital inputs. Pull-up resistors are built into some microcontroller input circuits, and the term weak simply refers to the fact that these have a high resistance, and will therefore have only a weak effect on the input – as well as low current consumption – when in use. The statement WPUAbits.WPUA4 = 1; can be used to enable the weak-pull-up resistor on input pin RA4.
The PIC16F1459 microcontroller’s other two ports, PORTB and PORTC, also have associated latch (LATB and LATC), tristate (TRISB and TRISC), and analogue (ANSELB and ANSELC) registers. PORTA and PORTB are the only registers that have weak-pull-up resistors, and the PORTB pull-up register is called WPUB.
How is it possible to know about all of the different registers involved in I/O? Microcontroller data sheets are an invaluable resource for all microcontroller-related information. The data sheets can be found on the Microchip Technology website – the product pages for the PIC16F1459 microcontroller and the PIC12F1840 microcontroller include features, specifications, and links to their data sheets.
Important! Configure before using
It is important to know that after a device power-up or reset, all of a PIC microcontroller’s I/O ports are set up as inputs, with any analogue inputs enabled, and with weak pull-up resistors enabled on the remaining digital inputs. This is done for safety, due to the fact that all input circuits have a high electrical impedance, which will hopefully prevent any attached output devices from activating before the software has had a chance to configure the I/O pins to their proper output levels.
Before using any I/O port (or pin) in your program, you will need to configure the ANSELx, TRISx, and WPUx (where x represents the port letter: a, b, or c) registers for each of the desired capabilities on each pin. You can see exactly how port configuration is accomplished in each program’s configuration function, which is located in the circuit board function file for the corresponding mirobo project (see How mirobo projects are structured for more information on the function file and the other files that make up a project). The UBMP4_config( ) function from the UBMP4.c function file is shown below:
// Configure hardware ports and peripherals for on-board UBMP4 I/O devices. void UBMP4_config(void) { OPTION_REG = 0b01010111; // Enable port pull-ups, TMR0 internal, div-256 LATA = 0b00000000; // Clear output latches before configuring PORTA ANSELA = 0b00000000; // Disable analog input on all PORTA input pins WPUA = 0b00001000; // Enable weak pull-up on SW1 input only TRISA = 0b00001111; // Set LED D1 and Beeper pins as outputs LATB = 0b00000000; // Clear output latches before configuring PORTB ANSELB = 0b00000000; // Disable analog input on all PORTB input pins WPUB = 0b11110000; // Enable weak pull-ups on pushbutton inputs TRISB = 0b11110000; // Enable pushbutton pins as inputs (SW2-SW5) LATC = 0b00000000; // Clear output latches before configuring PORTC ANSELC = 0b00000000; // Disable analog input on all PORTC input pins TRISC = 0b00001111; // Set LED pins as outputs, H1-H4 pins as inputs // TODO - Enable interrupts here, if required. }
Instead of configuring each port pin individually (and using a lot more program code), the example code above configures each port register and the group of I/O pins it is responsible for. In this example, the I/O pins in each port are specifically configured for the types of I/O devices connected to them in a typical UBMP4 circuit board build. The configuration functions for other mirobo circuits are different, but their functions would similarly configure the I/O ports for their circuit’s input and output requirements.
If you are creating a configuration function for a different circuit, or one that you have designed, the general steps for configuring I/O ports are:
Set any output pins to the state(s) they are supposed to be in before switching the function of the pins to outputs (this prevents what is known as a glitch – a port pin briefly outputting one signal level before it changes to the proper, opposite signal level).
Disable the analogue inputs on the pins that will be digital inputs, and enable the weak-pull-up resistors for the digital input pins, if needed.
Configure the tri-state register to enable the port output pins.
While it is convenient to organize all of the I/O port configuration steps into a single function as shown here, it is sometimes necessary to reconfigure the functions or features of one or more I/O pins during a program’s operation. For the best results, follow the same steps as when configuring a port: set the outputs to their desired states first, then configure the inputs, and finally enable or disable the outputs.
Simpler I/O pin naming
Hardware I/O pin naming started out simply enough, with microcontroller pin names such as RA4 and RC0 labelled directly on the circuit schematics. After learning about the port registers, things got a bit more complicated as we learned commands such as LATAbits.LATA4 = 1; are used to turn on output pin RA4. Now, let’s use a compiler feature to make I/O pin names simple again.
#define directives
A C language #define directive can be used to define a symbolic alias for each microcontroller register and bit. This feature makes it possible for us to assign a real name to an I/O port pin. Which real name? How about the schematic reference of the part connected to the I/O pin? That’s about as real as it gets!
Here is a small sample of the UBMP4’s header file showing the use of #define directives for the PORTA I/O pins:
// PORTA I/O pin definitions #define SW1 PORTAbits.RA3 // SW1/PROG/Reset (MCLR) pushbutton input #define BEEPER LATAbits.LATA4 // Piezo beeper (LS1) output #define LS1 LATAbits.LATA4 // Piezo beeper (LS1) output #define D1 LATAbits.LATA5 // LED D1/RUN LED output (active-low) #define LED1 LATAbits.LATA5 // LED D1/RUN LED output (active-low)
You can see that both the names BEEPER and LS1 have been defined as aliases for output pin RA4, and either of these names can be used instead of the actual port and bit address LATAbits.LATA4. Using a defined alias is simpler, since the statement BEEPER = 1; can now be used in a program set the RA4 pin high. Instead of BEEPER, the schematic and PCB part reference LS1 can be used too, making it easy for a programmer to glance at the circuit board and instantly know the name of many I/O pins.
The project header file for each mirobo circuit board (see How mirobo projects are structured for more information on the header file as well as the other files that make up a project) defines all of the I/O pins used by the circuit. There is no need to refer to the schematic diagram to look up which I/O pin corresponds to each component – simply refer to each part by its part reference on the board and the header definition will take care of substituting the proper port address.
Mircrocontroller I/O Terms
These are some of the terms you will need to be familiar with when learning about microcontroller I/O:
Input - a circuit inside a microcontroller that can measure the potential (voltage) provided by an external circuit
Output - a circuit inside a microcontroller than can produce a potential (voltage) for an external circuit attached to it
I/O pin - an individual wire on a microcontroller chip that can act as an input, an output, or either an input or output.
Port - a group of up to eight I/O microcontroller I/O pins that often share similar characteristics or internal circuits
Latch - a type of memory circuit attached to an I/O pin that allows it to hold the last value that was supplied to it
Tri-state - a type of circuit that allows the passage of the two states (0, and 1) of a digital signal, and has the ability to disconnect its output from its input which creates a third state with no signal (commonly called the high-impedance, or hi-Z state) which is important for disconnecting an output latch from an input circuit
Digital I/O - I/O pins that read or produce one of two output states, known as 0 and 1 (or low and high), which correlate to external potentials of 0V and 5V, respectively (or 0V and 3.3V on some microcontrollers)
Analogue I/O - I/O pins that read or produce a larger number of states than just two, allowing for more accurate measurement of external voltage or more accurate control of external devices
Multiplexer (Mux) - a multiple-input switch that allows a circuit to receive one selected signal out of a group of signals