Part 1 of this article summarized scope of work because software for an ECU basically have two core bulks – one is the core and main controller, while the second is the add-one modules and specialized IO. This second part is basically endless as we will be adding more features and more hardware, so in this summary I want to focus on the main controllers. The block diagram below outline the main components.
RTOS that I use can be FreeRTOS, ThreadX or whatever, but I always add a barebone, linear scheduler as well because that enables FW to scale better. Most of the times I just run barebone, but I can always add a thread shifter if needed. RTOS must provide a set of critical functions that all SW is allowed to use – two of them are functions like millis() and microes() to measure elapsed time. The key functionality is however an easy path to set up tasks and decide how they execute for the system to work.
HAL (Hardware Abstraction Layer) is whatever drivers you get from a vendor and that interface to hardware. The challenge with this layer is that it is not a proper abstraction layer as different vendors will have different drivers and diffenert aproack to do the same thing.
AL (Abstraction Layer) is my own C++ layer to access hardware or software that I want to re-use in an uniform way to protect code. Using HAL drivers comes at a price – overhead and specialized hardware interface + I sometimes need to deal with workarounds or simply want a simpler interface. Also I sometimes need low level drivers that are fast and C++ is well suited to have a nice interface to a tight assembly function should I need to. Ethernet as an example can be a hardware or software interface and I need an uniform way that hide this complexity. The AL is common in all my systems and RTOS is part of the AL library.
Primary CAN is the CAN port combined with the 8-30V PSU connector. I initially had a separate PSU connector, but decided that since all main boards have CAN-FD I can as well add one here using the NMEA2000 layout. This also gives me easy access to cable infrastructure. The primary CAN can be used like any other CAN port, but it is also linked to CLI giving access to all configuration.
USB is used as a serial driver to enable a PC to connect, even power a motherboard from USB and configure it using USB. We might add more sofisticated methods later, but as a start you will need to connect to the USB for (1) configuring the system and (2) downloading new firmware.
CLI (Command Line Interface) is a text based command utility that allow you to interact directly with the system, configure components, modules and load new firmware.
Bootloader is a small FW package that is executed at start-up to check if a new FW is needed. For the main controller we download FW as an image on the disk and as we restart that FW is loaded. For the modules this operation is controlled by the mainboard, so we download to mainboard that then download to the modules. The bootloader cannot be broken, but a FW upload might fail in which case we restart the download procedure. We also have the option to rollback to a previous download as we can store multiple images on Disc.
Power Management. As we have multiple IO modules drawing Power we need to monitor their power usage and switch them on/off as needed. All motherboards can switch on/off any sub-module as needed. This includes logic to blacklist non-working modules – in which case we raise a sub-system down alarm.
Dictionary is our central database partly in RAM and partly on disk. This is a list of variables, tables, modules etc – everything we use in the system. The Dictionary list the content primarely being an index, but the actual data can be located in user modules or copied in the Dictionary + FW images will always be on disk. One module interface to another through the dictionary which makes it easy to build a distributed system as modules can be located on different nodes if required.
Disk is an extension of dictionary where selected variables are stored on SPI Flash when changed. FW images are only stored on disk. At start-up the system will read all parameters to initialize the system. After that we save parameters then changed.
Event Distribution, As variables in the dictionary change we can signal receive tasks to pick up the changes – this can be done per variable or for a set of variables. If we are on the same MCU this will mean one task change a variable and another one deals with that change within 50yS. If we are on different Nodes we can still interact within 500yS and maybe faster. Also multiple modules can interact with the same module with no extra effort. Signals within tasks are very efficient, but you can also do a check every 10ms scheme or similar. If the receiver of a signal is a communication port then the receiver will pick up data and transfer it on the bus as fast as possible.
Module IO is a generic protocol as the motherboard assumes nothing about a module. In the config we will have a list of what we expect and as we start each module we do an identification. Each module have an unique type number and id, so the motherboard can instantly see if this is what we expected or something else. If it is a new module we need to load it’s part of the dictionary so that modules using this one find data they look for. Process for staring is very fast and easy, while process for connecting new modules will require a bit more communication depending on module.
Obviously it is a lot more details to the functionality than mentioned here, but is should give you a brief idea of how it all plugs together.

















