C/C++ for Microcontrollers - API Bindings

The C/C++ bindings for microcontrollers allow you to control Bricks and Bricklets with a C/C++ programm running on a microcontroller. The ZIP file for the bindings contains:

  • in source/bindings the source code of the bindings
  • in source/hal_* the source code of HALs for common platforms.
  • in examples/ the examples for every supported Brick and Bricklet

Supported Hardware

The C/C++ bindings for microcontrollers support Co-processor Bricklets (i.e. those with a 7-pole connector), as well as the HAT Brick and HAT Zero Brick. The older 10-pole Bricklets and other Bricks are not supported. See here for a complete list of supported devices.

As host (i.e. the device running your C/C++ program that uses the bindings), you can currently use the ESP32 Brick and ESP32 Ethernet Brick. Support for other hosts will be added in the future.

The bindings require a hardware abstraction layer (HAL). See here for details.


  • C compiler (C99 compatible) or C++ compiler


Because there is no precompiled library for the bindings there is nothing to install as such. The recommended way of using the bindings is to include their bindings and your preferred HAL directly into your C/C++ project.

Testing an Example

The examples for each supported Brick and Bricklet are not self-contained, as they depend on the host hardware (and its HAL) you want to use. Many HALs contain an example driver that can run any example. See the specific HAL documentation for examples.

UID or Port Name

All device *_create functions take a uid_or_port_name parameter allowing the program to specify which device to communicate with. This can be done in three ways:

  • Passing NULL selects the first unused device with matching type, regardless of UID or port name.
  • Passing a UID such as "XyZ" selects the first unused device with matching UID and type.
  • Passing a port name such as "A" selects the first unused device with matching port name and type.

In this case unused means the device is not attached to a device object. For example, there are two Temperature Bricklet 2.0, one connected to port A and one to port B. With passing NULL the first tf_temperature_v2_create call picks the one on port A, the second call picks the one on port B, because the one on port A is in use already.

Hardware Abstraction Layer

The C/C++ bindings for microcontrollers work somewhat different to the other available bindings. While other bindings, that communicate to a Brick Daemon over TCP/IP, require a IP connection, the C/C++ bindings for microcontrollers require usage of a hardware abstraction layer (HAL). The HAL is an abstraction over the platform specific way to communicate over SPI.

The bindings already contain HALs for the following platforms. For other platforms, a custom HAL must be implemented (see below).

Implementing a Custom HAL

If you want to use the bindings on another platform, you have to implement a custom HAL. This guide shows how.


Callbacks in the C/C++ bindings for microcontrollers work differently than those in the other bindings. As there is no multi-threading on many of the target platforms, there is no automatic polling for callbacks. Instead, if you want to receive callbacks, you have to poll for them yourself with the tf_hal_callback_tick available in every HAL. This function will poll any device with a registered callback handler for new packets.

Callbacks are not dispatched by another thread, but instead while executing a getter, setter or the tf_hal_callback_tick function. Because of this, callback handlers will run while the internal state machines of the bindings are in states, that do not support sending other packets. Because of this, calling getters, setters or tick functions inside a callback handler is not allowed.

Thread safety

As the primary target for these bindings are microcontrollers, the bindings are not thread safe. Some HALs support cooperative multi-tasking, however no calls to the bindings API are allowed when they have yielded. All functions will return TF_E_LOCKED if called inside a callback handler or while the bindings are yielded.

Optimizing Performance

To get the best performance out of the bindings, you can follow this list of optimizations:

  • Increase the SPI clock speed from 1.4 MHz to as close to 2 MHz as you can. Note that a too high clock speed will result in worse performance, as the attached Devices will fail to receive the payload. In our tests, the best performance can be reached at about 1.95 to 1.96 MHz, however this depends on your setup, the used HAL, as well as the stability of the host system's SPI clock.
  • Prefer to use callbacks. The bindings can poll a device for an available callback by clocking out a single byte. If you use getters to poll for rapidly changing state, a complete TFP request, response and acknowledgment must be sent over SPI, resulting in at least 23 bytes of data to send.
  • If you know exactly what callbacks to expect in what intervals, don't use tf_hal_callback_tick, instead use your own scheduling to poll with the specific tf_[device]_callback_tick functions. This allows spending more time on other tasks instead of polling the devices all the time. tf_hal_callback_tick uses a round-robin scheduler to poll all devices with a registered callback handler.
  • If multiple SPI devices are available, use multiple HAL instances. This will only work with HALs that support using a specific SPI device. However you can then communicate with multiple Devices in parallel. Each HAL instance may still only be used by a single thread.

Optimizing Flash and RAM Usage

To fit a firmware using the bindings onto smaller platforms, such as the Arduino Uno, you can change the following configuration options in bindings/config.h:

  • TF_INVENTORY_SIZE: This is the size of the inventory i.e. the mapping from UIDs to the ports under which they are reachable. If you know exactly how many devices you want to communicate with, set the inventory size to this number. Note that the HAL initialization will return TF_E_TOO_MANY_DEVICES if the inventory size is too small.
  • TF_IMPLEMENT_CALLBACKS: If you don't want to use callbacks, remove this define to remove the callback implementation.
  • TF_LOG_LEVEL: Reducing the log level will remove string constants at compile time. Valid values are
    • TF_LOG_LEVEL_NONE: Disables the logging completely
    • TF_LOG_LEVEL_ERROR: Logs only some HAL specific errors if they occur.
    • TF_LOG_LEVEL_INFO: Additionally logs the initial list of detected devices (default)
    • TF_LOG_LEVEL_DEBUG: Additionally logs all internal state changes in the SPITFP state machine
  • TF_IMPLEMENT_STRERROR: If you remove this define, the tf_hal_strerror function will not be implemented. This saves about 500 bytes of flash or RAM, depending on the HAL.

API Reference and Examples

Links to the API reference for the HALs, Bricks and Bricklets as well as the examples from the ZIP file of the bindings are listed in the following table. Further project descriptions can be found in the Kits section.

Name API Examples
HAL Arduino ESP32 Brick API Examples
HAL Arduino ESP32 Ethernet Brick API Examples
HAT API Examples
HAT Zero API Examples
Accelerometer 2.0 API Examples
Air Quality API Examples
Ambient Light 3.0 API Examples
Analog In 3.0 API Examples
Analog Out 3.0 API Examples
Barometer 2.0 API Examples
CAN 2.0 API Examples
CO2 2.0 API Examples
Color 2.0 API Examples
Compass API Examples
DC 2.0 API Examples
Distance IR 2.0 API Examples
Distance US 2.0 API Examples
DMX API Examples
Dual Button 2.0 API Examples
E-Paper 296x128 API Examples
Energy Monitor API Examples
GPS 2.0 API Examples
Hall Effect 2.0 API Examples
Humidity 2.0 API Examples
IMU 3.0 API Examples
Industrial Analog Out 2.0 API Examples
Industrial Counter API Examples
Industrial Digital In 4 2.0 API Examples
Industrial Digital Out 4 2.0 API Examples
Industrial Dual 0-20mA 2.0 API Examples
Industrial Dual AC Relay API Examples
Industrial Dual Analog In 2.0 API Examples
Industrial Dual Relay API Examples
Industrial PTC API Examples
Industrial Quad Relay 2.0 API Examples
IO-16 2.0 API Examples
IO-4 2.0 API Examples
Isolator API Examples
Joystick 2.0 API Examples
Laser Range Finder 2.0 API Examples
LCD 128x64 API Examples
LED Strip 2.0 API Examples
Linear Poti 2.0 API Examples
Load Cell 2.0 API Examples
Motion Detector 2.0 API Examples
Motorized Linear Poti API Examples
Multi Touch 2.0 API Examples
NFC API Examples
OLED 128x64 2.0 API Examples
One Wire API Examples
Outdoor Weather API Examples
Particulate Matter API Examples
Performance DC API Examples
Piezo Speaker 2.0 API Examples
Real-Time Clock 2.0 API Examples
Remote Switch 2.0 API Examples
RGB LED 2.0 API Examples
RGB LED Button API Examples
Rotary Encoder 2.0 API Examples
Rotary Poti 2.0 API Examples
RS232 2.0 API Examples
RS485 API Examples
Segment Display 4x7 2.0 API Examples
Servo 2.0 API Examples
Silent Stepper 2.0 API Examples
Solid State Relay 2.0 API Examples
Sound Pressure Level API Examples
Temperature 2.0 API Examples
Temperature IR 2.0 API Examples
Thermal Imaging API Examples
Thermocouple 2.0 API Examples
UV Light 2.0 API Examples
Voltage/Current 2.0 API Examples
XMC1400 Breakout API Examples
Bricklets (Discontinued)    
PTC 2.0 API Examples