MicroPython — искусство малых форм

Almaty Python Meetup #6

СС
Сергей Степанов
CEO Тяга

Можно ли писать на Python для микроконтроллеров, где RAM измеряется сотнями килобайт, а вместо привычного цикла разработки — прошивка через USB-порт? MicroPython говорит, что можно: компактная реализация Python3, REPL прямо на устройстве и обновление кода без перекомпиляции. Но где у этой магии границы и сколько на самом деле стоит «питон в железе» по скорости?

Сергей Степанов — CEO «Тяга», PhD в области теоретической физики, который имел дело с бозоном Хиггса, программировал роботов для игры в футбол и собрал CNC-станок, — на практике разбирает, как устроен MicroPython внутри и как выжать из него максимум.

В докладе: — как устроен MicroPython: REPL, WebREPL по вебсокетам, boot.py и main.py, frozen modules в прошивке — организация памяти на примере ESP32: 4 МБ Flash, 520 КБ RAM, ручной gc.collect() и настройка порога сборки мусора — работа с железом через GPIO, I2C, SPI, UART и кроссплатформенность ESP32, STM32, RP2040, PyBoard — эмиттеры кода bytecode vs @native vs @viper и во что они компилируются на ARM — живой замер скорости счёта импульсов: C-код до 360 кГц / 2.5 МГц, @viper до 70 кГц, @native до 50 кГц, чистый Python до 20 кГц — инструменты pyboard.py и mpremote: заливка файлов, mip install и Python на всех этапах мониторинга через PySide6

Презентация

Слайд 1: MicroPython - искусство 1 / 22
Текст презентации

Слайд 1: MicroPython - искусство

MicroPython - искусство малых форм Сергей Степанов CEO “Тяга”

Слайд 2: ● PhD в области теоретической физики,

● PhD в области теоретической физики, имел дело с бозоном Хиггса ● Программировал роботов для игры в футбол ● Собрал CNC-станок ● Устал от всего этого embedded software и захотел облегчить всем жизнь Кто? Когда? Зачем?

Слайд 3: Что получилось

Что получилось 📱 Компактная реализация Python3 Оптимизированная версия Python для микроконтроллеров с ограниченными ресурсами 🔧 Низкоуровневый доступ Прямая работа с GPIO, I2C, SPI, UART и другими аппаратными интерфейсами ⚙ Кроссплатформенность Единый код для ESP32, STM32, RP2040, PyBoard и других платформ ⚡ Интерактивная разработка REPL (Read-Eval-Print Loop) для быстрого прототипирования и отладки прямо на устройстве Закидываем новые библиотеки и апдейты кода без компиляции, на работающее устройство

Слайд 4: 🌐 Популярность

🌐 Популярность Open source (MIT лицензия), активное сообщество, пользовательские библиотеки для популярных IoT сценариев

Слайд 5: REPL (Read-Eval-Print Loop)

REPL (Read-Eval-Print Loop) Подключаем МК к USB порту - драйвера CP210x USB to UART bridge

Слайд 6: WebREPL (по вебсокетам)

WebREPL (по вебсокетам) Активируем на МК webrepl Подключаемся к WiFi ( перед этим обеспечиваем надежное питание, 0.2 А, 5 В) Тормозииит

Слайд 7: Организация памяти на примере ESP32

Организация памяти на примере ESP32 📦 Flash Memory (4MB) │ Bootloader │ ~28 KB │ Partition Table │ ~4 KB │ MicroPython Firmware │ ~1.5 MB │ • Интерпретатор │ │ • Встроенные модули │ │ • Frozen modules │ │ Файловая система │ ~2 MB │ /boot.py, /main.py │ │ /lib/* (библиотеки) │ │ Ваши скрипты │ ⚡ RAM (520 KB на ESP32) ┌─────────────────────────┐ High │ Stack │ ~8-16 KB │ • Вызовы функций │ │ • Локальные переменные │ ├─────────────────────────┤ ↓ растет вниз │ ...свободно... │ ├─────────────────────────┤ ↑ растет вверх │ Heap (Python объекты) │ ~100-150 KB │ • Строки, списки │ │ • Словари, объекты │ │ • Bytecode в RAM │ │ → gc.collect() здесь! │ │ Системная память │ ~300 KB │ • WiFi/BT буферы │ │ • FreeRTOS │ │ • Драйверы │ └─────────────────────────┘ Low import gc gc.collect() # Принудительная сборка мусора print(gc.mem_free()) # Свободная память gc.threshold(gc.mem_free() // 4 + gc.mem_alloc()) # Настройка порога GC Frozen Module — это Python- модуль, который компилируется в байт-код и "замораживается" прямо в прошивке MicroPython. Вместо хранения в файловой системе, такой модуль становится частью исполняемого образа

Слайд 8: Этапы загрузки

Этапы загрузки Вызываются: boot.py - всякие подготовительные одноразовые действия, например подключение к WiFi main.py - тут следует распологать вызов основного кода прикладной программы (можно циклить)

Слайд 9: Общаемся нормально

Общаемся нормально при живом REPL! с другого конца просто пишем в порт

Слайд 11: pyboard.py

pyboard.py Скриптик, который вы запускаете на своём ПК для взаимодействия с МК Выполнение команд интерпретатором Операции с файлам (ls, cp, mv, rm, mkdir, rmdir) import pyboard pyb = pyboard.Pyboard('/dev/ttyACM0', 115200) pyb.enter_raw_repl() ret = pyb.exec('print(1+1)') print(ret)

Слайд 12: Новые фичи

Новые фичи mpremote расширяет pyboard.py pip install --user mpremote mpremote cp utils/driver.py :utils/driver.py + exec "import app" mpremote mip install gitlab:org/repo@branch не pip, но mip! модули лишаются приставки u* uselect -> select, uasyncio -> asyncio

Слайд 13: Эмиттеры кода: bytecode vs native vs viper

Эмиттеры кода: bytecode vs native vs viper 🐍 Обычный Python Преобразование: Python → Bytecode → VM Выполнение: Интерпретатор выполняет байт-код в виртуальной машине Скорость: 1x (базовая) Гибкость: Максимальная 🐉 @native Преобразование: Python → Native ARM code Выполнение: Прямое выполнение процессором, но с Python объектами Скорость: ~2-5x Гибкость: Средняя 🐲 @viper Преобразование: Python → Typed Native code Выполнение: Прямое выполнение с машинными типами (int, uint) Скорость: ~5-10x Гибкость: Ограниченная # Python код def add(a, b): return a + b # Компилируется в байт-код: LOAD_FAST 0 (a) LOAD_FAST 1 (b) BINARY_OP_ADD RETURN_VALUE # VM интерпретирует каждую # инструкцию в цикле: while (pc < code_end) { opcode = *pc++; switch(opcode) { ... } @micropython.native def add(a, b): return a + b # Компилируется в ARM: ldr r0, [sp, #0] ; load a ldr r1, [sp, #4] ; load b bl mp_binary_add ; Python add bx lr ; return @micropython.viper def add(a: int, b: int) -> int: return a + b # Компилируется в ARM: add r0, r0, r1 ; r0 = r0 + r1 bx lr ; return

Слайд 14: # Глобальная блокировка для потоков

# Глобальная блокировка для потоков thread_lock = _thread.allocate_lock() def worker_thread(thread_id): while True: with thread_lock: # Только один поток может выполнять этот блок print(f"Thread {thread_id} in critical section") time.sleep(2) # Даже с sleep - другие потоки ждут time.sleep(0.5) # Вне критической секции # Отключаем прерывания irq_state = machine.disable_irq() # Критическая секция # Включаем прерывание machine.enable_irq(irq_state)

Слайд 15: Экспериментальная проверка скорости

Экспериментальная проверка скорости

Слайд 16: Код для счёта импульсов на пине

Код для счёта импульсов на пине

Слайд 17: LED PWM Controller

LED PWM Controller 5 MHz - easy trying to count pulses

Слайд 18: Считаем импульсы частотой от 0 до ∞ кГц!

Считаем импульсы частотой от 0 до ∞ кГц!

Слайд 19: C-код - до 360 кГц / 2.5 МГц

C-код - до 360 кГц / 2.5 МГц @Viper - до 70 кГц @Native - до 50 кГц Python - до 20 кГц

Слайд 20: Питон на всех этапах мониторинга и управления

Питон на всех этапах мониторинга и управления MicroPython Python PySide6 (Python + QT) USB Ethernet

Слайд 21: Чего достигли с этими питонами

Чего достигли с этими питонами

Слайд 22: Спасибо за внимание!

Спасибо за внимание! [email protected] @thrust_stepanov https://github.com/in-space-we-thrust

Другие доклады митапа