Skip to content

Development & Getting Started

vintagepc edited this page May 18, 2023 · 2 revisions

This page is designed to be a starting point for developing on MINI404

You will definitely want a copy of the STM32 reference manuals - they give decent descriptions of the periperhals and their operation, and are absolutely vital to your understanding if you want to dive into development. Other aspects of how the system is expected to behave can usually be inferred from software, specifically the HAL and its higher level functions that interface with hardware directly.

Layout

QEMU uses a c-based object model which results in each "peripheral" being a mostly independent piece of code only tied together through common interface functions. The net result is they are mostly self contained, apart from e.g. the buses they use (SPI, I2C, etc).

The changes for STM32 support in Mini404 generally live in hw/arm/prusa, (rather than spreading them throughout the entire codebase and having to jump all over the file tree to find them) and are further subdivided by folders specific to each chip:

  • STM32_chips contains the header definitions that provide info specific to a chip or family of chips - the peripheral layout, memory settings, IRQ enumerations, etc.
  • stm32_common contains code for peripherals that are common to multiple chips or families. If you look in these files you'll generally see multiple register definitions and instances for each supported item. It also contains some code to reduce boilerplate initialization of the different SoC families - usually it is enough to simply add a peripheral to the chip header and this template code will take care of it. However, in some cases manual wiring may be required, such as in the case of EXTI lines or some ADC linkages.
  • stm32_[f030|f407|g070] contain things unique to those families as well as the generic SoC instance responsible for tying all the peripherals together.
  • parts contains miscellaneous non STM32 related components that are required for sim functioning (such as stepper drivers, displays, etc).

General I/O

For any given peripheral, it is generally exposed as a memory-mapped IO device and you will find corresponding read/write methods that are invoked when the ARM firmware accesses the peripheral registers, and this is where most of the logic/simulation happens - such as sending a byte on the virtual SPI bus when the SPI data register is written.

Much like MK404, any I/O on an object usually happens in the form of IRQs; sometimes they are simple binary signals (such as GPIO events) and in other cases they may contain more complex data for the receiver, such as a PWM value or DMA address. Input IRQs can have any number of incoming signals, but an output IRQ may only have a single destination (so you will see use of IRQ splitters in situations where one signal must reach multiple consumers.).

Timers are also used widely for scheduling of events and adhering to time-based constraints (such as the time it takes to transmit a character over a UART at a given baud rate). They will almost always derive from the machine's virtual clock.

I've opted for prolific use of unions and bitfield registers throughout this code, especially because it makes debugging far less cumbersome; no time needs to be spent trying to decode a uint32_t into a pile of bits according to a page full of #defines.

Abstractions

There are a few helpful abstractions to reduce boilerplate and copy-pasta of code patterns. Most of these boil down to:

  • Peripheral initialization which can be found in the stm32_common.c file and greatly cuts down on the cruft for any one particular chip.
  • an abstract STM32Peripheral type/class which is a non-opaque parent to facilitate dealing with common properties in the STM32 infrastructure - Primarily DMA requests, peripheral IDs, and clocks. (The ID is also used for unique identification of peripherals and to make things like the memory tree printout more legible and consistent in its type names).
  • Register initialization/handling for "similar" peripherals across chips. In many cases, register layouts are often very similar and the main change across chips or families is the fields which are available for use. This reginfo abstraction facilitates using the same peripheral code for all cases while being able to properly initialize and mask off which bits are permitted for any specific instance.