Practical migration from 8-/16- to 32-bit PIC

It was less then a month after my first book on programming 16-bit PIC24 microcontrollers had been published, when I heard through the Microchip grapevine that a new 32-bit PIC32 microcontroller (MCU) had just come out of the “ovens” and a few alpha samples were already working on the benches. The rumor mill said it was based on a MIPS core, but at the same time there were claims of compatibility with the 16-bit pin out and the peripheral set of the PIC24 family. It was simply too much for me to ignore. Off I went to get one of those early samples and a fresh beta copy of the GNU-based MPLAB C32 C compiler.

I simply had to see for myself what this new product looked like. Would it still feel like a PIC MCU? Would it work on the same demo boards? After all, I had just written 15 chapters worth of 16-bit code and examples in C for the PIC24. To make a long story short, less than one month later, not only was I finished porting the code, but I was already working on a new book based on my experiences with the PIC32!

The following is a brief recount of what happened during that month. I'd love to be able to start by saying that I followed the best design rules and that I read the datasheet first, from cover to cover, but I would be lying! I did exactly what you would have done. I launched the MPLAB integrated development environment, which loaded my last PIC24 project, and hit the F10 button to try and build it right away.

Binary literals
A long list of errors filled the output window. Much to my surprise, all of the errors reported were apparently related only to my usage of the binary notation (0b00000000), a non-standard extension of the C language. I was trying to compile the first code example from Chapter 3 of the 16-bit book. This is a very simple piece of code that's supposed to illustrate the command of I/Os, accurate timing, and flow control (for loops) in C. I quickly decided to convert all the literals to the standard hexadecimal notation (0x00) and voila! The compiler and linker parsed my code with no errors.

Feeling lucky, I decided to plow ahead and try the code out on some real hardware: the Explorer 16 demonstration board. I had obtained a PIC32 Plug-In Module (PIM) and swapped it with the PIC24 PIM that I'd been using throughout my 16-bit book. I powered the board and watched it incredulously for a few seconds–no smoke! I grabbed an MPLAB Real ICE debugger and programmer, and connected it to the board. The MPLAB IDE quickly recognized the tool and reported that a PIC32 was found “alive” on the board.

After a quick and automatic firmware upgrade, I pressed the Program button almost immediately, followed by the Run command and … it did not work!

I mean, something was clearly happening on the board, but it didn't resemble even remotely what I was expecting. Let me explain. In the first three chapters of the 16-bit book, I show readers how to produce a first “Hello world” kind of example in C. In those chapters, I claim that the traditional way of doing it, sending a string of characters to a terminal, is not realistic or appropriate for the embedded-control world of applications. Instead, I take a more “entertaining” approach by making a single row of eight LEDs, flash rhythmically so that, when the board is held in hand and waved, it “paints” the desired message, thanks to the human eye's natural image persistence. This is actually easier to code than to describe.

A different clock
Fact is, the PIC32 seemed to get both the I/O pins and the timing all wrong.

As is customary, it was only at this point that I cracked open the datasheet and started working my way back to the root of the problem(s). It turns out that the PIC32's clock-generation module is a bit more complex than that of the PIC24F I had used in the 16-bit book. In fact, the PIC32's module more closely resembles the oscillator modules available on the latest PIC24H 16-bit MCU families. Also, in the PIC32 architecture, most peripheral modules are tied to a separate peripheral bus that can operate at a different frequency–lower than the system clock–to help manage power and, of course, EMI.

With a little patience, I figured out how to con' the peripheral bus to operate at the same frequency that the PIC24F used in the same project (16 MHz peripheral bus). I figured also that the same number of instructions could now be executed using only half the system frequency required by the PIC24F, since the PIC32 core can execute one instruction every clock cycle.

JTAG defaults to on
After clearing the clock issue, I rapidly glanced at the timer modules. There were five of them. They looked absolutely identical to the PIC24F and, further back in PIC MCU history, compatible all the way back to the PIC16C74 (circa 1994). I continued on to verify the I/O ports: same structures, same number of pins, same “historical” register names, a compatibility trail that can be extended perhaps all the way back to the first PIC16C54 (circa 1991).

I did one last quick check of the A/D converter module, which is a difficult peripheral for most PIC MCU beginners to learn. It multiplexes its inputs on top of an I/O port (PORTB on most 16-bit PIC devices), and it takes precedence at power up, so it won't let your digital inputs work unless you configure it properly. It was remarkably compatible with the PIC24, and I still couldn't explain the strange behavior of the LEDs.

Looking more closely, I realized that there were four LEDs, in particular, that either never lit up or were, apparently, constantly on. So, I went back to the datasheet once more to check them out in the pin-out diagrams, and there I found the culprit: the JTAG port.

Called the In-Circuit Serial Programming interface, it also sports an industry-standard, four-wire (E)JTAG port that not only allows boundary scans, but also enables complete device programming and debugging control. This is expected in the rarefied atmosphere of the high pin-count, 32-bit world; consequently, the PIC32 makes both interfaces active by default at power up. If not required, it's up to the application program to disable the JTAG port in order to reclaim some of PORTA I/Os.

Once I took care of the JTAG port, my first PIC32 project started working as expected, and sent its first “Hello” to the world, shown in Figure 1 .

The simple lessons learned thus far (oscillator configuration and JTAG port) quickly proved to be the key to the compatibility of most other projects in the early chapters of the 16-bit book, which ported quite uneventfully in the following few days of “exploration.” I used the UARTs to communicate with a PC. I used one of the SPI interfaces to communicate with a serial EEPROM. I used the Parallel Master Port to communicate with an LCD display module. I used the A/D to read a potentiometer first and a temperature sensor later, demonstrating how to interface the PIC32 with the analog world. All of these modules were working exactly how I was expecting them to and, apart from some extensions to the modules' capabilities, I found that my 16-bit code kept working with hardly any changes required.

Table 1 shows a PIC24F AD1CON side-by-side register comparison.

View the full-size image

Measuring performance
In those early days, my curiosity was consumed by the promise of performance that the PIC32 brought. In chapter 4 of the 16-bit book, “Numb3rs,” I had been counting the instruction cycles required to perform basic arithmetic operations and comparing them with various integer and floating-point types. This was a rational way to proceed in a world where clock cycles corresponded exactly to instructions executed, as happens in the core of the PIC24 and dsPIC DSC. But the PIC32 core, being of MIPS descent, added a new twist to the game. The number of instructions executed per clock cycle can vary, as wait states can be inserted when executing code faster than the Flash memory's rated speed (just add one clock cycle every 30 MHz); or can be removed thanks to a prefetch mechanism (capable of fetching four instructions at once). Finally, a cache memory can be activated, further improving the performance at high speed.

The PIC32's cache memory made the count of cycles somewhat unpredictable and perhaps meaningless. I felt like I had just upgraded from a stock muscle car to a Formula One race car. So, I decided I needed to include a new chapter in the 32-bit book about performance “tuning” of the PIC32. To give the PIC32 a serious workload, I rescued an old code example from my university days, when I was learning about basic digital signal processing: a Fast Fourier Transform. I used standard floating-point arithmetic with no hand or compiler optimizations. I also used a 32-bit timer to allow the PIC32 to time its own work, and then I gradually started playing with the new performance options.

First, I enabled the instruction prefetch, then I turned up the cache memory, then I manually trimmed the wait states. The performance gains were dramatic at first and more gradual later, as I kept refining the configuration. Eventually, I realized that the optimal configuration would have been application specific, but a good starting point was offered by a function SYSTEMConfigPerformance(), part of the standard device libraries.

Learning the libraries
This was my first encounter with the “standard” peripheral libraries, and the beginning of a long love/hate relationship of sorts. Having spent many years of code development in assembly on very small 8-bit devices with performance levels that required almost constant use of hand-optimized/custom code, I had been working mostly on my own; eventually developing my own set of libraries.

This time, more than one year before the release to production of the PIC32, not only had the 16-bit libraries already been ported, but they were extended to support a number of new features. I had no more excuses–I had to get in line and learn how to use them myself. See Listings 1 and 2 for a code example using the Peripheral Libraries.

View the full-size image

View the full-size image

By using the new libraries, the code compatibility between 16-bit and 32-bit applications becomes “absolute.” Even the smallest difference at the peripheral register level can be removed completely from the application code. In fact, this allows a single application to run on either a 16- or 32-bit MCU, and a developer can target both architectures while maintaining a unified codebase.

But, while the hardware-control register names are spelled (and each one of their bits is detailed) in the device datasheet, all of the function/macro names and their parameters are not. Most of the time, I found myself having to compare the individual include files with the device datasheet, trying to guess which combination of control bits would correspond to a specific library parameter. This was especially annoying with the simplest (thinnest) of the libraries, such as the I/O port manipulation, where the benefit of the library abstraction layer was more questionable to me.

Eventually, I found a balanced compromise could be taken; I would use the traditional method of accessing the most basic peripherals (I/O ports and timers, for example), but I would use the libraries when more complex/new peripherals were to be used. So, I quickly moved through several chapters of code, making practically no modifications whatsoever. These chapters included: SD/MMC interfacing, FAT16 file I/O and even WAV music file playback.

The usefulness of the libraries became really apparent when I decided to dig deeper into the subject of interrupts, first, and later when I started using the new Direct Memory Access (DMA) module of the PIC32.

Interrupts and determinism
The PIC32 offers two options to manage interrupts: a single vectored mode, which is more similar to the way the PIC16/18 8-bit architectures operate (incidentally more RTOS friendly, too), and a multivectored mode that closely resembles the way the 16-bit PIC24 MCUs and dsPIC DSCs operate. Setting things up one way or the other was a breeze with the interrupt.h library.

It was when I tried to port the code in Chapter 12: “The Dark Screen” that things got really interesting. With the PIC24, I had been able to demonstrate how a simple SPI port, three resistors, a couple of interrupts, and a little creativity could go a long way to generate a composite video signal and practically transform any TV set into a monochrome display. Creating the video signal required very precise coordination between the interrupt code and peripherals. In practice, since even a single clock-cycle difference on the output timing could result in a visible jitter on the left edge of the screen (making all vertical lines jagged), the exercise turned out to be an ideal magnifying lens for interrupt “determinism;” a characteristic where PIC architectures traditionally shine. Unfortunately, the instruction pre-fetch and memory cache mechanisms are, by definition, “nondeterministic.”

After much head scratching, it finally dawned on me. I was trying to fit a round peg in a square hole. A 32-bit core is designed for performance. Its mission is to run through C code as fast and efficiently as possible, leaving any hard timing work to its peripherals. The DMA peripheral, in particular, was a much better tool for the job.

Eventually, I figured out how to synchronize DMA data transfers to the SPI port, with a timer directly producing the composite video signal. The new solution offered deterministic timing, but also reduced the CPU overhead from about 25% to less than 5%. After a few hours of work, I had compelling 2D and 3D video demos up and running, together with animation and higher-resolution displays all the way up to a monochrome VGA (see Figures 2 and 3 for examples).

View the full-size image

Exploring the PIC32 quickly became a very addictive activity, and the results were so rewarding that I ended up embarking on a new 32-bit book project. The compatibility of the PIC32 with the previous generation of 16-bit PIC24 microcontrollers turned out to be quite seamless. The speed and performance of the new MIPS core did not fail to impress me, expanding considerably the envelope of applications beyond what any previous PIC MCU could. But, perhaps most interestingly, the new PIC32 still “felt” like a PIC MCU and behaved like a PIC MCU.

Lucio Di Jasio joined Microchip Technology Inc. in 1995 as a Field Application Engineer. Currently, he is a PIC32 Marketing Manger in Microchip's High Performance Microcontroller Division. Di Jasio is also the author of two books: Programming 16-bit Microcontrollers in C: Learning to Fly the PIC24 and Programming 32-bit Microcontrollers in C: Exploring the PIC32 both published by Elsevier. He received his MSEE from the University of Trieste, Italy.

1 thought on “Practical migration from 8-/16- to 32-bit PIC

  1. “2017 and found this very useful. I was thinking about migrating from PIC24 to PIC32 but was afraid that programming a 32-bit microcontroller will double the complexity/double datasheet size/ double reading through datasheets. That's what i felt when mig

    Log in to Reply

Leave a Reply

This site uses Akismet to reduce spam. Learn how your comment data is processed.