Зовнішні модулі MicroPython на мові C¶
При розробці модулів для використання з MicroPython ви можете зіткнутися з обмеженнями середовища Python — часто через неможливість доступу до певних апаратних ресурсів або через обмеження швидкості виконання Python.
Якщо ваші обмеження не вдається усунути за допомогою порад із Максимізація швидкості MicroPython, написання частини або всього модуля на C (та/або C++, якщо підтримується для вашого порту) є прийнятним варіантом.
Якщо ваш модуль призначений для доступу до загальнодоступного обладнання або бібліотек чи роботи з ними, будь ласка, розгляньте можливість його реалізації безпосередньо в дереві вихідних кодів MicroPython разом із подібними модулями та подайте його як pull request. Якщо ж ви орієнтуєтеся на маловідомі або пропрієтарні системи, можливо, доцільніше тримати цей код поза основним репозиторієм MicroPython.
У цьому розділі описано, як скомпілювати такі зовнішні модулі у виконуваний файл MicroPython або образ мікропрограми. Підтримуються інструменти збірки Make і CMake; при написанні зовнішнього модуля бажано додати файли збірки для обох інструментів, щоб модуль можна було використовувати на всіх портах. Однак при компіляції конкретного порту вам знадобиться лише один метод збірки — Make або CMake.
Альтернативним підходом є використання Нативний машинний код у файлах .mpy, що дозволяє писати власний код на C, який розміщується у файлі .mpy та може динамічно імпортуватися в працюючу систему MicroPython без необхідності перекомпіляції основної мікропрограми.
Структура зовнішнього модуля C¶
Користувацький модуль C для MicroPython — це директорія з такими файлами:
Вихідні файли
*.c/*.cpp/*.hдля вашого модуля.Зазвичай вони включають реалізацію низькорівневої функціональності та функції зв’язування MicroPython для відкриття функцій і модулів.
Наразі найкращим довідником для написання цих функцій/модулів є пошук схожих модулів у дереві MicroPython та їх використання як прикладів.
micropython.mkмістить фрагмент Makefile для цього модуля.$(USERMOD_DIR)доступний уmicropython.mkяк шлях до директорії вашого модуля. Оскільки він перевизначається для кожного модуля C, його слід розгорнути у вашомуmicropython.mkу локальну змінну make, наприкладEXAMPLE_MOD_DIR := $(USERMOD_DIR)Ваш
micropython.mkповинен додавати вихідні файли ваших модулів до зміннихSRC_USERMOD_CабоSRC_USERMOD_LIB_C. Перша буде оброблятися для визначеньMP_QSTR_таMP_REGISTER_MODULE, а друга — ні (наприклад, допоміжні функції та бібліотечний код, що не є специфічним для MicroPython). Ці шляхи повинні включати розгорнуту копію$(USERMOD_DIR), наприклад:SRC_USERMOD_C += $(EXAMPLE_MOD_DIR)/modexample.c SRC_USERMOD_LIB_C += $(EXAMPLE_MOD_DIR)/utils/algorithm.c
Аналогічно, використовуйте
SRC_USERMOD_CXXтаSRC_USERMOD_LIB_CXXдля вихідних файлів C++. Якщо ви хочете включити файли асемблера, використовуйтеSRC_USERMOD_LIB_ASM.Якщо у вас є власні параметри компілятора (наприклад,
-Iдля додавання директорій до пошуку заголовних файлів), їх слід додати доCFLAGS_USERMODдля коду C та доCXXFLAGS_USERMODдля коду C++.micropython.cmakeмістить конфігурацію CMake для цього модуля.У
micropython.cmakeви можете використовувати${CMAKE_CURRENT_LIST_DIR}як шлях до поточного модуля.Ваш
micropython.cmakeповинен визначати бібліотекуINTERFACEта пов’язувати з нею ваші вихідні файли, визначення компіляції та директорії включень. Потім бібліотека повинна бути пов’язана з ціллюusermod.add_library(usermod_cexample INTERFACE) target_sources(usermod_cexample INTERFACE ${CMAKE_CURRENT_LIST_DIR}/examplemodule.c ) target_include_directories(usermod_cexample INTERFACE ${CMAKE_CURRENT_LIST_DIR} ) target_link_libraries(usermod INTERFACE usermod_cexample)
Повний приклад використання наведено нижче.
Базовий приклад¶
Модуль cexample надає приклади функції та класу. Функція cexample.add_ints(a, b) складає два цілочисельні аргументи та повертає результат. Тип cexample.Timer() створює таймери, які можна використовувати для вимірювання часу, що минув з моменту створення об’єкта.
Модуль можна знайти у дереві вихідних кодів MicroPython у директорії examples і містить вихідний файл та фрагмент Makefile з вмістом, описаним вище:
micropython/
└──examples/
└──usercmodule/
└──cexample/
├── examplemodule.c
├── micropython.mk
└── micropython.cmake
Зверніться до коментарів у цих файлах для додаткових пояснень. Поряд із модулем cexample є також cppexample, який працює так само, але демонструє один зі способів змішування коду C та C++ у MicroPython.
Компіляція cmodule у MicroPython¶
Щоб зібрати такий модуль, скомпілюйте MicroPython (дивіться getting started), застосувавши 2 зміни:
Встановіть прапорець часу збірки
USER_C_MODULESдля вказівки на модулі, які ви хочете включити. Для портів, що використовують Make, ця змінна повинна бути директорією, в якій автоматично виконується пошук модулів. Для портів, що використовують CMake, ця змінна повинна бути файлом, який включає модулі для збірки. Дивіться нижче для деталей.Увімкніть модулі, встановивши відповідний макрос препроцесора C у 1. Це потрібно лише в тому випадку, якщо модулі, які ви збираєте, не вмикаються автоматично.
Для збірки прикладових модулів, що постачаються з MicroPython, встановіть USER_C_MODULES на директорію examples/usercmodule для Make або на examples/usercmodule/micropython.cmake для CMake.
Наприклад, ось як зібрати порт unix з прикладовими модулями:
cd micropython/ports/unix
make USER_C_MODULES=../../examples/usercmodule
Можливо, вам знадобиться один раз запустити make clean на початку при включенні нових користувацьких модулів до збірки. Результат збірки покаже знайдені модулі:
...
Including User C Module from ../../examples/usercmodule/cexample
Including User C Module from ../../examples/usercmodule/cppexample
...
Для порту на основі CMake, наприклад rp2, це виглядатиме дещо інакше (зверніть увагу, що CMake фактично викликається командою make):
cd micropython/ports/rp2
make USER_C_MODULES=../../examples/usercmodule/micropython.cmake
Знову ж таки, для того щоб CMake підхопив користувацькі модулі, можливо, спочатку знадобиться запустити make clean. Результат збірки CMake перераховує модулі за іменами:
...
Including User C Module(s) from ../../examples/usercmodule/micropython.cmake
Found User C Module(s): usermod_cexample, usermod_cppexample
...
Вміст micropython.cmake верхнього рівня можна використовувати для керування тим, які модулі увімкнено.
Для власних проектів зручніше тримати користувацький код поза основним деревом вихідних кодів MicroPython, тому типова структура директорій проекту виглядатиме так:
my_project/
├── modules/
│ ├── example1/
│ │ ├── example1.c
│ │ ├── micropython.mk
│ │ └── micropython.cmake
│ ├── example2/
│ │ ├── example2.c
│ │ ├── micropython.mk
│ │ └── micropython.cmake
│ └── micropython.cmake
└── micropython/
├──ports/
... ├──stm32/
...
При збірці за допомогою Make встановіть USER_C_MODULES на директорію my_project/modules. Наприклад, збірка порту stm32:
cd my_project/micropython/ports/stm32
make USER_C_MODULES=../../../modules
При збірці за допомогою CMake micropython.cmake верхнього рівня — що знаходиться безпосередньо в директорії my_project/modules — повинен include усі модулі, які ви хочете мати доступними:
include(${CMAKE_CURRENT_LIST_DIR}/example1/micropython.cmake) include(${CMAKE_CURRENT_LIST_DIR}/example2/micropython.cmake)
Потім зберіть командою:
cd my_project/micropython/ports/rp2
make USER_C_MODULES=../../../modules/micropython.cmake
Ви також можете вказати абсолютні шляхи до USER_C_MODULES.
Усі модулі, вказані змінною USER_C_MODULES (знайдені в цій директорії при використанні Make або додані через include при використанні CMake), будуть скомпільовані, але лише ті, що увімкнені, будуть доступні для імпортування. Користувацькі модулі зазвичай увімкнені за замовчуванням (це вирішується розробником модуля), і в такому випадку не потрібно нічого більше робити, крім встановлення USER_C_MODULES, як описано вище.
Якщо модуль не увімкнено за замовчуванням, відповідний макрос препроцесора C повинен бути увімкнений. Ім’я цього макросу можна знайти, виконавши пошук рядка MP_REGISTER_MODULE у вихідному коді модуля (зазвичай він з’являється в кінці основного вихідного файлу). Цей макрос повинен бути оточений парою #if X / #endif, і параметр конфігурації X повинен бути встановлений у 1 за допомогою CFLAGS_EXTRA, щоб зробити модуль доступним. Якщо пари #if X / #endif немає, то модуль увімкнений за замовчуванням.
Наприклад, модуль examples/usercmodule/cexample увімкнений за замовчуванням, тому має такий рядок у своєму вихідному коді:
MP_REGISTER_MODULE(MP_QSTR_cexample, example_user_cmodule);
В якості альтернативи, щоб цей модуль був вимкнений за замовчуванням, але вибирався через параметр конфігурації препроцесора, він виглядав би так:
#if MODULE_CEXAMPLE_ENABLED MP_REGISTER_MODULE(MP_QSTR_cexample, example_user_cmodule); #endif
У цьому випадку модуль вмикається шляхом додавання CFLAGS_EXTRA=-DMODULE_CEXAMPLE_ENABLED=1 до команди make або редагування mpconfigport.h чи mpconfigboard.h для додавання
#define MODULE_CEXAMPLE_ENABLED (1)
Зверніть увагу, що точний метод залежить від порту, оскільки вони мають різні структури. Якщо зробити це неправильно, код скомпілюється, але при імпорті модуль не буде знайдено.
Використання модуля в MicroPython¶
Після включення до вашої копії MicroPython модуль стає доступним у Python так само, як і будь-який інший вбудований модуль, наприклад
import cexample
print(cexample.add_ints(1, 3))
# should display 4
from cexample import Timer
from time import sleep_ms
watch = Timer()
sleep_ms(1000)
print(watch.time())
# should display approximately 1000
Динамічне виділення пам’яті C¶
MicroPython використовує власну «Python-купу» для Керування пам’яттю, яка відрізняється від «C-купи», що використовується функціями бібліотеки C malloc(), free() тощо. Не кожний порт MicroPython взагалі має «C-купу».
Порти рівнів 1 та 2 мають різний рівень підтримки динамічного виділення пам’яті C через «C-купу»:
Порти unix, windows, esp32 та webassembly підтримують динамічне виділення пам’яті C.
Порт rp2 не зможе виділити пам’ять під час виконання, якщо мікропрограма не зібрана з
MICROPY_C_HEAP_SIZE=nдля резервуванняnбайт пам’яті для C-купи. Ця пам’ять буде недоступна для використання кодом Python.Збірки портів alif, mimxrt, nrf, renesas-ra, samd та stm32, що включають динамічне виділення пам’яті C, завершаться з помилкою на етапі компонування з такими повідомленнями, як
undefined reference to `malloc'. MicroPython не має вбудованої підтримки динамічного виділення пам’яті C для цих портів. Будь-яке рішення потребує ручного додавання реалізації C-купи до власної збірки.Порт zephyr наразі не підтримує збірку з користувацькими модулями.
Python-купа як C-купа¶
Для коду C може бути практичним викликати функції динамічного виділення пам’яті «Python-купи», такі як m_malloc(), m_malloc0() та m_free().
Дивіться Пам’ять MicroPython із коду C для отримання додаткової інформації про цей підхід.
Модулі C++¶
Більшість портів MicroPython рівнів 1 та 2 (і деякі рівня 3) підтримують збірку користувацьких модулів C++ з використанням специфічних для C++ змінних середовища, описаних вище.
Успішна інтеграція C++ та MicroPython вимагає деяких додаткових міркувань:
Динамічне виділення пам’яті C++¶
Програми на C++ (а також функції стандартної бібліотеки C++) зазвичай використовують динамічне виділення пам’яті. Стандартний розподільник пам’яті C++ (тобто оператори new та delete) зазвичай реалізований як рівень поверх Динамічне виділення пам’яті C.
Для портів MicroPython, що не включають підтримку динамічного виділення пам’яті C, динамічне виділення пам’яті C++ можна підтримати одним із двох способів:
Реалізуйте динамічне виділення пам’яті C у вашій власній збірці.
Реалізуйте власний розподільник пам’яті C++ у вашій власній збірці.
Міркування щодо компонування¶
Оскільки MicroPython є проектом на C, будь-які символи, що пов’язані з MicroPython або від нього, повинні бути кваліфіковані як extern "C" у коді C++.
Наполегливо рекомендується дотримуватися шаблону, продемонстрованого в examples/usercmodule/cppexample, де модуль Python реалізований у мінімальному файлі-обгортці C навколо коду C++.