From peripherals to processors: rethinking communication inside the Crazyflie

For a long time, communication inside the Crazyflie was fairly simple. The main STM32 microcontroller handled flight control, sensors, motors and most of the system logic. A second microcontroller handled the radio and power management. The two processors communicated using Syslink, a small packet protocol over UART. Syslink was designed for one fixed connection between two known processors. Both sides were developed together, the number of packet types was limited, and there was little need for discovery or elaborate flow control.

This worked quite well for the system it was designed for.

Crazyflie 2.0 system architecture

Over time, however, the Crazyflie platform has changed. More and more expansion decks contain their own processors, firmware and internal state. Some decks are no longer simply collections of sensors connected over I2C or SPI. They are small embedded systems of their own. This has made communication between processors a recurring part of the platform rather than a special case.

Most of what we are talking about in this blog post is very much work in progress: this is a work that we are doing as part of yet another deck that will have an microcontroller.

From Syslink to CPX

Syslink was created for one link between two processors. Later, when developing the AI deck, we encountered a different problem. The system contained several processors and several communication links. Packets could travel between the Crazyflie STM32, the ESP32, the GAP8 and a computer connected either through Wi-Fi or through the normal Crazyradio path. This led to CPX, the Crazyflie Packet eXchange protocol. One of its main roles is routing packets between processors and transports.

CPX solved an important practical problem, and routing is still useful. At the same time, our experience with it has shown that not every processor link needs the whole routed communication model. For a connection between two known processors, requiring routing and application functions to be part of the same protocol can make the stack heavier than necessary.

The current work is therefore not intended to replace CPX. Instead, we are trying to separate the different responsibilities more clearly. CPX could eventually be one of the services transported over a lower-level peer-to-peer link when routing is required.

Yet another processor on a deck

We are currently working on a new deck with a processor that needs to communicate with the Crazyflie. The fastest solution would have been to create another deck-specific UART protocol. The endpoints are known, the initial set of commands is fairly small, and such a protocol would probably have been finished much earlier. That would have been a completely reasonable engineering choice but it would also have added one more custom processor protocol to the platform.

We expect processor-based decks to become more common, so we decided to use the this new deck as an opportunity to work on something more reusable. The new deck does not strictly require all the infrastructure we are building. Rather, it provides a real product in which we can develop and test a communication model that may also be useful elsewhere.

Building the stack in layers

One lesson from Syslink and CPX is that we should avoid putting every communication concern into one protocol. The current new design is split into layers:

physical transport

transport binding

peer-to-peer Link

services such as Control, CPX or Console

The physical transport may be UART, SPI, I2C or another suitable connection.

A transport binding handles the details required to move frames over that transport, such as framing, integrity checks and resynchronization.

Above this is the Link between two embedded peers. It is responsible for establishing a communication session, negotiating compatible parameters, discovering available services and managing bounded buffers.

The Link does not need to understand Wi-Fi configuration, console text or routed CPX packets. These belong to services above it. This separation is important. It allows a simple product to expose only the services it needs, while a more complex system can add routing, structured control, console traffic or other services without changing the basic Link.

The initial implementation uses UART for our new deck, but the communication model itself is not intended to be UART-specific.

A first useful example: Console

Since starting this work, we have implemented a first Console service on top of the new Link.

This also led to a more generic console interface in the Crazyflie firmware. Instead of assuming that all console output comes from the main STM32, the Crazyflie can now expose several console sources. With the current prototype, a client can receive the Crazyflie console, the new deck console, or both. The available consoles can be queried, and by default the normal Crazyflie console continues to behave as before. This is a fairly small feature, but it is a good example of what the architecture is intended to make possible.

Today, the nRF51 radio microcontroller is largely opaque from a debugging point of view. This was quite intentional in the original Crazyflie architecture: the STM32 was the processor we expected users to develop on, while the nRF51 mainly provided supporting functions such as radio and power management. That boundary has become less clear over time. The radio firmware has grown more capable, and both we and some users increasingly work on it directly. If the nRF51 eventually uses the same Link model, it could expose the same Console service. The existing client-side console infrastructure could then show its output without requiring another custom protocol and another special debugging path.

This is the kind of reuse we are looking for.

Control and reusable capabilities

Another service we are exploring is Control.

Control provides a structured way to get values, set values and call commands. It is somewhat similar to an RPC interface, but it also exposes organized sets of readable and writable state. Within Control, related functionality can be described using versioned contracts.

Wi-Fi provisioning is a useful example: adding Wi-Fi support to the AI deck was not only a matter of implementing a command in the deck firmware. The Crazyflie needed to forward the operation, the client library needed an API, and a user-facing tool was required to configure the connection. A future Wi-Fi-enabled deck would otherwise need another product-specific implementation of much of the same path.

With a common Wi-Fi contract, a deck could advertise through its Control service that it supports Wi-Fi provisioning. Generic software could discover this capability and use an existing interface for it, without needing to know which deck or processor implements it. More importantly this means that we can tie Wifi credential to a Crazyflie and not to the deck: for our current work we have decided to store the wifi credential in the Crazyflie. This means that, once setup, the Crazyflie will connect the same wifi network independent of which wifi-capable deck is attached to it. It makes the system much easier to work with. We may update the AI deck to follow the same model in the future.

The same idea can apply to much simpler features. Several decks contain visible RGB LEDs. Instead of every deck exposing a different way of setting a color, they could implement one common LED-control contract and use the same client API.

This is the higher-level counterpart to reusing the Link. The Link avoids rebuilding processor-to-processor communication, while services and contracts make it possible to reuse functionality above it.

Where this could lead

The protocol developed for our new deck is not yet a complete Syslink2 specification for the whole Crazyflie ecosystem. The current work mainly demonstrates the point-to-point Link architecture: sessions, service discovery, versioning and flow control based on actual available buffers. Console already provides a concrete service on top of it, while Control and its contracts are still more exploratory. There are also larger questions we are deliberately leaving outside the first Link. Routing remains a separate concern, and CPX may continue to provide it where it is needed.

More generally, discoverable services could make parts of the Crazyflie platform easier to extend. Today, replacing or experimenting with a subsystem often requires changes across several parts of the stack simply because both ends need to know exactly what hardware is present. If processors can instead advertise standard services and capabilities, some of those dependencies could become looser. For example, a future radio processor could expose a radio service instead of being identified only by its position in the hardware architecture. In principle, another processor or even a deck could expose a compatible service while the rest of the system continues to use the same interface.

This is still very much a direction rather than a finished architecture, but it is one of the possibilities that makes this work interesting to us. The new deck gave us a practical place to start. So far, the layered model seems to fit both our immediate use case and some of the communication problems we expect to encounter more often in future Crazyflie platforms.

Leave a Reply

Your email address will not be published. Required fields are marked *