Hexabitz Wiki


1.What is Hexabitz? #

Hexabitz is a novel modular electronic prototyping system. Each module or bit comes in a specific shape or size. Either hexagon (hence the name Hexabitz), pentagon, rectangle, square or triangle. Modules are soldered together horizontally to make larger electronic boards or arrays. You can easily combine modules in any shape or configuration you want. The resulting board will be close to custom-made ones in terms of size, weight and form-factor.

Each Hexabitz module has a specific functionality and a small MCU on the back side. MCUs are used to connect modules together in a wired-mesh network and execute various programs in parallel. Think “smart PCBs” where the small increase in cost and power consumption is dwarfed by huge gains in flexibility, scalability and reusability.

What’s Different About Hexabitz?

Here’s a run-down of the most differentiating characteristics of Hexabitz:

  1. Form-Factor: Hexabitz nature-inspired form-factor emphasizes horizontal integration since this is the default mode of construction for custom-made PCBAs and electronics. This makes Hexabitz prototypes more real-life ready than most other platforms.
  2. Dust Computing: Hexabitz does not follow the traditional model of a single motherboard (controller) and ‘dump’ daughter boards that has been around since the advent of micro-computing. We believe computing is cheap and small enough these days that it should be embedded in each PCB. All our modules -except the very simple power sources- feature a small and low-power MCU. Future high-performance implementations will probably feature FPGAs and other logic blocks.
  3. The Wired-Mesh: Standard electrical buses impose topology and capacity limitations that destroy modularity. Hexabitz is based, instead, on a wired-mesh, decentralized network concept. Pioneered by wireless sensor networks, mesh connectivity provides ultimate modularity and a more scalable system. Coupled with Hexabitz nature-inspired form-factor, the wired-mesh enabled us to put a much larger number of modules together (> 5 times more than other platforms) and still keep a small footprint.
  4. Distributed System: A side benefit of 2 and 3 is that you get a truly parallel and distributed system. Hexabitz application development is a little bit different than standard embedded systems: you emphasize connectivity and think about your application more holistically, breaking into separate chunks that run in-parallel on separate modules.
  5. Stand-Alone: Since all configurable Hexabitz modules come with their own MCU, they act as stand-alone development boards that can be used without any external controllers. You can access module functionality by using the Command Line Interface (CLI) and directly connecting to a PC or triggering actions with external buttons / switches. Eliminating the need for external controllers can reduce overall system size and cost in many applications.
  6. Automation-Ready: The current hardware implementation of Hexabitz is the most abstract one, geared more toward production than a plug-n-play solution. On top of this implementation, one can introduce multiple layers of add-ons that customize the platform for different applications (connectors, magnets, attachments, stress-relief mechanisms, etc.) One of the benefits of the abstract implementation is that arrays can be assembled automatically by pick-n-place machines and soldered by robots, providing opportunities for future automated prototyping factories or even a desktop-size prototyping machines.
Yes No
Last updated on June 12, 2019

1.1.Overview of Hexabitz Hardware Architecture #

This primer walks you through the world of Hexabitz hardware. Keep in mind that physical realizations might change, but the core Hexabitz philosophy will always be governed by the following concepts.

1. Modularity is a Fundamental Concept

Hexabitz modules have basic modular geometric shapes (namely, hexagonspentagonssquares and triangles) that when used with specific ratios, combine with each other to form contiguous surfaces. The modularity concept is found everywhere in nature from molecules and crystals to cellular and biological structures and even macro-structures (e.g., bee hives, insects). Why would you need a contiguous surface? Because it is more rigid, reliable and volume-efficient. It is also how most custom-made PCBAs are designed. And the ultimate goal of any prototyping/rapid-manufacturing platform is to mimic custom-made PCBAs as much as possible. Some geometric shapes even construct non-flat surfaces as shown below.

Modularity often translates into symmetricity, a very-tough design constraint. We strive to preserve modularity and symmetricity in all Hexabitz hardware and let these two characteristics guide design decisions. However, sometimes (not so often) we have to break the rules when it is near-impossible (or impractical) to follow them.

Figure 1. Modular Geometric Shapes

2. Form-factor is Important!

There is one basic shortcoming in many advanced electronics prototyping systems is that they are not optimized for form-factor, especially horizontal integration. This means they are not real-life-ready. Custom-made PCBs are horizontal by nature of PCB and semiconductor manufacturing techniques (traditional electroplating and lithography). By contrast, a configurable multi-module system that seeks access to same physical ports requires vertical integration. As a result, most prototyping solutions fail to produce a system that can replace custom-made PCBAs since they have a completely different form-factor.

Hexabitz emphasized horizontal integration from the beginning. This does not mean you can not do vertical integration. You can still mix both to create a complex form-factor. The only difference is that Hexabitz assembled boards (or arrays) are horizontal by nature. This makes them perfect substitute for custom-made PCBAs.

Form-factor has another dimensions to it, including volume and weight. Hexabitz mainstream implementation seeks to optimize weight and volume (and cost) as much as possible. Thus, eliminating connectors, wires and attachment mechanisms in favor of a simple and effective solder-based, exposed-PCB-edge connector system. Other implementations might optimize for different variables and offer different solutions.

Figure 2. Horizontal Integration (Hexabitz) vs. Vertical Integration

3. Dual-end System for Flexibility and Scalability

In order to build a truly scalable solution, one that works for virtually all types of functionality and for an arbitrary number of modules, Hexabitz is based on dual-end module architecture. The front-end is the module part (hardware and software) responsible about its unique functionality. The back-end is the module part handling all background activities including communication, addressing and other array-related functionality. The back-end, often represented by the MCU on module bottom side and the Bitz Operating System (BOS), effectively virtualizes and standardizes the interface to any type of front-end, whether it is analog signals, high-power motor, high-speed FPGA or just plain bits and pixels, they all share same interface to the Hexabitz ecosystem. The back-end resembles infrastructure in computer and communication networks, necessary to scale and standardize but not beneficial on its own. The front-end is the useful part of the process providing the end application.

The dual-end system necessitates using an MCU in every configurable module (i.e., one that can change any of its parameters as opposed to a dump battery module for example.) One might worry about cost or power consumption. The mainstream Hexabitz implementation is built around ARM Cortex-M0 MCUs, the industry-leading devices in efficiency and low-power consumption. These tiny beasts, if optimized well, are powerful enough to carry on all of the back-end (and some front-end) tasks while leaving minimal cost, weight and power consumption overhead. The massive gains in configurability and true parallel execution dwarf any overhead.

Module Anatomy

A typical configurable module (i.e., one that has an MCU) is shown in Figure 3. The top side (left) features front-end components (in this case an RGB LED) and the bottom side features the back-end hardware (i.e., the MCU).

Exposed copper areas on module corners and sides are used for physical and electrical connectivity in Hexabitz mainstream implementation. Corners are always used for power (+3.3V on top and 0V/Ground on bottom) while sides are always used for communication (TXD/transmission on top and RXD/reception on bottom). Communication ports are numbered sequentially (clockwise) starting from P1 in the module upper right corner. Some modules do not have the full set of power and communication ports due to space constraints or conflicts with other connectors. Communication ports allow serial communication (UART) between modules as well as connect the modules to the external world (e.g., cables, other boards or electronic devices). These ports can be re-purposed in many modules for other functionality (e.g., connecting input switches or different communication interfaces). Bootloader-based firmware update is also possible through communication ports. The port marked with (*) can be used with ST factory bootloader in the mainstream implementation, i.e., it allows firmware upload to an empty MCU. All Hexabitz modules back-end circuitry operates on 3.3V from the power ports. Voltages higher than 3.5V should never be applied to power or communication ports!

Another set of exposed, non-edge, copper areas (or pads) are used for non-essential connections such as programming/debugging. The MCU serial-wire debug (SWD) interface can be accessed through pads C (clock) and D (data) for programming and debugging. MCU reset pin is available on pad R and boot0 pin on pad B. These pads are duplicated on both module sides to allow surface connection to either side (or even though-hole connection by drilling the pad). Different Hexabitz implementations might have different set and characteristics of exposed pads.

Almost each Hexabitz module has at least one indicator LED on module top side. The general indicator LED for all modules is red-colored. It is useful to debug module status and relay various visual messages to the user. Some modules have extra indicator LEDs with different colors to convey other states (e.g., input or output power, communication, etc.)

Figure 3. Anatomy of a Configurable Module

Module bottom side features, almost always, module part number while the top side has Hexabitz truncated logo on the silkscreen. Never the less, it is recommended to distinguish sides using power connectors. Top side will always have the positive voltage (3V3) while bottom side will always have the ground (GND). 

Figure 4 shows a non-configurable module. This one provides power to the array and thus features only power ports. Note that some power modules are designed to generate output voltage a bit different from 3.3V (e.g., 3V) . They can still power other modules but with possibly some functionality limitation.

Figure 4. Anatomy of a Non-configurable Module


Some modules might have mounting holes but it is not generally the case with most of them. Another special case happens when there is not enough real-state to fit the module front-end functionality (figure 5), the module will then grow in size by permanently stacking multiple geometric shapes (e.g., two stacked hexagons). This ensures the module still adheres to modularity principle and geometrical properties of Hexabitz arrays and thus can interface easily with any other modules.

Figure 5: Example of a Module with the Footprint of Two Hexagons

The Array

When Hexabitz modules connect to each other, they form arrays. Arrays can have contiguous modules or ones that are kilometers away. They can be flat or curved, horizontally integrated or have a complex 3D form-factor. In theory, there is no limitation to number of modules in an array. In practice, physical limitations such as power drop and MCU memory size make it difficult to build arrays with very large number of modules. It is still possible, however, to connect as many as 100 modules together without any special interfaces. Much more than you can connect with many other platforms and in a much smaller form-factor.

Hexabitz arrays are pure peer-to-peermesh networks. We abandoned the conventional solution of a bus-architecture to remove any constraints related to bus topology or capacity. Hexabitz modules can connect to each other using any communication port to form any physically-possible configuration. The only assembly constraint is to make sure contiguous modules have same orientation (i.e., all facing up or down) to avoid shorting power.

Arrays are also fully distributed and decentralized systems. There is no requirement for a router or switch or main controller and thus no point of failure. The array could function in a completely decentralized way, or act as slave devices for a master module or an external controller. All depending on firmware implementation. Eventually, we hope that most of behind-the-scene work to facilitate this architecture will be carried out by the BOS to free users to focus on the end-application. All configurable Hexabitz modules are valid entry points into the array and are able to receive user commands (e.g., through BOS messages or the Command Line Interface-CLI) and process locally or forward to destination module(s).

One might be worried about latency in a peer-to-peer architecture. It is a valid concern. However, in practice, about 90% of typical applications will not suffer any latency limitation. First of all, inter-module communication is based on an optimized mix of high-speed hardware UART peripherals and direct memory access (DMAchannels. The combination of these two technologies offers a streamlined way of transferring data across the array without requiring much CPU intervention. As a result, modules can bypass communication messages with virtually no delay or CPU down-time. Hexabitz mainstream implementation can stream data across the array in speeds that can reach 1 Mbps or higher in specific configurations. Although the number might seem low for streaming applications, it is a relatively high datarate for control networks, which brings us to the next point in this discussion: Hexabitz back-end is optimized to stream control messages and not for bulk data transfers. By definition, a bunch of in-homogeneous modules (e.g., an LED, a motor drive, a BLE radio and a temperature sensor) have few things in common and will mostly need to exchange control messages occasionally. Bulk data transfers can be facilitated with other methods that break the modularity principle. Given said that, the mainstream implementation is not the only possible way to work with Hexabitz as explained in the next section. One can envision a custom implementation utilizing much faster MCUs (and even FPGAs) in the back-end if the application calls for this requirement.

Custom Implementations

Hexabitz is based on the assumption that custom electronics will always be more expensive for low quantities, take more time to make and serve only a single purpose. Although the concept itself is flexible enough to cover so many applications, one still needs to make few customized decisions regarding physical implementation. We designed Hexabitz mainstream implementation to cover the requirements of majority of anticipated real-life applications. These requirements include among others:

  • Full flexibility and upgradability.
  • Optimized form-factor (volume and weight) to mimic custom PCBs as much as possible.
  • Cost competitive solution to enable low-volume production.
  • A compromise between array stiffness / reliability and assembly time.
  • Robustness and reusability rate of at least 10 times during product lifetime.

Some applications, by nature, have different requirements. For example, a K12-kid-friendly application will need to replace soldering with plug-n-play connectors; a delay-intolerant application might require much faster (and more expensive) hardware for the back-end; and a safety-critical application will require even different types of components all together. Some customizations might preserve components but use different solder mask color or larger (or smaller) module size or even replace the PCB with an FPC (flexible printed circuit). Still, they can all adhere to same basic principles listed above. Initially, we will not target these special implementations and will focus only on the mainstream implementation (unless you have enough budget to pay for a custom set!). As time progresses, however, scale economics and network effects will make it economically feasible to offer more customized implementations.

Yes No
Last updated on June 12, 2019

1.2.Overview of Hexabitz Software Architecture #


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. I’m 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 posts.

In order to support a wide array of users, Hexabitz is designed for three developer levels/personalities:

  1. 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!
  2. 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).
  3. 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 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 over-simplified 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.


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 a 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 identify each one anywhere it is 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 and 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 and 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 particular important for duplex (two-way) communication. As mentioned before, the default state of all communication (array) ports is to have the transmit pad (TXD) on top and the receive pad (RXD) on bottom in order to offer complete modularity and flexibility in assembling modules. For bidirectional communication to take place, 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) and 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) to account for the few variations between these modules (e.g., their IDs, etc.) 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.

Yes No
Last updated on August 10, 2019

1.3.Module Naming (Part Numbers) Convention #

Hexabitz modules’ part numbers follow this naming convention:

HxxRyz (-E)


  • H: is module shape prefix. Available shapes are: H (hexagon), P (pentagon), T(triangle) and S (square). Note that some modules might repeat the same shape to gain more surface area, e.g., two hexagons or two squares permanently connected to each other. They still get a single prefix from the list above.
  • xx: is the main functionality (front-end) code, e.g., 01 for LEDs. This number is hexadecimal to support 256 main functionalities.
  • R: is a separator that separates main functionality and sub-functionality codes.
  • y: is the sub-functionality (front-end) code. This number is hexadecimal as well and thus the number of total functionalities supported is 16 x 256 = 4096.
  • z: is module hardware revision number. Older revisions will be depreciated.
  • -E: This is an optional extension used in some part numbers to define a deviation from standard hardware specifications (e.g., flex PCB or non-green silkscreen or different connectors).
Yes No
Last updated on June 16, 2019

1.4.Learn About Factsheets a.k.a. Documentation #

Factsheets are Hexabitz’s take on documentation! They’re a mix between a hardware datasheet and a software cheatsheet. They should get you up to speed on any module and provide quick access to the most needed information, e.g., major component part numbers, module APIs and Commands, etc. The software section features some simple examples as well to provide quick help with the syntax. Factsheets are designed to be printed double-sided and kept in nylon sheet covers so that they’re easily accessible when you need them.


Each module factsheet should generally contain the following information:

  • Module name (part number) and short description.
  • Technical specifications for major module components including available array ports, connectors, etc.
  • Module general classification (color-coded category). Please check below.
  • Factsheet release date.
  • If a module is programmable, the factsheet contains the following software parts as well:
    • Module CLI Commands. Note that BOS Commands are not included here rather in a separate factsheet dedicated for BOS. (Same thing also for BOS Messages and APIs.)
    • Module Messages and Message Codes.
    • Module APIs.
    • Software release version that corresponds to this factsheet version.

Factsheets can be downloaded from this Wiki or from each module website page..

Module Categories

Hexabitz modules are usually organized in the following categories. The factsheets are color-coded according to these categories to help you classify them quickly:

Yes No
Last updated on August 10, 2019

1.5.Dev Tools #

Software Development Tools

Most development tools used in Hexabitz are free and/or open-source to make it easily accessible by everyone. Here’s a list of tools that might be useful:

  • uVision: uVision is the industry-standard embedded software development tool for ARM devices. It is usually a pretty expensive tool and you are only allowed 32KB code size in the evaluation version. We are lucky, however, that ST and Keil (uVision developer, a subsidiary of ARM) decided to provide a free license for STM32F0 MCUs! (the ones used in Hexabitz mainstream implementation.) Click on the link in the top right corner in this page to claim your free license.
  • RealTerm: RealTerm is a handy (and free) serial and TCP terminal software that can be used to access Hexabitz CLI.
  • FLASHER-STM32: The STM32 Flash loader demonstrator is a free software PC utility from ST, which runs on PCs and communicates through the RS232 (serial port) with STM32 system memory bootloader (ST factory bootloader). It can be used to program Hexabitz modules as explained here.
  • STM32 ST-LINK Utility: The ST-LINK utility is a free, full-featured software interface for programming STM32 MCUs via ST-LINK programmer connected to JTAG/SWD ports. It can be used to program Hexabitz modules as explained here.

Hardware Development Tools

  • Circuit Maker: Circuit Maker is a cloud-based electrical CAD software available for free from Altium. We have designed our first 20+ modules on Circuit Maker and published most of them on our account there.
  • EAGLE: EAGLE is a famous electrical CAD software now owned by Autodesk. There is a free license with some limitations in board size and layer count but it should work with most of Hexabitz modules. We moved into EAGLE as our official electrical CAD software for module design. You can download our EAGLE design files from our Github account.
Yes No
Last updated on August 27, 2019

1.6.Where to find information? - TBD #

1.7.License & Disclaimer #


All Hexabitz software and hardware is released with MIT license. This means you are free to use our software in your own projects/products for personal or commercial applications. You are not required to open-source your projects/products as a result of using Hexabitz code inside it.

To our best knowledge, all third-party components currently included with Hexabitz software follow similar licenses (MIT, modified GPL, etc.). We will do our best to not include third-party components that require licensing or have restricted open-source terms (i.e., forcing you to open-source your project). There is no guarantee, however, that this does not happen. If we ever include a software component that requires buying a license or one that forces restrictive, open-source terms, we will mention this clearly. We advise you to verify the license of each third-party component with its vendor.



Yes No
Last updated on June 16, 2019

2.How-To #

A series of articles that guide you through using Hexabitz hardware and software.

Yes No
Last updated on June 17, 2019

2.1.Update module firmware #

There are currently two ways to load and update firmware on Hexabitz modules. Both methods work with individual modules only. You cannot currently perform mass update for the entire array at once.

1. Using ST Factory Bootloader and Array Ports

The MCU used in Hexabitz mainstream implementation comes from its manufacturer (STMicro) pre-loaded with a small application called factory bootloader. This application allows loading a new firmware to the MCU via one of the UART interfaces connected to array ports. The module port that enables bootloader-based update is marked with (*) . For this process, all you need is a UART-USB converter cable and a special application from ST called Flash Loader Demonstrator.
In general, when the STM32F0-based module is virgin (i.e., it has no firmware at all) it boots automatically in factory bootloader mode and you can upload firmware right away. However, when the module is already running a firmware, you need to force it to boot into the factory bootloader. You can do that with either one of these two methods:

  • Using the update CLI Command. Once you execute this command, the MCU will leave the running application and jump to factory bootloader. If you are already connected to the programming port, you will need to close this port in RealTerm (or any other terminal emulator) before you can use it to connect to the factory bootloader.
  • Applying 3.3V on module B (Boot0) pad while power cycling. Easiest way to achieve this is to connect a female-to-male jumper wire to one of power ports (edge ports) top connector, and then touch the B pad while power cycling the MCU.

Once the MCU is in factory bootloader mode, connect the USB-UART TXD/RXD (yellow/orange) wires to the appropriate port and run the Flash Loader Demonstrator. Select the appropriate COM port and leave other connection settings to default then click next. If everything is working correctly, you will see a green traffic light with “Target is Readable” message. Otherwise, if the module did not boot in bootloader mode correctly, you will get a red traffic light. Just power cycle and repeat the process again. Click next to select the default MCU Flash algorithm and another click to reach the programming tab. Choose “Download to Device” option and select the module firmware (it should be a HEX file). You can specify a full memory erase or only necessary pages. You can also enable verification. The “Jump to user program” option automatically runs the application after it has been downloaded. Otherwise, you will need to power cycle to run the application.

This article explains how to perform remote bootloader update, i.e., updating a module while connecting to another module.

Note 1: If you are not powering the module from the same USB-UART cable (or another cable connected to your PC), then you need to connect module GND and the PC GND together. Just connect the GND wire on the USB-UART cable to a module GND pad.

Note 2: If you compile your code with uVision, it does not generate a HEX file by default. You need to enable that in Options for Target >> Output Tab.



2. Using the Serial Wire Debug Interface

The Serial Wire Debug (SWD) interface enables fast firmware update as well as real-time debugging. You will need an STM32-compatible programmer/debugger (e.g., ST-LINK V2 or ST-LINK V3 or the Nucleo board) and a programming software tool (e.g., ST-LINK Utility or STM32CubeProg or via KEIL uVision IDE). Three module pads are needed for connection (plus GND): C (SWD Clock), D (SWD Data) and R (MCU Reset).

You will need to make the following connections between the programmer and the module (example provided for Nucleo programming header CN4):

  • Pin 2 (starting up) >> C.
  • Pin 4 >> D.
  • Pin 5 >> R.
  • You need to connect GND between ST-LINK and the module. If your module is powered from a USB-UART cable connected to the same laptop that you connect the ST-LINK to, then, they both have a common ground and you might not need a separate GND connection.

Note 1: Some ST-LINK programmers require target VCC to be connected as well. In this case, you need to connect one of the module power (top) pads as well.

Note 2: The ST-LINK Utility loads binary and HEX files while uVision IDE loads AXF files. You can use ARM fromelf.exe tool to convert AXF files in binary ones.

Yes No
Last updated on August 10, 2019

2.1.1.Remote Bootloader Update #

Sometimes you don’t have access to module programming port when it is embedded in the middle of the array and you don’t want to use the SWD pins; Or sometimes you design your array so that programming ports are connected to other ports; Sometimes you’re just feeling lazy and don’t want to connect to each module to update its firmware! For all these cases, we added a feature to remotely update a module via another one using ST factory bootloader and the ST Flash Loader Demonstrator tool. Check this article for a review of all firmware update methods in Hexabitz.


The same command used for normal bootloader update is used for remote update. There are two basic forms:


where you remotely update module 3 via another module. Note in this case module programming port (marked with asterisk) must be in the shortest path to the module. Example of this is updating module 2 in the array below while connecting to module 1, port 1.

The other format (via-port remote update) is:

update via #3 p5

where you remotely update the module that has its programming port connected to module 3, port 5. This format is useful in two situations:

  • If the module you want to program is not part of the array topology (i.e., it doesn’t have an ID and topology). Similar case happens when the module doesn’t have a firmware)
  • If the module programming port is not on the shortest path from where you are sending the command. Example of this is updating module 3 or 5 via module 1 in the array below. Programming ports are marked with red circles. It can be seen that shortest path from module 1 to module 3 (the yellow route) ends up in port 3 of module 3 which is not the programming (factory bootloader) port. In order to reach port 2 of module 3, you can use the command (the orange route):
    update via #4 p3

Note: After remote updates, you need to power cycle the entire array to restore normal operation.

How does it work?

The remote update command backend stacks up serial ports and DMA streams to construct a bidirectional pipeline between the port connected to ST Flash Loader tool and the programming port of target MCU (which will be running ST factory bootloader through that port). However, few things must happen first, in the correct order, before a successful connection:

  1. The source module sends a message to target module asking it to jump to its bootloader. In the remote update format #n.update, n is the target module. In the remote via-port update format update via #n pm, the target module is the one connected to module n, port m. Note that if you want to update firmware on an empty module (or one that doesn’t run Hexabitz firmware) and the module is not in its bootloader mode, you must manually force it into that mode.
  2. Baudrate for all involved serial ports must change to 57600 to be compatible with ST bootloader settings.
  3. Parity for all involved serial ports must change to EVEN to be compatible with ST bootloader settings.
  4. The last port before the target must swap its pins since the serial port in ST bootloader will restore its normal pinout configuration.
  5. Finally, a bidirectional DMA stream must be established. The stream is setup with infinite timeout and number of bytes. Thus, once you’re done with the update, you must power cycle the array to restore everything to normal operation.


The video below shows a step-by-step guide of remote updating module 2 in the array above from module 1, port 1.


Yes No
Last updated on August 10, 2019

2.2.Use the command line interface (CLI) #


Use your favorite serial terminal emulator tool to access the serial COM port of your computer, and thus, Hexabitz CLI via a USB-to-UART cable. Some examples include RealTermPuttyHyperTerminal and SecureCRT. The configurations displayed here are for RealTerm but they are similar to those in other terminal emulators.

If you use standard FTDI USB-UART 3.3V cable for power and communication, connect the colored cable wires as follows:

  • Red (VCC) to 3.3V (top power pad, i.e., corner edge pad).
  • Black (GND) to GND (bottom power pad, i.e., corner edge pad).
  • Yellow (RXD) to MCU TXD (top communication pad, i.e., side edge pad).
  • Orange (TXD) to MCU RXD (bottom communication pad, i.e., side edge pad).



RealTerm Configurations

In RealTerm Display tab, select Ansi as data display mode. This mode allows you to use the BACKSPACE key to delete previous characters. Optionally, enable Scrollback and increase number of Rows to a suitable number (e.g., 32).
In the Port tab, select 921600 baudrate and open the appropriate COM port. The port will only show up after you power the module and usually it will have \VCP (virtual COM port) in its name.


Once you open the port, press ENTER keyboard key to start the communication session. You should see the CLI welcome message shown below. It tells you the ID of the module you are connected to and through which array port. Note if the module is native, i.e., not part of an array via a fixed or explored topology, it will show up as ID = 0 (unless you change default ID in the code).



Setting up Baudrate

Default baudrate for all array ports is 921600. You can change this rate with the following methods:

  1. Connect P1 TXD and RXD together momentarily while power-cycling the module. This will setup all array ports to 115200. Once you connect to a CLI port, other messaging ports restore their default baudrate. Note that the CLI will restore its default baudrate on the next power cycle.
  2. Change the value of BOS.clibaudrate parameter in the CLI using set Command. This will save the baudrate value to the emulated EEPROM so that you can use the new baudrate each time. You need to reset the module to apply the new baudrate. Note that similar to method 1, the new baudrate will apply to all ports until you connect to the CLI.
  3. Change the value of BOS.clibaudrate parameter in the code and call this API UpdateBaudrate(port, baudrate) to apply the new baudrate to a given port. You can also save it to emulated EEPROM using this code (It will be loaded automatically from EEPROM on each startup):
EE_WriteVariable(VirtAddVarTab[_EE_CLIBaud], (uint16_t)BOS.clibaudrate);
EE_WriteVariable(VirtAddVarTab[_EE_CLIBaud+1], (uint16_t)(BOS.clibaudrate>>16));

If you want to restore the default CLI baudrate, you can either:

  • Set BOS.clibaudrate to default value either in CLI or in code as shown above.
  • Initiate a Factory Reset by connecting P1 TXD to programming port RXD or to last port RXD while power-cycling the module.
  • Set all parameters back to default using default params CLI command or using the following code:
memcpy(&BOS, &BOS_default, sizeof(BOS_default));

General Usage Tips

  • Type help anytime to view the list of commands enabled in this module (and this firmware). You can figure out the firmware version and compile time and date using the status Command.
  • If you misspell a command you can use the BACKSPACE keyboard key to delete last characters and replace them with correct ones as long as you have not pressed ENTER yet. BACKSPACE does not actually work in the terminal window as in a text editor but it will still work fine with the CLI. You cannot clear a character from the terminal window for example. When you press BACKSPACE, the blinking cursor will move back one step but the previous character will stay displayed. It will be removed, however, from CLI buffer. If you write a new character, it will replace the old one on the terminal window and it will be added to the CLI buffer.
  • If you misspell a command and press ENTER, it will be ignored and you will get an error message “Command not recognized”. This will happen, as well, if you type in the command with fewer parameters than expected. If you type more than the required parameters, then extra parameters will be ignored. Command parameters are separated by at least one white-space (SPACE key) and they will be parsed according to their order.
  • If you type in multiple white-spaces between parameters they will be parsed correctly. However, maximum number of characters in each command (i.e., line) should not exceed 49 (it’s adjustable in the code).
  • If you hit ENTER without writing anything, the last command will be repeated.
  • All CLI commands and parameters are case insensitive. You can write in lower-case, upper-case or mixed-case and they will recognized.
  • Each module is referred to by its ID prefixed with # (e.g. #7) or by its alias name.
  • The general format for a command is:
module.command parameter1 parameter2 parameter3

where module is module ID or name. Some examples:

#0.on 100
  • If you are typing local commands, i.e., BOS and module commands to be executed locally, you can remove the module ID/name prefix and type in the command directly, or replace module ID/name with the keyword me:
  • If you are broadcasting a command, replace module ID/name with the keyword all:
  • Module ID cannot be modified in the CLI. It is hard-coded inside the code through preprocessor directives. When exploring an array, all native modules will be assigned sequential IDs according to their distance to the module where the exploration was initiated (this module will get ID 1). Module alias name can be used anywhere a module ID is used. If you rename a module again, the new name replaces the old one. Once you rename a module, its alias name will show up between parenthesis when you ping it:
>name john
Module 0 is named john

[Press ENTER to execute the previous command again]
>name bob
Module 0 is named bob

[Press ENTER to execute the previous command again] 
Hi from module 0 (bob)

[Press ENTER to execute the previous command again]
Hi from module 0 (bob)

[Press ENTER to execute the previous command again]
  • Module alias name is stored in emulated EEPROM and loaded back on each power cycle. It’s not currently shared, however, with other modules in the array.
Yes No
Last updated on August 10, 2019

2.3.Power your modules - TBD #

2.4.Debug module C code - TBD #

2.5.Make a pre-built array topology file #

Inter-array communication in Hexabitz is done using a routing table stored in a special header (.h) file. This header file describes the number of modules and how they are connected to each other as well as other important information for the array, hence, it is called a topology header file. Currently, you need to make a topology file manually (by modifying an existing one) or ask the array generate it dynamically with the explore command and API. Later, we might provide a software tool to generate topology files especially for complex arrays. It is still a good practice, though, to go through creating a topology file for a simple array to help you understand the process.

An example topology header file is available here. It represents the array below.

Topology Test Array


What is inside a topology header file?

Let’s look in depth into a topology header file for 8-module array.

  • File name and version comment block: Modify to identify your file. It has no effect on operation.
    BitzOS (BOS) V0.0.0 - Copyright (C) 2017 Hexabitz
    All rights reserved

    File Name     : topology_1.h
    Description   : Array topology definition.
  • Prevent recursive inclusion of this file: Replace your file name after #ifndef and #define.
/* Define to prevent recursive inclusion -------------------------------------*/
#ifndef __topology_1_H
#define __topology_1_H
#ifdef __cplusplus
 extern "C" {
  • Define number of modules in the array (only programmable modules).
#define _N    8                    // Number of array modules
  • Define module IDs: Add one line for each programmable module in the array. IDs must be sequential.
// Array modules
#define _mod1	1<<3
#define _mod2	2<<3
#define _mod3	3<<3
#define _mod4	4<<3
#define _mod5	5<<3
#define _mod6	6<<3
#define _mod7	7<<3
#define _mod8	8<<3
  • Array topology (routing table): Modify based on your array shape and configuration. Each line for a single module. First value is module PN (note the underscore in the beginning). The next six values represent the six array ports of a hexagon. When a value is 0, the module port is not connected. A value of _modx|Py means this port is connected to port Py of module x. For example, last value in module 1 line means that P6 of module 1 is connected to P4 of module 3.
// Topology
static uint16_t array[_N][7] = {
{ _H01R0, 0, 0, 0, _mod2|P5, 0, _mod3|P4},			// Module 1
{ _H01R0, 0, 0, _mod4|P6, 0, _mod1|P4, 0},			// Module 2
{ _H01R0, 0, 0, 0, _mod1|P6, 0, _mod5|P3},			// Module 3
{ _H01R0, 0, 0, _mod6|P6, _mod7|P6, 0, _mod2|P3},		// Module 4
{ _H01R0, 0, 0, _mod3|P6, 0, _mod7|P4, _mod8|P3},		// Module 5
{ _H01R0, 0, 0, 0, 0, _mod7|P1, _mod4|P3},			// Module 6
{ _H01R0, _mod6|P5, 0, _mod8|P4, _mod5|P5, 0, _mod4|P4},	// Module 7
{ _H01R0, 0, 0, _mod5|P6, _mod7|P3, 0, 0}			// Module 8
  • Port directions for duplex communication: As mentioned in the hardware architecture here, array ports’ default configuration is TXD on top and RXD on bottom to allow full modularity. Ports cannot be connected, however, before swapping the polarity on one of them (since the TXD of one port should connect to the RXD of the other one and vice versa) unless single-line, half duplex communication is desired. You should add a similar section to define port swap status for each programmable module. The first line defines module PN (note the absence of underscore here). Subsequent lines define port polarity. normal is the default configuration (TXD top and RXD bottom) while reversed is the other way (RXD top and TXD bottom). Note that only one of the two connected ports must be reversed. It’s also preferable to leave all the unconnected ports to normal configuration. Usually, I start with module 1 by declaring all its ports normal, and then proceed to its neighbors declaring upstream ports normal and downstream ones reversed.
// Configurations for duplex serial ports
#if ( _module == 1 )
	#define	H01R0	1
	#define	_P1pol_normal	1
	#define	_P2pol_normal	1
	#define	_P3pol_normal	1
	#define	_P4pol_normal	1	
	#define	_P5pol_normal	1
	#define	_P6pol_normal	1
#if ( _module == 2 )
	#define	H01R0	1
	#define	_P1pol_normal	1
	#define	_P2pol_normal	1
	#define	_P3pol_normal	1
	#define	_P4pol_normal	1	
	#define	_P5pol_reversed	1
	#define	_P6pol_normal	1

How to enable/disable a topology file?

You can add the topology header file to your project by un-commenting its include directive in BOS.h and replacing it with the file name:

/* Array topology */
#include <topology_1.h> 

Modules can then be added to the same project using uVision Targets feature. Click on Manage Project Items >> New (insert) Project Targets and name them with module IDs to identify them. You can choose the current target from the Select Target drop-down menu. One important step after that is to go to each target and modify the following:

  • Options for Target >> C/C++ >> Define Preprocessor Symbols: Modify H01R0 to module PN and _module=1 to module ID.
  • Options for Target >> Output >> Name of Executable: Modify to match module ID.

Once done, you can click on Batch Build and select all modules then Rebuild them all. We suggest to always do batch rebuild (and not build) before loading the code to make sure you don’t load an old one.

Yes No
Last updated on August 10, 2019

2.6.Connect Hexabitz to external hardware - TBD #

2.7.H07R3x Audio Speaker and Headphone Jack Module #

2.7.1.Embed and play WAVE files #

You can use the module MCU Flash memory to store few short WAVE sound files. The available space is too small so you won’t be able to store more than few seconds. However, for simple applications, it saves the need to connect external storage like a uSD card module.

1. Generate your speech WAVE files

First step is to get some good.WAVE files for the speech or words you need in your project. You can, of course, record your own or somebody’s else voice and you can search for the files online and download them. However, to meet your exact needs, you’ll probably need a text-to-speech service. There are many options online, from free websites with clunky voices to Google and IBM ultra-realistic, deep learning text-to-speech engines. Here’re are some good free options:

  • https://www.text2speech.org/. Download WAVE files directly. Medium quality.
  • IBM’s Watson Text to Speech demo.  Note that you can type something in a the demo and download an MP3 file for free (and without registration). Just use any MP3 to WAVE converter to convert the files.

I used the IBM’s engine to generate four WAVE files for the words: “Learn”, “Play”, “Correct” and “Wrong”. You can download them in the file section of this project.

2. Convert the WAVE files into embedded C code

You can use this nice free software WAVToCode to convert WAVE files into a C file. Check their help page for instructions on using the software.

The DAC in our module accepts only 8 or 12 bit samples but this software generates 8, 16 and 24. Load each file to the software, choose “8 bit” for No. of bits per channel option and click Mix. Then use your mouse buttons to add two markers before and after the wave to remove empty sections. From the menu Tools you can listen to your mixed wave and then convert it into a C file from the menu File. Choose Unsigned for the output. The original C files generated from this converter are available in the file section marked with “orig_”.

Note: If the wave sound volume in the module is low, you need to set the autoscale option to Normalize.

3. Modify your wave C files to include in the module project

You need to perform some minor edits on the the wave C files before you can use them in the speaker module project:

  • Replace unsigned char with const uint8_t.
  • Remove #define NUM_ELEMENTS line and add the definition #include "wave.h" instead.
  • Replace the array data[NUM_ELEMENTS] with a meaningful definition, e.g., waveByteCode_Correct[WAVEBYTECODE_CORRECT_LENGTH]
4. Clone an empty H07R30 project and add the wave C files

Clone the module project from its repository here.  Inside the folder User,   delete all the C files (except main.c) and add your wave C files there. Then open the uVision project and add the modified wave C files to the User virtual folder in the project tree.

5. Add wave definitions to the project

In wave.h:

  • Update number of wave files.
  • Replace wave length definitions with those for your wave files.
  • Replace wave resolution (bits per sample) definitions with those for your wave files (usually 8).
  • Replace wave rate (sample per second) definitions with those for your wave files (file bitrate / 8).
  • Export the wave arrays defined in main.c

In main.c:

  • Define the wave arrays, their length and resolution arrays.
  • Add textual description for each wave.

Then compile and make sure there’s enough Flash in the MCU to store your all your waves! The current module with 128KB Flash can store about 42000 samples max (8 bit/sample).

Note 1: To find out current Flash size for your program, sum the numbers shown for Code and RO-data when you click Compile All. You can also double click Module 1 in project tree to view the memory map. Scroll down near the end and you will find Total RO Size which should be < 128K for the current module.

Note 2: You can increase optimization level for the compiler (if it’s not already O1 or higher) to get more Flash space to store your waves. From uVision Options for Target >> C/C++ >> Optimization, pick Level 1 (O1) and recompile, this should save you about 10-15k of Flash. Sometimes the program won’t compile or load if its near Flash limits so you must increase optimization level.

6. Finally use your embedded waves!

Use the following APIs to play your waves:

PlayWave("play", 1, 0);

where you define wave string name, number of repeats and a delay in ms between repeats.

Yes No
Last updated on June 18, 2019

2.8.H23R1x - Bluetooth V4.0 Dual Mode Module #

2.8.1.Update smartBASIC scripts on the BT900 module #

Using UwTerminalX and an Array Port

H23R10 module is based on Laird BT900 dual-mode Bluetooth/BLE V4 module which runs special scripts called smartBASIC. These scripts control the BT900 module internal operations.  Check these documents for more details about this scripting language (smartBasic core and extensions).

Here’s a step-by-step guide to update smartBASIC scripts on H23R10 modules:

  1. Load the latest firmware on H23R10 module. Check this article for firmware update instructions.
  2. Connect to H23R10 module CLI from any array port and delete the current smartBasic script by typing the following command:

    And then reboot the H23R10 module. You should see a solid green LED if the script was deleted successfully.

  3. If you already have a pre-compiled smartBasic script (binary .uwc file), skip to step 4. If not, navigate to this website to compile it online. Choose the appropriate smartBasic source code (.sb file). Select BT900 device from the list and as firmware version. Then click XCompile. The webpage generates and downloads a .uwc file, which is the binary smartBasic script.
  4. Download the UwTerminalX tool from Laird website. Click Accept if you get the help screen. Setup both Config and Terminal tabs as shown in the picture below. Make sure you set Baudrate to 115200, Handshaking to None and uncheck all RTS, DTR, BREAK, LoalEcho and LinMode options.
  5. Using the CLI and your favorite terminal software, connect to H23R10 module again (using any array port) and type in the following command
    bt-download-script uart                                                        
  6. Close this port from your terminal software.
  7. In UwTerminalX, click OK on Config tab, it will switch to Terminal tab and open the port. Right click and choose Load and choose your compiled smartBasic script (.uwc file). The file will be sent to the BT900 script. There’s 30 seconds timeout between entering the bt-download-script CLI command and sending the script file via UwTerminalX.
  8. Reboot the H23R10 module again and you should see a bright green LED blink and then the LED turns off meaning a smartBasic script is running successfully.

Updating Over-the-Air (OTA)


Yes No
Last updated on August 10, 2019

3.Code Overview #

This series of articles takes you into an in-depth overview of Hexabitz code.

Yes No
Last updated on June 12, 2019

3.1.Project Structure #

Hexabitz software is released as a compiled HEX file and a uVision project along with source files for each module. The project folder contains the following sub-folders:

  • BOS: Contains source files for the BOS.
  • H01R00 (or other PN): Contains source files for this module.
  • MDK-ARM: Contains uVision project files as well as output files (assembler output, linker output, compiler output, etc.)
  • Thirdparty: Contains source files for other libraries used in the project (e.g., HAL, FreeRTOS, etc.)
  • User: Contains user-modified files used for this particular array/project. This folder includes main.c that contains the User Task, the topology header files and the project header file project.h.

Updating to Latest Release

It is always recommended to update your projects to latest software release to get new features and bug fixes. Check release notes here for details on each release. To update your project to latest release, simply keep the user folder and replace all other folders with those in the module repository. To maintain this level of portability, you should try to keep all your custom code in main.c and project.h files.

Yes No
Last updated on June 17, 2019

3.2.Array Messaging #

Messaging Protocol

Messaging in the array is carried out via short unacknowledged packets in the following format (each field is a single byte):

| Length || Destination | Source | Code | Code | Par. 1 | Par. 2 | ------- | CRC ||
  • The Length byte is sent first. After that, the message is sent. Message length includes all message bytes including the CRC (without the length byte itself).
  • Maximum message length is 50, i.e., maximum number of parameters per message is 45 bytes.
  • Message length cannot be 0x0D = 13. If message length is 13, i.e., parameters are 8 bytes, then the message is padded with extra zero parameter to make the length 14.
  • Source and destination are IDs of source and destination modules.
  • If destination is 255 = 0xFF, the message is a broadcast.
  • If destination is 254 = 0xFE, the message is a multi-cast (going to a group).
  • If destination is 0, the message is sent to an adjacent neighbor only.
  • CODE is a 16-bit variable with the following format:
    • 16th bit (MSB): a flag for long messages. If 1, then message parameters continue in the next message.
    • 15th bit: a flag to enable/disable message-only response.
    • 14th bit: a flag to enable/disable CLI-only response. (note that to enable this bit, bit 15 must be enabled as well).
    • 13th bit: a flag to enable TRACE mode (visually trace message route).
    • Bits 1-12: Message codes. Maximum number of message codes is 212 – 1 = 4095.
  • Long messages can be broken down into maximum of 20 messages, i.e., 900 bytes of parameters. Any payload larger than 900 bytes will have to setup a DMA stream.
  • The CRC byte is not implemented yet. Right now it has a fixed value of 0x75. It will be replaced later with CRC8 of the message.

Messaging Workflow

  1. Source module builds a message in the form shown above.
  2. Source sends one byte representing the message length and waits for 1.5 msec.
  3. Destination receives the length byte (in the UART_RxCpltCallback) and activates a port-to-memory DMA stream to transfer the same amount of bytes requested. Once finished, receive interrupt is activated on this port again.
  4. After destination receives the last termination byte (0x75 or CRC), it executes UART_RxCpltCallback again and notifies the appropriate messaging task.
  5. Messages are parsed to read source, destination and code bytes. Received message length is checked againts the Length byte.
  6. If the message is a transit message, it’ll be forwarded directly. If it’s a broadcast message, it will be broadcasted and then processed.
  7. Messages are processed according to their message codes. After that buffers are cleared and receive interrupt is activated on this port again.
  8. Message response and TRACE flags are verified to generate the appropriate response.
  9. If the message is long, the longMessage flag is activated and can be used to concatenate consecutive payloads before processing them.


You can send Messages through the array using the following APIs. The first API sends a Message out a given port to the adjacent module. It is useful when you do not know your neighbor’s ID or you simply want to send something across all or some ports:

BOS_Status SendMessageFromPort(uint8_t port, uint8_t src, uint8_t dst, uint16_t code, uint16_t numberOfParams)

where port is the output port, src and dst are source and destination module IDs, code is the Message code and numberOfParams is number of Message parameters. If the Message is originating from this module, you can use src=myID. The next API sends a Message to a another module (whether adjacent or not) :

BOS_Status SendMessageToModule(uint8_t dst, uint16_t code, uint16_t numberOfParams)

where dst is the destination module (source is always the current module), code is the Message code and numberOfParams is number of Message parameters. SendMessageToModule() API basically calculates the optimal route to destination and which port leads to that route and then calls SendMessageFromPort() to send the Message from that port. Using 0xFF or BOS_BROADCAST for destination, broadcasts the Message to the entire array. You can control response settings of a unicast or broadcast Message via the BOS.response parameter:

  • BOS_RESPONSE_ALL: Enable response on all Messages.
  • BOS_RESPONSE_MSG: Disable response on remote CLI Commands and enable on other Messages.
  • BOS_RESPONSE_NONE: Disable response on any Message.

Before you call one of the APIs above to send a Message with parameters, you need to fill out the parameters array first:

messageParams[0] =  (uint8_t)(600000>>8); messageParams[1] = 2; messageParams[2] = FORWARD;etc..

Array topology (routing table) is saved in the two-dimensional array variable array, which will be either provided in the topology header file or generated by Explore()API. Learn more about topology header files here. The FindRoute() API utilizes Dijkstra‘s shortest path algorithm to calculate the optimal route between two modules in the array based on the pre-populated routing table:

uint8_t FindRoute(uint8_t sourceID, uint8_t desID)

To avoid sending redundant routing information with each Message, this API returns the module port (P1 .. Px) closest to destination module. The Message is sent through this port to the next module in the chain which runs the FindRoute() API again and forwards the Message until it reaches its destination. As mentioned above, you can call the SendMessageToModule() API and it will take care of the entire process.

Yes No
Last updated on June 17, 2019

3.3.Array Exploration #

Array exploration is a feature that lets you automatically configure your Hexabitz arrays and generate their topology without a sweat. It is particularly useful for large and complex arrays where manually generating the topology is usually cumbersome and error-prone.

Once you assemble your array and program all modules with native firmware, you can invoke the array exploration algorithm via the Explore() API or explore CLI command. The algorithm finds out number of connected modules (N), the topology routing table (stored in array matrix) and which ports are swapped. All of these information are stored either in the emulated EEPROM or a special Flash page so that you don’t have to explore again every time you reboot the array. Note that the module you’re connected to when invoking the exploration algorithm will be assigned ID #1. Other modules will be assigned sequential IDs based on their relative distance to the master (module #1).

Exploration Algorithm

Here’s a detailed step-by-step description of what happens in the exploration algorithm:

  1. The master module (ID #1) swaps all its ports and explores adjacent neighbors by sending them an inquiry message. Neighbors send back a response mentioning which port they heard the inquiry from.
  2. The master then starts assigning IDs to new modules and updating its topology array:
    1. Assign sequential IDs to neighboring modules (according to master ports they’re connected to).
    2. Update the master topology array with IDs and ports of neighboring modules.
    3. Ask neighbors to update their own topology array.
  3. The master asks each new module to explore its neighbors, basically repeating steps 1 & 2. This continues level by level until all modules are explored:
    1. Master asks a given module to swap its ports
    2. Master asks the module to explore its adjacent neighbors.
    3. Master assigns IDs to this module adjacent neighbors.
    4. Master updates its topology array.
    5. Master asks all discovered modules to update their topology array as well. Then the master repeats steps 1 to 5 asking next module down the list to explore.
  4. The master ensures all connected modules have been already discovered by reissuing exploration commands to all modules and watching out for any new un-IDed modules.
  5. If there are no un-IDed modules, the master generates and distributes appropriate port directions:
    1. Virtually reset the state of master ports to Normal.
    2. Update other modules ports starting from the last one and going backwards to ensure the communication chain is not broken.
    3. The master asks each module to update its port direction.
    4. The master officially updates its ports back to normal.
  6. The master tests new port directions by pinging all modules and recording their responses. It’s also displayed on the terminal if the CLI explore command was invoked.
  7. The master asks all modules to save the topology and port directions in specific Flash page (for topology) and emulated EEPROM (for number of modules and port directions).

The following video shows exploration of different arrays with various shapes and complexities. There are generous delays currently in the algorithm making it a bit lengthy (2 minutes for the sphere) but they will be optimized later.


Removing Current Topology

If you want to reconfigure the array in a different shape without erasing and reprogramming modules, you can invoke the CLI command default array to remove topology related information from Flash / emulated-EEPROM. Remember to do that before disassembling your array!

Yes No
Last updated on June 17, 2019

3.4.Buttons/Switches #

You can connect any mechanical button or switch to any of the array ports using the following API:

BOS_Status AddPortButton(uint8_t buttonType, uint8_t port)

where buttonType can be:

  • MOMENTARY_NO: A momentary tactile (push button) switch – naturally open.
  • MOMENTARY_NC: A momentary tactile (push button) switch – naturally closed.
  • ONOFF_NO: A toggle (ON/OFF) switch – naturally open.
  • ONOFF_NC: A toggle (ON/OFF) switch – naturally closed.

Once a button has been defined, it will be saved to emulated EEPROM and configured automatically each time on startup. You can also use the following API to remove a button and delete its definition from EEPROM:

BOS_Status RemovePortButton(uint8_t port)

Current button status can be accessed via button[i].state where i is port number. (You can refer to a button connected at port P1 with B1 and so forth.) A button status could be one of the following states:


Once a button/switch is defined, you can configure it to trigger certain events using this API:

BOS_Status SetButtonEvents(uint8_t port, uint8_t clicked, uint8_t dbl_clicked, uint8_t pressed_x1sec, uint8_t pressed_x2sec, uint8_t pressed_x3sec, uint8_t released_y1sec, uint8_t released_y2sec, uint8_t released_y3sec, uint8_t mode)
  • port: array port where the button is attached (P1 .. Px or B1 .. Bx).
  • clicked: Single click event (1: Enable, 0: Disable)
  • dbl_clicked: Double click event (1: Enable, 0: Disable)
  • pressed_X1secpressed_X2secpressed_X3sec: Press time for events X1, X2 and X3 in seconds. Use 0 to disable the event. This is a latching event.
  • released_Y1secreleased_Y2secreleased_Y3sec: Release time for events Y1, Y2 and Y3 in seconds. Use 0 to disable the event. This is a latching event.
  • mode: BUTTON_EVENT_MODE_CLEAR to clear events marked with 0, BUTTON_EVENT_MODE_OR to OR events marked with 1 with existing events.

These events can be linked to user callbacks to execute some tasks. Add the following callbacks to your code in main.c to make use of button events:

void buttonClickedCallback(uint8_t port)
{    }
void buttonDblClickedCallback(uint8_t port)
{    }
void buttonPressedForXCallback(uint8_t port, uint8_t eventType)
{    }
void buttonReleasedForYCallback(uint8_t port, uint8_t eventType)
{    }

where eventType is:

CLI Support

Port buttons/switches functionality is available through the CLI as well. You can use the conditional if … end if Command Snippet to link a button event to another command as well. Check the Command Snippets article for more details.

Yes No
Last updated on June 17, 2019

3.5.Data Logs #

Some modules such as mircoSD memory card module H1BR60 feature permanent logging functionality to a FAT-formatted micro-SD card. You can create up to 10 simultaneous logs with up to 30 different variables across all logs. Logs are tabulated text files that consist of a header section, a bunch of columns-each representing a single logged variable, and a bunch of rows-each representing the variables’ value at a given moment. Logs have two types: RATE and EVENT. The former type logs variable state periodically at a fixed rate, while the latter logs only a state change. Below is an example of RATE log

Datalog created by BOS V0.1.5 on H1BR6
Log type: Rate @ 10.00 Hz
Count    Float    UINT 32
1    120.400002    1123077325
2    120.400002    1123077325
3    120.400002    1123077325
4    120.400002    1123077325
5    120.400002    1123077325
6    120.400002    1123077325

And this is an example of an EVENT log:

Datalog created by BOS V0.1.5 on H1BR6
Log type: Events
Count,Switch 3 (E-stop)

Use this API to create a new log:

Module_Status CreateLog(const char* logName, logType_t type, float rate, delimiterFormat_t delimiterFormat, indexColumnFormat_t indexColumnFormat,const char* indexColumnLabel)


  • logName: Log file name (without extension).
  • type: Log type (RATE or EVENT)
  • rate: Logging rate in Hz (max 1000 Hz).
  • delimiterFormat: (FMT_TABFMT_COMMA)
  • indexColumnFormat: Index column (first column) format (FMT_SAMPLEFMT_TIME)
  • indexColumnLabel: Index column label text.

This API returns:

  • H1BR6_OK: Log created successfully.
  • H1BR6_ERR_LogNameExists: Log already exists on card.
  • H1BR6_ERR_WrongParams: Wrong log parameters.
  • H1BR6_ERR_SD: SD card failure.
  • H1BR6_ERR_MaxLogs: Maximum number of logs reached.

Once a log has been created, you can add new variables to the log using this API:

Module_Status LogVar(const char* logName, logVarType_t type, uint32_t source, const char* ColumnLabel)


  • logName: Log file name (without extension).
  • type: Log variable type (PORT_DIGITAL: log any array port as a digital input (TXD or RXD pin), PORT_DATA: log any array port as a serial data port, PORT_BUTTON: log any button/switch connected to array ports, MEMORY_DATA_UINT8, MEMORY_DATA_INT8, MEMORY_DATA_UINT16, MEMORY_DATA_INT16, MEMORY_DATA_UINT32, MEMORY_DATA_INT32, MEMORY_DATA_FLOAT: log any Flash/RAM memory location with a given data type).
  • source: log variable source (P1 .. Px if type is PORT_DATAB1 .. Bx if type is PORT_BUTTONP1TXD .. PxTXD or P1RXD .. PxRXD if type is PORT_DIGITAL; 32-bit memory address if type is MEMORY_DATA).
  • ColumnLabel: Logged variable column label text.

This API returns:

  • H1BR6_OK: Log created successfully.
  • H1BR6_ERR_LogDoesNotExist: Log was not found.
  • H1BR6_ERR_MemoryFull: Cannot allocate enough MCU memory for this variable (EVENT type logs only).
  • H1BR6_ERR_MaxLogVars: Maximum number of logged variables reached.

Adding a variable to a log does not start the logging process automatically. You can use the StartLog(), StopLog(), PauseLog() and ResumeLog() APIs to control the logging process for each log independently. You can also delete a log from the uSD card using DeleteLog() API.

Note: If the module booted without a uSD card (or with a malfunctioning one), the logging functionality will be aborted and the indicator LED will blink indefinitely. Just replace the card and the module should resume normal operation.

Yes No
Last updated on June 17, 2019

3.6.EEPROM Emulation #

Hexabitz modules do not have a dedicated EEPROM for non-volatile (i.e., permanent) storage. The MCU Flash memory, however, is used to emulate an actual EEPROM to store system and user parameters. The main difference between the MCU Flash and an EEPROM is that EEPROMs usually allow atomic access, i.e., storing, modifying and deleting a single byte/halfword/word, while MCU Flash memory can be erased only page by page (one page is 2 Kbytes in STM32F09 MCUs). A dedicated code in BOS_eeprom.c/.h solves this problem by using a double-page system and handles all operations in the background (initialization, addressing. memory-wear leveling, etc.) The MCU Flash memory can retain data up to 10-20 years and guarantee up to 10000 erase cycle (i.e., 10000 EEPROM variable updates).

Current emulated-EEPROM has a capacity to hold up 1024 variables, each with 16-bit size and 16-bit virtual address. The BOS reserves about 300 variables for system and module parameters. Users can use remaining space to store their non-volatile data. You can check out available virtual addresses in BOS_eeprom.h as shown below. Note that virtual addresses are arbitrary and they do not have to be adjacent as long as they are unique (and separated by enough distance in case the variable is larger than 16 bits).

/* EEPROM virtual addresses - Consider MaxNumOfModules is 25 */
#define _EE_NBase                 1    
#define _EE_portDirBase           2    // Move to RO - 25 modules
#define _EE_aliasBase             28    // 25 modules
#define _EE_DMAStreamsBase        159                
#define _EE_ButtonBase            167    // 4 * MaxNumOfPorts (10) variables for buttons: port(4 bits), type (4 bits), events (8 bits), pressed_for_x_1 (8 bits), released_for_y_1 (8 bits), etc.                                                                
#define _EE_EmptyVarBase          207
#define _EE_ParamsBase            500    // Parameter base: BOS response
#define _EE_ParamsDebounce        501    // Parameter: Button debounce
#define _EE_ParamsSinClick        502    // Parameter: Button single click
#define _EE_ParamsDblClick        503    // Parameter: Button double click (inter-click min and max)

To add a new variable, just assign a unique virtual address in project.h:

#define _EE_MyVar        407

Use the following API to store a new value to the emulated-EEPROM:

status = EE_WriteVariable(VirtAddVarTab[_EE_MyVar], MyVarValue);

The EE_WriteVariable API returns a 16-bit status:

  • 0x0000: Variable was written successfully.
  • 0x00AB: No valid page was found.
  • Any other codes: Flash write failed.

Read the stored value using this API (where &MyVarValue is the address of your variable in RAM):

status = EE_ReadVariable(VirtAddVarTab[_EE_MyVar], &MyVarValue);

It returns another 16-bit status:

  • 0x0000: Variable was found and read successfully.
  • 0x0001: Variable was not found.
  • 0x00AB: No valid page was found.
Yes No
Last updated on June 17, 2019

3.7.Remote Memory Access #

BOS offers a powerful remote memory read/write access functionality through specific Messages and APIs. This allows a module to access and modify almost any RAM or Flash memory location in another module in the array, thus, providing powerful synchronization and granular control.

BOS Variables

Typically, you will need a valid memory address to read a variable stored in RAM (e.g., 0x20000100) or Flash (e.g., 0x08000100). In order to simplify the process, BOS defines a set of RAM-based BOS Variables that you can use to easily exchange small amount of data across the array. BOS Variables can be addressed with easy-to-use virtual addresses (1 to N) without the need to know their actual RAM location. For example, you can link a float (or any other datatype) value to BOS Var 1 and access it from any other module in the array. There is a maximum number of bytes that each module reserve for BOS variables. It is defined in MAX_BOS_VARS preprocessor constant. If MAX_BOS_VARS=100, then you can define 100 1-byte variables, or 25 1-word variables and so forth.
The following examples demonstrate how to define BOS variables. Using the keyword volatile in front of a variable definition tells the compiler that this variable might change outside the program (e.g., by a remote module):

// Define local variables: LED state (ON/OFF), intensity and color (global or static if inside a function)                            
volatile bool state = true;                 
volatile uint8_t intensity = 50, color = WHITE;    

// Link to BOS variables (inside a function)    
AddBOSvar(FMT_BOOL, (uint32_t) &state);
AddBOSvar(FMT_UINT8, (uint32_t) &intensity);
AddBOSvar(FMT_UINT8, (uint32_t) &color);

The API AddBOSvar() accepts the following datatypes:


and returns the BOS variable index (or virtual address) or 0 if memory is full.

Note 1: BOS variables must be defined as global (e.g., outside a function) or static to ensure we are not referencing a temporary variable within a function stack.

Note 2: When using direct Flash and RAM addresses, pay special attention to memory alignment. Cortex-M0 MCUs do not accept non-word aligned memory access, which results in a processor Hardfault.

Remote Read

Use the ReadRemoteVar() and ReadRemoteMemory() APIs to read a remote module BOS variable or memory location. The first one returns a pointer to the remote value as well as a pointer to the remote BOS variable format. The second API only returns a pointer to the remote value. The remote format here should be specified by the local module since a memory location can contain any datatype. Note that the returned pointers must be casted to the correct datatype before their addressed value can be read correctly. The following examples demonstrate the proper use of these APIs:

volatile float myremotevar = 0;
// Reading remote address 0x2000001c from Module 2 with FLOAT format and 1000ms timeout
myremotevar = *(float *)ReadRemoteMemory(2, 0x2000001c, FMT_FLOAT, 1000);

volatile bool mybool;
varFormat_t format1;
/* Reading remote BOS variable 1 from Module 2 with 100ms timeout. Remote format is requested and stored in format1. It can be used to cast the variable properly in case we don't know the format beforehand */
mybool = *(bool *)ReadRemoteVar(2, 1, &format1, 100);

Both read APIs will block until the read value is returned or timeout reached (in which case a NULL pointer is returned). If the requested remote BOS variable does not exist, the APIs return BOS_ERR_REMOTE_READ_NO_VAR.

Remote Write

The WriteRemote() API is used to write to both a remote memory location or BOS variable. Setting up the remoteAddress parameter to any value between 1 and MAX_BOS_VARS writes to a BOS variable while setting it up to a valid RAM or Flash location writes to this memory location. If the BOS variable does not exist, a new one will be created in the remote module. Remote format is always specified by the local module. The following examples demonstrate the API usage:

volatile bool mybool = true;
/* Writing the value of mybool to remote BOS variable 1 in Module 2 with a BOOL format and 0 timeout, i.e., skipping confirmation */
WriteRemote(2, (uint32_t) &mybool, 1, FMT_BOOL, 0);

volatile uint32_t mynum = 0x12ABCDEF;
/* Writing the value of mynum to remote address 0x08016000 in Module 2 with UINT32 format and 100 timeout */
WriteRemote(2, (uint32_t) &mynum, 0x08016000, FMT_UINT32, 100);

A remote write with non-zero timeout will block until a confirmation message is received or timeout reached. A zero timeout disables sending a confirmation. The confirmation Message returns success (BOS_OK=0) or the following error codes:

  • BOS_ERR_REMOTE_WRITE_TIMEOUT: Remote module did not respond.
  • BOS_ERR_REMOTE_WRITE_MEM_FULL: No memory to create a new BOS variable.
  • BOS_ERR_REMOTE_WRITE_INDEX: BOS variable index (address) is out of range.
  • BOS_ERR_LOCAL_FORMAT_UPDATED: Remote BOS variable format does not match the requested one and thus was updated to match the request.
  • BOS_ERR_REMOTE_WRITE_ADDRESS: Remote address is not a valid Flash or RAM location.
  • BOS_ERR_REMOTE_WRITE_FLASH: Writing to a remote Flash location failed because the Flash page has not been erased.

Note: In order to write to an MCU Flash address, the address must contain 0xFFFFFFFF or the entire page (1024 bytes) must be erased beforehand. If you are aware of the consequences of this and still want to force erasing the page if needed, use the WriteRemoteForce() API.

Yes No
Last updated on June 17, 2019

3.8.Real-time Clock and Calendar #

Each programmable module has a real-time clock (RTC) and calendar that can be used to track time and date. The module will maintain an accurate internal time and date as long it is powered from a power source or battery and the initial time and date were setup correctly.

Current time and date can be accessed anywhere in the project by calling the GetTimeDate() API and reading the global BOS.time and BOS.date structs:

BOS.time.ampm      // Current time in 12-hour format: RTC_AM or RTC_PM
BOS.time.msec      // Milli-seconds: 0-999
BOS.time.seconds   // Seconds: 0-59
BOS.time.minutes   // Minutes: 0-59
BOS.time.hours     // Hours: 0-23
BOS.date.day       // Date: 1-31
BOS.date.month     // Month: JANUARY (1) to DECEMBER (12)
BOS.date.weekday   // Weekday: MONDAY (1) to SUNDAY (7)
BOS.date.year      // Year: Starting from 2000

You can setup the hour format (12 or 24) and calendar daylight saving settings (DAYLIGHT_SUB1H, DAYLIGHT_ADD1H, or DAYLIGHT_NONE) from the BOS.hourformat and the BOS.daylightsaving parameters, respectively. The settings will be saved in the emulated-EEPROM and loaded on startup.

The RTC takes firmware compile time and date as the initial time and date by default. You can reset the initial time and date via the BOS_CalendarConfig() API or the set CLI command. Even if the MCU was hardware-reset, it will keep its current initial time and date (and thus maintain time and date accurately) as long as it is powered. Once it loses power, it returns to the default firmware compile time and date and the RTC must be reconfigured again.

To display current time and date in the CLI, use the time and date commands. You can also configure the RTC in the CLI via the set time and set date commands.

Yes No
Last updated on June 17, 2019

3.9.Command Snippets #

Command Snippets is a CLI feature that empowers you to build small applications without writing or compiling any code. Simply get your USB-UART cable and open a terminal window wherever you are. Command Snippets are groups of CLI commands that can be executed based on a conditional event. Snippets are running continuously and simultaneously in the module backend at a rate governed by the FreeRTOS scheduler-about 1 kHz (think about Ladder programming for industrial controllers.) Snippets are stored in the emulated EEPROM so they get executed even after a power cycle. Currently, you can store up to 5 command snippets, however, you can add or delete snippets at any time and they will be executed sequentially. You can also pause and resume snippet execution at any time. Paused snippets are not executed on each cycle but they hold their place in the snippets buffer.

The general format for a command snippet is (each line separated by ENTER key):

if snippet condition
command 1
command 2
end if

The command snip displays a list of available snippets along with their execution status. Use the commands:

pause-snip i

act-snip i

del-snip i

to pause, activate (resume) and delete snippet i, respectively, where i is snippet order as shown by the snip command.

Snippet conditions can be one of four categories:

  1. Port button event: This condition evaluates the state of one of port button events (clicked, double clicked, pressed for X1, pressed for X2, pressed for X3, released for Y1, released for Y2, released for Y3).
  2. Module event: This condition evaluates module events.
  3. Module parameter with a constant: This condition compares module parameter with a constant. Mathematical comparison operations are: equal “=”, greater than “>”, smaller than “<“, greater or equal “>=”, smaller or equal “<=”, not equal “!=”.
  4. Module parameter with a module parameter: This condition compares a module parameter with another module parameter using same comparison operators.

Here are some examples for port button events:

if b1.clicked
end if
if b1.pressed for 3
color magenta 50
end if

And for comparing module parameter with a constant (range is a parameter for H08R6 module):

If range < 200
end if

Note 1: Be careful when comparing float numbers and parameters using the equal or not-equal operators as the condition might never be true.

Note 2: You can’t change the order of snippet execution. However, once you delete a snippet, subsequent ones get shifted to fill the empty place.

Yes No
Last updated on June 19, 2019

3.10.BOS Parameters #

3.11.Module Parameters & Events #

4.Firmware Release Notes #

Here are the Release Notes for Hexabitz firmware released on Github. There are three easy methods to get pre-compiled firmware HEX files for the release you want without cloning and compiling the firmware:

  • Navigate to module website page, Resources tab and download HEX files for latest firmware release.
  • Visit module firmware repository on Github and click on Releases, recent releases should show up with their associated HEX files.
  • Visit module firmware repository on Github and navigate to the Compiled folder within the code. The folder contains HEX files for current and previous releases. Simply copy the release you want if you have a local copy. If you are browsing the repository online, click on the HEX file then, click on Raw to display and download the file.
Yes No
Last updated on August 27, 2019

4.1.V0.1.0 #

First external release!

  • Broadcasting should work well with Messages and Commands – Don’t enable response for now.
  • Button definitions and events are saved to emulated EEPROM (i.e., won’t be lost if you reboot) >> Not thoroughly tested.
  • Added feature to select lower CLI baudrate at startup (115200). Momentarily connect P1 TXD and RXD together while power cycling then any CLI port you connect to will accept 115200. Other ports used for messaging will keep the higher rate (921600). Note that CLI baudrate goes back to default once you power cycle.
  • Added option to disable response on remote Messages/Commands. Basically you can choose to send response on all (Messages+Commands), or Messages only or disable both.
  • Added set/get CLI commands to setup some BOS parameters (response, button debounce settings, etc) They are saved directly to emulated EEPROM.
  • Added CLI command to put all parameters back to default default params.
  • Increased emulated EEPROM size to 1024 16-bit variables and moved the explored routing table to a different section in MCU Flash to free more EEPROM area.
Yes No
Last updated on June 15, 2019

4.2.V0.1.1 #

  • Bug fix: H01R00 CLI Commands with upper case (e.g., RGB) now work correctly.
  • Bug fix: Added UART error callback to _it file to catch any errors.
  • Bug fix: Fixed some errors in saving and recalling button definitions and events from EEPROM.
  • New feature: H05R00 SD card logging:
    — Establishing all basic logging functionality.
    — Log buttons.
    — Log memory locations.
    — Both rate and event-based.
    — Index only for samples .
  • New feature: Added CLI baudrate to BOS parameters and you can set it in the CLI. Once you set it’s saved in EEPROM so you can use the module always at this baudrate.
  • Improvement: Moved MCU startup file from user group to module drivers.
  • Improvement: Moved topology header define from main.h to a project-based project.h for better portability.
Yes No
Last updated on June 15, 2019

4.3.V0.1.2 #

New features

  • Added an important BOS feature for reading and writing data from other modules. A couple of easy-to-use APIs provide now extensive and precise reach to any remote module via:
    * Read any Flash memory location or RAM memory location in any remote module with the following formats: BOOL, UINT8, INT8, UINT16, INT16, UINT32, INT32, FLOAT.
    * Read any “BOS variable” using any of previous formats. These are general RAM variables that you can use and define for data exchange between modules without explicitly using their address. There are 20 in each module so you can simply use virtual addresses 1 to 20.
    * Write to any Flash memory location or RAM memory location in any remote module with the following formats: BOOL, UINT8, INT8, UINT16, INT16, UINT32, INT32, FLOAT.
    * Write also to any of the 20 BOS variable using any of previous formats.


  • Automatically handle Messages with length 13 by padding parameters with one zero byte.
  • Some simple optimizations to reduce RAM use by 1.5K.
  • Optimize logging code bit to Improve speed and reduce load.
  • Defer resetting button state until it’s logged by the logger if needed.

Bug fixes

  • Fix CLI remote messaging (i.e., CLI Commands for remote modules).
  • Make logging rate more accurate.
  • Log the normal state of a button in rate mode.
  • Fix some logging header problems (Starting multiple logs at the same time is still broken).
Yes No
Last updated on July 28, 2019

4.4.V0.1.3 #

New Features
  • Real-time clock (RTC) and calendar: all modules now have RTC and calendar logic using MCU RTC and the external oscillator (HSE). You can set time and date and read it back via APIs and CLI Commands. The internal RTC will maintain time as long as the module is powered. Initial (default) time/date is set to firmware compile time/date. You need to configure the RTC only once as long as module is powered (i.e., no need to reconfigure if you do hard/soft reset). However, a power-on reset will clear the clock and date back to default value.
  • Pulse-width modulation (PWM) in the H09R00 module: PWM functionality is added to control the SSR. All you need is to define the dutycycle as a percentage of 0 to 100%. Default PWM frequency is set to 20KHz. PWM is available via an API, a Message and a CLI Command.
  • New CLI Commands have been added to H05R00 module to create a new log, delete log, start/stop/pause/resume logs and add a logged variable.
  • New Messages have been added to H09R00 module to perform various actions remotely (on, off, toggle, PWM)
  • Added two new button events (PRESSED, RELEASED). These two are always defined and they have their own callbacks but they always reset on next RTOS tick.
  • Moved a bunch of extra files from User folder into Module and BOS folders to increase code portability. User folder now has only main.c, project.h and project topology header file.
  • Moved system clock configuration function from main.c to BOS.c to increase portability. Now main.c is 100% portable and should not need to be updated.
Bug Fixes
  • Clear UART state in error callback.
  • Fix Messages with 13-byte lenght (again!).
  • Fix reading button state by other tasks (particularly log task) before it’s reset.
  • Fix UART errors caused by initial check for a factory reset or baudrate change action.
  • Fix a bug preventing the CLI from reading decimal numbers.
Yes No
Last updated on July 30, 2019

4.5.V0.1.4 #

New Features

  • Enable GPIOF clock for HSE (high speed external oscillator)
  • Add module grouping to the BOS.
  • Add multicast support to the BOS.
  • Save topology in Flash RO instead of Flash emulated EEPROM area.
  • Adding TRACE mode.

Bug Fixes

  • Various fixes and improvements to array exploration algorithm.
Yes No
Last updated on July 28, 2019

4.6.V0.1.5 #

This was a big release!

New Modules

  • P01R00: Pentagon RGB LED module. Using similar code to H01R00.
  • H07R30: Audio speaker module. Initial module release supporting musical notes and basic wave play.
  • H08R60: IR Time-of-Flight module. Initial release with range sample and streaming functionality.
  • H0BR40: 3-axis IMU and Digital Compass module.  Initial release with sensor sample and stream functionality.
  • H23R10: Bluetooth V4 module. Initial module release with smartBasic support and Android app connection functionality.

New Features

  • Adding UUID CLI command to display MCU unique UID, ID_CODE and Flash size.
  • Adding remote bootloader update feature to the BOS. You can remotely update a module from another one via the shortest path or via a specific port.
  • Adding messages to control indicator LED.
  • Adding demo CLI commands to few modules.

Bug Fixes

  • Various fixes to memory and stack issues.
  • Improve uSD card module (H1BR60) initialization and make card fail or missing fault non-blocking.


  • All module PNs were updated to match our new product map.
  • DMA streams can be temporary or permanent (stored in the emulated EEPROM)
  • Improving TRACE mode and adding it to BOS parameters.
Yes No
Last updated on July 28, 2019

4.7.V0.1.6 #

New Features

  • Adding demo CLI commands for few modules.
  • Defining template module parameters and adding parameters for H08R60 module.

Bug Fixes

  • Refactor port button events code.


  • Big improvements to CLI Command Snippets. You can add, edit, delete, pause and resume snippets. Snippets are now stored in the emulated EEPROM as well. Multiple conditional modes are now supported including port buttons, modular parameters and mathematical operations.
  • The Front-end task is renamed as User task to empathize its use for user applications..

Known Issues

  • Firmware version number in the CLI still shows 0.1.5 (but firmware date is in June 2019)
  • USB port in H1ARx module can’t receive or transmit BOS messages.
Yes No
Last updated on July 28, 2019

5.Hardware Release Notes #

6.Factsheets #



You are leaving this website to visit our Hackster.io account. Enjoy your time!