Fun with Firmware: three implementations of a circular buffer

Fun with Firmware: three implementations of a circular buffer

Lately, I've been writing a lot of embedded software (firmware) at work. It's been a couple years since I spent a hefty amount of time in C/C++ land, and the embedded variation has some added challenges, but it's coming back to me. More importantly, I'm having a lot of fun. Mind-numbingly cryptic compiler errors aside, I'd forgotten how cathartic and rewarding software development can be. Software has an element of instant gratification that is hard to find in hardware. That's not to say I'll be hanging up my multimeter and 'scope probes for good, but I appreciate the variety.

Getting back into software had me itching to pic (heh) up a side project. I settled on an interview question I'd once been tasked with - implementing a circular buffer. This relatively simple task prompted me to get a couple of different toolchains set up, and got me messing around with a platform I hadn't visited since undergrad - Microchip's PIC microcontrollers.

Version 1: Python

I first coded up a circular buffer in Python, just to get the algorithm right. Python is an exceedingly fast language to get going with. Instead of getting hung up on the details, you can go (nearly) straight to the heart of the problem. In this case, Python made it fast and easy to get user input from the command line (push and pop), so I could focus on getting the logic right. It's even simple to sanitize user input with a quick application of regular expressions.

Here is the main execution loop of the Python implementation:

while (1):
    input = raw_input("Enter 'push', 'pop', or 'exit'\n") 
    check = re.match("push|pop|exit",input)
    if check is None: 
        print "Please enter a valid input\n"
        continue;
    else:
        if input == "push":
            push()
        elif input == "pop":
            pop()
        else:
            break

It's like reading a book. Check out the github repo for the Python circular buffer to peruse the full version.

Version 2: (non-embedded) C

After I worked the kinks out of the array indices and whatnot in Python, it was time to move the circular buffer over to C. I set up a Linux VM running Ubuntu 14, made sure I had gcc and Vim (running Solarized, of course), and I was off the races.

I had the algorithm down from the Python implementation, but this version still had some challenges. Namely, I spent most of my time tweaking the user input part of the code. It's not nearly as easy to get clean input from the command line in C as it is in Python. I had to strip newlines, discard excess characters lurking on stdin, etc. It's abit of a pain, but part of the game.

Here's the main method. Look at that string processing goodness.

void main()
{
    char input[SIZE];
    printf("Hello, World!\n");
    while(1)
    {
        int isPush, isPop;
        int last = SIZE-1;

        //get data from stdin
        printf("\nPlease enter 'push' or 'pop'.\n");
        fgets(input, SIZE, stdin);
        printf("You said: %s \n", input);

        //process stdin
        if (input[last-1] == '\n')
        {
            //strip newline
            input[last-1] = '\0';
        }
        else if (input[last-1] == '\0')
        {
            //do nothing (short input)
        }
        else
        {
            //there is extra input on stdin we need to get rid of
            scanf ("%*[^\n]"); (void) getchar ();
        }

        //file checking
        isPush = strcmp(input, "push") == 0 ? 1 : 0;
        isPop = strcmp(input, "pop") == 0 ? 1 : 0;

        if ( !isPush && !isPop )
        {
            printf("Please enter a valid input\n");
            continue;
        }
        else if (isPush)
        {
            push();
        }
        else
        {
            pop();
        }
        printState();
}

See the full code on github.

Version 3: embedded C

Finally, I was ready to get to the real goal - coding up some embedded C. There are a lot of platforms designed for you to get prototyping at record speed. Arduino, Rasperry Pi, Particle, the list goes on. But I didn't want that. I wanted to mess around with some baremetal firmware - tweaking lots of registers, mucking about in interrupt vectors, all that good stuff. So I neglected for one purveyor of fine microcontrollers with little interest in the hobbyist market - Microchip's PIC platform.

I picked up the PICKit 3 starter kit, which is a swell buy. You can even get a discount if you have a .edu e-mail address handy. You get a PIC16 as well as a slightly beefier PIC18, and a nice breakout board with programming headers. Additionally, the board is populated with LEDs, switches, and pots for you to mess around with. Combined with the sample code and the User's Guide, you have a great resource to get going on some low-level, *not* idiot-proof embedded software.

The PICKit 3 starter kit. Comes with 2 micros, the programmer, and a handy breakout board.

Once again, the challenge of this implementation didn't lie in the circular buffer algorithm, but rather in gathering user input. Instead of typing in messages from the command line, this version relies on two momentary pushbutton switches. These switches are pulling pins RA2 and RA3 to ground, and triggering an interrupt.

Unlike Arduino, where interrupts and pin configurations and all that are configured via a series of clear function calls, PIC development requires you to deal with the actual registers governing the hardware's functionality. Pin direction, analog vs. digital, enabling pull-ups, which pins cause jumps to the interrupt vector - all configured via registers. As such, developing on PIC requires a hefty amount of time leisurely perusing data sheets and user's guides. Once more - a bit of a pain, but that's how the game is played. And it's relatively smooth sailing once you've got the registers square.

Note some of the register configuration in the main function:

void main(void) {
    OSCCON = 0b00100010;  //500KHz clock speed
    TRISC = 0; //all LED pins are outputs   
    TRISAbits.TRISA2 = 1; //switch 1 input
    ANSELbits.ANS2 = 0; //digital for switch   
        
    //initialize LED array to all off
    LATC = 0b00000000; 
    
#ifdef PULL_UPS
    //by using the internal resistors, you can save cost by eliminating an external pull-up/down resistor
    WPUA2 = 1; //enable the weak pull-up for the switch     
    //this bit is active HIGH, meaning it must be cleared for it to be enabled
    nRABPU = 0; //enable the global weak pull-up bit    
#endif

    //setup interrupt on change for the switch
    INTCONbits.RABIE = 1; //enable interrupt on change global
    IOCAbits.IOCA2 = 1; //when SW1 is pressed/released, enter the ISR
    IOCAbits.IOCA3 = 1; //when SW2 is pressed/release, enter the ISR too 

    RCONbits.IPEN = 0; //disable interrupt priorities
    INTCONbits.GIE = 1; //enable global interrupts
    
    while (1)
    {
        continue; 
    }
}

Once again, see the full code on the github repo.

The final version of this one is a lot of fun. The buffer has been reconfigured from 5 slots to 4, and mapped to the LED array on the breakout board. Pushing button one pushes data into the buffer, causing the corresponding LED to light up. Pushing the other button pops data from the buffer, causing the corresponding LED to turn off. It's a simple little game, but surprisingly rewarding after a few hours of combing through PIC datasheets.

Fun with PIC

The Landscape of Electronic Housings

The Landscape of Electronic Housings

Getting voice interfaces for hardware right

Getting voice interfaces for hardware right