A RAM Loader for STM32
The aim is to create a self-contained Flash-resident Loader that allows a PC to load and execute bare-metal applications in RAM on an STM32 microcontroller. The PC communicates with the Loader over SPI, using an FT2232H interface and a host program called cLog.
The Loader is deliberately simple. It provides the basic facilities needed to transfer application code into RAM and start execution. The more substantial development work takes place in RAM, where applications can be loaded, tested and replaced without repeatedly programming the STM32's internal Flash.
The design is tested on STM325E5 but is intended to be adaptable to many other STM32 devices.
The Loader consists of four principal files:
`rom.a` - the assembler source implementing Loader.
`rom.ld` - the matching linker script defining the memory layout and placement of the Loader in Flash.
`STM32H5E5.inc` - the definitions include file. `rom.bat` - the batch file used to assemble, link and generate the binary.These files together establish a reproducible build, using simple CLI tools from the ST STM32CubeIDE program files. The assembly describes what the Loader does, while the linker script defines where its code and associated data are placed and the include file gives real hexadecimal values to system registers.
The resulting binary is less than half a KB, intended to be small enough to program into Flash easily, either using STM32CubeProgrammer or the resident STM32 bootloader.
Adapting the Loader to another STM32 will involve checking the memory map, clocking and GPIO configurations, the SPI peripheral configurations and the linker settings.
After reset, the STM32 begins executing the Flash-resident Loader. It configures the GPIO pins needed and SPI1, then waits for transactions from the PC.
The Loader uses a token-dispatch mechanism. Each transaction contains a token word and three parameter words. The token identifies the requested operation, while the parameters supply the addresses or other values required by that operation.
The current implementation uses 32-bit words, giving a fixed four word transaction. This simplifies the host software and makes transaction handling entirely predictable.
The SPI configuration is deliberately retained when execution passes to a RAM application. A RAM application can therefore continue communicating with cLog through the same physical SPI connection. It can also extend the number of token transactions with cLog as and when necessary.
The Loader provides two token transaction operations, they are:
`ram_load` - receives application data over SPI and writes it into a specified RAM region.
`ram_run` - branches execution to an application loaded into RAM.
A typical development session follows this sequence:
1. Reset the STM32 and start the Flash-resident Loader.
2. Use cLog to load an application into RAM.
3. Load additional applications or modules into other areas of RAM if required.
4. Issue a `ram_run` command to branch to the selected application.
The important point is that loading and execution are separate operations. One or more applications can be loaded before any aplication is run and the application itself can branch back to the Loader.
For the STM32H5E5, as with many STM32 devices, the RAM layout conveniently allows applications to occupy separate 64 KB regions, beginning at addresses such as `0x20000000` and `0x20010000`. These are convenient module boundaries. For other STM32 devices the beginning addresses can be different if needed and the apps loaded can be much shorter if RAM is limited.
An application can be developed and replaced independently of the others. Applications may be self-contained, or one may pass control to another. This makes it practical to build a collection of reusable test programs and development tools.
The host program, cLog, allows control of the development process. It sends the tokens and parameters, transfers the application images to RAM and determines when an application should run.
The host is responsible for supplying valid addresses, lengths and transaction sequences. The SPI transport is based on fixed-size transactions. All transactions are 4 word except for ram_load which is in two parts, an initial four word transaction followed by the entire block of data. Parameter 1 gives the destination base in RAM and Parameter 2 gives the length of the block in bytes. Blocks can be as small as 256 bytes and as large as RAM allows.
For four word transactions SPI is full-duplex, a transaction is sent to the MCU and the MCU returns status, every clock transfers data in both directions. During block transfer only data transferred is accounted for, there is no returned status information.
The principal advantage of a RAM Loader is a shorter development cycle. Once the Loader is resident in Flash, a modified application can be assembled, linked and transferred directly into RAM and tested again and again. There is no need to reset the MCU or erase and reprogram Flash for every app edit. The application can be tested immediately, modified and loaded again.
The Loader also provides a consistent starting point for different experiments. One application might configure GPIO, another a timer SPI or DMA or a more substantial peripheral. These separate modules can run in a sequence and each be improved without committing to Flash until later on.
The arrangement is particularly useful during early development of a new STM32 board, when much of the work involves apps that configure and test individual hardware features.
This design shares principles with conventional bootloaders, but its immediate purpose is different. A conventional bootloader generally receives an image and programs it into Flash so that it can run after a reset. This Loader transfers code into RAM instead for immediate execution. It does not provide breakpoints, variable inspection or single-stepping. Instead, it provides a fast, compact and predictable route from a PC-hosted development environment to executing code in RAM.
The guiding principle is to keep the Loader compact and putting flexibility into the PC host and the RAM applications.
To improve compatibility with other STM32 devices the Loader functionality is the minimum needed to initialise the hardware to transact with cLog, to transfer data into RAM and to branch to an app. More complex facilities can be implemented within the RAM applications as they are needed.
The result is not intended to replace the features of a professional debugging environment. It is a deliberately minimal execution Loader designed to make direct hardware development easier, faster and more repeatable. In many ways the rapid turnaround of code edits it allows goes a long way to surpass the complexity of many professional debugging environments.
The Loader is recommended as an educational aid in schools and further education. The modular approach provides a natural way to deal with difficulties during development. Individual app modules can be given to teams to develop and test individually. Suppose a app proves more troublesome than anticipated, rather than leaving one individual or group to struggle with the problem, members of the class can be reallocated to investigate it further together. Those who have completed their own modules can contribute their experience, suggest alternative approaches and help identify any problems.
This encourages cooperation rather than individual rivalry. Success can be measured not simply by whether each group completes its own module or assignment, but by whether the class cooperates as a whole to produce a working system together. The ability to replace, test and reintegrate individual apps in modules makes this practical.
An important advantage of developing with replaceable RAM app modules is that programming the final Flash image can be deferred.
An app module may be working correctly and become unlikely to change, yet there is no requirement to move it into Flash immediately. It can remain in RAM alongside other modules that are still being developed. For example, app modules A and B may be stable while app module C requires further work. The complete system can continue to be tested with all three app modules in RAM. Changes to C do not require A and B to be rebuilt.
This approach avoids making intrusive debugging hardware and code insertion the default means of understanding a program. Diagnostic code can alter timing, resource usage and execution behaviour. It can sometimes change or even create the very fault being investigated. The aim is not to reject hardware debugging, but to retain control over the development process, and encourage logical thinking based upon a good understanding of hardware and really good, cunning ways to get code working properly.