Покроковий довідник до статті про TinyML на Raspberry Pi Pico. Усе, що потрібно, аби повторити проєкт з нуля: середовище, бібліотеки, заливка, типові помилки.
Що знадобиться
Залізо:
- Raspberry Pi Pico (RP2040) — будь-яка ревізія
- MPU6050 (акселерометр + гіроскоп), модуль на GY-521 — підійде
- 4 перемички
- USB-кабель microUSB (для прошивки і живлення)
Софт:
- Arduino IDE 2.x (або 1.8.x)
- ядро
arduino-picoвід Earle Philhower - бібліотека
Chirale_TensorFlowLite - Python з TensorFlow (для навчання моделі — можна Google Colab у браузері)
Крок 1. Ставимо ядро arduino-pico
За замовчуванням Arduino IDE не знає про Pi Pico. Треба додати «board manager URL».
- Відкрий File → Preferences
- У полі Additional Boards Manager URLs додай:
12https://github.com/earlephilhower/arduino-pico/releases/download/global/package_rp2040_index.json - Tools → Board → Boards Manager, знайди
Raspberry Pi Pico/RP2040, постав. - Обери плату: Tools → Board → Raspberry Pi Pico/RP2040 → Raspberry Pi Pico
⚠ Не плутай з офіційним ядром Arduino Mbed OS. На вибір буде два —
arduino-pico(від Earle Philhower) іArduino Mbed OS RP2040 Boards(офіційне). Бери перше. Воно сумісніше з більшістю бібліотек і не тягне за собою mbed-абстракцій.
Крок 2. Ставимо Chirale_TensorFlowLite
Через стандартний Library Manager:
- Sketch → Include Library → Manage Libraries
- У пошуку:
Chirale_TensorFlowLite - Натисни Install
Бібліотека з офіційного каталогу Arduino: https://docs.arduino.cc/libraries/chirale_tensorflowlite/
⚠ Чесно про сумісність з Pico. Бібліотека офіційно протестована на Arduino Nano 33 BLE, Nicla, Portenta, Giga R1, Nano ESP32. RP2040 у списку підтверджених немає, автори прямо пишуть: «сумісність з іншими платами не гарантується, перевіряйте самі». Я перевірив — під
arduino-picoпрацює. Якщо хочеш канонічний шлях саме для Pico — єpico-tflmicro(офіційний порт під Pico SDK), але він живе вже не в Arduino IDE.
Крок 3. Підключаємо MPU6050
Чотири дроти між Pi Pico і модулем MPU6050:
| Pi Pico | MPU6050 |
|---|---|
| 3V3 (OUT, pin 36) | VCC |
| GND | GND |
| GP16 | SDA |
| GP17 | SCL |
⚠ Я живлю від 3.3 В, не від 5 В. Більшість GY-521 мають внутрішній регулятор і терплять 5 В, але рівні логіки I2C тоді стають 5 В — і прямо в I/O Pico вони не безпечні. Безпечніше тримати все на 3.3 В.
Pull-up резистори на SDA/SCL на модулях GY-521 уже розпаяні (зазвичай 4.7 кΩ або 10 кΩ), додаткові не потрібні.
Крок 4. Тренуємо модель (на ПК або в Colab)
Скрипт train_model_MPU.py створює просту модель і експортує її як model_data.h. Запускати не на Pico, а на ПК.
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 |
# === Google Colab Notebook === # Навчання моделі для розпізнавання жестів import pandas as pd import numpy as np def load_real_dataset(): X, y = [], [] # Словник файлів та їх міток (labels) data_files = { 'rest.csv': 0, 'up.csv': 1, 'down.csv': 2, 'shake.csv': 3 } for filename, label in data_files.items(): # Завантажуємо дані з CSV (припускаємо, що там 3 колонки: ax, ay, az) df = pd.read_csv(filename) for _, row in df.iterrows(): X.append([row['ax'], row['ay'], row['az']]) y.append(label) return np.array(X, dtype=np.float32), np.array(y, dtype=np.int32) X, y = load_real_dataset() print(f"Dataset: {X.shape}, Labels: {y.shape}") # 2. Створюємо просту модель model = tf.keras.Sequential([ tf.keras.layers.Dense(16, activation='relu', input_shape=(3,)), tf.keras.layers.Dense(4, activation='softmax') ]) model.compile(optimizer='adam', loss='sparse_categorical_crossentropy', metrics=['accuracy']) # 3. Навчаємо model.fit(X, y, epochs=50, validation_split=0.2, verbose=2) # 4. Конвертуємо в TFLite converter = tf.lite.TFLiteConverter.from_keras_model(model) converter.optimizations = [tf.lite.Optimize.DEFAULT] tflite_model = converter.convert() # 5. Зберігаємо with open('model.tflite', 'wb') as f: f.write(tflite_model) # 6. Конвертуємо в C array def convert_to_c_array(data): hex_array = ', '.join([f'0x{b:02x}' for b in data]) return f"const unsigned char model[] = {{\n {hex_array}\n}};\nconst int model_len = {len(data)};" c_array = convert_to_c_array(tflite_model) print("\n=== Скопіюйте це в Arduino ===") print(c_array[:500] + "...") print(f"\nРозмір моделі: {len(tflite_model)} байт") # Зберігаємо у файл with open('model_data.h', 'w') as f: f.write("#ifndef MODEL_DATA_H\n#define MODEL_DATA_H\n\n") f.write(c_array) f.write("\n\n#endif\n") |
Залежності:
|
1 2 |
pip install tensorflow pandas numpy |
Або, простіше — відкрий Google Colab, скопіюй вміст train_model_MPU.py у новий ноутбук, натисни «Run All». На виході — файл model_data.h, який можна завантажити.
Крок 5. Збираємо проєкт
Структура теки скетча:
|
1 |
// <code>Arduino_TFL_MPU6050_TrainModel.ino</code> #include <Wire.h> #include <Chirale_TensorFlowLite.h> #include "model_data.h" #include <tensorflow/lite/micro/micro_mutable_op_resolver.h> #include <tensorflow/lite/micro/micro_interpreter.h> #include <tensorflow/lite/schema/schema_generated.h> const int MPU_ADDR = 0x68; // TFLite об'єкти const tflite::Model* tfl_model = nullptr; tflite::MicroInterpreter* interpreter = nullptr; TfLiteTensor* input = nullptr; TfLiteTensor* output = nullptr; constexpr int kTensorArenaSize = 20 * 1024; alignas(16) uint8_t tensor_arena[kTensorArenaSize]; const char* class_names[] = {"Спокій", "Вгору", "Вниз", "Струс"}; void setup() { // 1. Чекаємо на Serial Serial.begin(115200); delay(2000); Serial.println("--- СТАРТ ПРОГРАМИ ---"); // 2. Ініціалізація I2C (ваші піни) Serial.println("Налаштування I2C (GP16, GP17)..."); Wire.setSDA(16); Wire.setSCL(17); Wire.begin(); // 3. Перевірка зв'язку з MPU6050 Wire.beginTransmission(MPU_ADDR); byte error = Wire.endTransmission(); if (error != 0) { Serial.print("ПОМИЛКА: Датчик не знайдено за адресою 0x68. Код помилки: "); Serial.println(error); while(1); // Зупиняємося тут, якщо датчик не відповідає } Serial.println("MPU6050 знайдено!"); // Пробудження датчика Wire.beginTransmission(MPU_ADDR); Wire.write(0x6B); Wire.write(0); Wire.endTransmission(); // 4. Ініціалізація нейромережі Serial.println("Завантаження моделі..."); tfl_model = tflite::GetModel(model); static tflite::MicroMutableOpResolver<3> resolver; resolver.AddFullyConnected(); resolver.AddRelu(); resolver.AddSoftmax(); static tflite::MicroInterpreter static_interpreter( tfl_model, resolver, tensor_arena, kTensorArenaSize); interpreter = &static_interpreter; if (interpreter->AllocateTensors() != kTfLiteOk) { Serial.println("Помилка виділення пам'яті тензорам!"); while(1); } input = interpreter->input(0); output = interpreter->output(0); Serial.println("СИСТЕМА ГОТОВА!"); Serial.println("-------------------------"); } void loop() { // 5. Читання даних Wire.beginTransmission(MPU_ADDR); Wire.write(0x3B); Wire.endTransmission(false); Wire.requestFrom(MPU_ADDR, 6, true); if (Wire.available() == 6) { int16_t raw_ax = (Wire.read() << 8 | Wire.read()); int16_t raw_ay = (Wire.read() << 8 | Wire.read()); int16_t raw_az = (Wire.read() << 8 | Wire.read()); // Конвертація в g float ax = raw_ax / 16384.0; float ay = raw_ay / 16384.0; float az = raw_az / 16384.0; // 6. Інференс if (input->type == kTfLiteInt8) { input->data.int8[0] = (int8_t)(ax / input->params.scale + input->params.zero_point); input->data.int8[1] = (int8_t)(ay / input->params.scale + input->params.zero_point); input->data.int8[2] = (int8_t)(az / input->params.scale + input->params.zero_point); } else { input->data.f[0] = ax; input->data.f[1] = ay; input->data.f[2] = az; } interpreter->Invoke(); // 7. Аналіз результату int best_class = 0; float max_prob = 0.0; for (int i = 0; i < 4; i++) { float prob = (output->type == kTfLiteInt8) ? (output->data.int8[i] - output->params.zero_point) * output->params.scale : output->data.f[i]; if (prob > max_prob) { max_prob = prob; best_class = i; } } // Виводимо в консоль навіть "Спокій", щоб переконатися, що програма працює Serial.print("Клас: "); Serial.print(class_names[best_class]); Serial.print(" | Впевненість: "); Serial.print(max_prob * 100, 1); Serial.println("%"); } else { Serial.println("Помилка: Дані з датчика не отримані!"); } delay(200); } |
|
1 2 3 4 |
Arduino_TFL_MPU6050/ ├── Arduino_TFL_MPU6050_TrainModel.ino ← головний скетч └── model_data.h ← згенерована модель |
Обидва файли мають бути в одній теці, з однаковою назвою теки і .ino-файлу.
Tools → Board → Raspberry Pi Pico Tools → Port → /dev/ttyACM0 (або відповідний COM на Windows) Sketch → Upload
Перший раз Pico треба перевести в bootloader: затисни кнопку BOOTSEL, встав USB. Після цього з’явиться як USB-флешка — Arduino IDE сам залиє прошивку, далі вже працює без BOOTSEL.
Крок 6. Перевірка
Tools → Serial Monitor, швидкість 115200.
Якщо все добре — побачиш:
|
1 2 3 4 5 |
Жест: Спокій упевненість: 0.94 Жест: Спокій упевненість: 0.91 Жест: Струс упевненість: 0.88 ... |
Типові помилки збірки
fatal error: TensorFlowLite.h: No such file or directory
Бібліотека Chirale_TensorFlowLite не встановлена або обрана не та плата. Перевір Tools → Board — має стояти Raspberry Pi Pico від arduino-pico, не Mbed-варіант.
model_data.h: No such file or directory
Файл model_data.h лежить не в теці скетча. Він мусить бути поряд з .ino-файлом, у тій самій папці.
AllocateTensors() failed
kTensorArenaSize замалий. Збільш у скетчі до 4 КБ або 8 КБ:
|
1 2 |
constexpr int kTensorArenaSize = 4 * 1024; |
Failed to get model schema. Schema version 4 is not supported.
Модель збережена новішою версією TFLite, ніж та, що в Chirale_TensorFlowLite. Або онови бібліотеку, або експортуй модель старшою версією TensorFlow.
Serial Monitor мовчить
Перевір швидкість (115200), порт у Tools → Port, і чи запалюється LED на Pico при підключенні. Якщо нема — натисни BOOTSEL + reset і залий знову.
MPU6050 видає нулі або однакові числа
Перевір I2C-адресу: за замовчуванням 0x68, але якщо пін AD0 підтягнутий до VCC — стане 0x69. Перевір живлення (3.3 В), GND, і дроти SDA/SCL (не переплутані).
Альтернативи
- ArduTFLite — обгортка над
Chirale_TensorFlowLiteз простішим API в Arduino-стилі. Підходить, якщо не хочеш бачитиMicroInterpreterі тензори напряму. - pico-tflmicro — офіційний порт TFLite Micro під Pico SDK. Канонічний шлях, але вимагає CMake-збірки замість Arduino IDE.
- Edge Impulse — веб-сервіс для збору даних і навчання моделі, генерує готову бібліотеку Arduino. Простіше для новачка, але приховує більшу частину «магії».