CSCI 255 — Pin I/O on the PIC32

Getting ready

You’ll need the follow hardware for this lab.

Microchip references

Pin I/O and the special function registers

On the PIC32 microcontroller there are a few special function registers (SFR’s) that are used for pin I/O. Some of these registers change the pins. Others configure the pins. The SFR’s are used just like memory locations: except that reading from and writing to them performs magic operations on the pins.

Most pins of the chip can be configured for several different modes of operation. Configuration is done by setting bits within special file registers dedicated to pin I/O configuration.

The GPIO pins are divided into sets called ports, each having its own set of SFR’s. This division will seem very arbitrary. On the PIC32 microcontrollers you will be using, Port A has five ports, RA0 to RA4, while Port B has fourteen ports, RB0 to RB5, RB7 to RB11, and RB12 to RB15. To keep things simpler we will only use the Port B pins. Still you will need a diagram to figure out where the specific pins are located. A diagram for the chips we’ll be using is found on page 5 of the PIC32MX1XX/2XX 28/36/44-PIN datasheet.

The SFR’s used to control the pins are known by special names, such as TRISA and TRISB. Note the are different names for the two ports. In PIC32 documentation, the name TRISx will be used to refer to either the TRISA and TRISB SFR.

Each SFR has a unique address. The C prototype for TRISA is found in the compiler include directory, in our case /opt/microchip/xc32/v1.40/pic32mx/include/proc, in files, such as p32mx250f128b.h. There you will find unusual declarations such as:
    extern volatile unsigned int TRISA __attribute__((section("sfrs")));
for the SFR’s. (By the way, Java also uses the modifier volatile for a similar purpose.)

The TRISA SFR has the virtual address 0xBF886010. That information is found in one of the many processor.o files distributed with the compiler. If you really want to find the compiler definitions for all the SFR’s, you can try out something like the following command:

/opt/microchip/xc32/v1.40/bin/xc32-objdump -t \
 /opt/microchip/xc32/v1.40/pic32mx/lib/proc/32MX250F128B/processor.o

Whew.

Characteristics of Pin I/O

For now we are going to look at a small subset of the many functions that can be performed using the pins of the PIC32 microcontroller. Because this subset is adequate to get us through the first few labs, we’re going to pretend that the pins are limited to the following characteristics during this lab.

Digital or Analog

Most, but not all, pins can be used as either digital or analog interfaces. When used for digital I/O, they interpret 0 v as 0 and 3.3 v as 1. When used for analog input, a 16-bit value giving the input voltage level at a pin is provided by the chip. Analog output is a much more complex and depends on PWM (Pulse Width Modulation), a concept that will be introduced in this lab and discussed more fully in a future lab.

The ANSELx registers are used to configure each pin as digital or analog. By default, pins are analog. That is a bit unexpected. Writing 0 sets the pin to digital.

In this lab description, we’ll follow the Microchip practice of referring to the related pair of special function registers as ANSELx.

Input or Output

A digital pin can be used for either input or output. By default, digital pins are set for output when the pin is configured for digital I/O using ANSELx.

The input or output setting is controlled using the TRISx registers. “TRIS” is an abbreviation for TRI-STATE®, a registered trademark of National Semiconductor. Writing 0 (looks like ‘O’ for output) sets the pin to digital. Writing 1 (looks like ‘I’ for input) sets the pin to digital.

Microchip recommends that unused pins be set to digital output with low written to the pin. This is something we will do in our labs.

Change notification

Pins can be set to interrupt the processor when an input to the chip is changed by an external circuit. This allows the chip to “sleep” and plays a significant role in reducing battery life in consumer devices.

Change notification is enabled using the CNENx registers.

Weak pull-ups and pull-downs

“Weak” pull-ups and pull-downs can be enabled on pins. When a pull-up is enabled, an input pin reads as 1 when no external voltage source is “driving” the line connected to the pin. To avoid the cost of an external resistor, weak pull-ups are often used when pins are connected to push-button switches.

When a pull-down is enabled, an input pin reads as 0 when no external voltage source is connected to the pin.

The pull-ups and pull-downs are enabled using the CNPUx and CNPDx registers. The PIC32 documentation states that weak pull-ups and pull-downs should be disabled when a pin is used for digital output.

This could be your only UNC Asheville class where pull-ups are discussed.

Open-drain

Some digital output pins can be set to “drain” their lines. An open-drain pin can “sink” current on a output line until 0 v is reached or it can allow the output to “float” to an externally set value, which might even be higher than 3.3 v.

Open drain is enabled using the ODCx registers; however, the drain is switched on and off using the TRISx registers.

Writing to a digital pin

Digital output pins are written using the LATx latch registers. The LATx registers store the output values that are being written to the external world. It is similar to a D-latch.

If a pin is configured for input, the LATx and PORTx registers can differ. Also, it can take a few microseconds for values written to the LATx register to be reflected in the PORTx registers.

Reading a digital pin

The values of the digital input pins are read using the PORTx registers. Do not write to these registers!

How it all looks

PIC 32 port module

Writing to GPIO registers

Here’s some C code that will initialize all the GPIO pins to be digital output pins.

    ANSELA = 0 ;           // Set all pins for digital
    ANSELB = 0 ;
    CNENA = 0 ;            // Disable change notification
    CNENB = 0 ;
    CNPUA = 0 ;            // Disable weak pull-up
    CNPUB = 0 ;
    CNPDA = 0 ;            // Disable weak pull-down
    CNPDB = 0 ;
    ODCA = 0 ;             // Disable open drain
    ODCB = 0 ;
    TRISA = 0 ;            // Set all pins for output
    TRISB = 0 ;
    LATA = 0 ;             // Write 0 to output
    LATB = 0 ;

Each of these registers has a bit devoted to a specific pin. In particular, the i’th bit of the register controls the i’th pin of the port.

If you want to write a 1 to port RB11 and not modify the other pins of Port B, you need the following line of code:
    LATB = LATB | 0x00000800 ;
0x00000800 is a literal with only its eleventh bit turned on.

Similarly to write a 0 to port RB11 without modifying other pins, you need
    LATB = LATB & 0xFFFFF7FF ;
which would be clearer if written as
    LATB = LATB & ~(0x00000800) ;
However, the proffered style is the C idiom
    LATB = LATB & ~(1<<8) ;

To make PIC32 code easier to write there are three additional SFR’s for each of the SFR’s mentioned above. To write a 1 to only one port use
    LATBSET = 0x00000800 ;
To write a 0 to only one port use
    LATBCLR = 0x00000800 ;
(Yes, you do write a one to store a zero.) And finally, if you want to toggle an output bit
    LATBINV = 0x00000800 ;

By the way, the computers chips used in both the Arduino and Raspberry Pi have similar sets of registers for manipulating GPIO pins.

The downside of multiple usage

Unfortunately, a single physical pin has several names, each reflecting a potential function. For example, physical pin 6 corresponds to analog pin 4 (AN4) and GPIO port B pin 2 (RB2). It also happens to be known as C1INB, C2IND, RPB2, SDA2, CTED13 and PMD2. That’s eight possible uses for one pin.

Back to the breadboard

We are are going to complete this lab using a Microstick II with an MX250F128B chip. The Microstick will be placed in a breadboard for wiring components. (If you want to try the lab with an MX220F032B chip on a breadboard with a Pickit 3, ask the instructor how this could be done.)

The Microstick allows access to almost all pins of the MX250F128B; however, the regulated 3.3 v power supply is not available for external use. This places some restrictions on what we can do today. When we need power, we will have to add a voltage regulator to the breadboard.

In this lab your chip will use two push-down switches for input and three LED’s as output. The LED’s will be arranged to look a little like a stoplight. The switches will be connected to pins 16 (RB7) and 17 (RB8) of the Microstick. LED’s will be connected, through resistors, to pins 11 (RB4) and 14 (RB5).

Microstick meets breadboard

Go ahead and carefully place your Microstick on the breadboard. The gold-plated pins on the Microstick are very fragile, so do not insert the Microstick until you are certain it is properly aligned. Arrange it so that pin 1 of the Microstick is in row 1 of your breadboard. This means that your Microstick will be occupying rows 1 to 14 of your breadboard and the USB interface of the Microstick will be hanging off the board. (Many of the larger breadboards use different numbers on the two edges of the breadboard. Use the numbers on the right side.)

Take a look at page 2 of the Microstick  Information Sheet. You need to wire the ground pins of the Microstick to the ground rails of the breadboard. This means running black wires from pins 8, 19, and 27 of your Microstick to the breadboard grounding rails.

The numbering of pins on the Microstick follows pin numbering standards of dual in-line package (DIP) circuits. The “top” of the chip is the short end with the notch. Pins are numbered counter-clockwise starting with the pin just to the left of the notch.
DIP pin numbers

Run the ground connections for your Microstick II on the breadboard and carefully place the Microstick on the board. It should like something like the following pictures.
wired Microsick II      wired Microsick II

Or maybe, when you are done, a bit like crudely drawn image.
connected chip

Adding lights and switches

Next you are going to connect three LED’s into your circuit. The LED’s should be run in series with a 1k Ω resistor between the Microstick and ground. If you don’t remember how to do this, you need to review the Adafruit lesson on digital inputs.

Wire the LED’s and resistors so that pin 7 connects to the green LED, pin 6 connects to the yellow LED, and pin 5 connects to the red LED.

Now it is time to connect the push-button switches. Again, if you don’t remember, review the Adafruit.

Connect your left switch to pin 17 and your right switch to pin 16.

Carefully make the connections, compare them with at least one other student, and let the instructor have a quick look at your work.

To review, your circuit should be wired up as specified in the table below.

Circuit elementMicrochip pinMicrochip port
Red LED5RB1
Yellow LED6RB2
Green LED7RB3
Right switch16RB8
Left switch17RB7

Programming the Microstick

In the world of embedded system development, programming refers to the process of downloading a program into the microcontroller.

Getting the hardware ready

Remove the Microstick from your breadboard, make sure your Microstick is not connected to the USB cable, and verify that your Microstick contains a PIC32MX250F128B microcontroller. If the Microstick does not contain a PIC32MX250F128B, replace the PIC microcontroller in the Microstick with a PIC32MX250F128B.

Starting the project

Start mplab_ide and create a project just like you have done in recent labs. When it comes time to choose the hardware device, select the PIC32MX250F128B that is on your Microstick.

When it comes time to select your tool, choose the serial number listed under Starter Kits. You may want to verify that the serial number matches the one on your Microstick.
Selecting the PIC32MX250F128B      Selecting the Microstick

Creating the program

Add a C main program to your project containing the following C code.

/*
 * File:   CSCI 255 Stoplight lab
 * Author: Your Name here
 */

#pragma JTAGEN=OFF
#pragma FWDTEN=OFF
#pragma FSOCEN=OFF

#include <xc.h>
#include <stdlib.h>

/*
 * Pins used in lab all on port B
 *   Pin  5 --  RB1  [PGEC1/AN3/C1INC/C2INA/RPB1/CTED12/PMD1/RB1]
 *   Pin  6 --  RB2  [AN4/C1INB/C2IND/RPB2/SDA2/CTED13/PMD2/RB2]
 *   Pin  7 --  RB3  [AN5/C1INA/C2INC/RTCC/RPB3/SCL2/PMWR/RB3]
 *   Pin 16 --  RB7  [TDI/RPB7/CTED3/PMD5/INT0/RB7]
 *   Pin 17 --  RB8  [TCK/RPB8/SCL1/CTED10/PMD4/RB8]
 */

/* All on port B */
#define GREENLED    3
#define YELLOWLED   2
#define REDLED      1

#define LEFTSWITCH  8
#define RIGHTSWITCH 7

void spin(int wastetime) {
    int i ;
    for (i=0; i<wastetime; ++i) ;
}

int main(void) {

    ANSELA = 0 ;           // Set all pins for digial
    ANSELB = 0 ;
    TRISA = 0 ;            // Set all pins for output except for SWITCHES
    TRISB = 0x0180 ;
    CNENA = 0 ;            // Disable change notification
    CNENB = 0 ;
    CNPUA = 0 ;            // Disable weak pull-up except for SWITCHES
    CNPUB = 0x0180 ;
    CNPDA = 0 ;            // Disable weak pull-down
    CNPDB = 0 ;
    ODCA = 0 ;             // Disable open drain
    ODCB = 0 ;
    LATA = 0 ;             // Write 0 to output
    LATB = 0 ;

    while(1) {
        spin(100000) ;
        LATBINV = 0x000E ;
    }
    return (EXIT_SUCCESS);
}

Build the program to make sure there are no compiler errors.

Programming the chip

Use the Make and Program Device icon Programming the Microstick to program your PIC32 processor. You should get the message Programming completed if this works. However, if the firmware on the Microstick II hasn’t been used in a while, it will be upgraded. This can take quite a while. Many lines will be output by the programmer during this process.

Now slowly and carefully place your PIC32 into the breadboard. Remember that the pins on the Microstick are fragile, so be careful. This program just flashes the flights off and on.

Demonstrate that your program is flashing all three signals.

Programming with a bit more style

This program has awful style. Let’s try to fix it up a bit.

Eliminating magic constants

Start by getting rid of the magic constants 0x0180 and 0x000E. Do this by defining masks for the bits. Here an example of an appropriate mask.
    #define GREENLEDMASK (1 << GREENLED)
It defines a bit mask that has a 1 in just the right place.

Add appropriate #DEFINE statements for all three LED’s and both switches.

Use these defined constants to eliminate the magic constants. You will probably need to use the logical or, |, in your program.

Why not use const, rather than #DEFINE? Well, because this is C. If this was C++, it might have been better to use something like
    static const int GREENLEDMASK = (1 << GREENLED) ;
which is also legal in C. But in C this just isn’t the usual practice. (Though, you are welcome to use it in this case.)

By the way, in C and C++ static has a very different meaning than it does in Java.

Using standard library routines

With the Arduino, we were able to use calls like pinMode to set a pin to input and output or digitalWrite to set the output value of a pin.

The Atmega chips used in the Arduino do have ports that contain several pins, just like the PIC; but the designers of Arduino chose the hide ports from most programmers. (See the picture of Arduino pin mappings to see how this is done.)

There is a PIC32 peripheral library which contains several useful routines for manipulating ports; however, it is a little hard to see much advantage in using
    PORTToggleBits(IOPORT_B, BIT_3|BIT_2|BIT_1) ;
rather than a simple write to LATBINV.

We could use the PIC32 peripheral library, just like we did last year, but Microchip is in the process of replacing the peripheral library with a new development system called Harmony which we will use in CSCI 320.

Doing a better job of keeping time

Take out a watch and count the number of times your light changes in 100 seconds. (That will be two counts per flash.) Use that count to adjust your argument to spin so that the light changes about once a second.

Compare your count with your neighbors. The numbers should be close. If you are way off, try again.

Also add a #define”ed constant called SPINSPERSECOND with the number you just discovered. Use it in your program to make the lights change every second.

Finally a stop light

Modify your program that is acts a bit like a traffic signal; however, don’t make the lights stay on as long as a real traffic signal. That would be very boring.

Remember, the traffic signal goes green-yellow-red and the yellow is shorter than the green and yellow phases.

Another encounter with PWM

Try speeding up the traffic signal by modifying the argument passed to spin. Make it 16 times faster, then 256 times faster, then 4096 times faster. Use one of the ugliest operators in C (and Java) the right shift assignment operator, >>=, to avoid division.

A some point the LED’s will blink so quickly that your eyes cannot tell that the LED is flashing on and off. It should be the case that the yellow LED will be dimmer than the red and green. (Your mileage may vary. Some LED’s are just brighter than others.) This is an example of pulse width modulation (PWM), turning a digital output off and on so quickly that it looks like an analog signal. PWM is used to control motors and speakers, which are motors for moving air.

Right now, the yellow LED should be on about 10% of the time while the red LED should be on about 50% of the time. To make the difference a bit more noticeable, modify your code so that the red and green LED’s are on 90% of the time and the yellow LED in on 10% of the time, This will require some recoding inside your loop.

Show off your dimmed lights.

Because some LED’s are brighter than others and because our eyes perceive some colors easier than others, it may be necessary to use three cheap red LED’s in this experiment, rather than the more colorful LED’s. If you do this the LED in the middle should be dimmer than the others.

Finally the switches

Because we don’t have access to 3.3 V from the Microstick, we must use our switches in pull-up mode. This means the switches will be open when they are not pressed and will be close when they are pressed. Consequently, a switch will read as 0 when pressed and as 1 when not pressed.

Also, when reading the value of the switch, you should read the SFR PORTB rather than LATB. Before going any further, think about the tests you would use to check if the switches are pressed.

Modify your code to accomplish the following: When the left button is pressed, the green and yellow LED’s are on. When the right button is pressed, the yellow and red LED’s are on. When no button is pressed, all LED’s are off.