Externí C moduly pro MicroPython

Při vývoji modulů pro použití s MicroPythonem můžete narazit na omezení prostředí Pythonu, často kvůli nemožnosti přístupu k určitým hardwarovým prostředkům nebo kvůli rychlostním omezením Pythonu.

Pokud vaše omezení nelze vyřešit pomocí doporučení v Maximalizace rychlosti MicroPythonu, je životaschopnou možností napsat část nebo celý modul v jazyce C (a/nebo v C++, je-li implementováno pro váš port).

Pokud je váš modul navržen pro přístup k běžně dostupnému hardwaru či knihovnám nebo pro práci s nimi, zvažte prosím jeho implementaci přímo ve zdrojovém stromu MicroPythonu vedle podobných modulů a jeho odeslání formou pull requestu. Pokud však cílíte na neobvyklé nebo proprietární systémy, může dávat větší smysl ponechat jej mimo hlavní repozitář MicroPythonu.

Tato kapitola popisuje, jak takové externí moduly zkompilovat do spustitelného souboru nebo image firmware MicroPythonu. Podporovány jsou jak nástroje Make, tak CMake, a při psaní externího modulu je dobré přidat soubory pro sestavení pro oba tyto nástroje, aby bylo možné modul použít na všech portech. Při kompilaci konkrétního portu však budete potřebovat pouze jednu metodu sestavení, buď Make, nebo CMake.

Alternativním přístupem je použití Nativní strojový kód v souborech .mpy, který umožňuje psát vlastní kód v jazyce C umístěný do souboru .mpy, jenž lze dynamicky importovat do běžícího systému MicroPython bez nutnosti rekompilace hlavního firmwaru.

Struktura externího C modulu

Uživatelský C modul MicroPythonu je adresář s následujícími soubory:

  • Zdrojové soubory *.c / *.cpp / *.h vašeho modulu.

    Ty obvykle zahrnují implementovanou nízkoúrovňovou funkcionalitu a vazební funkce MicroPythonu, které zpřístupňují tyto funkce a modul(y).

    V současnosti je nejlepší referencí pro psaní těchto funkcí/modulů najít podobné moduly ve stromu MicroPythonu a použít je jako příklady.

  • micropython.mk obsahuje fragment Makefile pro tento modul.

    $(USERMOD_DIR) je v souboru micropython.mk k dispozici jako cesta k adresáři vašeho modulu. Protože se předefinovává pro každý C modul, měla by se ve vašem souboru micropython.mk rozvinout do lokální make proměnné, např. EXAMPLE_MOD_DIR := $(USERMOD_DIR)

    Váš soubor micropython.mk musí přidat zdrojové soubory vašich modulů do proměnných SRC_USERMOD_C nebo SRC_USERMOD_LIB_C. První z nich bude zpracována kvůli definicím MP_QSTR_ a MP_REGISTER_MODULE, druhá nikoli (např. pomocné a knihovní kódy, které nejsou specifické pro MicroPython). Tyto cesty by měly zahrnovat vaši rozvinutou kopii $(USERMOD_DIR), např.:

    SRC_USERMOD_C += $(EXAMPLE_MOD_DIR)/modexample.c
    SRC_USERMOD_LIB_C += $(EXAMPLE_MOD_DIR)/utils/algorithm.c
    

    Obdobně použijte SRC_USERMOD_CXX a SRC_USERMOD_LIB_CXX pro zdrojové soubory C++. Chcete-li zahrnout soubory v jazyce symbolických adres, použijte SRC_USERMOD_LIB_ASM.

    Pokud máte vlastní volby překladače (jako -I pro přidání adresářů k prohledávání hlavičkových souborů), měly by se přidat do CFLAGS_USERMOD pro kód v C a do CXXFLAGS_USERMOD pro kód v C++.

  • micropython.cmake obsahuje konfiguraci CMake pro tento modul.

    V souboru micropython.cmake můžete jako cestu k aktuálnímu modulu použít ${CMAKE_CURRENT_LIST_DIR}.

    Váš soubor micropython.cmake by měl definovat knihovnu INTERFACE a přiřadit k ní vaše zdrojové soubory, definice pro kompilaci a adresáře s hlavičkami. Knihovna by pak měla být slinkována s cílem 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)
    

    Úplný příklad použití najdete níže.

Základní příklad

Modul cexample poskytuje příklady funkce a třídy. Funkce cexample.add_ints(a, b) sečte dva celočíselné argumenty a vrátí výsledek. Typ cexample.Timer() vytváří časovače, které lze použít k měření uplynulého času od instancování objektu.

Modul lze nalézt ve zdrojovém stromu MicroPythonu v adresáři examples a obsahuje zdrojový soubor a fragment Makefile s obsahem popsaným výše:

micropython/
└──examples/
   └──usercmodule/
      └──cexample/
         ├── examplemodule.c
         ├── micropython.mk
         └── micropython.cmake

Další vysvětlení najdete v komentářích v těchto souborech. Vedle modulu cexample je také cppexample, který funguje stejným způsobem, ale ukazuje jeden ze způsobů, jak v MicroPythonu kombinovat kód v C a C++.

Kompilace cmodulu do MicroPythonu

Pro sestavení takového modulu zkompilujte MicroPython (viz getting started) s 2 úpravami:

  1. Nastavte příznak USER_C_MODULES z doby sestavení tak, aby ukazoval na moduly, které chcete zahrnout. U portů využívajících Make by tato proměnná měla být adresář, který se automaticky prohledává kvůli modulům. U portů využívajících CMake by tato proměnná měla být soubor, který zahrnuje moduly k sestavení. Podrobnosti najdete níže.

  2. Povolte moduly nastavením odpovídajícího makra preprocesoru C na hodnotu 1. To je nutné pouze tehdy, pokud sestavované moduly nejsou povoleny automaticky.

Pro sestavení ukázkových modulů dodávaných s MicroPythonem nastavte USER_C_MODULES na adresář examples/usercmodule pro Make, nebo na examples/usercmodule/micropython.cmake pro CMake.

Zde je například ukázka, jak sestavit unixový port s ukázkovými moduly:

cd micropython/ports/unix
make USER_C_MODULES=../../examples/usercmodule

Při zahrnutí nových uživatelských modulů do sestavení může být potřeba na začátku jednou spustit make clean. Výstup sestavení zobrazí nalezené moduly:

...
Including User C Module from ../../examples/usercmodule/cexample
Including User C Module from ../../examples/usercmodule/cppexample
...

U portu založeného na CMake, jako je rp2, to bude vypadat trochu jinak (všimněte si, že CMake je ve skutečnosti vyvolán pomocí make):

cd micropython/ports/rp2
make USER_C_MODULES=../../examples/usercmodule/micropython.cmake

I zde může být nutné nejprve spustit make clean, aby CMake uživatelské moduly zachytil. Výstup sestavení CMake vypisuje moduly podle názvu:

...
Including User C Module(s) from ../../examples/usercmodule/micropython.cmake
Found User C Module(s): usermod_cexample, usermod_cppexample
...

Obsah souboru micropython.cmake na nejvyšší úrovni lze použít k řízení toho, které moduly jsou povoleny.

Pro vaše vlastní projekty je pohodlnější uchovávat vlastní kód mimo hlavní zdrojový strom MicroPythonu, takže typická struktura adresáře projektu bude vypadat takto:

my_project/
├── modules/
│   ├── example1/
│   │   ├── example1.c
│   │   ├── micropython.mk
│   │   └── micropython.cmake
│   ├── example2/
│   │   ├── example2.c
│   │   ├── micropython.mk
│   │   └── micropython.cmake
│   └── micropython.cmake
└── micropython/
    ├──ports/
   ... ├──stm32/
      ...

Při sestavování pomocí Make nastavte USER_C_MODULES na adresář my_project/modules. Například při sestavování portu stm32:

cd my_project/micropython/ports/stm32
make USER_C_MODULES=../../../modules

Při sestavování pomocí CMake by měl soubor micropython.cmake na nejvyšší úrovni – nacházející se přímo v adresáři my_project/modules – pomocí include zahrnout všechny moduly, které chcete mít k dispozici:

include(${CMAKE_CURRENT_LIST_DIR}/example1/micropython.cmake)
include(${CMAKE_CURRENT_LIST_DIR}/example2/micropython.cmake)

Poté sestavte pomocí:

cd my_project/micropython/ports/rp2
make USER_C_MODULES=../../../modules/micropython.cmake

K USER_C_MODULES můžete také zadat absolutní cesty.

Všechny moduly určené proměnnou USER_C_MODULES (ať už nalezené v tomto adresáři při použití Make, nebo přidané přes include při použití CMake) budou zkompilovány, ale pro import budou dostupné pouze ty povolené. Uživatelské moduly jsou obvykle povoleny ve výchozím nastavení (o tom rozhoduje vývojář modulu), v takovém případě není třeba dělat nic víc než nastavit USER_C_MODULES výše popsaným způsobem.

Pokud modul není povolen ve výchozím nastavení, musí být povoleno odpovídající makro preprocesoru C. Název tohoto makra lze nalézt vyhledáním řádku MP_REGISTER_MODULE ve zdrojovém kódu modulu (obvykle se objevuje na konci hlavního zdrojového souboru). Toto makro by mělo být obklopeno dvojicí #if X / #endif a konfigurační volba X musí být pomocí CFLAGS_EXTRA nastavena na 1, aby byl modul dostupný. Pokud dvojice #if X / #endif chybí, je modul povolen ve výchozím nastavení.

Například modul examples/usercmodule/cexample je povolen ve výchozím nastavení, takže má ve svém zdrojovém kódu následující řádek:

MP_REGISTER_MODULE(MP_QSTR_cexample, example_user_cmodule);

Případně, aby byl tento modul ve výchozím nastavení zakázán, ale volitelný prostřednictvím konfigurační volby preprocesoru, vypadalo by to takto:

#if MODULE_CEXAMPLE_ENABLED
MP_REGISTER_MODULE(MP_QSTR_cexample, example_user_cmodule);
#endif

V takovém případě se modul povolí přidáním CFLAGS_EXTRA=-DMODULE_CEXAMPLE_ENABLED=1 k příkazu make, nebo úpravou souboru mpconfigport.h či mpconfigboard.h přidáním

#define MODULE_CEXAMPLE_ENABLED (1)

Všimněte si, že přesná metoda závisí na portu, protože mají různou strukturu. Pokud to nebude provedeno správně, kompilace proběhne, ale import modul nenajde.

Použití modulu v MicroPythonu

Jakmile je modul sestaven do vaší kopie MicroPythonu, lze k němu nyní v Pythonu přistupovat stejně jako k jakémukoli jinému vestavěnému modulu, např.

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

Dynamická alokace paměti v C

MicroPython používá pro Správa paměti vlastní „Python heap“, což není totéž jako „C heap“ používaný funkcemi knihovny C malloc(), free() atd. Ne každý port MicroPythonu vůbec obsahuje „C heap“.

Porty úrovně Tier 1 a 2 mají různou míru podpory dynamické alokace paměti v C prostřednictvím „C heap“:

  • Porty unix, windows, esp32 a webassembly podporují dynamickou alokaci paměti v C.

  • Port rp2 při běhu nedokáže alokovat žádnou paměť, pokud není firmware sestaven s MICROPY_C_HEAP_SIZE=n pro vyhrazení n bajtů paměti pro C heap. Tato paměť nebude dostupná pro použití kódem v Pythonu.

  • Sestavení portů alif, mimxrt, nrf, renesas-ra, samd a stm32, která zahrnují dynamickou alokaci v C, selžou při linkování s chybami jako undefined reference to `malloc'. MicroPython nemá pro dynamickou alokaci v C na těchto portech žádnou vestavěnou podporu. Jakékoli řešení vyžaduje ruční přidání implementace C heap do vlastního sestavení.

  • Port zephyr v současnosti nepodporuje sestavení s uživatelskými moduly.

Python heap jako C heap

Pro kód v C může být praktické volat místo toho funkce dynamické alokace na „Python heap“, jako jsou m_malloc(), m_malloc0() a m_free().

Více informací o tomto přístupu najdete v Paměť MicroPythonu z kódu v C.

Moduly C++

Většina portů MicroPythonu úrovně Tier 1 a 2 (a některé úrovně Tier 3) podporuje sestavování uživatelských modulů v C++ pomocí výše popsaných proměnných prostředí specifických pro C++.

Úspěšná integrace C++ a MicroPythonu zahrnuje několik dalších úvah:

Dynamická alokace paměti v C++

Programy v C++ (stejně jako prvky standardní knihovny C++) obvykle využívají dynamickou alokaci paměti. Výchozí alokátor paměti C++ (tj. operátory new a delete) je obvykle implementován jako vrstva nad Dynamická alokace paměti v C.

U portů MicroPythonu, které neobsahují podporu dynamické alokace paměti v C, lze dynamickou alokaci paměti v C++ podpořit jedním ze dvou způsobů:

  • Implementujte dynamickou alokaci paměti v C ve svém vlastním sestavení.

  • Implementujte vlastní alokátor C++ ve svém vlastním sestavení.

Úvahy o linkování

Protože MicroPython je projekt založený na jazyce C, musí být všechny symboly, které se linkují do MicroPythonu nebo z něj, v kódu C++ kvalifikovány jako extern "C".

Důrazně se doporučuje řídit se vzorem ukázaným v examples/usercmodule/cppexample, kde je modul Pythonu implementován v minimálním obalu ze souboru C okolo kódu v C++.