Close

Microchip MPLAB Harmony, or How I Learned To Love State Machines In C

A project log for SCPI-over-Ethernet Foot Controller

A microcontroller-based solution to foot-controlling test equipment.

john-wJohn W. 06/14/2016 at 22:460 Comments

As Microchip explains it in their brochure for the MPLAB Harmony Integrated Software Framework, "MPLAB Harmony is a flexible, abstracted, fully integrated firmware development environment for PIC32 microcontrollers." What does this mean to people who aren't in the marketing department and majored in buzzwords?

Harmony is a software package that attaches itself to MPLAB. There's a large range of drivers for interfacing with internal and external devices connected to the PIC32 (ADC, Camera, CAN, Comparator, ethernet MAC and PHY, timers, USARTs, SD cards, SPI, Wi-Fi, it's a pretty extensive list) and libraries/stacks (TCP/IP, USB, audio and video decoders, cryptography, bluetooth, OS abstraction layer). You select which drivers, libraries, or stacks, you'd like to use with a check box-heavy Harmony Configurator utility, then build the skeleton of your application code with it. It takes care of piecing together the basic initialization code for the components you chose and dumps out a number of source and header files for you to put your application code into. There's even a pretty Clock Diagram tab for setting up the internal PLLs for CPU core and peripheral clock rates.

Where do the above mentioned state machines come into play? That's how Harmony multitasks! You are expected to build your application in the form of a state machine. This way, the larger Harmony state machine can run through its housekeeping tasks, such as managing DMA, peripherals, and stacks, and then take care of your application code in a timely fashion.

If you're not too familiar with writing a state machine in C, it's pretty simple! Here's a quick example:

typedef enum {
    APP_STATE_INIT,
    APP_STATE_TASK1,
    APP_STATE_TASK2,
    APP_STATE_IDLE
} APP_STATES;

APP_STATES state;
state = APP_STATE_INIT;

while(1)
{
    switch(state)
    {
        case APP_STATE_INIT:
        {
            //Do init state stuff here.
            state = APP_STATE_TASK1;
            break;
        }
        case APP_STATE_TASK1:
        {
            //Do the first part of your task here.
            state = APP_STATE_TASK2;
            break;
        }
        case APP_STATE_IDLE:
        {
            //Now we're gonna chill out here until we want to act again.
            if( taskStimulus )
                state = APP_STATE_TASK1;
            break;
        }
        default:
            break;
    }
}
In the enumerated type at the top (which Harmony puts in the application header file), you define names for your states for readablity. You initialize a variable to keep track of your current state, then run a loop that transitions between states as it needs to. The main challenge here is figuring out how to break up your work into chunks for each state.

There's a pitfall here though! I learned about it in the course of trying to figure out why my application was sitting in the state where it's supposed to collect data from the serial port for configuration where other stimulus should make it move into a packet sending state. Be very careful about whether the functions you use from the Harmony API are blocking or non-blocking.

A Blocking Function won't return to the caller until it's done whatever it's supposed to do. In my case, I was calling DRV_USART_Read() function, which was waiting until it read the 1 byte I specified as a parameter. Until that byte came in through the UART, it was just sitting on the function doing nothing else.

A Non-Blocking Function returns immediately and requires some way to check progress on the operation you started. In the Harmony USART driver API, you can set a buffer handler that will call a callback function that you write when a buffer transaction is completed. For instance, my project has a command line interface, so when the buffer detects that it has received one byte it calls my callback function and puts the application state machine into a state which takes care of echoing the byte, adding a newline character after a return character is echoed, backspacing the array where I store the characters after receiving them, and going back into the idle state.

Another easy mistake to make in a switch-case state machine is forgetting the "break;" from the end of your case. This will make your program flow fall into the next state, which you probably don't want. I mean, you might! Who knows?

Discussions