Framework Structure
Overview
While the MCUs supported share the same cores, Cortex-M33 and Cortex-M0+, including all functionality as defined by ARM in their respective architecture reference manuals, such as the NVIC, or the system tick, they are very different with respect to their architectures and functionalities as system-on-a-chip. A basic example is the clock subsystem for CPU, bus, and peripheral devices.
The RPs are remarkably similar to each other, but vastly different from what is used with the NXPs and the STMs. The latter two again are very different from each other, and even within products of one vendor, say NXP, there can be found different approaches and designs for the same purpose, say, clocking. The RPs are also very neatly designed and structured when looking as just one of the MCU instances, while the other two tend to be a bit messier. I suppose this is not least the result of offering way more MCU product lines, the re-use of certain common building blocks on the chip across many different products, and the corresponding (design) history.
For a framework such as Oberon RTK, the question arises how to deal with said differences, while maintaining a reasonable compatibility.
Approach
We can divide the MCU’s subsystems and modules which are not part of the ARM architecture definitions into three categories:
- required for the basic MCU operation, such as clocks or resets;
- quasi-standard modules, such as UART or SPI;
- chip and application specific functional subsystems, such as encryption engines, neural net processors, or operational amplifiers.
None of these subsystems are compatible on the hardware level across different MCU vendors and products; within vendors and products, we can find some degree of, or even full, compatibility. Peripheral devices such as UART or SPI provide compatibility on the application layer, but even here we find more or less extensive additional and diverging functionality. For example, UART devices than can act as SPI masters.
The approach chosen:
- hardware-specific driver modules that provide an API to control and manage the hardware, but omit higher level functionality required for programs, with as many as possible common names for data types and procedures; these modules also export any constants, such as clock sources, that are required for the parameters of their client modules (next point);
- modules that make use of these driver modules to provide program level functionality, eg. for UARTs (
PutString), or configure the run-time parameters for the MCU as required for a specific program, eg. for the clocks; these modules do not directly access the hardware;1 - program modules make use of the second layer if it exists, or directly of the basic hardware driver modules if not; program modules do not use the hardware directly, unless there is a benefit regarding performance.
For the hardware-specific driver modules, even if the names of data types and procedures are the same, they usually take different parameters for MCUs of different vendors. For example, a PLL as found in the RPs has a different set of configuration parameters than PLLs provided by NXP or ST. But the configuration procedure is always ConfigPLL, which takes a record type PLLcfg as parameter. Hence, we can achieve at least a conceptual consistence, with the same design patterns applicable for different MCU types.
Another example would be a module for UART or SPI devices, which always export a Device type, and provide procedures Init, Configure, Enable and so on, with Configure taking a DeviceCfg parameter.
Nothing new or earth-shattering here.
Required for Basic MCU Operation
This category includes all MCU subsystems that are not part of the CPU architecture specification, but are required to get even off the ground. Each vendor has very different names for their respective subsystems, so Oberon RTK uses the same names for each MCU type and vendor.
Currently Oberon RTK uses in lib/v3.0, for all MCUs:
CLK.mod: clocks configuration and control: oscillators, PLLs, global dividers;RST.mod: reset control: peripheral devices;PWR.mod: power control: eg. core voltage controllers for different core and bus clock frequencies, power saving states;FLASH.mod: flash memory configuration: eg. wait states, configuration data in flash;RAM.mod: RAM configuration: eg. wait states, clock gating.
Module Clocks then uses module CLK to configure and run the required clock for a program, implementing Clocks.Configure, which is called in module Main, as design pattern used for all MCUs.2 Clocks also uses modules PWR, FLASH, and RAM as needed for the required clock frequencies.
Quasi-standard Modules
Module UART defines the peripheral device in software as type, plus the procedures to set it up, as outlined above. Module UARTstr uses the thusly defined peripheral device to print strings to a serial terminal.
More
There are more types of modules than outlined above. As of now, I have focused on the very bottom of the structure, and defining and implementing a corresponding layer of modules for the basic operation of the MCU, plus serial text output.
(this page to be extended)
-
Exceptions can be made for modules where we want to limit its dependence on other modules, or where hardware access is required for the module’s core functionality, such as run-time error handling. ↩︎
-
Module
Clocksis located in directoryconfig, so it can be implemented specifically for a program or application. ↩︎