C Programming on a PIC32MX Microcontroller

main project image



Hardware Overview:

This is a microcontroller development board made by Microchip Technology (headquartered in Chandler, AZ, just outside of Phoenix). It's called the "Explorer 16 Development Board" and it was initially released in 2009- this specific model is the DM240001.

dev board image

This dev board has a socket to plug in a microcontroller (in this case a PIC32MX microcontroller of the 300 series). The dev board accepts PIMs (Plug-In Modules) to try out different microcontrollers. These are 100-pin packages, 25 pins per side arranged in a square.

The board has peripheral hardware like LEDs, an LCD screen, a serial port, and push buttons so you can write some programs to put the PIC32 through it's paces and see what it can do, interfacing with the world outside the PIM itself.

The photo also shows the PICkit3 programmer neccessary to load programs onto the microcontroller. Program code is written using Microchip's MPLAB software (on a host computer), and a USB cord connects the host computer to the PICkit3 to send the program over to flash memory on the dev board.


Book Overview:

The book is set up as 16 "Days" worth of material. Each chapter explores one basic hardware peripheral of the PIC32MX family of microcontrollers, and one aspect of the C language per day.

Recommended Hardware and Software to Use with the Book:

All of the above hardware and software is made by Microchip Technology Incorporated (headquartered in Chandler, AZ), with the exception of the microcontroller core design, which is made by MIPS (San Jose, CA).

The MCU:

The Dev Board:


Hardware Setup:

  1. Windows host computer running MPLAB IDE 8.91
  2. PICkit3 programmer plugged into Explorer 16 board (LEDs face the PIC32 PIM module) and the other end (USB) plugged into the host computer.
  3. Plug power into Explorer 16 board (optional - you can also set the PICkit3 to provide power to the board directly, via the IDE settings)

The Programmer (PICkit3):

In order to program the PIC32, you need a physical device in between your dev board and your computer. There are many devices made for this (all proprietary Microchip products):

How do these connect to the Dev Board?

The PICkit3 supercedes the other devices and it's all you need for every project in the book.

dev board image

Using MPLAB IDE:

Menu options:

The concept:

Here are the options for the Debugger (under Debugger -> Select Tool)

The first half of the book can just use the simulator (MPLAB SIM) Debugger, not actually programming the board with the PICkit3. The second half of the book requires programming the hardware itself.


Setting up the Primary Oscillator Clock Chain

This book really should have started with Day 7, which talks about all the configuration options that are set each time a new program is flashed to the PIC32MX chip.

These configuration options select which oscillator feeds into the Primary Oscillator Clock Chain (which then defines the System Clock and the Peripheral Bus Clock, both used extensively in Chapters 1-6)

This configuration can be set to be programmed whenever you flash new code to the PIC32MX, or can be programmed dynamically in source code. For this book (at least for the early chapters), we'll be setting them directly.

The high-level idea:

  1. Select the external fast (8 MHz) oscillator
  2. Hook this up to a PLL (Phased Locked Loop) in order to scale the incoming signal.
  3. An input divider is set to 1:2 (scales 8 MHz to 4 MHz)
  4. The PLL scales this to x18 (producing 72 MHz)
  5. An output divider is set to 1:1 (no effect)
  6. The SYSTEM_CLOCK is then set to 72 MHZ
  7. This signal is divided by 2 to define the PERIPHERAL BUS CLOCK (now resulting in 36 MHz)

All this is configured by setting just 3 special function registers (because these are each 32-bits wide). Within these SFRs are individual Fields which are set accordingly:

In the IDE, go to Configure -> Configuration Bits, and set the following:

dev board image

System Clock = 72 MHz

Peripheral Bus Clock = 36 MHz


dev board image

Book Introduction:

Written in 2008, author has background in 8-bit microcontrollers and assembly language. Now migrating to Microchip's 16-bit and 32-bit MCUs.

Microchip Inc. has a horrible way of naming their chips:

  • PIC16 (actually an 8-bit MCU)
  • PIC24 (actually a 16-bit MCU)
  • PIC32 (got it right!)

"The PIC32 is a powerful machine based on a well established 32-bit core (MIPS)"

MIPS (Microprocessor without Interlocked Pipelined Stages) is a family of reduced instruction set computer (RISC) instruction set architectures (ISA) developed by MIPS Computer Systems, now MIPS Technologies, based in the United States.

There are multiple versions of MIPS: including MIPS I, II, III, IV, and V; as well as five releases of MIPS32/64 (for 32- and 64-bit implementations, respectively). The early MIPS architectures were 32-bit; 64-bit versions were developed later. As of April 2017, the current version of MIPS is MIPS32/64 Release 6. MIPS32/64 primarily differs from MIPS I–V by defining the privileged kernel mode System Control Coprocessor in addition to the user mode architecture.

Documentation needed for the PIC32MX360F512L (all published by Microchip Technology Inc):

  • PIC32 DataSheet (214 pages, "PIC32MX3xx4xx_datasheet.pdf")
  • PIC32 Family Reference Manual (1138 pages)
  • MIPS M4K DataSheet (47 pages)
  • MIPS M4K Software User's Manual (235 pages)
  • Explorer 16 Development Board Users Guide (50 pages)

Power supply: an unregulated DC supply of 9V to 15V (preferably 9V) supplied to J12. For default functionality, a power supply with a current capability of 250 mA is sufficient. The Explorer 16 kit does not include a power supply.


Day 1: The Adventure Begins (PortA)

This chapter focuses on the embedded programming version of "hello world", light up some LEDs. A peripheral is examined: PortA. This is a 12-pin input/output peripheral routed to 12 pins on the PIC32 itself. It is controlled primarilty by two SFRs:

    PORTA (set 1 for +3.3V output, set 0 for 0.0V output)

    TRISA (set each pin as an Input or an Output)

8 of these pins are connected to LEDs on the Explorer 16 board. These LEDs live right below the PIM module and are in order: bits 7 6 5 4 3 2 1 0.

    PORTA is 32-bits, so: 0x000000E1

    will turn on LEDs in this fashion: 11100001

The "Hello World" program introduces the MPLAB IDE and it's built-in MPLAB SIM simulator.

dev board image

The header file contains a long list of definitions for all the internal special-function registers (SFRs).

Running a program:

  1. The MCU will first execute a short initialization code segment auto-inserted by the MPLAB C32 linker, known as Startup Code, or "crt0 code".
  2. The main() function is executed

I/O pins on the PIC32:

  • Arranged in modules or "ports" of 16 pins each, named A, B, C, D, E, F, G, H (8 ports total).
    • Some have fewer. PortA has 12 pins for example.
  • Each port has several SFRs assigned to control its operation.
  • Naming:
    • TRISA (SFR, 32-bits) Set each pins direction: Input or Output
      PORTA (SFR, 32-bits) Set each pins value: 0 or 1

Build All (Project):

  1. Compiler is invoked, generating object code (.o) All the addresses of functions and variables are yet undefined.
  2. Linker is invoked, proper memory position established. Produces executable (.hex)
  3. All Source Files will be compiled. All Object Files will be linked together. All Library Files will be used also.
  4. A Linker Script can be used for advanced config: change the order and priority of data and code sections (book uses default).

Building the First Project:
  1. Build All
  2. Debugger -> Select Tool -> MPLAB SIM
  3. Open Configure -> Settings -> Debugger (tab)
    • Automatically save files before running
      Remove breakpoints upon importing a file
      Reset device to the beginning of main function
  4. Open View -> Watch, add the PORTA SFR
  5. Press the Simulator Reset button
  6. Step through program, see value of PORTA change.

****************************************************************** * Important note when using the simulator: That "Simulator Reset Button" is the key - it sets the green arrow / breakpoint to the main() function, and allows you to step through the program from there, using the step-into and step-over buttons. ******************************************************************

A closer look at PortA:
  • each pin (there are 12 of them) is driven by some somewhat complex logic, mainly 3 signals that reach the output pin (each output pin). Essential in configuring each pin to be Input or Output.
  • default is Input pin for each pin in Port A
  • Another SFR, called TRISA, allows you to configure the direction of each pin in PortA.
    • TRISA = 0; /* sets each pin to Output */
JTAG Port (testing on real hardware, one more step):
  • PortA covers 16 pins of the PIC32 chip (the PIC32MX360F512L has 100 pins total). These pins are multiplexed with another function however, the in-circuit programmer and/or debugger, known as ICSP/ICD (historically) and the JTAG interface (the newer method of communicating with the programmer/debugger.)
  • By default, some of the pins (RA0, RA1, RA4, RA5) are set as JTAG port pins, and not I/O pins (in the case of the PIC32 Starter Kit board) and the Explorer 16 board is the same.
  • You need to disable the JTAG port in order for those pins to be used as I/O pins, and get to the LEDs they are attached to:
    • DDPCONbits.JTAGEN = 0;
dev board image

Day 2: Walking in Circles (Timer1)

This project expands on Hello World by using another peripheral, Timer1, to systematically turn LEDs on and off. Also covered:

  1. Creating a main function that loops forever, instead of the flow of code going straight to exit()
  2. Peripheral in this module: 16-bit Timer1, driven by Peripheral Bus Clock with selectable prescaler.
  3. Features of the MPLAB SIM tool: Animate mode, and Logic Analyzer view
  • Project -> Project Wizard
  • Device: PIC32MX360F512L
  • Active Toolsuite: Microchip PIC32 C-Compiler Toolsuite
  • Configuration Bits: Set SC = 72 MHz and PBC = 36 MHz

Key concept: "When the main() function terminates and returns back to the startup code crt0, a new function _exit() is called and the PIC32 remains stuck there in a tight loop from which it can escape only if a processor reset is performed"

A proper application main loop is needed so it doesn't go to this _exit()

dev board image
To run this:
  • Debugger -> Select Tool -> MPLAB SIM
  • Debugger -> Animate
  • View -> Watch -> "Add SFR" button (PORTA selected)
  • Step through code to see PORTA value toggle between 00 and FF
To control runtime speed:
  • Debugger -> Settings -> Animation Real Time Updates
  • Then run the program using the >> button (Animate)
  • The program will run and show you each line it's executing.
Timer1:

To run on real hardware, you'll need to utilize the Timer1 peripheral to introduce a delay between toggling the PORTA value (otherwise the LED will toggle several million times a second).

There are 3 SFRs that control most of Timer1's functions:
    TMR1 16-bit counter value (0 to 65,535)
    T1CON 32-bit SFR, enable Timer1, set Prescaler value
    PR1 rollover value for TMR1 (default is 65,535)
dev board image

Day 3: Message in a Bottle

dev board image

Day 3's Project: lighting up the LED set attached to PortA in such a way that when the Explorer 16 board is moved quickly back and forth, a message will appear in big letters.

Use Logic Analyzer:
  1. Go to Debugger -> Settings -> Osc/Trace tab
    • check Trace All
  2. Then go to View -> Simulator Logic Analyzer
  3. Click on the Channels button, add output device pins such as RA0 for PortA. For this project (Day 3, Message) select all the pins from RA0 to RA7.
  4. Remember to reduce delay values by a factor of about 100 in order for the scale of the signal view to be usable/meaningful.
  5. Neat feature: put the cursor (in the code editor) where you want the simulator to run to and pause. For Day 3 project, go to the section of the LONG_DELAY so you can see the HELLO signals on the output pins. Put the cursor there, then r-click and select Run to Cursor.
The role of the startup (crt0) code:
  1. An array declared in your code will be moved from Flash memory (where all the program code resides before running) and allocated in RAM, and this happens BEFORE the main() function gets called.
  2. All globally declared variables will be initialized to 0 by the crt0 code. This could cause a delay if you have a big global array.
  3. You can define a special function:
      void _on_reset(void)
    if there is time-critical code that needs to run immediately upon power reset (to avoid that potential problem with large allocations before main() runs).

Day 4: Numbers

The PIC32 has 32 registers (32-bits wide), and a 32-bit ALU.

The MPLAB C32 compiler assigns the following bit-sizes per datatype:

dev board image

As far as PERFORMANCE, the 32-bit CPU can work as effienctly on 8, 16, or 32 bit values (one cycle each for math operations, thanks to the MMU and MDU units in the PIC32). 64 bit values will take a slight performance hit though.

As far as RESOURCES (limited RAM), datatype choices have direct consequences. For example, 8-bit values (char) only take up 1 byte of memory, not 4.

dev board image

MIPS M4K Core ISA

To make sense of the disassembled code above, knowledge of the MIPS M4K ISA is needed. Like all other ISAs, this defines the number of registers, their definitions/names, the instruction types and instruction encoding, as well as the overall architecture such as the LOAD-STORE architecture MIPS uses.

The LOAD-STORE architecture is also known as "register-to-register" architecture. Basically, it means all operations (math/logic) are performed only on REGISTER contents, and so to work with RAM, values must be brought in from RAM (LOAD) and results must later be stored back into RAM (STORE).

dev board image
dev board image
dev board image
dev board image
Review of Instruction Format Fields:
  • opcode 6-bit primary operation code
  • rd 5-bit specifier for the destination register
  • rs 5-bit specifier for the source register
  • rt 5-bit specifier for the target (source/destination) register or used to specify functions within the primary opcode REGIMM
  • immed 16-bit signed immediate used for logical operands, arithmetic signed operands, load/store address byte offsets, and PC-relative branch signed instruction displacement
Stepping through a Program:
  • You can single step through the assembly code if you are in the Listing window, or you can single step through the C code if you're in the editor window.
  • In both cases, you can view several things:
    • Watch (shows you internal CPU registers)
    • Local Variables (shows you variables in the C code, their values)
A note about Reset:
  • When you want to reset the program:
      Debugger -> Reset -> Processor Reset
  • Note that this doesn't clear RAM or all RAM contents, so variables will retain their previous values!
dev board image
Integer Division (32-bit vs. 64-bit)
    32-bit division can be handled directly, with a single div instruction.
    64-bit division will rely on a subroutine:
      libgcc2.c (found in MPLAB C32 directory)
Floating Point Numbers:
    Regardless of 32-bit (float) or 64-bit (double, long double), the MPLAB C32 compiler will use library calls (and expensive ones at that) to perform math operations on floating point numbers.
Measuring Performance:
  • Debugger -> Stop Watch
  • Do this after compiling and linking a project. Then, single-step through the program and the Stop Watch will measure the time!
  • Time is in cycles, in microseconds: Debugger -> Settings -> Osc/Trace
Compiler Defines Data Size:
  • The older microcontrollers (16-bit PIC and dsPIC) used an older compiler, MPLAB C30.
  • The PIC32's compiler, MPLAB C32 assigns different widths for each datatype. For example, 'int' is now 32-bits, another name for a 'long'.
  • In the older compiler (C30) an 'int' was 16-bits wide.
  • This makes porting code between compilers difficult, even though the code is written in strict C.
  • There is a library inttypes.h that define exact widths regardless of what compiler, for example:
    • uint16_t will always be a 16-bit unsigned integer type
    • int32_t will always be a 32-bit signed integer type
  • Another useful library file, stddef.h, defines a datatype (size_t) that will hold #bytes of another object. And there are many functions in the string.h library that make use of that datatype, like the sizeof() function.
  • MPLAB C32 supports other ANSI C libraries:
      limits.h (holds size limits of all datatypes)
      float.h (more size limits)
      math.h (things liek M_PI)

Day 5: Interrupts

  • Often microcontrollers can't afford the luxury of a multi-tasking operating system. Interrupts are used to divide attention to different tasks.
  • C doesn't directly support interrupts, so you have to define special functions to implement them. The MPLAB C32 compiler provides support to take advantage of the PIC32's interrupt mechanisms.
  • PIC32 = up to 64 distinct interrupt sources
    • Each can have an ISR (Interrupt Service Routine) defined
  • The C32 compiler has libraries and language extensions to help.
MIPS core
  • The MIPS M4K core handles "exceptions" by maintaining an ISR vector table (a table of function pointers). Interrupts are a type of exception:
      divide by zero
      reset command
      access to memory that isn't there
  • These ISR vectors can live in Data RAM or Program ROM, or both, and the most important interrupts have default vectors declared/defined at startup.
  • Example Exception Sources:
      Reset
      On-Chip Debug (used by EJTAG devices)
      Cache Error
      General Exception
      Interrupt
  • Notice just one vector is assigned to "Interrupt". That function uses a special register called "cause" to determine what triggered the interrupt.
  • The ISR will first save the execution process of the CPU, called saving the "prologue", and be able to restore it, called the "epilogue".
ISR for Interrupts:
  1. PROLOGUE (save processor state)
  2. query CAUSE
  3. EPILOGUE (restore previous state)
This central Interrupt vector can call any special ISR we define, using the following rules for ISR function definitions:
    ISR function return type void
    No arguments allowed - parameter type void
    These functions can't be called by other functions
    These functions shouldn't call other functions
An example: One of the UART interfaces (there are 2), can generate any of the following 3 interrupts:
  1. New data received and is available in the receive buffer for processing.
  2. When data in the transmit buffer has been sent and the buffer is empty, ready and available to transmit more.
  3. When an error condition has been generated and action might be required to reestablish communication.
Up to 96 independent events can be managed by the PIC32 interrupt control module.
    So 96 sources, handled by 64 vectors
7 Control Bits per Interrupt
    These are dispersed across several SFRs (of course they are).
    1. enable bit (all interrupts off by default) "Interrupt Enable"
    2. trigger bit (set when interrupt occurs) "Interrupt Flag"
    3. "Group Priority Level" ipl1, ipl2, ... , ipl7 The higher the priority level, handled first. The PIC32 itself has a priority level kept in the MIPS core, and so the CPU will ignore any interrupts lower than it's value (3-bits):
      • Core priority level = ipl4
        Ignores any interrupts of level ipl1, ipl2, ipl3
    4. "Subpriority Level" 2 more bits
How to Define an Interrupt Handler in C:
dev board image


Summary of Book (Topics):

  1. Intro to hardware (PIC32 and Dev Board) and software (MPLAB). Hello World in C.
  2. Loops in C. How to use MPLAB's simulator (MPLAB SIM) and compiler (MPLAB C32).
  3. Datatype declarations, arrays, for and while loops in C.
  4. All numeric datatypes, MCU registers, Disassembly Listings, how division is implemented.
  5. Interrupts, interrupt handlers, functions in C, hardware timers.
  6. Memory, allocation techniques, Flash vs. RAM, strings in C, Memory Configuration Table, pointers, PIC32MX Bus and Memory Mapping.
  7. The Clock System and the Memory Cache System
  8. Communication peripherals (UART, I2C, SPI)
  9. Asynchronous Communication (RS232 interface)
  10. Interfacing with simple LCD screen
  11. Analog sensors
  12. Reading user input (buttons and sliders)
  13. Implementing NTSC video output
  14. Storing larger datasets onto SD Card
  15. File I/O (File System Implementation)
  16. Sound output (analog waveforms)

dev board image

PIC32MX (Microcontroller Concepts Learned):

  • Register set
  • MPLAB usage and setup of initialization file
  • How the 32-bit multiplier effects performance
  • Memory management module
  • Counting # assembly instructions (Disassembly Window)
  • Counting instruction cycles (SIM StopWatch)
  • Timers
  • Interrupt system and Interrupt handlers
  • Comparing efficiency of various numeric types
  • The clock system
  • Memory Cache system
  • Protocols of basic communication peripherals (UART, I2C, SPI, RS232)
  • Analog sensors, User Input, Video and Sound output
dev board image

dev board image


Back to Darron Vanaria's Portfolio