chipKIT® Development Platform

Inspired by Arduino™

Programming Hints

Posted 2013-02-16 13:42:25 by Majenko

Use defines

At the abstraction layer, everything is the same, at least we hope so. At the low level you will have to re-write it. You should NEVER include anything from the avr libraries i.e. #include <avr/io.h> for any project that does not use an AVR chip. From now on, you should be doing this:

/* For AVR */
#if defined(__AVR__)
    #include <avr/io.h>
#endif

/* For PIC32 */
#if defined(__PIC32MX__)
    #include <p32xxxx.h>    /* this gives all the CPU/hardware definitions */
    #include <plib.h>       /* this gives the i/o definitions */
#endif

There are also a few predefined macros which may be useful as arguments to #ifdef:

/* For PIC32 */
#define __PIC32MX__ 1

/* For Uno32 */
#define __32MX320F128H__ 1

/* For Max32 */
#define __32MX795F512L__ 1

I/O: PIC32 vs AVR

The I/O port modules on the PIC32 microcontrollers and the AVR microcontrollers are implemented differently. The ports on PIC32 parts are organized as 16 bit ports. The ports on AVR parts are organized as 8 bit ports. Each I/O port on an AVR part has an associated data direction register (DDRx) used to control whether the pins in the port are inputs or outputs. For example, DDRD is the data direction register for PORTD. On an AVR microcontroller, setting a DDR register bit to '1' makes the associated pin anoutput. Each I/O port on a PIC part has an associated tristate register (TRISx) used to control pin direction. For example, the equivalent register in a PIC part would be TRISD. On a PIC microcontroller, setting a TRIS bit to '1' makes the corresponding pin an input. On an AVR part, writing to the PORT register writes to a latch. If the pin is configured as an output the value in the latch sets the state of the pin. If the pin is configured as an input, it controls turning on or off an internal pull-up resistor. On a PIC, writing to the PORT register actually writes to a register called LAT (latch). Writing to the PORT or the LAT register has the same effect. If the pin is configured as an output, it sets the state of the pin. If the pin is configured as an input, it doesn't do anything but write to the latch. On an AVR part you read the pin state by reading the PIN register. On a PIC part, you read the pin state by reading the PORT register. On an AVR part if you read from the PORT register, you read the last value written. To read the last value written on a PIC part, you read the LAT register. AVR parts have a programmable internal pull-up resistor on every I/O pin. On pins that are configured as inputs, the pull-up resistor is enabled by writing a '1' to the corresponding bit in the PORT register. The pull-up is disabled by writing a '0' to the bit. PIC32 parts only have programmable pull-up resistors on the pins that are capable of generating a pin change interrupt (CNx pins). The pull-ups are enabled by setting the appropriate bits in the CNPUE register. PIC32 parts have the ability to make any pin an open drain output. Each port has an associated open drain control (ODC) register. A pin is made open drain by setting the corresponding ODC bit to '1'. Setting an ODC bit to '0' makes the pin a normal digital output. PIC32 parts also have SET, CLR, and INV registers associated with the TRIS, LAT, and PORT registers for each each I/O port. The SET register is used to set one or more bits to '1'. The CLR register is used to clear one or more bits to '0'. The INV register is used to toggle the state of one or more bits. In each case a '1' bit in the value written will cause the effect (SET, CLR, or INV) to occur to the corresponding bit in the register . Each bit written as '0' has no effect on the bits in the register. For example, writing the value 0x0001 to TRISxSET will set bit 0 of the TRISx register to 1, leaving all other bits unchanged. Writing the value 0x0001 to the TRISxCLR register will clear bit 0 of the TRISx register to 0. Writing the value 0x0001 to the TRISxINV register will toggle the state of bit 0 in the TRISx register. When writing to a SET, CLR, or INV register, the hardware reads the associated register, modifies the value based on the '1' bits in the value written, and writes the result back to the register. This is performed as an atomic (uninterruptible) operation, and is more efficient than the typical read-modify-write sequence used on most other processors. The output compares in the PIC32 aren't associated with timers the way they are in the AVR. Each output compare can be set to trigger from timer 2 or timer 3. So, you can toggle up to 5 pins from the same timer.

Adding libraries

To add libraries, put them in a folder in your Arduino sketch folder called libraries. These are picked up as contributed libraries and will show up for both PIC and AVR environments. Libraries placed here are also preserved when you upgrade MPIDE.

Fastest way to toggle bits on a PIC32

The fastest way to toggle a pin on PIC32 is going to be something like

      while(1)
        {
          LATGINV = B01000000;
        }

For PIC32, usually the LAT registers are best for output and the PORT registers are best for input. The *INV (invert) registers are the fastest way to toggle an SFR bit. There are also *SET and *CLR registers that allow for quick bit sets and bit clears.

Interrupt handler setup

void setup() {
........
OpenTimer2( T2_ON | T2_PS_1_1 | T2_SOURCE_INT, 0xFFFF);
ConfigIntTimer2((T2_INT_ON | T2_INT_PRIOR_3));
........
}

#ifdef __cplusplus
extern "C" {
#endif

void __ISR(_TIMER_2_VECTOR,IPL3AUTO) comms_handler(void)
{
  mT2ClearIntFlag();  // Clear interrupt flag
digitalWrite(LED, HIGH);
}
#ifdef __cplusplus
}
#endif

Do not put INTEnableSystemMultiVectoredInt in your setup code or any place else, it is already called BEFORE your setup code gets called. Look at wiring.c for details.

Device configuration words and the bootloader

#pragma config will not work in an MPIDE sketch because, like the Arduino's fuses, the PIC32's configuration words are set in the bootloader code and the chipKIT application linker scripts are set to discard the configuration-word values. To change the configuration-word values, change them in the bootloader project (with the bootloader linker script). If for some reason, you need the configuration-word values in the application linker script, you can modify the linker script with regards to the config regions and .config_xxx sections. You can use the bootloader linker script as a guide.

Missing header files?

If the main sketch does not #include all needed header files then it will not compile and you will get missing file errors. This also includes any #include statements that your 3rd party libraries may have. So, check those libraries and make sure that all #include statements in them are also in your sketch.  

Using attachInterrupt() / detachInterrupt()

 /************************************************************************
  ** ExtIntTest - Sketch to Test External Interrupts
  *************************************************************************
  ** This sketch tests the attachInterrupt and detachInterrupt functions.
  ** It assumes that pin 3 has been looped back to the external interrupt
  ** pin to be tested. It generates edges on pin 3 to trigger interrupts.
  ** The interrupt service routine then blinks the LED to show that it
  ** has been entered.
  **
  ** Uno32 External Interrupt pins:
  ** INT0 = 38, INT1 = 2, INT2 = 7, INT3 = 8, INT4 = 35
  **
  ** Max32 External Interrupt pins:
  ** INT0 = 3, INT1 = 2, INT2 = 7, INT3 = 21, INT4 = 20
  **
  *************************************************************************
  ** History:
  **
  ** 08/08/2011(GeneA): created
  **
  ************************************************************************/

 /* ------------------------------------------------------------ */
 /* Local Symbol Definitions */
 /* ------------------------------------------------------------ */

 #if defined(_BOARD_UNO_)
 #define pinINT0 38
 #define pinINT1 2
 #define pinINT2 7
 #define pinINT3 8
 #define pinINT4 35
 #define pinSrc 3 // used as external interrupt source

 #elif defined(_BOARD_MEGA_)
 #define pinINT0 3
 #define pinINT1 2
 #define pinINT2 7
 #define pinINT3 21
 #define pinINT4 20
 #define pinSrc 4 // external interrupt source

 #else
 #error "No supported board specified.
 #endif

 #define INT0 0
 #define INT1 1
 #define INT2 2
 #define INT3 3
 #define INT4 4

 /* Since the Max32 doesn't have the second LED on pin 43,
 ** this test assumes that an external LED has been connected
 ** to pin 43 on that board.
 */

 #define pinLED1 13
 #define pinLED2 43

 #define intTest INT1

 /* ------------------------------------------------------------ */
 /* Local Variables */
 /* ------------------------------------------------------------ */

 volatile int intStat;

 /* ------------------------------------------------------------ */
 /* Forward Declarations */
 /* ------------------------------------------------------------ */

 void IsrTest();

 /* ------------------------------------------------------------ */
 /* Procedure Definitions */
 /* ------------------------------------------------------------ */
 /** setup
  **
  ** Parameters:
  ** none
  **
  ** Return Value:
  ** none
  **
  ** Errors:
  ** none
  **
  ** Description:
  ** Initialization function called at the beginning of execution.
  */

 void setup() {

 /* Use the LEDs to indicate activity
  */

 pinMode(pinLED1, OUTPUT); // indicates foreground task activity
 pinMode(pinLED2, OUTPUT); // indicates ISR activity
 digitalWrite(pinLED1, LOW);
 digitalWrite(pinLED2, LOW);

 /* Make all of the external interrupt pins be inputs.
  */

 pinMode(pinINT0, INPUT);
 pinMode(pinINT1, INPUT);
 pinMode(pinINT2, INPUT);
 pinMode(pinINT3, INPUT);
 pinMode(pinINT4, INPUT);

 /* Make the stimulus pin be an output.
  */

 pinMode(pinSrc, OUTPUT);
 digitalWrite(pinSrc, LOW);
 }

 /* ------------------------------------------------------------ */
 /** loop
  **
  ** Parameters:
  ** none
  **
  ** Return Value:
  ** none
  **
  ** Errors:
  ** none
  **
  ** Description:
  ** Application event loop
  */

 void loop() {
 int itrTest;

 /* Install the handler for the interrupt being tested.
 ** Test rising edge triggering first.
 */

 intStat = 0;
 attachInterrupt(intTest, IsrTest, RISING);

 /* For rising edge triggered interrupts, both LEDs
 ** should go on and off at the same time.
 */

 for (itrTest = 0; itrTest < 10; itrTest++) {
     digitalWrite(pinLED1, HIGH);
     digitalWrite(pinSrc, HIGH);
     delay(500);
     digitalWrite(pinSrc, LOW);
     digitalWrite(pinLED1, LOW);
     delay(500);
 }

 /* Uninstall the handler and test to make sure that
 ** we aren't getting interrupts any more.
 */

 detachInterrupt(intTest);
 digitalWrite(pinLED2, LOW);

 /* Only one LED should be blinking here.
 */

 for (itrTest = 0; itrTest < 5; itrTest++) {
     digitalWrite(pinLED1, HIGH);
     digitalWrite(pinSrc, HIGH);
     delay(500);
     digitalWrite(pinSrc, LOW);
     digitalWrite(pinLED1, LOW);
     delay(500);
 }

 intStat = 0;
 digitalWrite(pinLED1, HIGH);
 digitalWrite(pinSrc, HIGH);
 delay(1000);

 attachInterrupt(intTest, IsrTest, FALLING);

 /* For falling edge triggered interrupts, the LEDs
 ** should blink out of phase with each other.
 */

 for (itrTest = 0; itrTest < 10; itrTest++) {
     digitalWrite(pinLED1, LOW);
     digitalWrite(pinSrc, LOW);
     delay(500);
     digitalWrite(pinSrc, HIGH);
     digitalWrite(pinLED1, HIGH);
     delay(500);
 }

 /* Uninstall the handler and test to make sure that
 ** we aren't getting interrupts any more.
 */

 detachInterrupt(intTest);
 digitalWrite(pinLED2, LOW);

 /* Only one LED should be blinking here.
 */

 for (itrTest = 0; itrTest < 5; itrTest++) {
     digitalWrite(pinLED1, LOW);
     digitalWrite(pinSrc, LOW);
     delay(500);
     digitalWrite(pinSrc, HIGH);
     digitalWrite(pinLED1, HIGH);
     delay(500);
 }

 /* Show that this iteration of the test has completed
 */

 for (itrTest = 0; itrTest < 5; itrTest++) {
     digitalWrite(pinLED1, HIGH);
     delay(100);
     digitalWrite(pinLED1, LOW);
     delay(100);
 }

 delay(1000);

 }

 /* ------------------------------------------------------------ */
 /** IsrTest
  **
  ** Parameters:
  ** none
  **
  ** Return Value:
  ** none
  **
  ** Errors:
  ** none
  **
  ** Description:
  ** Interrupt handler routine.
  ** This toggles the state of LED2 each time it is entered.
  */

 void IsrTest() {

 intStat = 1 - intStat;
 if (intStat != 0) {
     digitalWrite(pinLED2, HIGH);
 }
 else {
     digitalWrite(pinLED2, LOW);
 }

 }

 /* ------------------------------------------------------------ */

 /***********************************************************************/

Notes on porting issues from one Novice's perspective

There are several challenges in porting stuff from Arduino to Max32

Overloaded pins

Almost every pin on the MAX32 does many different things. This is, in part, due to the fact that the attached pins on the PIC32 do many different things. However, it can make it hard to find functional pins not in use to do what you want to do. Especially if your application wants to manipulate several bits at once (e.g. an 8 bit data path to an LCD display).

Crazy pin layouts

Not sure why they chose to do things this way, but many of the pin functions on the Max 32 are in very different places from the Mega 2560. Both platforms, for example, suffer from the fact that there is only one place where you can get 8 pins mapped to consecutive bits on a single register (at least if you want them all on the same connector). On the Mega 2560, these are 22-29 where 22 is bit0 and 29 is bit 7 of register D. On the Max32, pin 30 is bit 7 and pin 37 is bit 0 of register E. The I2C bus is still on 20-21. SPI is still on 50-53, but 53 can't be used for SS if you are using the built-in Ethernet MAC. The Ethernet MAC<->Phy connection ties up a lot of pins. It would have been nice if they'd put the Phy on-board and moved the conflicting pins on to the pins 70-84 connector and made some of the Arduino-intersecting pins available for more Arduino-like purposes. Unfortunately, instead, they blocked off pins 7, 40-43, 45-49, and 53 and those pins are tied up for a stack-on Phy daughter card. Thankfully, they used the RMII and not the MII. It takes some digging in the documentation to find all the pin reservations and whatnot for the various peripherals. The most useful guide to resolving pin conflicts is the Max32 Reference Manual from the diligent web site, but, one must constantly go back and forth between the text describing each advanced peripheral and the pin-map at the end of the document in order to decipher all the conflicts. It would be nice if someone could produce a table which listed each of the built-in peripherals on one axis and each of the pins on the other axis with the squares filled in for each pin used by each peripheral. Another table that would be handy would be one with each peripheral on both axis and squares filled in showing which peripherals were mutually exclusive. For example, if you're using CAN1, UART3 conflicts. If you're using CAN2, I2C conflicts, etc.

Ethernet uses MANY pins all over the place (bad) and a software stack (maybe good)

The built-in Ethernet MAC uses a lot of pins to talk to the RMII Phy. Someone made a post on the forum asking why no MII. While it's true that MII would allow faster communications with less processor overhead, the simple reality is that for a board with 83 pins, this board already has surprisingly few functionally available pins. Especially if you're building something that you want to be a shield that can be used with both Mega2560 and Max32. (In fact, see crazy pin layouts for one reason this is near impossible). The Ethernet RMII interface is the worst offender in the pin conflict world. It ties up pins 7, 40-43, 45-49, and 53 for exclusive use. Of course there are different pins tied up by the W5100 interface which has a built-in phy and a built-in TCP/IP stack. The built-in phy means not as many pins tied up on the Mega 2560 using the W5100. Even with a software-stack based MAC chip with an external Phy, on the Mega 2560 platform this is usually implemented with the MAC and Phy on the same shield so that the MAC<->Phy interface doesn't use any GPIO pins from the ATMega chip. However, this isn't (entirely) Digilent/ChipKit's fault. In this case, the PIC32MX975 implements the MAC requiring an external Phy. This means that the point where the MAC<->Phy pins are tied up is inside the MCU itself. The implementation of a simple MAC that isn't overloaded with a built-in TCP-IP stack does make programs larger (add about 45k for the IPv4 stack, for example), but it allows you to implement whatever protocol you want on top of the MAC. (For example, I am hoping to take the IPv6 library contributed by someone else and combine it with the existing chipKITEthernet library to produce a dual-stack Max32. Well, actually, I'm hoping someone else will do that soon, but, if they don't, I'll end up tackling the project). OTOH, the W5100 chip commonly used on the Arduino platform requires only 1 dedicated SS pin and uses the SPI bus for everything else. It's slower and can only do IPv4 (unless you use RAW packet IO mode to emit hand-crafted ethernet frames). Since IPv4 will likely be 99% obsolete in less than 5 years, that seems like a serious limitation to the platform. In my discussions with Wizard, they've pretty much blown off the idea of supporting IPv6 on their chips so far, so, I'm very glad the PIC32 has a raw MAC built-in and that Max32 went with this implementation.

Differing approaches to low-level IO

The AVR and the PIC have radically different low-level IO mechanisms. Unfortunately, the standard libraries for both the Arduino and the Max32 platforms give you two choices for dealing with this. This is more a natural result of the way MPIDE evolved than something the developers set out to do wrong, but, the end result is still a relatively high level of dysfunctionality. The Arduino developers didn't conceive of the need to support anything but ATMEGA and wrote accordingly. The MPIDE developers had a lot of work to do just to add multiple-platform support to existing abstraction libraries, so, abstracting more direct chip-level interactions to provide new functionality that would require library surgery for both platforms was probably perceived as an unnecessary additional adventure at the time. However, to make for clean integrated portable development in the future, this kind of needs to happen. A highly abstracted (slow) compatible library (digitalRead/Write, analogRead/Write) for dealing with a single PIN at a time and essentially raw access to the hardware registers. What is needed is a standard abstraction layer at the lowest level possible that provides a standard mechanism for doing multiple-pin simultaneous register manipulation using the most efficient hardware method available while remaining just abstract enough to hide the hardware differences. It definitely doesn't help that the PIC32 has 16 bit hardware registers while the ATMega uses 8-bit registers, but, this could still be handled: For example, having a library that provided an API such as assign()/set()/clear()/invert() each of which took the form set(RegisterC, 0xffc3) where RegisterC was a handle for hardware register C (PIC32) or C & D (ATMega) and 0xffc3 were the bits to set. On the PIC32, this would be implemented as LATC = 0xffc3. On the ATMega it would be implemented as PORTC |= (0xffc3 >> 8); PORTD |= (0xffc3 & 0x00ff);. assign() would simply assign the argument value to the appropriate register(s). set(), clear(), and invert() would function along the lines of the SET/CLR/INV register aliases on the PIC32. It might be better to implement 8-bit based arguments, however. This will make the code more ATMega native and slower on the PIC, but, wouldn't require ATMega programmers to understand that PORTC means PORTC at 0xff00 and PORTD at 0x00ff in the arguments. I'm really not sure what the ideal solution is, which at the end of the day may be a hard question to answer and could be part of the reason this hasn't happened yet.

Weird AVR-specific mechanisms like PROGMEM peppered throughout libraries

The best thing about the Arduino platform (arguably it's only saving grace) is that there are a ton of libraries out there for supporting an incredibly wide variety of hardware. This is no small feat and is the only explanation I can find for Arduino's extreme popularity, especially as a micro controller entry-level platform. (Though the IDE is helpful for beginners too, but, with MPIDE, that's no longer an Arduino-exclusive advantage). Unfortunately, no effort whatsoever has been made in the Arduino world to make the libraries at all portable to other platforms and many libraries make use of non-abstracted low-level hardware functions that are AVR specific. In particular, it seems to be very popular in the Arduino to load static arrays into PROGMEM and then use special functions for accessing their contents. (see any graphical device library with built-in font(s) for an example). Beyond that, there's the low-level IO stuff mentioned in the previous subtopic. Both of these things mean that I've had to perform surgery on almost every library I've used.

MPIDE is both ahead of and behind Arduino IDE

MPIDE development drove many of the changes/enhancements incorporated into Arduino IDE 1.00. Unfortunately, MPIDE did not keep up with the changes to Arduino IDE in 1.00 that it was not driving, so, MPIDE is trapped in this weird limbo between 0023 and 1.00 for now. (March 6, 2012) Work is ongoing to resolve this and I plan to make an effort to contribute to that work, but, this means even more libraries need surgery to back port them to the Max32 environment for the time being. One place where this is hideously obvious is in the Ethernet library(ies).

Both platforms suck at networking

On the Arduino, for example, the W5100 chip exports link state only through a single hardware pin. There is no way to read the value of this pin short of performing hardware surgery on the Arduino board. The pin goes from the W5100 chip through an LED (and possibly a resistor) to ground and that's all she wrote. You'd have to intercept the trace before the first external component and tie that to a digital pin to be able to detect link state in your program. The W5100 chip, among its other faults does not export this value in a software-readable register. The situation is slightly better in the Max32. Because the RMII is external to the PIC32 MAC, the PIC32 has to make this information (from the Phy) available in a register so that software can correctly control the MAC<->Phy interface. The chipKITEthernet library has a function for reading this called MACIsLinked(). For some bizarre reason, this function is in the lower-level utility/... part of the library and is not exported as part of the public API. There seems to be an assumption that if you're using the Ethernet Library, you don't necessarily have any concerns about how long it takes for calls to return (sort of). The Chipkit library is actually written with these performance capabilities in mind at the lower level, but, for unknown reasons, they are only partially exported to the application. For example, you can set the timeout (in seconds) for a client.connect() call, but you can't set the timeout for begin() with DHCP. The lower level library allows this to be done, but, does not export the capability. Worse, the Chipkit Ethernet.begin() returns void while the Arduino 1.00 returns int. With the Arduino, if int=0, something bad happened. With Chipkit, determining whether or not something bad happened is a bit of an adventure. Both have standard DHCP timeouts taken directly from desktop computer implementations (around 30 seconds). This is fine if networking is critical to your application and you don't mind pending until the network works, but, if the network is a "nice to have" feature your application will take advantage of when it is working (as in my particular application), you want DHCP to be almost non-blocking (a 5 second timeout is tolerable in my application, but a 30-second hang is untenable). Ideally this should be controllable from the application. In the case of the Chipkit library, the needed functionality is almost there, but, tDHCPTimeout in the low level library is defined as a local (?!!) constant (?!) within the begin() function. Making this a global variable and exporting it to the API is all that is needed to provide the necessary functionality.

Miscellaneous additional rants

For an "Arduino Compatible" board, getting my hardware working on the Max32 has been an unexpected adventure. Don't get me wrong, I LOVE that my frame rate has gone from 250ms+/frame to ~25ms/frame and that my buttons have become reasonably responsive. I LOVE that the TCP/IP is a library and not a chip-based mechanism (I want to dual-stack my Max32 eventually). I think I found an IPv6 library for it and there is already the IPv4 library that ships with the MPIDE, so, hopefully I can marry them. (additional note: Don't use the Ethernet library that Chipkit ships with the MPIDE. Download the package that includes the chipKITEthernet library. It's a much better starting point). Another example, however, is that the MPIDE Ethernet Library has functionality in 0023 beyond the Arduino 0023 Ethernet Library, but, falls short of the Ethernet Library in Arduino 1.0.

Concluding thought

I hope these notes on my experience are helpful to other developers. I also hope that the IDE developers at both Chipkit and Arduino will work on implementing these suggestions and merging the two IDEs into a single standardized environment for multiple-platform development.