Thursday, July 12, 2018

On Choosing a Microcontroller

I haven't posted in ages, I know!

A friend recently asked me to describe the differences between microcontrollers.  It's a wide field, but there are a few things that I tend to look for.

It came up shortly after I deployed a temperature logger.  The initial revision was just an Arduino, a TMP36 sensor, with a ProtoShield with a tiny breadboard to tie everything together.  I also wrote a Python program to log the temperature from the probe, and also log the local METAR reports (so we had the outdoor weather), and serve this up with nice plots; all the logging and graphing is through RRDtool.

That got me a quick deployment, but has its drawbacks.  The biggest drawback is that the TMP36 only has ±2°C accuracy; I wanted something better.  I could use the MCP9808 high-accuracy sensor (which I had planned to do), but human comfort — the biggest reason for this particular project — depends heavily on humidity.  Instead, I chose to build this around the SHT31-D, a high-accuracy temperature and humidity probe.  This brought my BOM cost above my initial $20 design point, but I decided it was worth it for the project in question.

In the process, I decided to change my microcontroller; I had only chosen the Arduino because I had lots of them on hand, and it was dead-easy to code for.  My original plan was for an Adafruit Trinket M0 with a SHT31-D breakout board on a tiny breadboard, but I changed that.  Instead, I decided to change my microcontroller to an Adafruit Feather M0 Express, on which I could directly mount the SHT31-D board with no need for a breadboard.  For the remote probes, I settled on an ESP3266 Feather board, which I would need to mate with the SHD31-D board.

When I talked about changing the microcontroller for the next design, my friend asked about the differences between different microcontrollers; here's what I told him.

A microcontroller is pretty much a scaled-down SoC; the boundary is moving.  The main differences that distinguish one microcontroller from another are in the CPU, the onboard peripherals, and — for the microcontrollers that have them — the circuit board.

An Arduino is a pretty classic microcontroller among DIYers these days.  The CPU is from the Atmel AVR line, which is an architecture designed for microcontrollers.  The peripherals on the chip are pretty standard: there’s a serial port (which, on an Arduino, is hooked to an FTDI USB-Serial chip), an I2C interface (used to talk to a LOT of peripherals; what USB is to the PC world, so I2C is to the microcontroller world), an SPI interface (more complex than I2C, and less common, but more powerful; if I2C is like USB, then SPI is like FireWire), a few ADCs, and a PWM controller (which can usually take the place of a DAC for simple tasks like adjusting a motor or LED). Some versions of it have more or fewer I/O pins, or serial ports, or what have you.  The Arduino design has an easy-to-use board that includes a reasonably robust power supply design and clocking circuitry.  It’s dead simple to use for things like the temperature probe.

More powerful microcontrollers are usually ARM-based, often using a Cortex M0.  The peripherals vary a lot, as does the board.

For the temperature probe, I had picked out the Adafruit Trinket M0.  It’s an ARM based chip that’s tiny, about the size of a quarter.  Its board has pins at is standard DIP pitch, so I can put it on a breadboard.  At that size, you can’t fit in a lot of I/O pins in DIP pitch, but I don’t need many: the new version of the temperature probe uses I2C, which only uses two I/O pins (plus ground).  I chose the M0 version instead of the regular Trinket because the latter’s Atmel chip doesn’t have any built-in serial ports, so talking to the computer would have been annoying.

Some microcontrollers are much simpler.  For instance, the PIC microcontrollers are low-cost microcontrollers designed for controlling things like alarm clocks, remote controls, car ignitions, etc.  The PIC10F320, for instance, costs 36¢ (in bulk).  (The microcontroller I'm using for the project I'm working on now is a PIC16F1619, and it costs $1.51 for individual units).  Unlike the Arduino or Trinket, these aren't usually on carrier boards: instead, I would program the chip using a separate device, and then put it in the circuit where it's supposed to be used.  I happen to be prototyping mine in a board that has a socket, a built-in programmer, four LEDs, one potentiometer, and one switch, so that I can easily test changes without taking it out of the circuit.  But once it's done, I'm going to just put the chip on a breadboard or PCB.  (Actually, in this case, I'm hoping to put it in a ballpoint pen.)  The PIC has a very strange architecture: for instance, there's only one general-purpose register, the program is in 14-bit words, there are no multiply or divide instructions, using lookup tables is a royal pain, there's essentially no support for recursion, etc.  All this is in pursuit of having a cheap, low-power processor; this thing could run for 29 months if it was active from a pair of LR44s (the tiny ⌀11.25mm x 5mm batteries in laser pointers)... and that's just if it's actively running; for standby, its 50nA sleep current will outlive any power supply.  And it has a lot of onboard peripherals (I'll just list them for brevity, but can explain these if you want): four configurable logic cells, a complementary waveform generator, two capture/compare/PWM modules, two PWM generators, two signal measurement timers, an angular timer, seven timers with different capabilities, a proportional-integral-derivative controller, a CRC generator, UARTs for serial/I2C/SPI/etc, a 12-channel 10-bit ADC, two voltage comparators, an 8-bit DAC, a zero-crossing detector, 18 GPIO pins, and a multiplexer that can reroute these signals internally or externally to the different pins based on your needs.

The opposite end has devices that are barely on the "microcontroller" end of the microcontroller/SoC boundary.  The Raspberry Pi is very much board-bound, but runs Debian on an ARM, which means you can actually write shell scripts to manipulate the GPIOs, or use the GPU to run the computer-vision library OpenCV.  It ships with Mathematica, and Wolfram has added a bunch of functions to make it easier to write microcontroller-based programs in the Wolfram Language, such as functions to manage GPIOs.  It's a breeze even for beginner programmers, since you can just plug in a mouse, keyboard, and monitor, and start writing Python - or even use Minecraft extensions to use the GPIOs!

For my interests today, my current pick of the litter is the ESP8266.  This is is the descendant of the previous IoT darling, the ESP32.  It's one of the few microcontrollers with solid WiFi and Bluetooth LE support.  But first, I'll talk about its foundations: the ESP8266 runs FreeRTOS, which is (by design) barely an OS: it handles memory management, process scheduling, and interprocess communication, and nothing else.  But FreeRTOS is as solid as you can get if you need hard realtime guarantees: when you do anything, you can calculate the upper bound of how long it will take, which is something that very few OSs can guarantee.  If I were building a satellite, I would base it on FreeRTOS.  On top of this, the ESP8266 has built-in Bluetooth LE and WiFi radios, and has libraries for Bluetooth LE, WiFi, TCP/IP, NTP, DNS, HTTP, and even over-the-air updates (where it pulls new version of the firmware online, like phones do), which makes it ideal for IoT devices.  It also has wear-leveling filesystems, and has libraries for FAT if you want to use it with a commodity SD card that you can plug into a computer.  It also has a super-simple ultra low-power processor that can run while the main CPU is in sleep mode, so it can do things like take readings from the environment and wake up the main CPU if something "interesting" happens.  In addition to the WiFi and BTLE, it also has onboard peripherals for ADCs, DACs, GPIOs, I2C, I2S, SPI, timers, touch sensors, a few different PWM modules, LED control, SDMMC (for SD cards and similar buses), and UARTs.  My current drawn-out project is monitoring and watering orchids based on an ESP8266.  The original orchids have died, but that's my fault rather than that of the chip (or the LEGOs that support it).  There are a few different boards based around the ESP8266; Sparkfun has a good one that's pretty much the dev design (that's what I use), and Adafruit has one that fits in better with their microcontroller ecosystem (which was my first introduction to it, and I'll get back to that).

Now, let's go just to a bit beyond microcontrollers, through the grey area of the microcontroller/SoC boundary, to the most heavyweight end.  At that end, we have the Nvidia T194 "Xavier" SoC.  This uses eight ARM-compatible Carmel CPUs, so can run ARM Linux, Android, etc.  There are 512 GPU cores with the new Volta architecture; you can use those to play games at high frame rates, but they're mostly designed for managing neural nets.  In that pursuit, Xavier also has the open-sourced Deep Learning Accelerator, which is designed for running neural nets at high speeds and low power consumption.  I use the Xavier's predecessor, Parker, at home as my lab computer; that's even what I used to do my taxes.  The Parker is sold as the center of the Jetson TX-2, which is a Parker chip surrounded by lots of the support circuitry.  The Xavier is going to have a similar kit.  The Jetson TX-2 dev kit's carrier board is pretty big, but the module at its core is about the size of a stack of 10 playing cards.  I've seen Parkers used at the core of autonomous drones and rovers (because of how much computer vision processing they need to do), and such, but it's closer to a full-blown computer than a microcontroller.

(Disclaimer: I work for Nvidia.  The opinions in this blog are all my own, and are not official Nvidia communications.)

Some vendors, such as Adafruit, design microcontroller boards for particular purposes.  For instance, the Feather line of microcontrollers all have a common pinout, so they can share add-on peripherals.  Some of these ecosystems make it easy to use prebuilt common add-on boards: these are called shields in Arduino culture, HATs for Raspberry Pi, wings for the Feather line, etc.  A lot of companies make microcontroller boards that are pinout-compatible with the Arduino so they can work with existing shields.

Often, the availability of libraries is important.  Again, a lot of boards have some degree of Arduino software compatibility so they can be used with the Arduino SDK and libraries.  Adafruit also has a system called Circuit Python that lets you write programs in Python, and the board-specific libraries have all the bits to connect you to the GPIOs etc.  When you plug in a Circuit Python board, it shows up as a flash drive and (if the board supports it) a serial port, so you can just put your program on the flash drive and connect to the serial console.  If using Python on a microcontroller sounds exciting, you should also check out MicroPython.

There are a LOT of variations within this; I've only given you a few of the "landmarks".  For instance, if I was choosing a wearable microcontroller, just off of the top of my head, I can think of two that are Arduino-compatible, two that are Circuit Python compatible, and one that is the size of a quarter, and I'm not even into wearables.

When I choose a microcontroller, here's the things I'm looking at:

  • Does it have enough pins to support the devices I'm trying to work with?

  • Does it have the built-in peripherals I want?

  • Does it have a form factor that I need?

  • If I'm working on a battery-powered device, is the power consumption appropriate?

  • If I still have multiple choices (and I usually do), which one is easiest to program?

  • For the first revision of the thermometer probe, I chose an Arduino (specifically, an Arduino UNO clone with a Sparkfun Protoshield that I previously mounted a micro-sized breadboard on), based on some basic requirements:

    • I needed one ADC to read the analog temperature probe (a TMP36), and no other GPIOs (most uCs can meet this)

    • I needed a serial-USB port (leaves out the Gemma M0 and a few others)

    • I wanted something fairly small

    • I didn't care about power consumption

    • I have lots of Arduinos lying around

    • Arduinos are really easy to connect devices to, particularly with the Protoshield breadboard I had lying around

    • It's dead-simple to program simple stuff on an Arduino (although it's hard to write complex stuff on)

    • For the second revision (in progress), I wanted to design for size.  I'm switching from the TMP36 (analog output) to a SHT31-D (I2C interface), because the latter has a superior accuracy.  (I also picked it for the humidity sensor, and other aspects that I can discuss separately.)  For the ones plugged into computers, for the microcontroller, I settled on the Adafruit Feather M0 Express, because:

      • I only need one I2C I/O channel (two pins), and no other GPIO,

      • I need a serial console (which excludes the Adafruit Trinket)

      • It can be powered by USB, so I don't need an external power supply,

      • The prototyping space lets me mount the SHT31-D breakout board without needing a breadboard,

      • It has a UART that can show up as a regular USB serial port (which, sadly, lets out the Adafruit Trinket),

      • It is in stock this week (which, much more sadly, lets out the Adafruit Trinket M0; I originally spec'd this for the Trinket M0, but before I ordered it, they ran out of stock) (update: it's back in stock)

      • It's cheap

      • For the WiFi remote probes, my overwhelming concern was easy-to-use WiFi, which pretty much left me with the ESP8266 (or ESP32).  Among these, I chose the Adafruit Feather ESP8266, for easy compatibility with the Adafruit Feather ecosystem.  Since I had already chosen a Feather for the teathered (USB-connected) probes, I figured that sticking with a Feather would be best for the remotes.  (The ESP8266 boards I've used previously weren't Feathers, but there's not a huge difference for the purposes in front of me.)

        There were lots of choices for these probes, so a lot of this was "what can I get working in the least amount of time".  Of course, the aspect of "time" is asymmetrically weighed between developer time and wall time.  As it happened, I had the parts at home to build the version with an Arduino and TMP36, which is also the easiest one to code, so I could quickly deploy it and start getting readings.  But for higher accuracy, smaller size, and placement flexibility (which needs to take into account WiFi capability and power consumption), there were a few other choices, so I'm drastically switching microcontroller architectures for the rev2 deployment.