I got no replies to my original posting, but in the mean time I decided to go ahead and work with all three architectures. Here’s a summary of my experiences working with ARM Cortex-M3, PIC32, and AVR32 UC3.
I started with the ARM Cortex-M3 in the guise of the NXP LPC1768. The LPC1768 is a Cortex-M3 MCU with 512KB of FLASH, 64KB of RAM, Ethernet, CAN, USB 2.0, 12-bit ADC, etc. The core is based on the ARM Cortex-M3, which uses the newer ARMv7-M architecture.
From the perspective of an RTOS, the Cortex-M3 architecture was certainly the easiest of the three to port to. It has a nested, vectored interrupt controller (NVIC) that’s standard across all Cortex-M3 variants regardless of manufacturer. Ditto for the SysTick timer, which is typically used to generate basic timing for an OS. If I ever port my RTOS to another vendor’s MCU, I’ll no doubt have to rewrite most of the peripheral drivers, but the interrupt handling and basic timer tick code won’t need to change.
The Cortex-M3 has a well thought-out interrupt handling and priority mechanism with all the support needed by the typical RTOS. One particularly useful feature is PendSV, which pends a software interrupt. I program the interrupt priority of PendSV to the lowest in the system and use it to invoke my scheduler. Since its priority is lower than any other interrupt source, I can invoke it anywhere, even in an interrupt handler, without worrying about nesting within the kernel. The scheduler is thus entered only after all other higher priority (i.e. all of them) handlers complete. This is fast and efficient when combined with the M3’s interrupt tail-chaining abilities. Measured context switch time on the LPC1768 running at 100MHz is 4.2 microseconds.
The CPU automatically saves several registers when entering an interrupt and, together with the way the C compiler allocates registers, it’s possible to write interrupt handlers purely in C with no assembly wrapper or __attribute__ or pragma statements needed. The vector table is also simple: each table entry is the 32-bit address of the associated handler. The table itself can be relocated, if needed.
The LPC1768 has a full complement of peripherals expected on a modern 32-bit MCU, including eight channels of 12-bit analog to digital conversion, a 10-bit DAC, Ethernet, USB 2.0, CAN, UARTS, SPI, I2C, timers, PWM, motor control PWM, watchdog timer, and real-time clock. All of these are modern implementations that do not require unnecessary jumping through hoops on the part of the programmer in order to implement drivers.
I’ve not implemented drivers for all of these yet, so can only comment on the few I have worked with. The UARTs are pretty standard. They have the usual transmit and receive FIFOs and the receive side has a character timeout interrupt for those times when fewer characters arrive than the current FIFO threshold. Baud rate calculation is somewhat involved (the datasheet has a full page flowchart detailing the algorithm). I created a spreadsheet to calculate common rates and use a table-driven approach in my driver.
The I2C channels support 100 kHz and 400 kHz data rates. The I2C Status register provides five bits of state information indicating the current bus state. The user’s guide has a full complement of flow charts and state tables indicating the various state transitions. I’ve only implemented master mode in my driver so far, and found it to be fairly straightforward and trouble free.
SPI support is pretty standard too and I didn’t have any problems implementing a driver. The SPI peripheral supports Motorola SPI, 4-wire TI SSI, and National Semiconductor Microwire modes. Frame size support ranges from 4-16 bits. I wish more MCU makers would expand this to 4-32 bits as I’ve encountered a number of parts with odd-ball SPI interfaces that have 24 bit frames and such. These can be supported using 16 bit frames and manual control of the slave select line, which is not optimum, but it works.
The real-time clock is a “real” RTC in that it has its own clock and power domains. Most RTCs that I’ve encountered on MCUs recently have separate clock domains (usually 32.768 kHz), but few have separate power domains. The LPC1768 has a VBAT pin that can be connected to a 3v lithium button cell to keep the RTC powered when the CPU itself is not powered. Most MCUs will only keep time when the CPU itself has power, which is about as useless as a screen door on a submarine.
I haven’t done anything with the Ethernet, USB, CAN, or motor control PWM peripherals yet, so can’t comment on them here.
My development environment consists of Rowley’s CrossWorks for ARM and a Segger J-LINK J-TAG unit. Both of these work very well. I’ve used many IDEs over the years and CrossLinks rates right up there at the top. It’s fast, clean and uncluttered, and doesn’t get in my way when debugging. The CrossWorks/J-LINK combination downloads code to the LPC1768 FLASH in a heartbeat, and single stepping performance is fantastic with no perceptible lag or delay. I rate both of these tools as A+.
After working with the LCP1768, I turned my attention to the Microchip PIC32. When Microchip implemented their 32-bit MCU, they didn’t follow in the footsteps of most of their fellow vendors by choosing an ARM core. They instead went to MIPS and chose the M4K core. MIPS have been around since the mid-80’s and their cores are solid and well-understood.
The M4K architecture is more RISC-like than the Cortex-M3. There are fewer, if any, instructions like the “stm” instruction, which stores multiple registers in memory in a single instruction. The interrupt mechanism is simpler as well, making the programmer do part of the work himself. For example, on an interrupt, an ARM Cortex-M3 will automatically stack r0-r3, r12, lr, psr, and the pc. The M4K core saves nothing on an interrupt (except for the return address). It’s up to code to save/restore any registers used by the interrupt handler. This means that interrupt handlers written in C require the compiler to generate prolog/epilog code. This is done via __attribute__((interrupt)) or pragma statements in C code.
The PIC32 has two software interrupts whose function is similar to the Cortex-M3’s PendSV implementation. Interrupt priorities range from 1-7, which is more than enough for most systems, especially when take into account the four supported levels of subpriority. There’s a system level counter that can generate interrupts just like the Cortex-M3’s SysTick timer. The interface takes a little more work, however, but the basic functionality is the same.
The PIC32 peripherals will be mostly familiar to anyone who’s used an 8-bit or, particularly, a 16-bit PIC. As expected, the PIC32 supports all of the common peripherals, including Ethernet , USB 2.0, CAN, UART, ADC, DAC, PWM,I2C, SPI, timers, RTC, WDT, etc. In general, these peripherals are somewhat lesser featured than those on the LPC1768. For example, while the UARTs have transmit and receive FIFOs, the receive side doesn’t have a character timeout interrupt. This mechanism must either be implemented by polling or through the use of a general purpose timer, which makes the driver more complex.
Another omission lies in the real-time clock/calendar. While it does have a separate 32.768 kHz clock domain, it doesn’t have a separate power domain, so it only runs while the main CPU is powered. It’s things like this that make me wonder “what were they thinking?” when they designed this peripheral.
My PIC32 development environment consisted of a chipKIT Max32 development board from Digilent, and a PICkit2 in-circuit debugger from Microchip. The Max32 is intended to be Arduino compatible, but I did not use it with that in mind. It also supports standard Microchip in-circuit debugging tools, such as the PICkit2 and the ICD3. The Max32 sports a PIC32MX795F512 processor running at 80 MHz, 512KB FLASH, and 128KB RAM. The board itself is hardware compatible with many existing Arduino shields.
I used the recently released MPLAB-X 1.1 IDE from Microchip and the C32 compiler. The IDE is free and the compiler is free in a form that does not support compiler optimizations. The IDE itself is an improvement on Microchip’s somewhat ancient MPLAB tools, and is not bad for only recently being out of beta. It still has a few rough edges, however, which I’m sure will go away as newer revisions come out.
One big disappointment is the speed of the picKIT3. It’s slow—really slow. Downloading my RTOS code (about 25kB code and data) to the PIC32 takes around 25 seconds, and single-stepping C code is completely unusable, as it takes roughly 12-15 seconds to step a single line of code. I haven’t been able to determine if this is normal behavior for the picKIT3 (it’s Microchip’s $39 bottom-of-the-line debugger, and it only supports 12 Mbit/sec full-speed USB). The next step up is the ICD3, which runs $200. I may decide to splurge on an ICD3 if it has reasonable performance.