Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / Languages / shell

King MM - The Joys of a Harmony-less Life - Part 1, PIC32MM Hello World!

4.65/5 (9 votes)
7 Jul 2018CPOL14 min read 20.6K   122  
Programming Microchip PIC32MM microcontrollers in C++ and assembly

1 Introduction

This is the first article of a series on PIC32MM programming using C++ and assembly.

Here, I show how to set up the minimal hardware and software environment required for a microcontroller Hello World! project, that is the 'blink a LED' application.

2 Background

A microcontroller (MCU) is a (small) 'computer on a chip', featuring a CPU, program and data memory, digital and analog ports, peripherals (like, for instance, UART) on a single piece of silicon.

The MCU we are considering is a little jewel for the hobbyist: a true 32 bit architecture (MIPS core) in 28 pin SPDIP package (see Pictures 1 and 2), at about one-and-half a dollar!

The PIC32MM MCU very simplified block diagram

Picture 2: Very simplified block diagram of a PIC32MM microcontroller.

In order to make the MCU run an application, we have to follow the steps given below:

  • Produce an executable on the PC, using a cross-compiler (that is a compiler, running on a machine, able to produce code for another machine, having a different hardware architecture).
  • Transfer the produced executable to the microcontroller internal program (FLASH) memory, using a hardware tool called programmer.

The build and program process

Picture 3: The build and program process.

3 Requirements

3.1 Hardware Components

Some hardware is required, even for our minimal project.

Microchip details the bare minimum external components required in order to make the PIC32MM run (see Chapter 2 of the device datasheet). To such components, we add a:

  • breadboard
  • red LED
  • LED-current-limiting resistor
  • bunch of jumping wires

to eventually obtain the hardware shown in Picture 4.

The required hardware

Picture 4: The required hardware, a red '+' marks the positive lead of the polarized components.

3.2 Hardware Tools

We need to power the MCU and to 'flash' (program) it, so some hardware tools are required. Some optional tools, are listed as well, for convenience.

PICKit 3 Programmer (Required)

The PICKit 3 is required for programming the PIC32 MCU (i.e., transferring the executable, produced by the cross-compiler on the PC, to the PIC32 flash memory).

Albeit it is the most cheap choice among Microchip programmers, still the PICKit 3 costs about 50 $. The good news is: it can program practically all the Microchip microcontrollers (8, 16 and 32 bit families).

There are cheaper PICKit 3 clones available. The original one, however, is the recommended choice.

The PICKit 3 programmer

Picture 5: The PICKit 3 programmer. The white arrow marks the MCLR position.

Stabilized Power Supply (Required)

A stable power supply is required by the MCU in order to work properly (i.e., to prevent you from going mad).

A laboratory DC power supply would be ideal, however there are many cheaper alternatives, like good switching adapters or even batteries. Just make sure your power device can provide a stable DC source in the allowed range (2.0 to 3.6 V).

Multimeter (Optional)

Albeit not required, a multimeter is highly recommended, because it is a very useful tool especially if something goes wrong in the circuit. There are many cheap multimeters available.

Oscilloscope (Optional)

The oscilloscope is an expensive instrument. It is also overkilling for a minimalist circuit like the one presented here. However, if you like to experiment with electronics, then I encourage you to buy one (the fun comes with the 'scope!).

Twizeers (Optional)

Twizeers greatly ease the task of inserting components in the breadboard clips.

3.3 Software Tools

MPLABX

MPLAB X is the Integrated Development Environment (IDE) gently provided for free by the Microchip.

It is a Java, NetBeans-based program: you might run it on your favourite OS (Windows, Linux or MAC). So far, so good.

The bad news is more abundant: it is slow (did I mention Java?), bloated (did I mention Java developers?), somewhat instable and buggy.

Installing the latest release is recommended (the project presented here was built using the release 3.65).

XC32

Compilers are not bundled in the MPLABX installation, so you have to download and install separately the XC32 C++ compiler.

XC32 is based on gcc, that is a great C++ compiler. The version 1.44, based on gcc 4.8.3, allows us to use all the nice C++11 features on such a tiny piece of hardware. It is thrilling, isn't it?

Remarks
  • We are going to use the free version of the compiler, which doesn't provide any optimization.
  • The bundled C++ Standard Library is possibly not so updated (check out the related information in the forums).

    In any case, we are not going to use it.

3.4 Documentation

The MCU documentation is available at the Microchip website dedicated to the PIC32MM family.

The most important documents are the microcontroller datasheet and the "PIC32 Family Reference Manual".

PIC32MM0064GPL036 FAMILY Datasheet

The datasheet, together with the "PIC32MM0064GPL036 FAMILY Silicon Errata and Data Sheet Clarification" is the ultimate reference on the MCU.

It shows the exact pinout of the device, details every register, states the electrical specifications and more.

PIC32 Family Reference Manual

The PIC32 Family Reference Manual (FRM) explains features and peripherals of the microcontrollers of all the Microchip PIC32 families. It contains many sections, individually downloadable.

The datasheet and the FRM complement each other: the FRM shows you how to use a peripheral to obtain a task, often providing sample code, while the datasheet reports the exact details of the MCU you are using.

My usual approach is:

  • Read the relevant FRM section
  • Check out the FRM sample code
  • Adapt the FRM sample code to the MCU using the datasheet as reference

In other words: it is hard to do something based on datasheet info, so use instead the relevant section of FRM to understand. However, be careful while adapting FRM solutions to your favourite MCU, keeping the datasheet at hand.

Tutorial, or, Better, Book on the MIPS Architecture

A general understanding of the MIPS architecture is required. There are many tutorials and books available on such a topic (Google is your friend)

In order to use assembly code, some reference material, like the MIPS® Architecture for Programmers - Volume II-B: The microMIPS32™ - Instruction Set" available on the Imagination Technology website, is recommended.

4 Building the Circuit

4.1 The Schematic

The circuit is very simple (see the Picture 6 below).

As per datasheet requirement (chapter 2: "GUIDELINES FOR GETTING STARTED WITH 32-BIT MICROCONTROLLERS"), the minimal required components to make the PIC32 run are:

  • A 10 uF tantalum capacitor connecting VCAP to VSS
  • A 0.1 uF ceramic capacitor connecting VDD to VSS
  • Another 0.1 uF ceramic capacitor connecting AVDD to AVSS
  • A10 kOhm pull-up resistor to tie the MCLR pin to VDD

The "Hello World!" specific, additional hardware is just the series formed by the 1 kOhm resistor and the red LED, between VDD and the RB7 digital output.

The circuit schematic

Picture 6: The circuit schematic.

Remarks

  • The tantalum capacitor is polarized, pay attention to connect properly its positive lead to the VCAP pin.
  • The red LED is polarized as well, connect its anode to VDD.
  • Usually, the clock to a microcontroller is provided by an external crystal. Additionally, another crystal can be used to provide the secondary (RTCC) one. However, we are going to use neither of them: the clock is provided by the internal Fast Resistor-Capacitor (FRC) oscillator.
  • The schematic shows also the connections to the PICKit 3 tool, required in order to actually program the device.

4.2 The Board, Step 1: Put Components in Place

Board construction, step 1

Picture 7: Bread board after step 1: be careful with polarized components.

4.3 The Board, Step 2: add VDD and VSS Jumper Wires

Board construction, step 2

Picture 8: Bread board after step 2.

4.4 The Board, Step 3: Add Jumper Wires for the Programming Signals

Board construction, step 3

Picture 9: Bread board after step 3: the 'final mess'.

OK, you may now look at the produced spaghetti mess, that is the pride and joy of every solderless hobbyist.

The board can now be connected to the power supply and to the PICKit 3 programmer.

The PICKit 3 pinout is shown in "PICkit™ 3 - In-Circuit Debugger/Programmer - User’s Guide", available at the Microchip's website:

  1. MCRL (marked by white arrow) -> connect to orange wire
  2. VDD -> connect to red wire
  3. VSS -> connect to black wire
  4. PGD -> connect to yellow wire (MCU pin 14)
  5. PGC -> connect to green wire (MCU pin 15)

5 Eventually, Let's Program It!

5.1 Creating a Fresh Project

If you didn't do it yet, it's time to install the required software tools: the MPLAB X IDE and the XC32 cross compiler.

Now open MPALB X and:

  • Start the wizard in order to create a new project, select Microchip Embedded category and Standalone Project in the Choose Project window.
  • Proudly choose the PIC32MM0064GPL028 device.
  • Select the PICKit 3 hardware tool.
  • Select the XC32 compiler toolchain.
  • Give a meaningful name to your project, like "PigsOnTheWing".

5.2 Adding the Code

The Configuration Bits

The configuration bits are flags (stored at special locations of the FLASH memory) providing the static configuration of the microcontroller.

They control many features of the PIC32, like, for instance, the selection of the clock source.

Setting the configuration bits is achieved using the XC32 ad hoc directive pragma config. Luckily, the MPLAB X IDE's Configuration Bits window provides a convenient way to set such flags, just selecting the decided options. The IDE then will generate the corresponding code for us.

Our fundamental choices are:

  • Run from FRC (fast RC, 8 mhz) internal oscillator (this way, we don’t need an external quartz) using the PLL module to achieve a 24 mhz clock.
  • Disable all nice features (like for instance the watchdog) useful on the ultimate product, but rather annoying while experimenting with the board.

Configuration bits excerpt

Picture 10: Configuration Bits window excerpt, showing the clock source selection

Add a new C++ file to the project (call it confbits.cpp) and copy there the generated source code.

I/O Pins Configuration

The microcontroller provides some registers useful to configure the I/O pins. Typically, we must decide if a pin is:

  • analog or digital, using an ANSEL register
  • input or output, using a TRIS register

There are other details we skip for the moment. Pins are grouped in ports. The PIC32MM provides three ports: A, B, C.

There is a corresponding set of ANSEL and TRIS registers. Here, we are going to use none of the analog features of the MCU, hence ANSELA=ANSELB=ANSELC=0;

Note the ANSEL registers have all bits set at reset, hence all of the pins are configured analog by default. This is somewhat unexpected and, more importantly can produce unexpected, annoying results. If something goes wrong with a pin (even while controlled by a peripheral), have a look at the corresponding bit in the ANSEL register as the most probable culprit.

We are going to use just one output pin (namely RB7), all the other pins left unused. Microchip recommends to configure unused pins as outputs and to drive them low (or high). Hence, we set TRISA = TRISB = TRISC = 0; (all outputs) and LATA=LATB=LATC=0; (all driven low).

That’s all: write the above statements in the (newly created) config.cpp source file:

C++
#include <xc.h> // definitions of ANSELA, ANSELB, ...
// configure all the pin as digital output, drive all of them low
void config()
{
  ANSELA = ANSELB = ANSELC = 0;
  TRISA = TRISB = TRISC = 0;
  LATA = LATB = LATC = 0; 
}

The Actual Application Code

In the main function, we are going to alternatively switch ON and OFF the LED in an infinite loop (the microcontroller main should never return, after all there is no OS sitting out there, waiting for it). In order to toggle the pin value, we might use the LATINV register: LATBINV = 0x80 does the trick.

The MIPS core doesn't provide atomic bit manipulation instructions, so Microchip augmented peripheral registers like TRISx, LATx, ... with additional -INV, -SET, -CLR register (e.g. the TRISAINV, TRISASET, TRISACLR are linked to TRISA). All the bits set in any -INV register will toggle atomically the corresponding bit in the linked register while all the bit cleared in the -INV registers do not affect the linked register (similar operation are performed by -SET and -CLR registers, see the documentation).

Since the human eye is not able to perceive a too fast ON/OFF toggle, we have to add a delay after the instruction. We can simply waste MCU cycles in a empty loop. Since the PIC32 runs at 24 mhz, 24,000,000 iterations should do the trick. So we try the following main function:

C++
#include <cstdint>
#include <xc.h>
#include "cfg.hpp"

void delay()
{
  for (uint32_t n=0; n<24000000;++n){} // waste MCU cycles
}

int main()
{
  config();
  for (;;)
  {
    LATBINV = 0x80; // toggle the RB7 pin state
    delay();
  }
}

You might compile and flash the code with the "Make and Program Device Main Project" MPLAB X command.

Code execution is a bit disappointing, however. The blink is very slow, the LED is ON for about 11 (eleven !) seconds and then OFF for the same amount of time.
What's happened?
Is our empty loop, really empty?
Is it instead full of saturated fat?

Another MPLAB X window is helpful to get insight. Searching for delay into the Execution Memory window, we eventually find:

Execution Memory window

Picture 11: Execution Memory window excerpt, showing the machine code of the delay function.

Without digging into the generated assembly code, we might still appreciate the inefficiency of the compiler: an empty for loop expanded into 18 (eighteen!) assembly instructions.

This is what you get for what you pay: the free compiler version provides no optimization at all (malicious folks say the free version of the compiler deliberately puts bloat in the generated code).

Now since we hobbyists are not willing to pay Microchip the about 1000 $ required for the PRO licence of the compiler, or, put in a more idealistic way 'we strive to understand the inner details of the things', let's turn our attention to MicroMIPS assembly language and see if we can do better.

Of course, we can: write the following code into the newly created util.S file and then add it to the project:

ASM
  #include <xc.h>
    
   .section .text, code
   .set noreorder
   
   .global asm_delay_1_sec

asm_delay_1_sec:
   li t0, 12000000      // 12 millions cycles
asm_delay_1_sec_l1:
   bnez t0, asm_delay_1_sec_l1 
   addi t0, t0, -1      // this is the MIPS branch delay slot instruction, 
                        // always executed after the previous one
   jrc ra

The code is pretty simple: execute 12 millions iterations of a two-cycles body. Then modify the main function this way:

C++
#include <cstdint>
#include <xc.h>
#include "cfg.hpp"

extern "C" 
{
  void asm_delay_1_sec();
}
int main()
{
  config();
  for (;;)
  {
    LATBINV = 0x80; // toggle the RB7 pin state
    asm_delay_1_sec();
  }
}

And, again, flash it using the PICKit 3 tool.

This time, the blinking is good: the red LED state remains the same for exactly one second. The oscilloscope track, in Picture 12 below, gives a nice confirmation.

Oscilloscope track

Picture 12: The oscilloscope track, showing the voltage level on RB7 output pin.

Of course, one can obtain a similar result empirically, modifying the number of iterations of the C++ code, but that's not the point. The focus here is in control: the assembly code gives us the control the C++ code can't. For instance, in this project, just the MicroMIPS assembly code has a predictable execution time.

Points of Interest

The Harmony-less Joke

Harmony is a Microchip framework, in Microchip's own words (Harmony website): "MPLAB® Harmony is a flexible, abstracted, fully integrated firmware development platform for PIC32 microcontrollers. It takes key elements of modular and object oriented design, adds in the flexibility to use a Real-Time Operating System (RTOS) or work without one, and provides a framework of software modules that are easy to use, configurable for your specific needs, and that work together in complete harmony."

As a matter of fact, Harmony-produced code is very complex, full of bugs and a nightmare to debug. Many developers on Microchip forums agree at least on one point: Harmony is just the inevitable evil required while dealing with complex hardware peripherals, like the USB. Luckily, the PIC32MM microcontroller family features just simple peripherals. So we don't have to deal with Harmony and our life is full of joy!

Note: My experience dates back to 2016, when I decided to discard such framework even while developing for the PIC32MZ family. I hope things have been changed for the best, and Harmony has been greatly improved, in the meantime.

The MicroMIPS Pros and Cons

The original MIPS is a clean RISC architecture (do you remember DLX?). On the other hand, the microMIPS architecture (the PIC32MM one) strives to reduce the size of the produced code and it pretty succeeds in the task: as per Microchip claims, the MicroMIPS executable code size is about 35% smaller than the standard MIPS one, while the execution speed remains pretty similar.

The price we pay for such a miracle is a cluttered instruction set: MicroMIPS is a bit tricky to grasp, for the hobbyist.

Conclusions

This was the first article of a (in progress...) series on PIC32MM microcontroller programming using C++ and assembly.

The very basics of the hardware and software requirements have been sketched, and the usefulness of the assembly language remarked. Unfortunately, no room has left for C++ code or experimentation with fancy peripherals. All of that should be amended in the next articles of the series.

History

  • Roma, July 7th 2018: Fixed the schematic picture, thanks to Francisco
  • Roma, July 26th 2017: First release

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)