Skip to content

Manwlis/sensor_playground

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

49 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

sensor_playground

STM32H755 / FreeRTOS based project for testing, integrating and evaluating sensors and supporting embedded modules.

As of now it includes:

  • Driver for the LIS3DHTR MEMS accelerometer.
  • Driver for the PmodALS ambient light sensor.
  • Driver for the internal temperature sensor connected on the on-chip ADCs.
  • A custom lightweight logging module.

LIS3DHTR Driver

Driver implementation for the LIS3DHTR accelerometer providing both high level and low level APIs. It abstracts the underlying transport layer (I2C/SPI) and is easily portable to other MCUs.

High Level API

The high level API exposes device functionality rather than register operations. It uses the ll API and expects that it is blocking.

Instead of manipulating registers and bitfields directly, the user interacts with device features through functions such as:

  • LIS3DHTR_enable_aux_adcs()
  • LIS3DHTR_disable_temp_sensor()
  • LIS3DHTR_get_acceleration()

The goal is to expose the device capabilities while hiding register layout and configuration details.

Low Level API

The low level API provides register level access. Two implementations are provided:

  • No-OS: Blocks until communication with the device completes.
  • FreeRTOS: The task yields execution and resumes when an interrupt notifies that the transfer has completed.

The ll API consists of the following functions:

  • LIS3DHTR_read_reg(): Reads from a register on the device.
  • LIS3DHTR_write_reg(): Writes to a register on the device.
  • reg_get_field(): Gets the value of a specific field of a register.
  • reg_set_field(): Sets the value of a specific field of a register.

Example usage:

	rv = LIS3DHTR_read_reg( device , LIS3DHTR_TEMP_CFG_REG , &data );
	reg_set_field( &data , TEMP_CFG_ADC_EN , LIS3_ENABLE );
	rv = LIS3DHTR_write_reg( device , LIS3DHTR_TEMP_CFG_REG , data );

The user only specifies the registers and fields to access or modify, which are provided as C enumerations in the header files of the driver.

Internal Design

The primary design goal is a simple user-facing API. To achieve that, all device metadata (register mappings, field definitions, access rights, etc.) is stored internally by the driver.

Example:

  enum {
    LIS3DHTR_STATUS_REG_AUX ,
    LIS3DHTR_OUT_ADC1_L ,
    ... ,
    LIS3DHTR_NUM_REGS
  };

  LIS3DHTR_memory_map[LIS3DHTR_NUM_REGS] = {
    { 0x07 , REG_R }, // physical address & access rights of STATUS_REG_AUX
    { 0x08 , REG_R }  // physical address & access rights of OUT_ADC1_L
    ...
  }

Avoiding Parallel Data Structures

The above example, while simple, is problematic. The enum and the array are parallel 1 to 1 data structures. A modification in one structure may not be reflected in the other. To improve maintainability, X macros are used instead.

Example:

// All register metadata is defined in one place.
#define REG_MAP( X ) \
  X( STATUS_REG_AUX , 0x07 , REG_R ) \
  X( OUT_ADC1_L ,     0x08 , REG_R ) \
  ...

 // helper macros
 #define GEN_ENUM( name , address , access ) LIS3DHTR_##name,
 #define GEN_ARRAY( name , address , access ) { address , access } ,

  enum {
    REG_MAP( GEN_ENUM )
    LIS3DHTR_NUM_REGS
  };

  LIS3DHTR_memory_map[LIS3DHTR_NUM_REGS] = {
    REG_MAP( GEN_ARRAY )
  };

The X macros are the single point of truth, and all data structures are automatically generated by the preprocessor. Similar macros are used to generate the field bit positions and masks.

PmodALS Driver

Driver implementation for the PmodALS ambient light sensor. The device is connected through SPI but it is read-only, thus there is no MISO line.

SPI must be configured as follows:

    • 12-bit data size. Although the ADC output is 8 bits, the device transmits leading and trailing padding bits; the driver removes them.
  • MSB first.
  • Baud rate between 1 & 4 Mbps.

Two implementations are provided:

  • No-OS: Blocks until communication with the device completes.
  • FreeRTOS: The task yields execution and resumes when an interrupt notifies that the transfer has completed.

The API consists of the following functions:

  • pmodals_create_handle(): The user must specify the sensor supply voltage (Vcc) and load resistance (Rload).
  • pmodals_get_lux(): Returns the ambient light intensity in lux.

Ambient light is calculated with the following formula:

$$ Lux = \frac{ADC_{value}}{ADC_{max}} \times \frac{V_{cc}}{R_{load}} \times Sensor_{transfer_function} $$

where

$$ Sensor_{transfer_function} = 2 \times 10^6 $$

Temperature Sensor

The module provides the MCU temperature on demand. Internally, the built-in temperature sensor is sampled periodically through ADC3.

The following configuration is used:

  • ADC conversions are triggered every 1 second using TIM2.
  • Samples are transferred to CM7 through DMA1.
  • DMA operates in double-buffer mode using the HAL multi-buffer DMA API. This provides deterministic low-latency access while avoiding torn reads.
  • In addition to the temperature sensor, the Vrefint channel is also sampled to compensate measurements for supply-voltage variation.
  • ADC3 is supplied with a 100 MHz clock. To achieve the ≥ 5 µs effective sampling time typically recommended by ST, the sampling time is configured to 810.5 cycles (~8.1 µs).

Three modes of calculating the temperature are provided:

  • Floating-point arithmetic.
  • Integer arithmetic.
  • Fixed-point (Q2.30) arithmetic to reduce division overhead.

As the internal sensor is relatively noisy, smoothing is applied in all three modes.

Lightweight Logging (LWL)

LWL is a lightweight logging system designed for low runtime and memory overhead. It is intended primarily for crash and failure analysis, by enabling high-granularity logging during normal operation with minimal impact on system performance. It is most useful when combined with a more verbose, higher-level logging system.

Firmware Implementation

LWL consists of a circular buffer stored in memory and a minimal API for writing and extracting log data. It supports variable-length records containing primitive data types.

API:

  • lwl_init(): Initializes or resets the module by clearing the buffer.
  • lwl_enter_record(): Main logging function. Appends a record to the buffer. See the following section for format requirements and limitations.
  • dump_log(): Exports the buffer contents and next write position through UART in binary format for post-mortem decoding.

The implementation tracks only the next write position in the buffer. Records cannot be inspected during runtime; the buffer is intended to be extracted and decoded post-mortem.

lwl_enter_record() Format

lwl_enter_record() is a variadic function. To ensure that it functions correctly and it is compatible with the decoding script that parses the codebase, all log writes must use the following format:

lwl_enter_record( MODULE_ID, FUNCTION_ID, "argument_types", arguments... );

Example:

lwl_enter_record(
    LIS3DHTR_LWL_ID,
    LIS3DHTR_READ_LWL_ID,
    "cc",
    device->i2c_address,
    device->memory_map[reg_address].address
);

IDs

MODULE_ID and FUNCTION_ID must be macros defined in lwl.h. MODULE_ID must be a single byte values, preferably single character literal to improve readability of the logs. FUNCTION_ID can be any string.

Example:

#define LIS3DHTR_LWL_ID      'L'
#define LIS3DHTR_READ_LWL_ID "RX"

The ( MODULE_ID, FUNCTION_ID ) combination must be globally unique across the project.

Argument types

The third argument is a format string describing the types of the remaining arguments.

Supported types:

Type Size Meaning
c 1 byte uint8_t / byte
s 2 bytes int16_t
h 2 bytes uint16_t
d 4 bytes int32_t
u 4 bytes uint32_t
f 4 bytes float

The number and order of format characters must match the provided arguments.

Examples:

lwl_enter_record( MOD, FUNC, "" );
lwl_enter_record( MOD, FUNC, "c", status );
lwl_enter_record( MOD, FUNC, "uh", timestamp, value );
lwl_enter_record( MOD, FUNC, "f", temperature );

Decoding script

The script lwl_decoder.py decodes the binary dumps of the LWL circular buffers.

The script parses the firmware source tree and automatically builds a database of all lwl_enter_record() calls, including:

  • module IDs and function IDs
  • argument names
  • format strings
  • entry sizes

The decoder then reads a binary buffer dump (dump.bin) and reconstructs the logged records using the generated database.

Requirements:

  • The source tree used by the script must match the firmware version that generated the dump. Changes to log definitions, IDs, formats or sizes can make decoding invalid.

  • The dump directory must contain an info.txt file with a line:

    next_entry_index = <number>
    

    where <number> is the buffer write index.

The script automatically:

  • resolves ID macros from lwl.h
  • unwraps the circular buffer
  • detects the non-wrapped buffer case
  • determines the most likely decoding start point
  • decodes and prints the recovered log entries in tabular form.

Repo Structure

code/
├── CM7/    # FreeRTOS + LwIP application
├── CM4/    # Secondary core application
├── sensor_playground.ioc   # CubeMX project file
lwl_decoder.py    # script that decodes lwl buffers

Tools Used

  • STM32CubeMX 6.17.0
  • STM32Cube FW_H7 V1.13.0
  • STM32CubeIDE 2.1.1
  • FreeRTOS 10.6.2
  • CMSIS-RTOS 2.1.3

About

Just experimenting with some sensors and the Nucleo-STM32H755ZI

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors

Languages