Getting Started with the PIC32 and MPLAB X

Created by Dakotah Simpson, last modified on Aug 11, 2016

Introduction

Microchip offers a wide portfolio of 32-bit microcontrollers. This guide was created using the PIC32MX360F512L microcontroller. Although there are many varieties, Microchip has conveniently created documents for each of their 32-bit microcontroller families. These documents will be explained in this guide later on. This purpose of this guide is to help people who are just beginning to get into Microchip’s PIC32. This page will guide you through the MPLAB X IDE (Integrated Development Environment) software, as well as show you how to set up some of the main peripherals with the help of example code. A basic understanding of embedded systems and the C programming language will be needed.

Tools

Hardware

Using a starter kit or development board will make learning the PIC32 much easier. Using a starter kit also eliminates the need to buy a debugger/programmer as they already have one built in. The hardware that was used for writing this guide is listed below. It should be noted that there are other starter kits that should also work.

Part Description Includes Part#
PIC32 Starter Kit A small development board intended for new users to get a grasp on the hardware and software of the PIC32 architecture. The board includes 3 LEDs and 3 pushbuttons, so very basic programs can be implemented using only this board. PIC32 board with integrated debugger, USB cable, 3 LEDs on board, 3 pushbuttons on board DM320001-ND
PIC32 Expansion Board For use with one of the PIC32 kits, including the one above. This does not include a microcontroller and WILL NOT WORK BY ITSELF. This board will give the user access to all of the pins so that more involved programs can be implemented. Expansion board, Multiple options for accessing microcontroller pins, Connector for connecting PIC32 starter kits, Power jack (power supply not included) DM320002-ND
Adafruit Accelerometer, Gyroscope, Magnetometer breakout board This chip includes 3 sensors for adding motion, direction, and orientation to your project. It can measure acceleration (including gravity), magnetic force, and rotational motion. Breakout board with LSM9DS0 sensor chip, Pin headers, I2C and SPI interfaces 1528-1205-ND
Parallax 2-Axis Joystick Joystick that can be used to add analog input to you project. Two independent potentiometers (one per axis) control the analog output to the microcontroller. Joystick on board, Headers already attached for easy breadboarding 27800-ND
USB to UART interface board Simple board for easy USB to UART communication to your computer. Interface board, USB cable 336-2613-ND

The accelerometer board, joystick, and USB to UART (Universal Asynchronous Receiver/Transmitter) interface board are not needed for this tutorial but serve as good examples later on. They are all fairly simple to use for communicating to the PIC32 through its peripherals.

Software

You will need to write code, compile it, and program it on to the PIC. To do this you need some software, but thankfully Microchip provides it free of charge. A list of needed software and a link to where you can find it on Microchip’s website can be found below.

Microchip also offers other software such as MPLAB IPE (Integrated Programming Environment) and MPLAB Harmony, but neither of these will be used in this tutorial.

Documentation/Additional Support

There is way too much to learn about the PIC32 to put into this getting started guide so here are a few other resources that you can use to help you with the PIC32.

Title Description
PIC32MX Family Reference Manual This is the “go-to” document for any questions you may have. It goes in depth on everything that the PIC can do and also lists all of the registers. It may be overwhelming at first but after getting used to it, it’ll be your best friend. This manual is broken up into 52 sections which can be found at the link below this table.
PIC32MX3XX/4XX Data Sheet This will go over pin assignments, how to implement the PIC32 into your own board, and also cover the features of the PIC but not as in depth as the Family Manual. This manual is for the microcontrollers with part numbers beginning with PIC32MX3XX or PIC32MX4XX. This can also be found on the link below.
MPLAB X IDE User’s Guide Everything you need to know about using the MPLAB X IDE
PIC32 Starter Kit User’s Guide The user guide for the starter kit has a lot of useful information about the board if you are using it. If you decide to build your own board with a PIC32, this guide has some great schematics to reference.
I/O Expansion Board Information Sheet You will need this for prototyping if you are using the starter kit with the expansion board. It has useful schematics that tell you what each pin is on the board.
Microchip Developer Help Microchip created this tutorial to train developers who are new to their products. There is a lot of helpful information on this site about everything that has to do with PIC microcontrollers. There are also self-paced and live online trainings on here.
Programming 32-bit Microcontrollers in C, by Lucio Di Jasio This book gave rise to a lot of information on this getting started guide. Lucio starts out at the very basics and through hands-on training teaches you all of the necessities of the PIC32. This book was written when an older version of MPLAB was out, though, so not all of the code will compile correctly on MPLAB X.

All of the documentation for the 32-bit PIC microcontrollers, including the family reference manual and data sheet from above, can be found at this website: 32-bit Microcontrollers (MCUs) | Microchip Technology

MPLAB X IDE

MPLAB X IDE is the development environment used to write C code, compile it, and program the PIC32. It also has a simulator which is good to use to debug code that has not yet been programmed onto a PIC. MPLAB X has a plethora of options and settings, but we will just be going over the basics to get you started with it.

Creating a New Project

After downloading MPLAB and the XC32 compiler, you can now open up MPLAB. It should look like the picture below.

The first thing to do is start a new project by going to File>>New Project. Under the Categories section click on “Microchip Embedded” and under the Projects section click on “Standalone Project”. These options will be used throughout this whole guide. Click Next.

The next page is the device selection page. For the Family section click on “32-bit MCUs (PIC32)” and for the Device section find your device. If you are using the PIC32 Starter Kit, then the device will be the PIC32MX360F512L. Click Next.

The wizard will take you to the tool selection page next. For right now we will just use the Simulator tool. This tool will not allow you to program the PIC but it will let you compile and debug the code. Click Next.

Next you will choose the compiler. Just click on XC32 and click Next.

The final screen is the Select Project Name and Folder screen. This is where you can name your project and select where to save it. Don’t worry about the Encoding option. Select Finish and you have created a new project!

Now your MPLAB IDE window should look like this.

You will notice that your project is empty. To start writing code you need a source file, which can be added by right-clicking on the “Source Files” tab and selecting New>>C Source File. Name your source file and select Finish. You are now ready to start writing some code!

Writing to Registers

There are multiple ways to write to the PIC’s registers. The choice of how to do it is mainly made on personal preference or ease of reading the code. Not every single way is mentioned in this guide, but here are a few common ways to do it.

  • Writing to the entire register directly. This will configure every bit in the register to what you want. Example: TRISD = 0x0000

  • Using SET, CLR, and INV registers. Only bits specified with a ‘1’ are modified. Bits specified with a ‘0’ are not modified

    • SET will set the bits. Example: TRISASET = 0x0001 will only set bit 0 in register TRISA
    • CLR will clear the bits. Example: PORTDCLR = 0x0002 will only clear bit 1 in register PORTD
    • INV will invert the bits. Example: LATCINV = 0x0003 will only invert bit 0 and bit 1 in register LATC
  • Writing to each bit individually. This and writing to the entire register will be the most common ways to do it throughout this tutorial. There is a sort-of formula for this method: REGISTERbits.BIT, where REGISTER is where you type the name of the register where the bit is and BIT is the name of the bit. Example: LATCbits.LATC6 = 1 will write a 1 to bit LATC6 in register LATC.

Programming the PIC32

To program the PIC, you will need to switch from the simulator tool to using a debugger. To do this, click on the wrench on the left side of the Dashboard window. The Dashboard window is the window in the bottom left of the screen. When the Project Properties window shows up, choose the debugger you are using under the Hardware Tool list. If you are using the PIC32 Starter Kit, the debugger tool will be found in the “Microchip Stater Kits” folder and then in the “Legacy Starter Kits” folder. It should be listed as “SKDE PIC32”.

Once you have chosen the correct debugger and have connected the PIC, it is best to clean to build the project before programming the PIC. This will make sure that your code is written correctly and there are no syntax errors. The button to clean and build is found at the top of the screen and looks like a hammer with a broom in front of it. After the project has been built and there are no errors, you can program your PIC by clicking on the button that has a green arrow pointing down towards an IC on it. This is the “Make and Program Device Main Project” button.

Clock Configuration

Setting up the clock on the PIC32 can be overwhelming at first because of the many options that are provided. From choosing the internal oscillator or an optional external oscillator to dividing and multiplying the clock to get the exact frequency you want, there is no shortage of options for choosing the system clock.

Oscillator Options

There are four options for selecting the oscillator. There is a fast internal RC oscillator, a low-power internal RC oscillator and the option to connect two external oscillators. For this guide, we will be using the fast internal RC oscillator, which has a frequency of 8MHz. The PIC32 Starter Kit has a primary external 8MHz oscillator as well.

The low-power internal and secondary external oscillators are typically have a low-frequency and are used for real-time clock/calendar (RTCC) applications, watchdog timer sources, and power-up timers among other uses.

The Primary Oscillator Clock Chain

The clock goes through a number of stages before it is used as the system clock. These stages let you choose which frequency you want to use for your application. The figure, from Lucio Di Jasio’s book, shows the three stages that the clock goes through.

The first stage is the input divider. Next is the phase locked loop circuit (PLL). In order to work, the frequency must be between 4MHz and 5MHz before entering the PLL, so we must use the input divider to divide the 8MHz internal oscillator by two. The PLL circuit will multiply the frequency. The last stage is the output divider and after that you have the system clock.

Configuring the Clock

To set up the clock, you could go to the huge family reference manual, search for the correct register, read through all of the options, and then write your own code, or you could use the nifty “Configuration Bits” option that Microchip has provided.

To find this menu, click on Window at the top of the MPLAB IDE window and go down to “PIC Memory Views”. A menu will pop up to the side. Click on “Configuration Bits”. A window like the one shown will pop up below your source code window.

This menu is amazingly helpful and will speed up the clock configuration process. The most important columns on this menu are the far right two. “Category” tells you what you are changing and “Setting” tells you what you have changed it to. In this guide, we will be using the fast internal RC oscillator to make the system clock run at 20MHz. We will also be using this frequency to run the peripherals as well. The options that we will be changing from the default are listed below

  • The oscillator runs at 8MHz, but we need it to be between 4MHz and 5MHz to enter the PLL, so we must choose “2x Divider” for the input divider option.
  • Choose “20x Multiplier” for the PLL Multiplier option.
  • Choose “PLL Divide by 4” for the output divider option. After the input divider, the frequency is 4MHz. After the PLL multiplier, the frequency is 80MHz, so we want to divide by 4 to get to 20MHz.
  • Select “Fast RC Osc with PLL” for the oscillator selection option.
  • Disable the secondary oscillator.
  • Disable internal/external switch over.
  • Select “Pb_Clk is Sys_Clk/1” for the peripheral clock divisor option. This will make the peripheral clock run at the same frequency as the system clock.
  • Disable the watchdog timer.

The Configuration Bits window should now look like this.

These settings will be used for every example used in this guide. To put this into your code, you need to click on “Generate Source Code to Output”. A new window will show up. Just highlight all of the text, and copy and paste it to the beginning of your source code and that’s it! The code should look like the code below. This will be at the top of all of the example code throughout the rest of this tutorial.

// PIC32MX360F512L Configuration Bit Settings
 
// 'C' source line config statements
 
// DEVCFG3
// USERID = No Setting
 
// DEVCFG2
#pragma config FPLLIDIV = DIV_2         // PLL Input Divider (2x Divider)
#pragma config FPLLMUL = MUL_20         // PLL Multiplier (20x Multiplier)
#pragma config FPLLODIV = DIV_4         // System PLL Output Clock Divider (PLL Divide by 4)
 
// DEVCFG1
#pragma config FNOSC = FRCPLL           // Oscillator Selection Bits (Fast RC Osc with PLL)
#pragma config FSOSCEN = OFF            // Secondary Oscillator Enable (Disabled)
#pragma config IESO = OFF               // Internal/External Switch Over (Disabled)
#pragma config POSCMOD = OFF            // Primary Oscillator Configuration (Primary osc disabled)
#pragma config OSCIOFNC = ON            // CLKO Output Signal Active on the OSCO Pin (Enabled)
#pragma config FPBDIV = DIV_1           // Peripheral Clock Divisor (Pb_Clk is Sys_Clk/1)
#pragma config FCKSM = CSDCMD           // Clock Switching and Monitor Selection (Clock Switch Disable, FSCM Disabled)
#pragma config WDTPS = PS1048576        // Watchdog Timer Postscaler (1:1048576)
#pragma config FWDTEN = OFF             // Watchdog Timer Enable (WDT Disabled (SWDTEN Bit Controls))
 
// DEVCFG0
#pragma config DEBUG = OFF              // Background Debugger Enable (Debugger is disabled)
#pragma config ICESEL = ICS_PGx2        // ICE/ICD Comm Channel Select (ICE EMUC2/EMUD2 pins shared with PGC2/PGD2)
#pragma config PWP = OFF                // Program Flash Write Protect (Disable)
#pragma config BWP = OFF                // Boot Flash Write Protect bit (Protection Disabled)
#pragma config CP = OFF                 // Code Protect (Protection Disabled)
 
// #pragma config statements should precede project file includes.
// Use project enums instead of #define for ON and OFF.
 
#include <xc.h>

GPIO Configuration

General purpose inputs and outputs (GPIO) are a necessity in embedded systems. They are one of the most basic features of microcontrollers, but they are also one of the most important. GPIO pins let you control and monitor digital electronics.

There are multiple I/O ports on PIC32s. To find out how many your device has, you will have to go to the PIC32MX3XX/4XX Data Sheet in the Documentation section. Most of the I/O pins have other uses as well, so be careful to which ones you use. With the PIC32 Starter Kit, there are 3 LEDs and 3 pushbuttons that are on the board, which we will be using as our output and input components.

Important Registers

There are three important I/O registers; the TRIS (tri-state), PORT, and LAT registers. Using these three registers you can set the pin to be either an input or an output and read or write to that pin.

The TRIS register lets you choose whether the pin will be an input or an output. You must configure this register first before using the I/O pins. The default after a power-on reset is that they are all defined as inputs.

  • TRIS bit = 1 → pin is configured as an input
  • TRIS bit = 0 → pin is configured as an output
  • A handy way to remember this is that 1 looks like an “I” and 0 looks like a “O”

The PORT and LAT registers let you read and write to the I/O pins. They are very similar to one another, so this can be confusing, but there are a few rules to follow when reading and writing to these registers.

  • Always write to the LAT registers
  • Always read from the PORT registers

Writing to a PORT register will also update the LAT register, but it is better practice to write to the LAT register. The PORT register, when read, will tell you the actual state of that pin at any given time. Reading the LAT register will only tell you the last thing you wrote to it. If you set a LAT bit high, but the pin is accidentally grounded, the LAT bit will read high because that is what you wrote to it, but the PORT bit will read low because that is the actual voltage on that pin.

Example

For this example, the PIC32 Starter Kit LEDs and pushbuttons will be used. We will simply make the LEDs turn on when their corresponding buttons are pushed. The LEDs are connected to pins RD0, RD1, and RD2. The pushbuttons are connected to RD6, RD7, and RD13. The example code for this is shown below. It should be noted that there are pull-up resistors on the input pins, so to tell if a button is being pressed, you need to check for the signal going low.
GPIO Example

// PIC32MX360F512L Configuration Bit Settings
 
// 'C' source line config statements
 
// DEVCFG3
// USERID = No Setting
 
// DEVCFG2
#pragma config FPLLIDIV = DIV_2         // PLL Input Divider (2x Divider)
#pragma config FPLLMUL = MUL_20         // PLL Multiplier (20x Multiplier)
#pragma config FPLLODIV = DIV_4         // System PLL Output Clock Divider (PLL Divide by 4)
 
// DEVCFG1
#pragma config FNOSC = FRCPLL           // Oscillator Selection Bits (Fast RC Osc with PLL)
#pragma config FSOSCEN = OFF            // Secondary Oscillator Enable (Disabled)
#pragma config IESO = OFF               // Internal/External Switch Over (Disabled)
#pragma config POSCMOD = OFF            // Primary Oscillator Configuration (Primary osc disabled)
#pragma config OSCIOFNC = ON            // CLKO Output Signal Active on the OSCO Pin (Enabled)
#pragma config FPBDIV = DIV_1           // Peripheral Clock Divisor (Pb_Clk is Sys_Clk/1)
#pragma config FCKSM = CSDCMD           // Clock Switching and Monitor Selection (Clock Switch Disable, FSCM Disabled)
#pragma config WDTPS = PS1048576        // Watchdog Timer Postscaler (1:1048576)
#pragma config FWDTEN = OFF             // Watchdog Timer Enable (WDT Disabled (SWDTEN Bit Controls))
 
// DEVCFG0
#pragma config DEBUG = OFF              // Background Debugger Enable (Debugger is disabled)
#pragma config ICESEL = ICS_PGx2        // ICE/ICD Comm Channel Select (ICE EMUC2/EMUD2 pins shared with PGC2/PGD2)
#pragma config PWP = OFF                // Program Flash Write Protect (Disable)
#pragma config BWP = OFF                // Boot Flash Write Protect bit (Protection Disabled)
#pragma config CP = OFF                 // Code Protect (Protection Disabled)
 
// #pragma config statements should precede project file includes.
// Use project enums instead of #define for ON and OFF.
 
#include <xc.h>
 
#define LED1        LATDbits.LATD0
#define LED2        LATDbits.LATD1
#define LED3        LATDbits.LATD2
#define SW1         PORTDbits.RD6
#define SW2         PORTDbits.RD7
#define SW3         PORTDbits.RD13
  
 
main()
{
    /*Configure tri-state registers*/
    TRISDbits.TRISD6 = 1;   //SW1 as input
    TRISDbits.TRISD7 = 1;   //SW2 as input
    TRISDbits.TRISD13 = 1;  //SW3 as input
     
    TRISDbits.TRISD0 = 0;   //LED1 as output
    TRISDbits.TRISD1 = 0;   //LED2 as output
    TRISDbits.TRISD2 = 0;   //LED3 as output
     
    while(1)
    {
        LED1 = !SW1;    //LED1 turns on when SW1 is pressed
        LED2 = !SW2;    //LED2 turns on when SW2 is pressed
        LED3 = !SW3;    //LED3 turns on when SW3 is pressed
    }
}

UART Configuration

A universal asynchronous receiver/transmitter (UART) is a device that is responsible for implementing serial communication without the use of a clock. Instead of a clock to keep track of the data bits being sent and received, a specified rate at which data will be sent (called the baud rate) is used. The PIC32 has two UART modules. Starting on page 26 of section 21 in the family reference manual, there is a great configuration guide for the UART. Like most everything else on the PIC32, there are a lot of options when configuring the UART, but we will go over the ones needed to operate it.

To use the PIC32’s UART, you will need something to communicate with. In this example, the USB to UART interface board that is mentioned in the hardware section is connected to the PIC32 Starter Kit through the I/O expansion board. The USB end of the interface board is connected to a computer running a terminal program (HyperTerm, PuTTY, etc.). Here is a great resource if you are new to terminal programs: Serial Terminal Basics - SparkFun Learn

Important Registers

The important registers for configuring and reading/writing to the UART are the UxMODE, UxSTA, UxBRG, UxTXREG, and UxRXREG registers. The ‘x’ in each of the registers above represents which UART module you are using. In this demonstration we will use module 1, so UxMODE will be U1MODE.

First we will set up the baud rate. To do this we will need to configure one bit in the U1MODE register, the BRGH bit to be precise, and write a value to the U1BRG register. The two equations below describe how to configure the baud rate.

As you can see, the BRGH bit only changes the factor that the peripheral bus clock frequency (remember this is equal to the system clock which is 20MHz) is divided by. Whatever value you set the UxBRG register to will be placed into this equation as well. Here are the values we will use to get a baud rate of 9600:

  • U1MODEbits.BRGH = 0, because 9600 is a small baud rate so it is better to divide the peripheral bus clock frequency down as much as we can.
  • U1BRG = 129, this will give us a baud rate of 9615.4, which is only 0.16% higher than 9600, so it is close enough to work.

The UxMODE register is the control register for the UART module. Most of the configuration will be done by writing to this register. In this tutorial we will use a baud rate of 9600, 8-bit data, and 1 stop bit. These are the following bits that will need to be configured in the U1MODE register for this demonstration:

  • SIDL = 0, UART operation is continued during sleep mode
  • IREN = 0, the infrared encoder and decoder is disabled
  • RTSMD = 0, RTS pin is in flow control mode
  • UEN = 0, the pins that transmit and receive data are used (instead of choosing random GPIO pins), CTS and RTS pins are controlled by PORT latches
  • WAKE = 1, UART is woken up when a start-bit is detected during sleep mode
  • LPBACK = 0, loopback mode is disabled
  • RXINV = 0, the receive pin’s idle state is ‘1’
  • PDSEL = 0, 8-bit data, no parity bit
  • STSEL = 0, 1 stop bit
  • ON = 1, the UART module is enabled

The UxSTA register is the status register. There are a few bits in this register that need to be configured, but this register is mostly used for determining if the transmitting or receiving buffers are full among other things that give the status of the UART. Here are the few bits that need to be configured:

  • UTXINV = 0, the transmit pin’s idle state is ‘1’
  • URXEN = 1, the UART receiver is enabled
  • UTXEN = 1, the UART transmitter is enabled

The UxTXREG and UxRXREG registers where the data is actually communicated. You write data to the UxTXREG register to send it out, and you read from the UxRXREG register to see what has been sent to you. The following examples will show how to use these registers.

Examples

These examples include code to configure a UART module, a few functions that you can use in your own code, and an example of their implementation.

The first function demonstrates how to easily configure a UART module.

initUART

void initUART(void)
{
    U1MODEbits.BRGH = 0;                // Baud Rate = 9600
    U1BRG = 129;
     
    U1MODEbits.SIDL = 0;                // Continue operation in SLEEP mode
     
    U1MODEbits.IREN = 0;                // IrDA is disabled
     
    U1MODEbits.RTSMD = 0;               // U1RTS pin is in Flow Control mode
     
    U1MODEbits.UEN = 0b00;              // U1TX, U1RX are enabled
     
    U1MODEbits.WAKE = 1;                // Wake-up enabled
     
    U1MODEbits.LPBACK = 0;              // Loopback mode is disabled
     
    U1MODEbits.RXINV = 0;               // U1RX IDLE state is '1'
     
    U1MODEbits.PDSEL = 0b00;            // 8-bit data, no parity
     
    U1MODEbits.STSEL = 0;               // 1 stop bit
     
    U1STAbits.UTXINV = 0;               // U1TX IDLE state is '1'
     
    U1MODEbits.ON = 1;                  // UART1 is enabled
     
    U1STAbits.URXEN = 1;                // UART1 receiver is enabled
     
    U1STAbits.UTXEN = 1;                // UART1 transmitter is enabled
}

The next two functions will send a character and a string, respectively, over the UART line. The inputs to the functions are whatever character or string you want to send over the UART.

SendChar

void SendChar(char c)
{
    U1STAbits.UTXEN = 1;                // Make sure transmitter is enabled
    // while(CTS)                       // Optional CTS use
    while(U1STAbits.UTXBF);             // Wait while buffer is full
    U1TXREG = c;                        // Transmit character
}

SendString

void SendString(char *string)
{
     
   int i = 0;
     
   U1STAbits.UTXEN = 1;                // Make sure transmitter is enabled
     
   while(*string)
    {
        while(U1STAbits.UTXBF);         // Wait while buffer is full
        U1TXREG = *string;              // Transmit one character
        string++;                       // Go to next character in string
    }
}

The final two functions before we get to a full example are for receiving information through UART. The first function will read a single character and the second one will read a string of a specified length.

ReadChar

char ReadChar(void)
{
    //RTS = 0                           // Optional RTS use
    while(!U1STAbits.URXDA)             // Wait for information to be received
    //RTS = 1
    return U1RXREG;                     // Return received character
}

ReadString

void ReadString(char *string, int length)
{  
    int count = length;
     
    do
    {
        *string = ReadChar();               // Read in character
        SendChar(*string);                  // Echo character
         
        if(*string == 0x7F && count>length) // Backspace conditional
        {
            length++;
            string--;
            continue;
        }
         
        if(*string == '\r')                 // End reading if enter is pressed
            break;
         
        string++;
        length--;
         
    }while(length>1);
     
    *string = '\0';                         // Add null terminator
}

In this example, the UART to USB interface board will be connected to both the PIC32 Starter Kit and a desktop computer with PuTTY running. Whenever a string is typed into the terminal and enter is pressed, the PIC will echo the string back to the terminal program. The RTS and CTS pins will not be used to control the flow of data, so we will just set them to 0.

UART Example

// PIC32MX360F512L Configuration Bit Settings
 
// 'C' source line config statements
 
// DEVCFG3
// USERID = No Setting
 
// DEVCFG2
#pragma config FPLLIDIV = DIV_2         // PLL Input Divider (2x Divider)
#pragma config FPLLMUL = MUL_20         // PLL Multiplier (20x Multiplier)
#pragma config FPLLODIV = DIV_4         // System PLL Output Clock Divider (PLL Divide by 4)
 
// DEVCFG1
#pragma config FNOSC = FRCPLL           // Oscillator Selection Bits (Fast RC Osc with PLL)
#pragma config FSOSCEN = OFF            // Secondary Oscillator Enable (Disabled)
#pragma config IESO = OFF               // Internal/External Switch Over (Disabled)
#pragma config POSCMOD = OFF            // Primary Oscillator Configuration (Primary osc disabled)
#pragma config OSCIOFNC = ON            // CLKO Output Signal Active on the OSCO Pin (Enabled)
#pragma config FPBDIV = DIV_1           // Peripheral Clock Divisor (Pb_Clk is Sys_Clk/1)
#pragma config FCKSM = CSDCMD           // Clock Switching and Monitor Selection (Clock Switch Disable, FSCM Disabled)
#pragma config WDTPS = PS1048576        // Watchdog Timer Postscaler (1:1048576)
#pragma config FWDTEN = OFF             // Watchdog Timer Enable (WDT Disabled (SWDTEN Bit Controls))
 
// DEVCFG0
#pragma config DEBUG = OFF              // Background Debugger Enable (Debugger is disabled)
#pragma config ICESEL = ICS_PGx2        // ICE/ICD Comm Channel Select (ICE EMUC2/EMUD2 pins shared with PGC2/PGD2)
#pragma config PWP = OFF                // Program Flash Write Protect (Disable)
#pragma config BWP = OFF                // Boot Flash Write Protect bit (Protection Disabled)
#pragma config CP = OFF                 // Code Protect (Protection Disabled)
 
// #pragma config statements should precede project file includes.
// Use project enums instead of #define for ON and OFF.
 
#include <xc.h>
  
 
void initUART(void);
void SendChar(char c);
void SendString(char *string);
char ReadChar(void);
void ReadString(char *string, int length);
 
main()
{
    initUART();
    PORTDbits.RD14 = 0;                 // Set RTS and CTS pins to 0
    PORTDbits.RD15 = 0;
    char string[90];
    while(1)
    {
        ReadString(string,90);          // Read in a string
        SendString(string);             // Echo that string
    }
}
  
 
void initUART(void)
{
    U1MODEbits.BRGH = 0;                // Baud Rate = 9600
    U1BRG = 129;
     
    U1MODEbits.SIDL = 0;                // Continue operation in SLEEP mode
     
    U1MODEbits.IREN = 0;                // IrDA is disabled
     
    U1MODEbits.RTSMD = 0;               // U1RTS pin is in Flow Control mode
     
    U1MODEbits.UEN = 0b00;              // U1TX, U1RX are enabled
     
    U1MODEbits.WAKE = 1;                // Wake-up enabled
     
    U1MODEbits.LPBACK = 0;              // Loopback mode is disabled
     
    U1MODEbits.RXINV = 0;               // U1RX IDLE state is '1'
     
    U1MODEbits.PDSEL = 0b00;            // 8-bit data, no parity
     
    U1MODEbits.STSEL = 0;               // 1 stop bit
     
    U1STAbits.UTXINV = 0;               // U1TX IDLE state is '1'
     
    U1MODEbits.ON = 1;                  // UART1 is enabled
     
    U1STAbits.URXEN = 1;                // UART1 receiver is enabled
     
    U1STAbits.UTXEN = 1;                // UART1 transmitter is enabled
}
  
 
void SendChar(char c)
{
    U1STAbits.UTXEN = 1;                // Make sure transmitter is enabled
    // while(CTS)                       // Optional CTS use
    while(U1STAbits.UTXBF);             // Wait while buffer is full
    U1TXREG = c;                        // Transmit character
}
  
 
void SendString(char *string)
{
     
   int i = 0;
     
    U1STAbits.UTXEN = 1;                // Make sure transmitter is enabled
     
    while(*string)
    {
        while(U1STAbits.UTXBF);         // Wait while buffer is full
        U1TXREG = *string;              // Transmit one character
        string++;                       // Go to next character in string
    }
}
  
 
char ReadChar(void)
{
    //PORTDbits.RD15 = 0;                // Optional RTS use
    while(!U1STAbits.URXDA);             // Wait for information to be received
    //PORTDbits.RD15 = 1;
    return U1RXREG;                      // Return received character
}
  
 
void ReadString(char *string, int length)
{  
    int count = length;
     
    do
    {
        *string = ReadChar();               // Read in character
        //SendChar(*string);                  // Echo character
         
        if(*string == 0x7F && count>length) // Backspace conditional
        {
            length++;
            string--;
            continue;
        }
         
        if(*string == '\r')                 // End reading if enter is pressed
            break;
         
        string++;
        length--;
         
    }while(length>1);
     
    *string = '\0';                         // Add null terminator
}

SPI Configuration

Serial Peripheral Interface (SPI) is an interface used for communication between devices. Unlike UART, a SPI interface is synchronous, meaning that there is a clock connected between the two devices to keep them synchronized. Asynchronous communication does not guarantee that both devices are running at the same rate, so synchronous communication is typically a better way to transmit and receive data. One great advantage of using a SPI bus is that more than one device can be communicated with it. There are four pins that need to be connected for SPI communication; Master Out Slave In (MOSI), Master In Slave Out (MISO), the clock, and a chip select. The MOSI and MISO lines are the lines that carry the information. The chip select pin chooses which device to communicate with. Here is a good resource to freshen up on SPI information: Serial Peripheral Interface (SPI) - SparkFun Learn

The PIC32 has two separate SPI buses. Using the SPI bus is almost exactly like using UART communication. The PIC can be configured as either a master or a slave. Starting on page 21 of section 23 of the family reference manual, there is information on the PIC32 SPI buses and how to set them up. Later on in this guide we will also do examples of setting up the SPI bus. In the example, an accelerometer will be used for communication.

Important Registers

The most important registers for configuring and reading and writing from the SPI bus are the SPIxCON, SPIxSTAT, SPIxBUF, and SPIxBRG registers.

Before using the SPI bus for communication, we must initialize the module and configure it to our specifications. This includes choosing how much information will be communicated at one time, the clock frequency, and the idle state for the clock, among other options. Most of the options are configured using the SPIxCON register.

First, the clock frequency must be set up. This is done by writing a value to the SPIxBRG register. Much like the configuration of the UART, there is a simple equation for determining the clock rate, which is shown below.

FPB is the peripheral bus clock, which we set equal to 20MHz. In the examples, we will use a SPI clock frequency of 2.5MHz, so we will set SPIxBRG to 3

We will now go over the most important options in the SPIxCON register when configuring the SPI bus. This is how the SPI bus will be configured in the later examples:

  • FRMEN = 0, framed SPI support is disabled
  • SIDL = 0, operation is continued in idle mode
  • DISSDO = 0, SDO (data out) pin is controlled by the module
  • MODE16 = 1, MODE32 = 0, 16-bit data width
  • CKP = 1, clock idle state is high
  • CKE = 0, data changes on transition from idle clock state to active clock state
  • SSEN = 0, SSx pin not used for slave mode
  • MSTEN = 1, PIC32 is the master
  • SMP = 1, input data is sampled at the end of data output time
  • ON = 1, SPI peripheral is enabled

When configuring the SPI peripheral, only one bit in the SPIxSTAT register is needed. SPIROV = 0 will clear any overflow that has happened. Other bits in the SPIxSTAT register will be used later when reading and writing to the SPI bus.

The SPIxBUF register is the where information to be sent is written to and also where received information is read. This buffer reads in information at the same time as it is sending it out, so it is very efficient. As opposed to the UART, where there is a buffer for transmitting data and a different one for receiving information, the SPI peripheral only uses one buffer for both.

Examples

These examples will closely follow the format of the examples for the UART. There will be an example of configuring the SPI peripheral, a function for writing and reading from the buffer, and an example of their implementation.

This first example shows how to setup the SPI peripheral.

initSPI

void initSPI(void)
{
    CS = 1;                     // Set CS high (idle state)
     
    IEC0bits.SPI1EIE = 0;       // SPI interrupts disabled
    IEC0bits.SPI1RXIE = 0;
    IEC0bits.SPI1TXIE = 0;
     
    SPI1CONbits.ON = 0;         // Turn off SPI module
     
    SPI1BUF = 0;                // Clear the receive buffer
     
    SPI1BRG = 3;                // FSCK = 2.5MHz
     
    SPI1STATbits.SPIROV = 0;    // Clear overflow flag
     
     
    /* SPI1CON settings */
    SPI1CONbits.FRMEN = 0;      // Framed SPI support is disabled
    SPI1CONbits.SIDL = 0;       // Continue operation in IDLE mode
    SPI1CONbits.DISSDO = 0;     // SDO1 pin is controlled by the module
    SPI1CONbits.MODE16 = 1;     // 16 bit mode
    SPI1CONbits.MODE32 = 0;
    SPI1CONbits.CKP = 1;        // Idle state for clock is high, active state is low
    SPI1CONbits.CKE = 0;        // Output data changes on transition from idle to active
    SPI1CONbits.SSEN = 0;       // Not in slave mode
    SPI1CONbits.MSTEN = 1;      // Master mode
    SPI1CONbits.SMP = 1;        // Input data sampled at the end of data output time
     
    SPI1CONbits.ON = 1;         // Turn module on
}

The next example is a function that is used for writing to the SPI buffer and also reading the information that is received. The input to this function is the data that is needed to be transmitted, and the returned value is the received information.

WriteReadSPI

short WriteReadSPI(unsigned short i)
{
    CS = 0;                         // Set the chip select low
    SPI1BUF = i;                    // Write to buffer for transmission
    while (!SPI1STATbits.SPIRBF);   // Wait for transfer to be completed
    CS = 1;                         // Set the chip select back high
    return SPI1BUF;                 // Return the received value
}

This last example is the implementation of the configuration and writing functions. In this example, the PIC32 initializes the accelerometer, and constantly reads in data from it. This data can then be displayed or used to do whatever you want.

SPI Example

// PIC32MX360F512L Configuration Bit Settings
 
// 'C' source line config statements
 
// DEVCFG3
// USERID = No Setting
 
// DEVCFG2
#pragma config FPLLIDIV = DIV_2         // PLL Input Divider (2x Divider)
#pragma config FPLLMUL = MUL_20         // PLL Multiplier (20x Multiplier)
#pragma config FPLLODIV = DIV_4         // System PLL Output Clock Divider (PLL Divide by 4)
 
// DEVCFG1
#pragma config FNOSC = FRCPLL           // Oscillator Selection Bits (Fast RC Osc with PLL)
#pragma config FSOSCEN = OFF            // Secondary Oscillator Enable (Disabled)
#pragma config IESO = OFF               // Internal/External Switch Over (Disabled)
#pragma config POSCMOD = OFF            // Primary Oscillator Configuration (Primary osc disabled)
#pragma config OSCIOFNC = ON            // CLKO Output Signal Active on the OSCO Pin (Enabled)
#pragma config FPBDIV = DIV_1           // Peripheral Clock Divisor (Pb_Clk is Sys_Clk/1)
#pragma config FCKSM = CSDCMD           // Clock Switching and Monitor Selection (Clock Switch Disable, FSCM Disabled)
#pragma config WDTPS = PS1048576        // Watchdog Timer Postscaler (1:1048576)
#pragma config FWDTEN = OFF             // Watchdog Timer Enable (WDT Disabled (SWDTEN Bit Controls))
 
// DEVCFG0
#pragma config DEBUG = OFF              // Background Debugger Enable (Debugger is disabled)
#pragma config ICESEL = ICS_PGx2        // ICE/ICD Comm Channel Select (ICE EMUC2/EMUD2 pins shared with PGC2/PGD2)
#pragma config PWP = OFF                // Program Flash Write Protect (Disable)
#pragma config BWP = OFF                // Boot Flash Write Protect bit (Protection Disabled)
#pragma config CP = OFF                 // Code Protect (Protection Disabled)
 
// #pragma config statements should precede project file includes.
// Use project enums instead of #define for ON and OFF.
 
#include <xc.h>
  
void initSPI(void);
short WriteReadSPI(unsigned short i);
void initAccel(void);
float ReadAccelX(void);
 
#define CS LATDbits.LATD1
  
 
main()
{
    TRISDbits.TRISD1 = 0;       // make CS an output
    initSPI();                  // initialize SPI module
    initAccel();                // initialize the accelerometer
    float X;
     
    while(1)
    {
        X = ReadAccelX();       // get an accelerometer value
         
        /*Use the value to do something*/
    }
}
 
 
void initSPI(void)
{
    CS = 1;              // Set chip select high (idle state is high)
     
    IEC0bits.SPI1EIE = 0;       // SPI interrupts disabled
    IEC0bits.SPI1RXIE = 0;
    IEC0bits.SPI1TXIE = 0;
     
    SPI1CONbits.ON = 0;         // Turn off SPI module
     
    SPI1BUF = 0;                // Clear the receive buffer
     
    SPI1BRG = 3;                // FSCK = 2.5MHz
     
    SPI1STATbits.SPIROV = 0;    // Clear overflow flag
     
     
    /* SPI1CON settings */
    SPI1CONbits.FRMEN = 0;      // Framed SPI support is disabled
    SPI1CONbits.SIDL = 0;       // Continue operation in IDLE mode
    SPI1CONbits.DISSDO = 0;     // SDO1 pin is controlled by the module
    SPI1CONbits.MODE16 = 1;     // 16 bit mode
    SPI1CONbits.MODE32 = 0;
    SPI1CONbits.CKP = 1;        // Idle state for clock is high, active state is low
    SPI1CONbits.CKE = 0;        // Output data changes on transition from idle to active
    SPI1CONbits.SSEN = 0;       // Not in slave mode
    SPI1CONbits.MSTEN = 1;      // Master mode
    SPI1CONbits.SMP = 1;        // Input data sampled at the end of data output time
     
    SPI1CONbits.ON = 1;         // Turn module on
}
  
short WriteReadSPI(short i)
{
    CS = 0;                         // Set the chip select low
    SPI1BUF = i;                    // Write to buffer for transmission
    while (!SPI1STATbits.SPIRBF);   // Wait for transfer to be completed
    CS = 1;                         // Set the chip select back high
    return SPI1BUF;                 // Return the received value
}
 
void initAccel(void)
{
    WriteReadSPI(0b0010000001100111); // CTRL_REG1_XM
    WriteReadSPI(0b0010010010010100); // CTRL_REG5_XM
    WriteReadSPI(0b0010010100000000); // CTRL_REG6_XM
    WriteReadSPI(0b0010011000000000); // CTRL_REG7_XM
}
 
float ReadAccelX(void)
{
   short X_H = WriteReadSPI(0b1010100100000000);    // Read first data register
   short X_L = WriteReadSPI(0b1010100000000000);    // Read second data register
   X_L = X_L & 0b0000000011111111;                  // Combine the data from both registers
   X_H = X_H << 8;
   X_H = X_H & 0b1111111100000000;
   signed short X = X_H | X_L;
   float value = X * 0.000061;                      // Convert to units of g
   return value;
}

In this example, you may be confused about the seemingly random numbers being sent to the accelerometer, but these are not of importance. The implementation of the SPI module and the functions is the most important part of the example.

ADC Configuration

Analog to digital converters (ADCs) are a great way to add compatibility with the physical world to your project. ADCs will take an analog voltage signal from a circuit or sensor such as a potentiometer circuit or a temperature sensor, and convert that signal to a digital signal that the PIC32 can understand and use. The PIC32 allows up to 16 analog input pins for its 10-bit ADC. That’s a lot of inputs! Information on the PIC32’s ADC can be found in section 17 of the family reference manual.

In this tutorial, a Parallax joystick will be used with the PIC’s ADC. The joystick is simply two separate potentiometer circuits, one for each direction. The joystick in the example will need 2 different input pins. A joystick is a cheap, simple way to play around with an ADC.

Important Registers and Examples

Since there is only one ADC module on the PIC32, none of the register names will have the ‘x’ in them like previously. Like the UART and SPI, there are a lot of options for the ADC configuration, but this tutorial will help you get started with a simple configuration. There are quite a few registers associated with the ADC module, so not all of them will be listed here. The most important ones for configuring the module are the AD1CON1, AD1CON2, and AD1CON3 registers. For more help on configuring the ADC, a thorough guide can be found in the family reference manual on page 26 of section 17.

Unlike the previous configuration example in this guide, we will start with an example function of how to simply configure the ADC module, and then an explanation of the settings will follow. The function can be found below.

initADC

void initADC(void)
{
    AD1PCFGbits.PCFG0 = 0;          // Analog input in Analog mode
    AD1PCFGbits.PCFG1 = 0;
    TRISBbits.TRISB0 = 1;           // Pin set as input
    TRISBbits.TRISB1 = 1;
     
    AD1CHSbits.CH0NA = 0;           // Channel 0 negative input is VR-
    AD1CHSbits.CH0SA = 0;           // Channel 0 positive input is AN0
     
    AD1CON1bits.FORM = 0;           // Integer 16-bit output
     
    AD1CON1bits.SSRC = 0b111;       // Internal counter ends sampling and starts conversion
     
    AD1CSSL = 0;                    // No scanning required
     
    AD1CON2bits.VCFG = 0;           // Internal voltage references
     
    AD1CON2bits.CSCNA = 0;          // Do not scan inputs
     
    AD1CON2bits.BUFM = 0;           // Buffer configured as one 16-word buffer
     
    AD1CON2bits.ALTS = 0;           // Always use MUX A input multiplexer settings
     
    AD1CON3bits.ADRC = 0;           // Clock derived from PBclock
    AD1CON3bits.ADCS = 0b00111111;  // TAD = 2*TPB
     
    AD1CON3bits.SAMC = 0b11111;     // 31 TAD auto-sample time
     
    AD1CON1bits.ON = 1;             // A/D converter module is operating
}

Here is an explanation of each of the settings:

  • AD1PCFGbits.PCFG0 and AD1PCFGbits.PCFG1 = 0, puts pins AN0 and AN1 into analog mode
  • TRISBbits.TRISB0 and TRISBbits.TRISB1 = 1, sets pins AN0 and AN1 as inputs
  • AD1CHSbits.CH0NA = 0, makes the negative reference equal to VR-
  • AD1CHSbits.CH0SA = 0, makes AN0 the input to the ADC (can be changed later on)
  • AD1CON1bits.FORM = 0, sets the output as a 16-bit integer
  • AD1CON1bits.SSRC = 0b111, internal counter ends sampling and starts conversion (auto convert)
  • AD1CSSL = 0, no input scanning
  • AD1CON2bits.VCFG = 0, uses internal reference voltages (VDD and VSS)
  • AD1CON2bits.CSCNA = 0, module does not scan inputs
  • AD1CON2bits.BUFM = 0, buffer configured as one 16-word buffer
  • AD1CON2bits.ALTS = 0, always use MUX A input multiplexer settings
  • AD1CON3bits.ADRC = 0, clock is derived from peripheral bus clock
  • AD1CON3bits.ADCS = 0b00111111, sets the conversion clock
  • AD1CON3bits.SAMC = 0b11111, sets the auto-sample time
  • AD1CON1bits.ON = 1, turns on the ADC module

In order to use the ADC, you will need to start a sampling process and then read a value out of the buffer. The output format that is used in this example is an unsigned 16-bit integer that has a range from 0-1023. When the joystick is pushed one way, the ADC will output a value close (but not equal to) 0 and when it is pushed the other way it will be close to 1023. When the joystick is resting, the value will be close to 511, but to find the exact number some testing will need to be done. Reading a value from the ADC is fairly simple and can be done with one small function, which can be found below. The input is the channel that you want to read. For the joystick the options are either 0 or 1.
ReadADC

int ReadADC(int ch)
{
    AD1CHSbits.CH0SA = ch;          // Select input channel
    AD1CON1bits.SAMP = 1;           // Start sampling
    while(!AD1CON1bits.DONE);       // Wait for conversion to complete
    return ADC1BUF0;                // Read conversion result
}

The next example is a template to get the ADC working quickly. To implement this into your own project, just hook up the outputs of the joystick to the AN0 and AN1 pins of your PIC32 and fill in the blank spaces in the conditional statements. In this example you will notice that there is a “buffer zone” for when the joystick is not being pushed. This is the safest way to make sure that nothing will happen when the joystick is released.

ADC Example

// PIC32MX360F512L Configuration Bit Settings
 
// 'C' source line config statements
 
// DEVCFG3
// USERID = No Setting
 
// DEVCFG2
#pragma config FPLLIDIV = DIV_2         // PLL Input Divider (2x Divider)
#pragma config FPLLMUL = MUL_20         // PLL Multiplier (20x Multiplier)
#pragma config FPLLODIV = DIV_4         // System PLL Output Clock Divider (PLL Divide by 4)
 
// DEVCFG1
#pragma config FNOSC = FRCPLL           // Oscillator Selection Bits (Fast RC Osc with PLL)
#pragma config FSOSCEN = OFF            // Secondary Oscillator Enable (Disabled)
#pragma config IESO = OFF               // Internal/External Switch Over (Disabled)
#pragma config POSCMOD = OFF            // Primary Oscillator Configuration (Primary osc disabled)
#pragma config OSCIOFNC = ON            // CLKO Output Signal Active on the OSCO Pin (Enabled)
#pragma config FPBDIV = DIV_1           // Peripheral Clock Divisor (Pb_Clk is Sys_Clk/1)
#pragma config FCKSM = CSDCMD           // Clock Switching and Monitor Selection (Clock Switch Disable, FSCM Disabled)
#pragma config WDTPS = PS1048576        // Watchdog Timer Postscaler (1:1048576)
#pragma config FWDTEN = OFF             // Watchdog Timer Enable (WDT Disabled (SWDTEN Bit Controls))
 
// DEVCFG0
#pragma config DEBUG = OFF              // Background Debugger Enable (Debugger is disabled)
#pragma config ICESEL = ICS_PGx2        // ICE/ICD Comm Channel Select (ICE EMUC2/EMUD2 pins shared with PGC2/PGD2)
#pragma config PWP = OFF                // Program Flash Write Protect (Disable)
#pragma config BWP = OFF                // Boot Flash Write Protect bit (Protection Disabled)
#pragma config CP = OFF                 // Code Protect (Protection Disabled)
 
// #pragma config statements should precede project file includes.
// Use project enums instead of #define for ON and OFF.
 
#include <xc.h>
 
void initADC(void);
int ReadADC(int ch);
 
main()
{
    initADC();              // Initialize ADC module
     
    int x,y;
     
    while(1)
    {
        x = ReadADC(0);     // Set x equal to channel 0 value
        if(x>521)           // When joystick is pushed one way
        {                   // (could be horizontal or vertical direction)
 
        }
        else if(x<501)      // Joystick pushed the other way
        {
 
        }
        else                // Joystick is at rest or being pushed in
        {                   // perpendicular direction only
 
        }
         
         
        y = ReadADC(1);     // Set y equal to channel 1 value
         
        if(y>521)           // When joystick is pushed one way
        {                   // (could be horizontal or vertical direction)
                            
        }
        else if(y<501)      // Joystick pushed the other way
        {
             
        }
        else                // Joystick is at rest or being pushed in
        {                   // perpendicular direction only
             
        }
    }
}
 
void initADC(void)
{
    AD1PCFGbits.PCFG0 = 0;          // Analog input in Analog mode
    AD1PCFGbits.PCFG1 = 0;
    TRISBbits.TRISB0 = 1;           // Pin set as input
    TRISBbits.TRISB1 = 1;
     
    AD1CHSbits.CH0NA = 0;           // Channel 0 negative input is VR-
    AD1CHSbits.CH0SA = 0;           // Channel 0 positive input is AN0
     
    AD1CON1bits.FORM = 0;           // Integer 16-bit output
     
    AD1CON1bits.SSRC = 0b111;       // Internal counter ends sampling and starts conversion
     
    AD1CSSL = 0;                    // No scanning required
     
    AD1CON2bits.VCFG = 0;           // Internal voltage references
     
    AD1CON2bits.CSCNA = 0;          // Do not scan inputs
     
    AD1CON2bits.BUFM = 0;           // Buffer configured as one 16-word buffer
     
    AD1CON2bits.ALTS = 0;           // Always use MUX A input multiplexer settings
     
    AD1CON3bits.ADRC = 0;           // Clock derived from PBclock
    AD1CON3bits.ADCS = 0b00111111;  // TAD = 2*TPB
     
    AD1CON3bits.SAMC = 0b11111;     // 31 TAD auto-sample time
     
    AD1CON1bits.ON = 1;             // A/D converter module is operating
}
 
int ReadADC(int ch)
{
    AD1CHSbits.CH0SA = ch;          // Select input channel
    AD1CON1bits.SAMP = 1;           // Start sampling
    while(!AD1CON1bits.DONE);       // Wait for conversion to complete
    return ADC1BUF0;                // Read conversion result
}

Questions/Comments

Any questions or comments please go to Digi-Key’s TechForum

1 Like