Introduction
Hexabitz arrays are fully-distributed, multiprocessor systems. Each configurable module has its own MCU. Modules connect with each other to form an array, similar to the way computers connect to form a network. Having separate processors -instead of embedded multi-cores- in the same array gives you the flexibility of running them at different clock speeds or even mixing powerful, expensive processors with small and cheap ones.
This architecture dictates having a separate firmware uploaded to each module. You are required currently to write, debug and compile code for each module separately -while keeping in mind all the interactions between these modules, since there is no technology to support intuitive multi-processor software development. We’re looking forward to the day where we can write a single software -as if we were developing for a single target- and the intelligent compiler would then dissect this software into small pieces, figuring out all the required interactions between these modules on its own. Hopefully, it won’t be that long!
Until we get our hands on such a revolutionary tool, we have to figure out ways to design and develop code for complex arrays so that an array of 10 modules does not take 20x development efforts. Fortunately, there are lots of tricks and solutions to substantially reduce the amount of work needed. We will discuss some here and in future articles.
In order to support a wide array of users, Hexabitz is designed for three developer levels/personalities:
- Advanced Developers (The Do-It-Yourself Folks): Users with critical needs and very high skills in embedded systems development can take Hexabitz hardware and write their own software from scratch using low-level, third-party hardware drivers available from chip manufacturers. Being an open-source hardware system gives these hardcore users the freedom of developing their own system without the hassle of hardware design and the nightmares of production lines and supply chains!
- Experienced and Mid-level Developers (Mix-n-Match Heros): You do not have to start from scratch! We laid down the ground work for software development through our operating system (BOS), APIs and module drivers, all coupled with a set of optimized third-party middleware (e.g., real-time operating system, FAT system, etc.). Users can either use all of the above and focus on application-level development or pick the level of integration and design the components that they feel are important to differentiate their product (e.g., using only BOS or only module drivers).
- Noobs (The I-Have-No-Idea Newbies): Newbies and beginners can still utilize Hexabitz through Command Line Interface (CLI). CLI is a set of plain-English commands that you can send to the modules to execute. These commands are very handy to perform quick tests and run simple applications. In fact, probably 80% of simple applications can be executed through a set of CLI commands via what we call Command Snippets. The CLI command set will be expanded regularly to offer more features while preserving simplicity and easy-access as much as possible.
Any Other Options?
We will focus, in the time being, on developing and supporting hardware and software tools and components mentioned above. We will do our best to offer more options in the future, especially in higher-level programming languages (e.g., visual programming, Python, MATLAB, etc.)
Hexabitz Software Components
Figure 1 illustrates the hierarchy of Hexabitz software components and how they map to different users. We will discuss next these components and detail important ones.
Figure 1. Hexabitz Software Components
1. Low-level Hardware Drivers – Developed by ST & ARM
The Hardware Abstraction Layer (HAL) and the Cortex Microcontroller Software Interface Standard (CMSIS) developed by ST and ARM, respectively, are the core low-layer drivers for Hexabitz mainstream-implementation MCU hardware. They interface directly with MCU registers and perform the most basic tasks. Most developers do not need to worry about these drivers but it helps to understand how they work and how they can be implemented if you want to dig in further in embedded systems development and optimize the code for your application.
2. Third-party Middleware – Developed by various vendors
A higher-level of third-party drivers is required to perform advanced functionality such as real-time control and data storage management. Two major third-party, middleware components are currently used in Hexabitz software: FreeRTOS and FATfs. It is also very likely that we add more components in the future as functionality expands.
FreeRTOS
FreeRTOS is the de-facto real-time operating system (RTOS) for MCUs and small embedded systems. It is open-source and free to use in its standard format. Thus, accessible to everyone. An RTOS is a critical tool to ensure synchronous, predictable and real-time execution of software in embedded systems. Although FreeRTOS does not have all the capabilities of a computer-grade RTOS, it does have basic multi-threading (multi-tasking), memory management and access control. FreeRTOS code is complex and difficult to understand for non-specialists. However, from a user perspective, interactions with FreeRTOS become minimal if Hexabitz’s BOS and module drivers are utilized. Currently, Hexabitz is not making use of all of FreeRTOS features but as the platform evolves, we will integrate more and more of these powerful features. In general, each module is always running the following threads (tasks) in parallel (Note that parallel here is an oversimplified notion as MCUs can only execute code serially. The FreeRTOS scheduler, however, cycles between these tasks on every RTOS beat to simulate parallel execution.)
- Backend Task: This task handles backend communication with other modules and external hardware by parsing the circular communication buffers and triggering either the CLI or other Messaging tasks to analyze received packets and streams.
- N Messaging Tasks (where N is the number of module array ports): These tasks parse remote BOS and module messages. All N tasks can run simultaneously only when triggered by the backend task, transmitting and receiving data, without affecting each other. Their impact on MCU performance can be effectively reduced when using DMAs.
- CLI Task: This task is responsible about communicating with human users. It handles the delay in human interaction without blocking the system. This means you can connect to the module to inquire about and update its parameters and send new commands at any given time (and from any array port) without stopping or delaying front-end processing and array communication. Right now, you can only open one CLI session on the same module at any given time. You can, however, open as many CLI sessions as you want on different modules in the same array! This means you can connect to two different modules and simultaneously send two different commands across the array and they will be transferred and executed properly. It is possible to open and utilize a CLI session by non-human entity (e.g., an external hardware). However, it is much more efficient to use Messages for this.
- Default Task: This task is running in the background and is used by the BOS to execute some behind-the-scene activities such as initiating array ports scan and controlling module indicator LED.
- Module (Front-end) Task (optional): Some modules have their own task(s) running in the background as well, and performing things related to front-end functionality. This task is concealed from the end user and is used to run various module-specific, background applications.
- User Task: This empty task is exposed to the user through the project main.c file. It is essentially a scratch pad that can be customized for each project. The end-user can effectively ignore all other running tasks and treat the system as a single-thread program with the user task simulating the super-loop used in traditional, single-threaded, C programs.
Figure 2. shows a screen capture of the CLI interface displaying tasks running in H01R00 module along with their running time (i.e., CPU utilization) percentage.
Figure 2: Tasks (threads) running in a Hexabitz module. There are six communication tasks (PxMsgTask), a CLI task (UARTCmd), a DefaultTask, a FrontEndTask and a module-specific task (RGBledTask). IDLE and Tmr Svc are tasks created by FreeRTOS for its internal use.
FATfs
File Allocation Table (FAT) is a system to manage files on storage media. It is used on personal computers and many embedded systems. FATfs is a free, memory-optimized FAT module for small embedded systems. It enables you to read and write files stored on various storage media (e.g., SD/MMC cards, Flash memory, SRAM memory, etc.) and same files can be viewed or modified on personal computers as well. Storage-related modules utilize FATfs as part of their module drivers. FATfs code is highly optimized and difficult to read and understand. As with FreeRTOS, using module drivers effectively relieves the user from understanding FATfs intricacies while utilizing its features.
3. Bitz Operating System (BOS) – Developed by Hexabitz
BOS is Hexabitz own operating system, built on top of FreeRTOS, and specifically designed to support Hexabitz arrays. It relieves the user from writing code to perform initialization, inter-array communication and housekeeping tasks, among others. BOS also manages the Command Line Interface (CLI); a simple and intuitive tool for user interaction, as well as non-volatile storage of module and array parameters (and syncing these parameters across the array). Future software releases will introduce more intelligence and automation to let users focus on their end-application and delegate all background activities to the BOS. BOS is an essential part of every Hexabitz software project. Both the BOS and module drivers have the following sub-components:
- Commands: CLI Commands are a set of easy-to-use control commands in plain English language. They can be sent to the module from any serial terminal tool (e.g., RealTerm) via any of the array ports. Commands can be executed locally (i.e., on the module itself), forwarded to another module (or a group of modules) or broadcasted to all modules in the array. There are Commands available to control BOS and most of Hexabitz configurable modules. In general, most Commands return a textual response to the user, either from the local module or from a remote one. The response can be turned on/off by the user.
- Messages: Messages are the machine-format version of CLI Commands. They are transferred across the array in short packets that are faster and more efficient but not human-decipherable. They can be used by external hardware to control Hexabitz modules. They are also used inside the array for inter-array communication. When you send a Command to a remote module, it is forwarded to its destination via a special Message. The textual response is also packed into another Message that is sent back to the source module, which eventually displays it to the user. As with Commands, there are specific Messages for BOS and for the modules. Messages have globally unique IDs that help to identify each one anywhere used in the array. Modules only respond to their own Messages and to BOS Messages. They can, however, forward any Message to any other module regardless of its content.
- Data Streams: You can setup Data Streams to transfer a large amount of data across the array. Streams utilize Direct Memory Access (DMA) mechanisms. Thus, transfer data efficiently without much CPU intervention. Streams form physical connections (pipelines) between array ports and disable any other interaction with these ports (e.g., Commands and Messages). You can setup a Stream to transfer a specific amount of bytes, or to timeout after a specific time. This guarantees that utilized ports go back to their default state when the Stream is no longer needed. You can setup complex single-input-single-output (SISO) or single-input-multiple-output (SIMO) data Streams across the array easily using our APIs, Messages or Commands. Streaming speed will be close to maximum speed allowed by the MCU UART peripherals regardless of Stream complexity. (Streaming speed can reach or exceed 1 Mbps in Hexabitz mainstream implementation with custom configuration.)
- APIs: Application Programming Interfaces (APIs) are functions available to the user to programatically access all BOS and module functionality. They are exported in BOS and module header files. Thus, can be called from the User Task (or from other places) as part of user applications. APIs provide much more flexibility than CLI Commands since they allow standard embedded C-based programming. APIs are only executed locally. If you want to call an API in a remote module, you should send the appropriate Message to that module and it will automatically trigger the execution of the appropriate API once it reaches there.
- Internal Functions: These functions are hidden from the user and are utilized by the module itself (or BOS) for internal use.
- Peripheral Drivers: These are sets of higher-level hardware drivers that sit between low-level drivers (e.g., HAL) on one side, and the BOS and module drivers on another side. Peripheral drivers are usually added to the project in separate files that handle each peripheral (e.g., UART, DMA) and are hidden from users (i.e, not exported).
Routing and Array Topology
The BOS is responsible about routing messages and streams within the array. Routing is based on an internal routing table that defines the number of connected modules, their Hexabitz part number and how they are connected to each other, as well as the polarity of each of the serial array ports. The last one is particularly important for duplex (two-way) communication. As mentioned before, the default state of all communication (array) ports is to have the transmitting pad (TXD) on top and the receiving pad (RXD) on bottom, in order to offer complete modularity and flexibility while assembling modules. For making a bidirectional communication, however, you need the TXD line of first module to connect with the RXD line of second module, and vice-versa. This is possible by programatically swapping one of these port pins whenever the modules are connected, and putting the port back into normal polarity when they are separated.
Routing tables can be generated in two ways:
- Predefined routing tables are created offline, either manually or automatically, and then added to each module project as a topology header file.
- Routing tables are generated online via the BOS explore API/Command. Users can program array modules with standard single-module firmware (called native modules) and then connect them together and power up the array. The explore API/Command can then be used to query all connected modules and figure out array topology, and assign appropriate port polarities. The generated routing table is broadcasted to all modules and stored in their non-volatile memory.
Every time the array boots up, it verifies its actual connections against the stored routing table, so that any topology change can be accounted for.
4. Module Drivers – Developed by Hexabitz
Module drivers provide all module hardware-related functionality, i.e., all the software needed to use and configure module front-end. Similar to BOS, modules have their own APIs, Messages, Streams and Commands. Each software project is usually created for a single module (because it will be loaded on a single MCU). Thus, you should not add another module drivers to the same project. If you have an array of similar modules (e.g., multiple H01R00 modules), you can create a single project and use Targets feature in the software development tool (uVision, STM32CubeIDE) to account for the few variations between these modules (e.g., their IDs, etc.). In this way you are able to generate, with a single click, firmware images for all modules from that same software project.
5. Command Line Interface (CLI) – Developed by Hexabitz
In order to make the platform easily accessible for non-embedded-programmers, Hexabitz features a Command Line Interface (CLI) based on the core CLI functionality of FreeRTOS. CLI is available at all array ports and can be accessed from any computer (or any device!) running a serial terminal tool. There are plenty of serial terminal tools that are free, and require no or minimal drivers installation. We use a free terminal tool called RealTerm but users can use any other similar software as well. All you need to use the CLI is a cable that bridges module UART ports with other common PC communication ports (e.g., USB-UART cable).
Commands sent via CLI are executed instantly and thus there is no need to compile any code. Once the command is sent you cannot go back and change it. It is also difficult to execute complex logic efficiently in the CLI. Still, though, the CLI is a very handy tool to perform quick tests and build small applications using Command Snippets without the need to read or write a single line of code!
6. User Applications
As mentioned before, user applications ideally reside in the User Task. Users can forget about other threads and develop their own application just like they would do on a single-thread platform (with few exceptions). Confining user application to the Front-end Task makes it practically effortless to upgrade any existing project to use the latest release of Hexabitz BOS and module drivers.