I was excited to see recently that ARM announced their new Cortex-M7 microcontroller core, and that ST announced their line using that core, the STM32F7. I had briefly played around with the STM32 before, and I talked about how I was going to start using it — I never followed up on that post, but I got some example programs working, built a custom board, didn’t get that to work immediately, and then got side-tracked by other projects. With the release of the Cortex M7 and the STM32F7, I thought it’d be a good time to get back into it and work through some of the issues I had been running into.
First of all though, why do I find these chips exciting? Because they present a tremendous value opportunity, with a range of competitive chips from extremely low-priced options to extremely powerful options.
The comparison point here is the ATmega328: the microcontroller used on the Arduino, and what I’ve been using in most of my projects. They currently cost $3.28 [all prices are for single quantities on digikey], for which you get a nice 20MHz 8-bit microcontroller with 32KB of flash and 2KB of ram. You can go cheaper by getting the ATmega48 which costs $2.54, but you only get 4KB of program space and 512B of ram, which can start to be limiting. There aren’t any higher-performance options in this line, though I believe that Atmel makes some other lines (AVR32) that could potentially satisfy that, and they also make their own line of ARM-based chips. I won’t try to evaluate those other lines, though, since I’m not familiar with them and they don’t have the stature of the ATmegas.
Side note — so far I’m talking about CPU core, clock speeds, flash and ram, since for my purposes those are the major differentiators. There are other factors that can be important for other projects — peripheral support, the number of GPIOs, power usage — but for all of those factors, all of these chips are far far more than adequate for me so I don’t typically think about them.
The STM32 line has quite a few entries in it, which challenge the ATmega328 on multiple sides. On the low side, there’s the F0 series: for $1.58, you can get a 48MHz 32-bit microcontroller (Cortex M0) with 32KB of flash and 4KB of RAM. This seems like a pretty direct competitor to the ATmega328: get your ATmega power (and more) at less than half the price. It even comes in the same package, for what that’s worth.
At slightly more than the cost of an ATmega, you can move up to the F3 family, and get quite a bit better performance. For $4.14 you can get a 72MHz Cortex M3 with 64KB of flash and 16KB of RAM.
One of the most exciting things to me is just how much higher we can keep going: you can get a 100MHz chip for $7.08, a 120MHz chip for $8.26, a 168MHz chip for $10.99, and — if you really want it — a 180MHz chip for $17.33. The STM32F7 has recently been announced and there’s no pricing, but is supposed to be 200MHz (with a faster core than the M4) and is yet another step up.
When I saw this, I was pretty swayed: assuming that the chips are at least somewhat compatible (but who knows — read on), if you learn about this line, you can get access to a huge number of chips that you can start using in many different situations.
But if these chips are so great, why doesn’t everyone already use them? As I dig into trying to use it myself, I think I’m starting to learn why. I think some of it has to do with the technical features of these chips, but it’s mostly due to the ecosystem around them, or lack thereof.
Working with the STM32 and the STM32F3 Discovery board I have (their eval board), I’m gaining a lot of appreciation for what Arduino has done. In the past I’ve haven’t been too impressed — it seems like every hobbyist puts together their own clone, so it can’t be too hard, right?
So yes, maybe putting together the hardware for such a board isn’t too bad. But I already have working hardware for my STM32, and I *still* had to do quite a bit of work to get anything running on it. This has shown me that there is much more to making these platforms successful than just getting the hardware to work.
The Arduino takes some fairly simple technology (ATmega) and turns it into a very good product: something very versatile and easy to use. There doesn’t seem to be anything corresponding for the STM32: the technology is all there, and probably better than the ATmega technology, but the products are intensely lacking.
Ok so I’ve been pretty vague about saying it’s harder to use, so what actually causes that?
Family compatibility issues
One of the most interesting aspects of the STM32 family is its extensiveness; it’s very compelling to think that you can switch up and down this line, either within a project or for different projects, with relatively little migration cost. It’s exciting to think that with one ramp-up cost, you gain access to both $1.58 microcontrollers and 168MHz microcontrollers.
I’ve found this to actually be fairly lackluster in practice — quite a bit changes as you move between the different major lines (ex: F3 vs F4). Within a single line, things seem to be pretty compatible — it looks like everything in the “F30X” family is code-compatible. It also looks like they’ve tried hard to maintain pin-compatibility for different footprints between different lines, so it looks like (at a hardware level) you can take an existing piece of hardware and simply put a different microcontroller onto it. I’ve learned the hard way that pin compatibility in no way has to imply software compatibility — I thought pin compatibility would have been a stricter criteria than software compatibility, but they’re just not related.
To be fair, even the ATmegas aren’t perfect when it comes to compatibility. I’ve gotten bitten by the fact that even though the ATmega88 and ATmega328 are supposed to be simple variations on the same part (they have only a single datasheet), there some differences there. There’s also probably much more of a difference between the ATmegaX8 and the other ATmegas, and even more of a difference with their other lines (XMEGA, AVR32).
For the ATmegas, people seem to have somewhat standardized on the ATmegaX8, which keeps things simple. For the STM32, people seem to be pretty split between the different lines, which leads to a large amount of incompatible projects out there. Even if you’re just trying to focus on a single chip, the family incompatibilities can hurt you even if you’re not trying to port code — it means that the STM32 “community” ends up being fragmented more than it potentially could be, with lots of incompatible example code out there. It means the community for any particular chip is essentially smaller due to the fragmentation.
What exactly is different between lines? Pretty much all the registers can be different, the interactions with the core architecture can be different (peripherals are put on different buses, etc). This means that either 1) you have different code for different families, or 2) you use a compatibility library that masks the differences. #1 seems to be the common case at least for small projects, and mostly works but it makes porting hard, and it can be hard to find example code for your particular processor. Option #2 (using a library) presents its own set of issues.
Lack of good firmware libraries
This issue of software differences seems like the kind of problem that a layer of abstraction could solve. Arduino has done a great job of doing this with their set of standardized libraries — I think the interfaces even get copied to unrelated projects that want to provide “Arduino-compatibility”.
For the STM32, there is an interesting situation: there are too many library options. None of them are great, presumably because none of them have gained enough traction to have a sustainable community. ST themselves provide some libraries, but there are a number of issues (licensing, general usability) and people don’t seem to use it. I have tried libopencm3, and it seems quite good, but it has been defunct for a year or so. There are a number of other libraries such as libmaple, but none of them seem to be taking off.
Interestingly, this doesn’t seem to be a problem for more complex chips, such as the Allwinner Cortex-A’s I have been playing with — despite the fact that they are far more complicated, people have standardized on a single “firmware library” called Linux, so we don’t have this same fragmentation.
So what did I do about this problem of there being too many options leading to none of them being good? Decide to create my own, of course. I don’t expect mine homebrew version to take off or be competitive with existing libraries (even the defunct ones), but it should be educational and hopefully rewarding. If you have any tips about other libraries I would love to hear them.
Down the rabbit hole…
Complexity of minimal usage
I managed to get some simple examples working on my own framework, but it was surprisingly complicated (and hence that’s all I’ve managed to do so far). I won’t go into all the details — you can check out the code in my github — but there are quite a few things to get right, most of which are not well advertised. I ended up using some of the startup code from the STM32 example projects, but I ended up running into a bug in the linker script (yes you read that right) which was causing things to crash due to an improper setting of the initial stack pointer. I had to set up and learn to use GDB to remotely debug the STM32 — immensely useful, but much harder than what you need to do for an Arduino. The bug in the linker script was because it had hardcoded the stack pointer as 64KB into the sram, but the chip I’m using only has 40KB of sram; this was an easy fix, so I don’t know why they hardcoded that, especially since it was in the “generic” part of the linker script. I was really hoping to avoid having to mess with linker scripts to get an LED to blink.
Once I fixed that bug, I got the LEDs to blink and was happy. I was messing with the code and having it blink in different patterns, and noticed that sometimes it “didn’t work” — the LEDS wouldn’t flash at all. The changes that caused it seemed entirely unrelated — I would change the number of initial flashes, and suddenly get no flashes at all.
It seems like the issue is that I needed to add a delay between the enabling of the GPIO port (and the enabling of the corresponding clock) and the setting of the mode registers that control that port. Otherwise, the mode register would get re-reset, causing all the pins get set back to inputs instead of outputs. I guess this is the kind of issues that one runs into when working at this level on a chip of this complexity.
So overall, the STM32 chips are way, way more complicated to use than the ATmegas. I was able to build custom ATmega circuits and boards very easily and switch away from the Arduino libraries and IDE without too much hassle, but I’m still struggling to do that with the STM32 despite having spent more time and now having more experience on the subject. I really hope that someone will come along and clean up this situation, since I think the chips look great. ST seems like they are trying to offer more libraries and software, but I just don’t get an optimistic sense from looking at it.
So, I’m back where I was a few months ago: I got some LEDs to blink on an evaluation board. Except now it’s running on my own framework (or lack thereof), and I have a far better understanding of how it all works.
The next steps are to move this setup to my custom board, which uses a slightly different microcontroller (F4 instead of F3) and get those LEDs to blink. Then I want to learn how to use the USB driver, and use that to implement a USB-based virtual serial port. The whole goal of this exercise is to get the 168MHz chip working and use that as a replacement for my arduino-like microcontroller that runs my other projects, which ends up getting both CPU and bandwidth limited.