IoT und KI

TensorFlow Lite Micro: Einstieg auf einem Mikrocontroller mit MQTT-Ausgabe

Eine praxisnahe Einführung in TensorFlow Lite for Microcontrollers: Modell, Op-Resolver, Tensor-Arena und Interpreter erklärt, ein vollständiges Beispiel auf einem ESP32-S3-Board und eine Seriell-zu-MQTT-Bridge, die mit jedem Board funktioniert.

Illustration zum Einstieg in TensorFlow Lite Micro mit einem quantisierten Modell, das in einen Mikrocontroller geladen wird, und Vorhersagen, die an einen MQTT-Broker gesendet werden
Probieren Sie es auf Ihrem eigenen MQTT-Broker ausHolen Sie sich im Free-Tarif einen privaten, verwalteten MQTT-Broker und starten Sie mit fertigen Codebeispielen.
Kostenlos testen Codebeispiele Keine Kreditkarte erforderlich

TensorFlow Lite Micro ist die Runtime, mit der die meisten TinyML-Projekte neuronale Netze auf Mikrocontrollern ausführen, und ihre API ist überschaubar, sobald Sie vier Objekte verstanden haben: das Modell, den Op-Resolver, die Tensor-Arena und den Interpreter. Dieser Einstiegsleitfaden erklärt jedes davon, führt Sie durch ein vollständiges Beispiel auf einem ESP32-S3-Board und zeigt, wie Sie Vorhersagen an einen MQTT-Broker senden, entweder direkt über WLAN oder über eine serielle Bridge für Boards ohne Netzwerk.

Was TensorFlow Lite Micro ist

TensorFlow Lite for Microcontrollers (oft mit TFLM abgekürzt und inzwischen Teil der LiteRT-Familie von Google) ist eine C++-Inferenz-Engine für Geräte ohne Betriebssystem, ohne dynamische Speicherzuweisung während der Inferenz und mit sehr wenig RAM. Sie liest dasselbe .tflite-Flatbuffer-Format wie TensorFlow Lite auf Smartphones, unterstützt aber einen kleineren Satz an Operatoren und wird direkt in Ihre Firmware kompiliert.

TFLM übernimmt ausschließlich die Inferenz. Das Training findet in TensorFlow auf einem Computer statt; der Mikrocontroller erhält ein konvertiertes, meist int8-quantisiertes Modell. Wenn Sie das größere Bild suchen, warum man ML überhaupt auf so kleinen Chips ausführt, lesen Sie was TinyML ist.

Die vier Kernkonzepte

Objekt Was es ist Was schiefgeht
tflite::Model Der Flatbuffer, als Byte-Array im Flash eingebettet Schema-Version passt nicht zur Bibliothek
MicroMutableOpResolver<N> Die Liste der in die Firmware gelinkten Operator-Kernels Fehlender Operator zur Laufzeit; Template-Wert N zu klein
Tensor-Arena Ein statischer Byte-Puffer für Eingaben, Ausgaben und Zwischentensoren AllocateTensors() schlägt fehl, wenn sie zu klein ist
MicroInterpreter Verbindet Modell, Resolver und Arena und führt Invoke() aus Eingabe als float geschrieben, obwohl das Modell int8 erwartet

Ein typisches Programm erledigt beim Start einmalig Folgendes: Modell abbilden, die verwendeten Operatoren registrieren, den Interpreter erzeugen, Tensoren allokieren. Dann für jede Inferenz: Eingabetensor schreiben, Invoke() aufrufen, Ausgabetensor lesen. Das ist die gesamte Runtime-API, die Sie für die meisten Projekte brauchen.

Schritt 1: Ein Modell trainieren und konvertieren

Das klassische erste TFLM-Modell lernt sin(x). Es ist klein, lässt sich mit bloßem Auge überprüfen und nutzt quantisierte Ein- und Ausgaben. Dieses Skript trainiert es, konvertiert es vollständig nach int8 und schreibt C++-Quelldateien für die Firmware.

pip install tensorflow numpy
# train_sine.py
import numpy as np
import tensorflow as tf

rng = np.random.default_rng(1)
x = rng.uniform(0, 2 * np.pi, 3000).astype(np.float32)
y = (np.sin(x) + 0.05 * rng.standard_normal(x.size)).astype(np.float32)

model = tf.keras.Sequential([
    tf.keras.Input(shape=(1,)),
    tf.keras.layers.Dense(16, activation="relu"),
    tf.keras.layers.Dense(16, activation="relu"),
    tf.keras.layers.Dense(1),
])
model.compile(optimizer="adam", loss="mse")
model.fit(x, y, epochs=200, batch_size=64, validation_split=0.2, verbose=0)
print("val mse:", model.evaluate(x[:500], y[:500], verbose=0))


def rep():
    for v in np.linspace(0, 2 * np.pi, 300, dtype=np.float32):
        yield [np.array([[v]], dtype=np.float32)]


conv = tf.lite.TFLiteConverter.from_keras_model(model)
conv.optimizations = [tf.lite.Optimize.DEFAULT]
conv.representative_dataset = rep
conv.target_spec.supported_ops = [tf.lite.OpsSet.TFLITE_BUILTINS_INT8]
conv.inference_input_type = tf.int8
conv.inference_output_type = tf.int8
tfl = conv.convert()
print("model bytes:", len(tfl))

with open("tflm_sine/main/model_data.cc", "w") as f:
    f.write('#include "model_data.h"\n')
    f.write("alignas(16) const unsigned char g_model[] = {%s};\n" % ", ".join(f"0x{b:02x}" for b in tfl))
    f.write(f"const unsigned int g_model_len = {len(tfl)};\n")
with open("tflm_sine/main/model_data.h", "w") as f:
    f.write("#pragma once\nextern const unsigned char g_model[];\nextern const unsigned int g_model_len;\n")

Bevor Sie Firmware schreiben, prüfen Sie die Operatoren im konvertierten Modell. Wenn Sie die .tflite-Datei in einem Modellbetrachter wie Netron öffnen oder tf.lite.experimental.Analyzer.analyze(model_content=tfl) verwenden, sehen Sie, dass dieses Modell nur FULLY_CONNECTED-Operatoren enthält. Genau diese Liste registrieren Sie im Resolver.

Warum für TensorFlow Lite Micro quantisieren

Mikrocontroller rechnen mit Ganzzahlen weitaus schneller als mit Gleitkommazahlen, und viele haben überhaupt keine Gleitkommaeinheit. Vollständige int8-Quantisierung speichert jedes Gewicht in einem statt vier Bytes und führt jede Schicht mit Ganzzahl-Kernels aus. Das verringert den Flash-Bedarf, verkleinert die Tensor-Arena und lässt optimierte Bibliotheken wie CMSIS-NN oder die Kernels von Espressif ihre Arbeit tun. Der Preis ist ein kleiner Präzisionsverlust und der zusätzliche Schritt, Ein- und Ausgaben mit scale und zero_point des jeweiligen Tensors umzurechnen, was die Firmware weiter unten explizit erledigt.

Flash und RAM: Wo das Modell liegt

Das Modell-Array ist als const deklariert, daher legt der Linker es im Flash ab, und es kostet keinen RAM. Die Tensor-Arena ist der RAM-Posten: Eingaben, Ausgaben, Zwischenaktivierungen und etwas Verwaltungsaufwand des Interpreters. Bei der ESP32-Familie ist interner RAM die schnelle Option; sehr große Arenen können auf Boards mit PSRAM dort liegen, mit gewissen Geschwindigkeitseinbußen. Für dieses Sinusmodell braucht die Arena deutlich weniger als die unten reservierten 4 KB, was Sie an der Logzeile der Firmware ablesen können.

Schritt 2: Das Board-Projekt einrichten

Dieser Leitfaden verwendet ein ESP32-S3-Entwicklungsboard, das sich auch mit der Arduino IDE programmieren lässt, baut aber mit ESP-IDF, weil die Komponente esp-tflite-micro von Espressif der Upstream-API von TFLM folgt und optimierte Kernels hinzufügt. Auf anderen Boards bleibt der C++-Code aus dem nächsten Schritt gleich; nur das Build-System ändert sich.

tflm_sine/
  CMakeLists.txt
  main/
    CMakeLists.txt
    main.cc
    model_data.cc   (generated)
    model_data.h    (generated)
# tflm_sine/CMakeLists.txt
cmake_minimum_required(VERSION 3.16)
include($ENV{IDF_PATH}/tools/cmake/project.cmake)
project(tflm_sine)
# tflm_sine/main/CMakeLists.txt
idf_component_register(SRCS "main.cc" "model_data.cc" INCLUDE_DIRS ".")
cd tflm_sine
idf.py set-target esp32s3
idf.py add-dependency "espressif/esp-tflite-micro"

Schritt 3: Inferenz mit TensorFlow Lite Micro ausführen

Die Firmware durchläuft x von 0 bis 2π, quantisiert jeden Wert mit Skalierung und Nullpunkt des Eingabetensors, führt das Modell aus und gibt pro Vorhersage eine JSON-Zeile auf der seriellen Konsole aus.

// tflm_sine/main/main.cc
#include <algorithm>
#include <cmath>
#include <cstdio>

#include "esp_log.h"
#include "esp_timer.h"
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "tensorflow/lite/micro/micro_interpreter.h"
#include "tensorflow/lite/micro/micro_mutable_op_resolver.h"
#include "tensorflow/lite/schema/schema_generated.h"

#include "model_data.h"

static const char *TAG = "tflm";
static constexpr int kArenaSize = 4 * 1024;
alignas(16) static uint8_t tensor_arena[kArenaSize];

extern "C" void app_main(void) {
  // 1. Map the model
  const tflite::Model *model = tflite::GetModel(g_model);
  if (model->version() != TFLITE_SCHEMA_VERSION) {
    ESP_LOGE(TAG, "model schema %lu != runtime %d", (unsigned long)model->version(), TFLITE_SCHEMA_VERSION);
    return;
  }

  // 2. Register only the operators the model uses
  static tflite::MicroMutableOpResolver<3> resolver;
  resolver.AddFullyConnected();
  resolver.AddQuantize();
  resolver.AddDequantize();

  // 3. Build the interpreter over a static arena and allocate tensors
  static tflite::MicroInterpreter interpreter(model, resolver, tensor_arena, kArenaSize);
  if (interpreter.AllocateTensors() != kTfLiteOk) {
    ESP_LOGE(TAG, "AllocateTensors failed, increase kArenaSize");
    return;
  }
  ESP_LOGI(TAG, "arena used %u of %d bytes", (unsigned)interpreter.arena_used_bytes(), kArenaSize);

  TfLiteTensor *input = interpreter.input(0);
  TfLiteTensor *output = interpreter.output(0);
  const float two_pi = 2.0f * (float)M_PI;
  int step = 0;

  while (true) {
    float x = two_pi * (step % 40) / 40.0f;

    // 4. Quantize the input: q = x / scale + zero_point
    int q = (int)lroundf(x / input->params.scale) + input->params.zero_point;
    input->data.int8[0] = (int8_t)std::min(127, std::max(-128, q));

    int64_t t0 = esp_timer_get_time();
    if (interpreter.Invoke() != kTfLiteOk) {
      ESP_LOGE(TAG, "Invoke failed");
      break;
    }
    int us = (int)(esp_timer_get_time() - t0);

    // 5. Dequantize the output: y = (q - zero_point) * scale
    float y = (output->data.int8[0] - output->params.zero_point) * output->params.scale;
    printf("{\"x\":%.3f,\"y\":%.3f,\"ref\":%.3f,\"us\":%d}\n", x, y, sinf(x), us);

    step++;
    vTaskDelay(pdMS_TO_TICKS(500));
  }
}
python train_sine.py
cd tflm_sine
idf.py build flash monitor

Der Monitor zeigt Zeilen wie {"x":1.571,"y":0.98,"ref":1.000,"us":...}. Das vorhergesagte y sollte ref eng folgen, mit kleinen Abweichungen durch die Quantisierung. Ist jede Ausgabe derselbe Wert, wird die Eingabe mit an Sicherheit grenzender Wahrscheinlichkeit ohne Quantisierung geschrieben.

Schritt 4: Vorhersagen über MQTT senden

Es gibt zwei Wege, diese Vorhersagen auf einen Broker zu bringen.

Direkt über WLAN. Auf WLAN-fähigen Boards wie dem ESP32-S3 fügen Sie den MQTT-Client von ESP-IDF hinzu und veröffentlichen jede JSON-Zeile, statt sie auszugeben. Das Tutorial zu TinyML auf dem ESP32 enthält eine vollständige Firmware, die genau das tut, einschließlich Last Will und Veröffentlichung bei Änderungen.

Serielle Bridge. Viele beliebte TFLM-Boards, etwa Cortex-M-Boards mit integrierten Sensoren, haben kein WLAN. Sie können JSON über USB-seriell ausgeben, und ein Computer oder Raspberry Pi leitet es weiter. Diese Bridge funktioniert mit der obigen Firmware ohne Änderungen:

pip install pyserial "paho-mqtt>=2.0"
# serial_to_mqtt.py  usage: python serial_to_mqtt.py COM5   (or /dev/ttyACM0)
import json
import sys
import uuid

import paho.mqtt.client as mqtt
import serial

port = sys.argv[1]
run = uuid.uuid4().hex[:8]
topic = f"connectmq/demo/{run}/lab/tflm-board/inference"

client = mqtt.Client(mqtt.CallbackAPIVersion.VERSION2, client_id=f"tflm-bridge-{run}")
client.will_set(f"connectmq/demo/{run}/lab/tflm-board/status", "offline", qos=1, retain=True)
client.connect("broker.connectmq.com", 1883, keepalive=60)
client.loop_start()
client.publish(f"connectmq/demo/{run}/lab/tflm-board/status", "online", qos=1, retain=True)
print("publishing to", topic)

with serial.Serial(port, 115200, timeout=2) as ser:
    while True:
        line = ser.readline().decode("utf-8", errors="ignore").strip()
        if not line.startswith("{"):
            continue                      # skip boot and log lines
        try:
            json.loads(line)              # forward only valid JSON
        except json.JSONDecodeError:
            continue
        client.publish(topic, line, qos=0)
        print(line)

Schließen Sie zuerst idf.py monitor, da nur ein Programm die serielle Schnittstelle belegen kann. Beobachten Sie den Datenstrom dann von einem beliebigen Rechner aus:

mosquitto_sub -h broker.connectmq.com -p 1883 -i tflm-viewer-12 -t 'connectmq/demo/+/lab/tflm-board/#' -v

Der kostenlose öffentliche MQTT-Broker eignet sich für Tests und Prototypen, solange Sie daran denken, dass jeder dort veröffentlichte Nachrichten lesen kann; echte Produktionsdaten gehören nicht dorthin. Produktive Geräte sollten private Zugangsdaten und TLS auf Port 8883 verwenden; in der Bridge bedeutet das, client.tls_set() und client.username_pw_set() mit den Werten Ihres ConnectMQ-Brokers aufzurufen und die ausgegebene Client ID zu verwenden.

Das Bridge-Muster hat einen nützlichen Nebeneffekt: Die Board-Firmware bleibt frei von Netzwerkcode, Zertifikaten und Zugangsdaten, sodass Sie Broker wechseln, TLS ergänzen oder Topics auf dem Gateway ändern können, ohne etwas neu zu flashen. Der Nachteil ist ein weiteres Gerät, das am Laufen gehalten werden muss. Für eine Handvoll Laborboards an einem Computer ist das ideal; für eine Flotte im Feld sind Boards mit integriertem WLAN, die direkt veröffentlichen, meist einfacher zu betreiben.

Entscheiden, was veröffentlicht wird

Ein Regressionsmodell wie dieses liefert bei jedem Aufruf einen Wert, doch die meisten realen Anwendungen interessieren sich für Ereignisse: einen Wert, der eine Grenze überschreitet, eine Klasse, die wechselt, oder eine sinkende Konfidenz. In der Fertigung ist das typischerweise ein Vibrationsmodell an Motor oder Spindel, das für Predictive Maintenance nur dann meldet, wenn sich das Schwingungsbild einer Maschine auffällig ändert; im Werk landet ein solches Ereignis dann unter einem Topic wie acme/werk-muenchen/montage/linie-1/spindel-3/anomalie. Legen Sie auf dem Gerät oder in der Bridge fest, was als Ereignis zählt, und veröffentlichen Sie diese Ereignisse plus einen langsamen Heartbeat. So bleibt der Datenverkehr deutlich innerhalb der Ratenlimits pro Client, und nachgelagerte Konsumenten werden einfacher.

Fehlerbehebung bei TensorFlow Lite Micro

  1. Didn't find op for builtin opcode: Registrieren Sie den fehlenden Operator und erhöhen Sie die Template-Anzahl des Resolvers.
  2. AllocateTensors() schlägt fehl: Vergrößern Sie die Arena und verkleinern Sie sie, sobald es funktioniert, auf arena_used_bytes() plus etwas Reserve.
  3. Hard Fault oder Absturz in Invoke(): Das Modell-Array ist nicht ausgerichtet, oder die Arena überlappt anderen Speicher. Behalten Sie alignas(16) bei beiden bei.
  4. Genauigkeit deutlich schlechter als auf dem PC: Der repräsentative Datensatz hat den realen Eingabebereich nicht abgedeckt, sodass die Quantisierung Werte abgeschnitten hat. Verwenden Sie echte Samples in representative_dataset.
  5. Kompilierfehler zu MicroErrorReporter oder AllOpsResolver: Sie mischen alten Beispielcode mit einer neueren TFLM-Version. Aktuelle Versionen verwenden den oben gezeigten Konstruktor und MicroMutableOpResolver.

Wie es weitergeht

Ersetzen Sie das Sinusmodell durch einen Klassifikator, der mit echten Sensormerkmalen gespeist wird, wie in TinyML auf dem ESP32, oder lassen Sie mit dem Edge-Impulse-MQTT-Tutorial eine Plattform die Pipeline für Sie erzeugen. Das offizielle TensorFlow-Lite-Micro-Repository dokumentiert die unterstützten Operatoren und die Portierung auf neue Boards. Für den architektonischen Kontext zeigt der Leitfaden zu KI und IoT, wie Mikrocontroller-Modelle neben Cloud-Analysen ihren Platz finden.

Starten Sie mit ConnectMQ

Ob Vorhersagen Ihr Board über WLAN oder über eine Bridge verlassen: Sie brauchen ein zuverlässiges Ziel. ConnectMQ ist ein verwalteter MQTT-Broker mit TLS, MQTT 3.1.1 und 5.0 und einem MQTT-Client im Browser, mit dem Sie Nachrichten beim Eintreffen prüfen können. Erstellen Sie einen Broker im kostenlosen Serverless-Plan unter app.connectmq.com, ohne Kreditkarte.

Häufig gestellte Fragen

Was ist der Unterschied zwischen TensorFlow Lite und TensorFlow Lite Micro?

TensorFlow Lite richtet sich an Smartphones und Linux-Geräte mit Betriebssystem und dynamischem Speicher. TensorFlow Lite Micro ist eine abgespeckte Runtime für Mikrocontroller, die eine statische Arena, einen reduzierten Operatorsatz und keine Betriebssystemabhängigkeiten nutzt.

Wie wähle ich die Größe der Tensor-Arena?

Beginnen Sie mit einer großzügigen Größe, rufen Sie AllocateTensors() auf, lesen Sie dann arena_used_bytes() aus und setzen Sie die Arena auf diesen Wert plus eine Sicherheitsreserve. Der Bedarf hängt von den größten Zwischentensoren des Modells ab.

Unterstützt TensorFlow Lite Micro Float-Modelle?

Ja, viele Operatoren haben Float-Kernels, aber int8-Modelle sind auf den meisten Mikrocontrollern kleiner und deutlich schneller. Vollständige Ganzzahlquantisierung ist die übliche Wahl für die Produktion.

Kann TensorFlow Lite Micro selbst Daten über MQTT senden?

Nein. TFLM führt nur die Inferenz aus. Ergebnisse veröffentlichen Sie mit einem separaten MQTT-Client, etwa dem MQTT-Client von ESP-IDF, PubSubClient oder einer seriellen Bridge auf einem Gateway.

Verwandte Artikel

Alle Artikel →

Ihr privater MQTT-Broker ist nur wenige Klicks entfernt

Konto anlegen, Broker-Zugangsdaten erhalten und das erste Gerät verbinden — ohne Server, die installiert, gepatcht oder skaliert werden müssen.