TMF8829 Universal Driver 0.1.0
Loading...
Searching...
No Matches
tmf8829_universal_driver

A small, portable, multi-instance C11 driver for the ams-OSRAM TMF8829 direct time-of-flight ranging sensor.

License: MIT Version C11 Unit tests

Why another TMF8829 driver?

ams-OSRAM publishes two reference driver packages, one for Arduino and one for the Linux kernel. They are functional and well-commented, but their shared "shim" architecture has properties that make integration into a larger firmware project awkward:

  • The bus (I2C vs SPI) is selected at compile time via mutually exclusive USE_I2C / USE_SPI #defines. You cannot run one TMF8829 on I2C and another on SPI in the same firmware.
  • Single-device assumption baked in: enable / interrupt pin numbers are global #defines. The platform shim is not parameterized by an instance handle.
  • Layering inversion: the platform "shim" includes the application driver header and calls into it.
  • Tight platform coupling in headers: including the core driver header transitively pulls in <linux/kernel.h> / <linux/gpio.h> / <Arduino.h>.
  • Application policy in the driver: CSV printing, frame post-processing, and console input are part of the "shim" interface.
  • Buffer sizing escapes the platform layer: DATA_BUFFER_SIZE is set in the shim header and used to size a member of the core driver struct, so the driver layout depends on the platform port.

This project keeps the register-level protocol logic of the ams-OSRAM drivers but wraps it in a tighter abstraction:

  • One tmf8829_driver_t instance per sensor.
  • Bus type (I2C or SPI) is a runtime field on the driver struct.
  • All platform interaction happens through a single tmf8829_ops_t callback table (read, write, delay_us, systick_us, write_pin_enable, read_pin_int).
  • Driver headers depend on <stdint.h> and <stddef.h> only.
  • Pin configuration, interrupt registration, bus initialization, and clock setup are entirely the host's concern. The driver only knows "drive enable pin high" / "what's the IRQ pin reading?".
  • Logging is one optional callback. CSV / printf macros are gone.
  • Frames are delivered by pull: clear interrupts with tmf8829_get_and_clr_interrupts, then tmf8829_read_results() / tmf8829_read_histogram().
  • The driver does no dynamic allocation. The buffer memory is caller-provided.
  • The default firmware image is opt-in. It lives under image/ as a separate CMake target you only link if you want the bundled blob. This allows storing the image in external storage instead of embedding it in firmware.

Project layout

tmf8829_universal_driver/
├── cmake/
│ ├── tmf8829_universal_driverConfig.cmake.in
│ └── tmf8829_version.h.in
├── include/tmf8829/
│ ├── tmf8829.h # init, enable, measurement API
│ ├── tmf8829_types.h # tmf8829_bus_t, tmf8829_err_t, log levels, forward decls
│ ├── tmf8829_ops.h # tmf8829_ops_t platform callback table
│ └── tmf8829_regs.h # register addresses, command opcodes, frame layout
├── src/
│ ├── tmf8829.c # protocol implementation
│ └── tmf8829_internal.h # init magic + inline guards (not for app use)
├── image/ # opt-in vendor firmware (not built by default)
│ ├── CMakeLists.txt
│ ├── tmf8829_fw_image.h # vendored ams-OSRAM app binary
│ ├── tmf8829_fw_image.c # app image binary blob
│ ├── tmf8829_fw_source.h # fw_image_read adapter
│ └── tmf8829_fw_source.c
├── tests/
│ ├── CMakeLists.txt
│ └── test_*.cpp
├── CMakeLists.txt # core target + options + install/export package files
├── CMakePresets.json # dev/CI build presets
└── LICENSE # MIT

Requirements

  • CMake ≥ 3.13
  • C11 compiler for the library (any modern GCC / Clang / armcc / IAR)
  • C++17 compiler for the tests (only when TMF8829_BUILD_TESTS=ON)

The library itself has no third-party dependencies. Catch2 is fetched only when tests are built.

Build

As a CMake subdirectory (the typical case)

add_subdirectory(external/tmf8829_universal_driver)
target_link_libraries(your_firmware PRIVATE tmf8829::tmf8829)

Bundled application image

set(TMF8829_INCLUDE_DEFAULT_IMAGE ON CACHE BOOL "" FORCE)
add_subdirectory(external/tmf8829_universal_driver)
target_link_libraries(your_firmware PRIVATE tmf8829::tmf8829 tmf8829::default_image)

Then assign drv.fw_image_read = tmf8829_fw_source_read before tmf8829_init, and call tmf8829_download_firmware(&drv, TMF8829_FW_IMAGE_LOAD_ADDR_DEFAULT, use_fifo) from bootloader context.

Standalone (for development / running tests)

cmake -S external/tmf8829_universal_driver -B build -DTMF8829_BUILD_TESTS=ON
cmake --build build
ctest --test-dir build --output-on-failure

When the project is built as the top-level CMake project, TMF8829_BUILD_TESTS is auto-enabled.

Install and consume via CMake's find_package

This project installs headers, static libraries, and CMake package files for find_package(tmf8829_universal_driver CONFIG REQUIRED) consumers.

cmake -S external/tmf8829_universal_driver -B build -DTMF8829_INCLUDE_DEFAULT_IMAGE=ON
cmake --build build
cmake --install build --prefix /your/install/prefix

Consumer example:

find_package(tmf8829_universal_driver CONFIG REQUIRED)
target_link_libraries(your_firmware PRIVATE tmf8829::tmf8829)
# Optional bundled image target
# (only available if installed with TMF8829_INCLUDE_DEFAULT_IMAGE=ON):
# target_link_libraries(your_firmware PRIVATE tmf8829::default_image)

CMake options

Option Default Purpose
TMF8829_BUILD_TESTS OFF (auto-ON standalone) Build Catch2 unit tests. Pulls Catch2 v3 via FetchContent. Disable on embedded targets.
TMF8829_INCLUDE_DEFAULT_IMAGE OFF Build the bundled vendor firmware image as tmf8829::default_image.
TMF8829_BUILD_DOCUMENTATION OFF Build Doxygen API documentation (requires Release build type).

Usage sketch

static const tmf8829_ops_t my_stm32_ops = {
.read = my_stm32_read,
.write = my_stm32_write,
.delay_us = my_stm32_delay_us,
.systick_us = my_stm32_systick_us,
.write_pin_enable = my_stm32_write_pin_enable,
.read_pin_int = my_stm32_read_pin_int,
};
static uint8_t buffer_a[512];
static tmf8829_driver_t sensor_a = {
.i2c_addr = 0x41,
.user_ctx = &my_i2c_handle_for_sensor_a,
.buffer = buffer_a,
.buffer_len = sizeof(buffer_a),
};
int rc = tmf8829_init(&sensor_a, &my_stm32_ops);
Public API for the portable TMF8829 driver.
int tmf8829_init(tmf8829_driver_t *drv, const tmf8829_ops_t *ops)
Validate parameters, bind ops, and reset driver state.
@ TMF8829_BUS_I2C
Definition tmf8829_types.h:44
struct tmf8829_driver tmf8829_driver_t
Definition tmf8829_types.h:33
struct tmf8829_ops tmf8829_ops_t
Definition tmf8829_types.h:34

A second sensor on SPI in the same firmware is just a second driver instance with bus = TMF8829_BUS_SPI, no recompilation, no #ifdef flags.

Current scope

  • Public C API in include/tmf8829/ for init, power/enable, command/config, bootloader firmware download, and result/histogram reads.
  • Optional default firmware image packaged as tmf8829::default_image (image/tmf8829_fw_image.* + image/tmf8829_fw_source.*).
  • Catch2 unit tests under tests/ with fakes for bus, time, and pin behavior.
  • Doxygen API documentation under doc/, deployable to GitHub Pages.
  • CI workflow in .github/workflows/ci.yml building and testing matrix targets.

License & attribution

This project is released under the MIT License (see LICENSE).

It is a clean reimplementation, the protocol details (register addresses, command opcodes, bootloader sequence, frame layout) were learned from the ams-OSRAM TMF8829 Arduino and Linux reference drivers, both of which are distributed under the MIT license. The LICENSE file contains the acknowledgment.

The optional default firmware image under image/ is derived from the ams-OSRAM Arduino tmf8829_image.c (MIT) and redistributed under the same terms.

Read Next
Integration Guide