hello_mlc_activity

Example of the component cube32esp/cube32_bsp v0.0.17
# Hello MLC Activity Recognition

LSM6DSOX Machine Learning Core (MLC) activity recognition demo for CUBE32.

## Overview

This demo uses the **on-sensor decision tree engine** in the LSM6DSOX IMU to
classify real-time motion into 5 activities with **zero CPU overhead**:

| MLC Output | Activity    | UI Color  |
|------------|-------------|-----------|
| 0x00       | Stationary  | Blue-grey |
| 0x01       | Walking     | Green     |
| 0x04       | Jogging     | Orange    |
| 0x08       | Biking      | Blue      |
| 0x0C       | Driving     | Purple    |

> **Note:** MLC output values are non-sequential (0, 1, 4, 8, 12). The helper
> function `lsm6dsox_activity_is_known()` validates output codes.

The MLC processes accelerometer data at 26 Hz in a sliding window, computing
statistical features (mean, variance, energy, peak-to-peak) and running them
through a decision tree — all in hardware.

## UI Features (LVGL, adapts to the screen size)

The layout is computed from the display size at start-up (the log shows `Screen WxH, canvas WxH`):

| Screen | Chart canvas |
|--------|--------------|
| 240×240 | 220×60 |
| 320×320 | 300×140 |

The canvas spans the full width (10 px margins) and all the height between the duration timer and the legend; the history bars and the accelerometer waveform stretch with it, and the legend columns are spread over the screen width.

- **Activity icon + label** — large center display of current classification
- **Stability timer** — how long the current activity has persisted
- **History timeline** — color-coded scrolling bar of recent activities
- **Accel waveform** — real-time acceleration magnitude mini-graph
- **Legend** — color key for all 5 activity classes

## Prerequisites

Enable the following in `idf.py menuconfig`:

1. **CUBE32 Board Configuration → IMU Configuration → Enable IMU (LSM6DSO)**
2. **CUBE32 Board Configuration → IMU Configuration → Enable Machine Learning Core (MLC)**
3. **CUBE32 Board Configuration → Display Configuration → Enable LVGL**
4. **Component config → LVGL → Font usage**: Enable Montserrat 10, 14, 18, 24
5. **CUBE32 Application Selection → Hello MLC Activity Recognition Example**

Optional:

6. **CUBE32 Board Configuration → MLC diagnostic logging** — enables verbose
   register dumps at startup (register values, program memory readback, etc.)

## Build & Flash

```bash
source ~/esp/v5.5.1/esp-idf/export.sh 2>/dev/null
idf.py set-target esp32s3
idf.py menuconfig    # Enable options above
idf.py build flash monitor
```

## How It Works

1. `cube32_init()` initializes the board and IMU with default settings
2. `load_mlc_config()` calls `imu.loadMLC()` with the UCF data (773 register
   writes), which:
   - Iterates each `{address, data}` pair, writing to the sensor over I2C
   - Adds 100 µs delays after program-memory writes (register 0x09) since the
     I2C bus at 400 kHz is faster than the sensor's internal memory write cycle
   - Adds 300 µs delays after page-switch writes (register 0x01)
   - Reconfigures the accelerometer to ±4 g @ 26 Hz
   - Programs the decision tree into MLC hardware memory
   - Enables the MLC engine
3. After a 5-second warmup (for the sliding window to fill), the MLC
   continuously classifies motion in hardware
4. The demo polls `MLC0_SRC` on the embedded function page for classification
   results at 5 Hz via an LVGL timer

## Console Output

The demo prints activity status to the serial console at 1 Hz:

```
Activity: Stationary   |mag|=1.00g  Duration: 5s  MLC0_SRC=0x00  MLC_STATUS=0x01
Activity: Walking      |mag|=1.23g  Duration: 2s  MLC0_SRC=0x01  MLC_STATUS=0x01
Activity: Jogging      |mag|=1.85g  Duration: 3s  MLC0_SRC=0x04  MLC_STATUS=0x01
```

---

## How to Create and Import an MLC Decision Tree

This section explains the full pipeline for converting a decision tree model
into a UCF configuration that runs on the LSM6DSOX MLC hardware.

### Toolchain Overview

```
Training Data (.csv)
       │
       ▼
┌──────────────────┐
│  ST Unico GUI /  │  ← Desktop tool from ST
│  MEMS Studio     │
└───────┬──────────┘
        │  generates
        ▼
   UCF file (.ucf)       ← Register-write sequence (text)
        │
        ▼
┌──────────────────┐
│  Convert to C    │  ← Python script or manual
│  header (.h)     │
└───────┬──────────┘
        │
        ▼
   ucf_line_t array       ← Compiled into firmware
        │
        ▼
┌──────────────────┐
│  imu.loadMLC()   │  ← Written to sensor over I2C at boot
└──────────────────┘
```

### Step 1: Collect Training Data

Record accelerometer data for each activity class you want to detect. Use the
LSM6DSOX evaluation board or the CUBE32 itself to log raw data to CSV:

- Sample at the target ODR (typically 26 Hz for activity recognition)
- Collect at least 30–60 seconds per class
- Include varied conditions (pocket, hand, desk, etc.)

### Step 2: Design the Decision Tree in Unico GUI / MEMS Studio

ST provides two desktop tools for MLC configuration:

- **Unico GUI** — legacy tool, well-documented
  ([download](https://www.st.com/en/development-tools/unico-gui.html))
- **MEMS Studio** — newer replacement
  ([download](https://www.st.com/en/development-tools/mems-studio.html))

Within the tool:

1. Select the **LSM6DSOX** device
2. Open the **MLC** tab
3. Configure the **accelerometer** settings (ODR, full-scale)
4. Define **features** for each window (mean, variance, energy, peak-to-peak,
   min, max, etc.)
5. Import your training CSV data and assign class labels
6. The tool trains a decision tree and shows accuracy metrics
7. Click **Generate UCF** → save as `.ucf` file

> **Tip:** ST provides pre-built UCF examples for common use cases at
> [github.com/STMicroelectronics/STMems_Machine_Learning_Core](https://github.com/STMicroelectronics/STMems_Machine_Learning_Core).
> The activity recognition UCF used in this demo comes from the
> `lsm6dsox/activity_recognition_for_mobile` example in that repository.

### Step 3: Convert the UCF File to a C Header

A `.ucf` file is a text file with lines like:

```
Ac 10 00
Ac 11 00
Ac 01 80
Ac 04 00
...
```

Each line is: `Ac <register_address_hex> <data_hex>`.

Convert this to a C header containing a `ucf_line_t` array:

```python
#!/usr/bin/env python3
"""Convert a .ucf text file to a C header with ucf_line_t array."""

import sys, re

def convert_ucf(ucf_path, header_path, array_name):
    entries = []
    with open(ucf_path) as f:
        for line in f:
            m = re.match(r'Ac\s+([0-9A-Fa-f]{2})\s+([0-9A-Fa-f]{2})', line.strip())
            if m:
                entries.append((m.group(1).upper(), m.group(2).upper()))

    guard = array_name.upper() + "_H"
    with open(header_path, "w") as out:
        out.write(f"#ifndef {guard}\n#define {guard}\n\n")
        out.write("#ifndef MEMS_UCF_SHARED_TYPES\n")
        out.write("#define MEMS_UCF_SHARED_TYPES\n")
        out.write("typedef struct { uint8_t address; uint8_t data; } ucf_line_t;\n")
        out.write("#endif\n\n")
        out.write(f"static const ucf_line_t {array_name}[] = {{\n")
        for addr, data in entries:
            out.write(f"  {{0x{addr}, 0x{data}}},\n")
        out.write("};\n\n")
        out.write(f"static const size_t {array_name}_size =\n")
        out.write(f"    sizeof({array_name}) / sizeof(ucf_line_t);\n\n")
        out.write(f"#endif /* {guard} */\n")

    print(f"Converted {len(entries)} entries → {header_path}")

if __name__ == "__main__":
    convert_ucf(sys.argv[1], sys.argv[2], sys.argv[3])
```

Usage:

```bash
python3 convert_ucf.py  my_model.ucf  my_model.h  my_model_ucf
```

### Step 4: Add the Header to the CUBE32 Project

Place the generated header in the MLC include directory:

```
components/cube32/include/drivers/imu/mlc/my_model.h
```

### Step 5: Include and Load in Your Application

```cpp
#include "drivers/imu/mlc/my_model.h"

// In app_main or init function:
cube32::IMU &imu = cube32::IMU::instance();
cube32_result_t ret = imu.loadMLC(my_model_ucf, my_model_ucf_size);
if (ret != CUBE32_OK) {
    ESP_LOGE(TAG, "MLC load failed");
}
```

### Step 6: Read MLC Output

```cpp
uint8_t mlc_out = 0;
imu.readMLCOutput(0, mlc_out);  // tree_index = 0
// mlc_out now contains the class label from the decision tree
```

### Important Notes

- **Do not modify the UCF data.** The register writes must be played back
  verbatim in the exact order generated by Unico/MEMS Studio. The sequence
  includes page switches, embedded function configuration, and program memory
  writes that depend on ordering.
- **Output class values are defined by the UCF.** They are typically
  non-sequential integers assigned during training. Add helper functions
  (like `lsm6dsox_activity_label()`) to map them to human-readable strings.
- **The MLC needs a warmup period** after loading. The sliding window must
  fill before the first valid classification. At 26 Hz with a 1-second window,
  allow ~5 seconds for stable output.
- **I2C bus speed matters.** At 400 kHz the bus can outpace the sensor's
  internal program-memory write cycle. The CUBE32 driver adds micro-second
  delays automatically in `loadMLC()` to handle this.
- **Up to 8 decision trees** can run simultaneously on the LSM6DSOX MLC.
  Read tree N output with `imu.readMLCOutput(N, result)` or all 8 at once
  with `imu.readMLCOutputAll(results)`.

## File Structure

```
hello_mlc_activity/
├── main.cpp                          # Demo application
├── README.md                         # This file
└── (UCF header is at)
    components/cube32/include/drivers/imu/mlc/
    └── lsm6dsox_activity_recognition.h   # 773-entry UCF + helpers
```

## References

- [STMems_Machine_Learning_Core](https://github.com/STMicroelectronics/STMems_Machine_Learning_Core) — ST's official MLC examples and UCF files
- [LSM6DSOX Datasheet](https://www.st.com/resource/en/datasheet/lsm6dsox.pdf) — Register map and MLC description
- [AN5259](https://www.st.com/resource/en/application_note/an5259-lsm6dsox-machine-learning-core-stmicroelectronics.pdf) — ST Application Note: LSM6DSOX Machine Learning Core
- [Unico GUI](https://www.st.com/en/development-tools/unico-gui.html) — GUI tool for MLC configuration

To create a project from this example, run:

idf.py create-project-from-example "cube32esp/cube32_bsp=0.0.17:hello_mlc_activity"

or download archive (~15.12 KB)