Sunday, December 13, 2020

Trying to test a "ten cent" tiny ARM-M0 MCU


A few months ago, while browsing LCSC, I found a surprisingly cheap ARM M0 MCU.  At the time it was 16.6c in single-unit quantities, with no higher-volume pricing listed.  From the datasheet LCSC has posted, there was enough information in English to tell that it has 2kB RAM, 16kB flash, and runs up to 32MHz with a 1.8V to 3.6V power supply.  Although the part number suggests it may be a clone or is compatible with the STM32F030, it's not.  The part number for the STM32F030 clone is HK32F030F4P6.

Some additional searching brought me to some Chinese web sites that advertised the chip as a 32-bit replacement for the STM8S003.  The pinout matches the STM8S003F3P6, so in theory it is a drop-in replacement for the 8S003.  Unlike the STM32F0, it has no serial bootloader, so programming has to be done via SWD.  And with no bootloader support, there's no need to be able to remap the flash from 0x0800000 to 0x0000000 like the STM32.  A small change to the linker script should be all it takes to handle that difference.  Even though I wasn't sure how or if I'd be able to program the chips, I went ahead and ordered a few of them.  I already had some TSSOP20 breakout boards, so the challenge would be in the software, and the programming hardware.

Since I'm cheap, I didn't want to buy a dedicated DAPlink programmer.  I have a STM32F103 "blue pill", so I considered converting it to a black magic probe.  But since I've been playing with the CH554 series of chips, I decided to try running CMSIS-DAP firmware on a CH552.  If you're not familiar with CMSIS-DAP and SWD, I recommend Chris Coleman's blog post.  Before I tried it with with the HK32F030MF4P6, I needed to try it with a known good target.  Since I had recently been working with a STM32F030, that's what I chose to try first.

The two main alternatives for open-source CMSIS-DAP software for downloading, running, and debugging target firmware are OpenOCD and pyOCD.  pyOCD is much simpler to use than OpenOCD; after installing it with pip, 'pyocd list' found my CH552 CMSIS-DAP:

However that's as far as I could get with pyOCD.  There seems to be a bug in the CMSIS-DAP firmware or pyOCD around the handling of the DAP_INFO message.  Fixing the bug may be a project for another day, but for the time being I decided to figure out how to use OpenOCD.

To use OpenOCD, you need to create a configuration file with information about your debug adapter and target.  It's all documented, however it's very complicated given that OpenOCD does a whole lot more than pyOCD.  It's also complicated by the fact that since the release of v0.10.0, there have been updates that have made material changes to the configuration file syntax.  I had a working configuration file on Windows that wouldn't work on Linux.  On Linux I was running OpenOCD v0.10.0-4, but on windows I was running v0.10.0-15.  After installing the xPack project OpenOCD build on Linux, the same config file worked on both Linux and Windows, which I named "cmsis-dap.cfg":

adapter driver cmsis-dap

transport select swd
adapter speed 100

swd newdap chip cpu -enable
dap create chip.dap -chain-position chip.cpu
target create chip.cpu cortex_m -dap chip.dap

dap info

With dupont jumpers connecting SWCLK, SWDIO, VDD, and VSS on my STM32F030 breakout board, here's the output from openocd.

After making the same connections (factoring the different pinout) to the HK32F030MF4P6, I was getting no response from the MCU.  Before connecting, I had done the usual checks for shorts and continuity, making sure all my solder connections were good.  Next I tried just connecting VDD and VSS, while I probed each pin.  Pin 2, SWDIO, was pulled high to 3V3, as was nRST.  All other pins were low, close to 0V.  The STM32F030 pulls SWDIO and nRST high too.  I tried reconnecting SWDIO and SWCLK, and connecting a line to control nRST.  I added "reset_config trst_and_srst" to my config file, and still didn't get a response.  Looking at the debug output from openocd (-d flag) shows the target isn't responding to SWD commands:

Debug: 179 99 cmsis_dap_usb.c:728 cmsis_dap_swd_read_process(): SWD ack not OK @ 0 JUNK Debug: 180 99 command.c:626 run_command(): Command 'dap init' failed with error code -4

Since the datasheet says that after reset, pin 2 functions as SWDIO, and pin 11 functions as SWCLK, I'm at a bit of an impasse.  I'll try hooking up my oscilloscope to the SWDIO and SWCLK lines to make sure the signals are clean.  I've read that in some ARM MCUs, DAP works while the device is in reset, so I'll peruse the openocd docs to figure out how to hold nRST low while communicating with the target.  And of course, suggestions are welcome.

Before I finish this post, I wanted to explain the reference to a "ten cent" MCU.  LCSC does not list volume pricing for the part, but when I searched for the manufacturer's name, "Shenzhen Hangshun Chip Technology Development", I found an article about the company.  In the article, the company president, Liu Jiping, refers to the 10c ($0.1) price.  I suspect that pricing is for quantities over 1000.  Assuming these chips can actually be programmed with a basic SWD adapter, then even paying 20c for a 20-pin, 32MHz M0 MCU looks like a good deal to me.

Read part 2 to find out how I got SWD working.

Monday, December 7, 2020

STM32 Starting Small


For software development, I often prefer to work close to the hardware.  Libraries that abstract away the hardware not only use up limited flash memory, they add to the potential sources of bugs in your code.  For a basic test of STM32 library bloat, I compiled the buttons example from my TM1638NR library in the Arduino 1.8.13 IDE using stm32duino for a STM32F030 target.  The flash required was just over 8kB, or slightly more than half of the 16kB of flash specification on the STM32F030F4P6 MCU.  While I wasn't ready to write my own tiny Arduino core for the STM32F, I was determined to find a more efficient way of programming small ARM Cortex-M devices.

After a bit of searching, looking at Bill Westfield's Miimalist ARM project, libopencm3, and other projects, I found most of what I was looking for in a series of STM32 bare metal programming posts by William Ransohoff.  However instead of using an ST-Link programmer, I decided to use a standard USB-TTL serial dongle to communicate with the ROM bootloader on the STM32.

To enable the bootloader, the STM32 boot0 pin must be pulled high during power-up. then the bootloader will wait for communication over the USART Tx and Rx lines.  On the STM32F030F4P6, the Tx line is PA9, and the Rx line is PA10.  In order reset the chip before flashing, I also connected the DTR line from my serial module to NRST (pin 4) on the MCU as shown in the following wiring diagram:

For flashing the MCU, I decided on stm32flash.  While installation on Debian Linux is as simple as, "apt install stm32flash", I had some difficulty finding a recent Windows build.  So I ended up building it myself.  Although my build defaults to 115.2kbps, I found 230.4kbps completely reliable.  At 460.8kbps and 500kbps, I encountered intermittent errors, so I stuck with 230.4kbps.  After making the necessary connections, and before flashing any code to the MCU, do a test to confirm the MCU is detected.

One thing to note about stm32flash is that it does not detect the amount of flash and RAM on the target MCU.  The numbers come from a hard-coded table based on the device ID reported.  The official flash size in kB is stored in the system ROM at address 0x1FFFF7CC.  On my STM32F030F4P6, the value read from that address is 0x0010, reflecting the spec of 16kB flash for the chip.  My testing revealed that it actually has 32kB of usable flash.

I used William's STM32F0 GPIO example as a template to create a tiny blinky example that uses less than 300 bytes of flash.  Most of that is for the vector table, which on the Cortex-M0 has 48 entries of 4 bytes each.  To save space, I embedded the reset handler in an unused part of the vector table.  Since the blinky example doesn't use any interrupts, all but the initial stack pointer at vector 0 and the reset handler at vector 1 could technically be omitted.  I plan to re-use the vector table code for other projects, so I did not prune it down to the minimum.

The blinky example will toggle PA9 at a frequency of 1Hz.  That is the UART Tx pin on the MCU, which is connected to the Rx pin on the USB-TTL dongle.  This means when the example runs, the Rx LED on the USB-TTL dongle will flash on and off.

I think my next step in Cortex-M development will be to experiment with libopencm3.  It appears to have a reasonably lightweight abstraction of GPIO and some peripherals, so it should be easier to write code that is portable across multiple different ARM MCUs.