2024 October 22 18:25

Fixing the STM32 flash programming code

I just discovered that a change I made back in June (commit b0ca148c) to add basic support for the STM32C031 (and the Nucleo-C031C6 board) broke muforth’s flash memory programming code for STM32F0 and STM32F3 devices!

When it comes to programming the flash, STM32 devices fall into two groups: those that specify which page to erase by its address, and those that specify it by an index. Roughly speaking, the STM32F0 and F3 are in the first group; the F4 and C0 are in the second.

Unfortunately, it’s not that simple. Within the second group, there are variations – which is why I needed to make changes to support the C031.

For some reason I copied a few lines of code from the C0 programming code and pasted it into the F0/F3 code. This code used ldrb and strb instructions to access the FLASH_SR register. On STM32C0 devices, this is fine; on STM32F0 and F3 devices, it’s forbidden. The reference manual says “only 32-bit references are allowed”.

It turns out that if you make a byte- or halfword-sized reference to the flash registers of an F0 or F3, the chip crashes spectacularly. I’m not sure what kind of fault or undefined state it enters, but it has to be forcibly halted (via ST-LINK), and at that point the PC and SP are garbage.

It took me some hours to figure out that this code – which had been totally reliable and was now mysteriously and spectacularly failing – had been broken simply by adding the wrong kind of memory reference!

After fixing it, I decided to simplify and refactor the code. When I first wrote the code, I thought that using a second (argument) stack – Forth style – would be beneficial. It actually made the code more complicated. Most of the routines do one simple thing, and several of them take no arguments. Any that do take arguments use them immediately and do not pass them to called code.

So it was easy, in each routine, to first pop the arguments into registers, and then, if necessary, push the LR.

I applied the same simplification and refactoring to the code that was the source of the failure in the first place: the STM32F4 and C0 code.

Improving tethered execution on ARM targets

In the process of investigating the spectacular crashes, I improved the way that code on tethered ARM targets is executed, especially over ST-LINK (on ST Discovery and Nucleo boards), and CMSIS-DAP (eg, on Freescale/NXP FRDM boards). Until this change, the host would sit in a potentially inifinite loop, waiting for the code executing on the target to finish, execute a BKPT instruction, and halt. But if the processor went off the rails – for any reason – the host would hang, waiting. And sometimes the ST-LINK would get wedged, requiring that the board be power cycled. (The reset switch on Discovery boards only resets the target CPU, not the ST-LINK.)

I decided it would be much nicer to loop a fixed number of times, delaying between each query for whether the target had halted. The code that talks to the ST-LINK and CMSIS-DAP debug interfaces loops a maximum of 1,000 times, delaying for 1 millisecond each time; effectively a 1 second timeout.

Each time we execute a piece of code on the target we can capture the number of host queries before the target halted. Since each query takes 1 millisecond, this also gives us an idea of the execution time. Most simple code halts before the host can even start waiting. But something like a flash page erase or flash page programming cycle takes several milliseconds; the actual elapsed time is now easily observable.

Improving the readability and writability of comments

Before discovering that I had broken the STM32 flash programming code, I spent several days improving muforth’s comments. That probably sounds trivial, but it ended up being a multi-stage – and multi-day – process, in part because I didn’t at first see a simple solution that had been under my nose for several years. (I’ll get to that!)

The first step was to write a script that rewrote parenthesized block comments, like this one:

  ( This file is muforth/mu/startup.mu4. It contains high-level Forth code
    necessary to the useful execution of muforth. This file is loaded and
    interpreted every time muforth starts up.)

into this:

  ( This file is muforth/mu/startup.mu4. It contains high-level Forth code
  | necessary to the useful execution of muforth. This file is loaded and
  | interpreted every time muforth starts up.)

I thought that the vertical bar in the left margin made a nice vertical line that made the comment blocks stand out from the surrounding code.

I ran the rewriter script on the entire muforth tree and checked in the changes.

Then I realized that the vertical bar could do everything that the parentheses were doing and more; not only that, but it makes a much better block comment character than any other ASCII character!

I should have realized this a long time ago because I have been using it for ever in the chip equates files – starting with the S08 and AVR targets – to put a comment after the register definition that either explains what the register does, or shows a “map” of its constituent bits. Why I never thought, until recently, to apply this idea to all of muforth is a mystery!

This is what had been “under my nose” for years.

I modified my rewriter script (again) and ran it on the tree (again) – this time to replace the opening left parenthesis with a vertical bar, and to possibly remove the closing right parenthesis. (There are cases when you want to leave it alone.) When to leave the right parenthesis alone was yet another iterative process that took a while to get “right”.

The example comment from above now looks like this:

  | This file is muforth/mu/startup.mu4. It contains high-level Forth code
  | necessary to the useful execution of muforth. This file is loaded and
  | interpreted every time muforth starts up.

The “license boilerplate” at the beginning of almost every muforth (.mu4) file now looks like this:

  | This file is part of muforth: https://muforth.dev/
  |
  | Copyright 2002-2024 David Frech. (Read the LICENSE for details.)

Removing the trailing parenthesis in this case would be obviously wrong – but how do you write a pattern to correctly match all the cases?

By adopting the vertical bar I rendered obsolete the existing “treat the rest of the line as a comment” word: --. So I added a subcommand to my script to rewrite -- as | (under most circumstances, but not all – and getting this right was another long and iterative process).

The last thing that I did was undo a stupid mistake I had been making for years. Since in a parenthesized block comment any right parenthesis will end the comment, it’s hard to make parenthetical remarks inside the block comment. So I started using [ and ] for this. Now that the parenthesis problem has gone away – using | to create block comments is perfectly compatible with “parenthetical remarks” – I needed to rewrite all the square brackets as parentheses.

This engendered another subcommand, and another iterative process to get it right – or close to right. With all of these runs of the rewriter script against the entire muforth tree some hand editing of a few files was required.

I kept -- as a “legacy” line comment word, and with the tree all rewritten to use |, I could now simplify my Vim comment settings so that they actually work!

I used to struggle – with the qg command in particular – because Vim was only able to reformat the first two lines properly. After that the indent was wrong, and I had to fix it up manually. Using block comments delimited by parentheses, with no textual “leader” other than whitespace for the middle lines, makes it impossible for Vim to figure out what to do.

Using | for block comments is trivial for Vim. It’s now very satisying to read and write comments in muforth!


Read the 2023 journal.