Externa C-moduler i MicroPython¶
När du utvecklar moduler för användning med MicroPython kan du upptäcka att du stöter på begränsningar i Python-miljön, ofta på grund av att det inte går att komma åt vissa hårdvaruresurser eller på grund av Pythons hastighetsbegränsningar.
Om dina begränsningar inte kan lösas med förslagen i Maximera hastigheten i MicroPython är det ett gångbart alternativ att skriva delar av eller hela modulen i C (och/eller C++ om det är implementerat för din port).
Om din modul är utformad för att komma åt eller arbeta med vanligt förekommande hårdvara eller bibliotek bör du överväga att implementera den i MicroPythons källträd tillsammans med liknande moduler och skicka in den som en pull request. Om du däremot riktar in dig på obskyra eller proprietära system kan det vara mer logiskt att hålla detta utanför huvudrepositoriet för MicroPython.
Det här kapitlet beskriver hur du kompilerar sådana externa moduler in i MicroPythons körbara fil eller fast programvara. Både byggverktygen Make och CMake stöds, och när du skriver en extern modul är det en bra idé att lägga till byggfilerna för båda dessa verktyg så att modulen kan användas på alla portar. Men när du kompilerar en viss port behöver du bara använda en byggmetod, antingen Make eller CMake.
Ett alternativt tillvägagångssätt är att använda Inbyggd maskinkod i .mpy-filer, vilket gör det möjligt att skriva anpassad C-kod som placeras i en .mpy-fil, som kan importeras dynamiskt in i ett körande MicroPython-system utan att den huvudsakliga fasta programvaran behöver kompileras om.
Strukturen hos en extern C-modul¶
En användardefinierad C-modul i MicroPython är en katalog med följande filer:
*.c/*.cpp/*.hkällkodsfiler för din modul.Dessa innehåller vanligtvis den lågnivåfunktionalitet som implementeras samt MicroPythons bindningsfunktioner som exponerar funktionerna och modulen/modulerna.
För närvarande är den bästa referensen för att skriva dessa funktioner/moduler att hitta liknande moduler i MicroPython-trädet och använda dem som exempel.
micropython.mkinnehåller Makefile-fragmentet för den här modulen.$(USERMOD_DIR)är tillgängligt imicropython.mksom sökvägen till din modulkatalog. Eftersom det omdefinieras för varje C-modul bör det expanderas i dinmicropython.mktill en lokal make-variabel, t.ex.EXAMPLE_MOD_DIR := $(USERMOD_DIR)Din
micropython.mkmåste lägga till modulens källkodsfiler till variablernaSRC_USERMOD_CellerSRC_USERMOD_LIB_C. Den förstnämnda bearbetas för definitioner avMP_QSTR_ochMP_REGISTER_MODULE, den senare inte (t.ex. hjälpfunktioner och bibliotekskod som inte är specifik för MicroPython). Dessa sökvägar bör inkludera din expanderade kopia av$(USERMOD_DIR), t.ex.:SRC_USERMOD_C += $(EXAMPLE_MOD_DIR)/modexample.c SRC_USERMOD_LIB_C += $(EXAMPLE_MOD_DIR)/utils/algorithm.c
Använd på liknande sätt
SRC_USERMOD_CXXochSRC_USERMOD_LIB_CXXför C++-källkodsfiler. Om du vill inkludera assemblerfiler använder duSRC_USERMOD_LIB_ASM.Om du har anpassade kompilatoralternativ (som
-Iför att lägga till kataloger att söka i efter headerfiler) bör dessa läggas till iCFLAGS_USERMODför C-kod och iCXXFLAGS_USERMODför C++-kod.micropython.cmakeinnehåller CMake-konfigurationen för den här modulen.I
micropython.cmakekan du använda${CMAKE_CURRENT_LIST_DIR}som sökväg till den aktuella modulen.Din
micropython.cmakebör definiera ettINTERFACE-bibliotek och koppla dina källkodsfiler, kompileringsdefinitioner och inkluderingskataloger till det. Biblioteket bör sedan länkas till måletusermod.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)
Se nedan för ett fullständigt användningsexempel.
Grundläggande exempel¶
Modulen cexample tillhandahåller exempel på en funktion och en klass. Funktionen cexample.add_ints(a, b) adderar två heltalsargument och returnerar resultatet. Typen cexample.Timer() skapar timers som kan användas för att mäta den tid som förflutit sedan objektet instansierades.
Modulen finns i MicroPythons källträd i exempelkatalogen och har en källkodsfil och ett Makefile-fragment med innehåll enligt beskrivningen ovan:
micropython/
└──examples/
└──usercmodule/
└──cexample/
├── examplemodule.c
├── micropython.mk
└── micropython.cmake
Se kommentarerna i dessa filer för ytterligare förklaring. Bredvid modulen cexample finns även cppexample som fungerar på samma sätt men visar ett sätt att blanda C- och C++-kod i MicroPython.
Kompilera cmodule in i MicroPython¶
För att bygga en sådan modul kompilerar du MicroPython (se komma igång) och tillämpar två ändringar:
Sätt byggtidsflaggan
USER_C_MODULESså att den pekar på de moduler du vill inkludera. För portar som använder Make ska denna variabel vara en katalog som automatiskt genomsöks efter moduler. För portar som använder CMake ska denna variabel vara en fil som inkluderar de moduler som ska byggas. Se nedan för detaljer.Aktivera modulerna genom att sätta motsvarande C-preprocessormakro till 1. Detta behövs bara om de moduler du bygger inte aktiveras automatiskt.
För att bygga de exempelmoduler som följer med MicroPython sätter du USER_C_MODULES till katalogen examples/usercmodule för Make, eller till examples/usercmodule/micropython.cmake för CMake.
Här är till exempel hur du bygger unix-porten med exempelmodulerna:
cd micropython/ports/unix
make USER_C_MODULES=../../examples/usercmodule
Du kan behöva köra make clean en gång i början när du inkluderar nya användarmoduler i bygget. Byggutdata visar de moduler som hittats:
...
Including User C Module from ../../examples/usercmodule/cexample
Including User C Module from ../../examples/usercmodule/cppexample
...
För en CMake-baserad port som rp2 ser detta lite annorlunda ut (observera att CMake faktiskt anropas av make):
cd micropython/ports/rp2
make USER_C_MODULES=../../examples/usercmodule/micropython.cmake
Återigen kan du behöva köra make clean först för att CMake ska upptäcka användarmodulerna. CMakes byggutdata listar modulerna med namn:
...
Including User C Module(s) from ../../examples/usercmodule/micropython.cmake
Found User C Module(s): usermod_cexample, usermod_cppexample
...
Innehållet i micropython.cmake på toppnivå kan användas för att styra vilka moduler som aktiveras.
För dina egna projekt är det bekvämare att hålla anpassad kod utanför MicroPythons huvudsakliga källträd, så en typisk projektkatalogstruktur ser ut så här:
my_project/
├── modules/
│ ├── example1/
│ │ ├── example1.c
│ │ ├── micropython.mk
│ │ └── micropython.cmake
│ ├── example2/
│ │ ├── example2.c
│ │ ├── micropython.mk
│ │ └── micropython.cmake
│ └── micropython.cmake
└── micropython/
├──ports/
... ├──stm32/
...
När du bygger med Make sätter du USER_C_MODULES till katalogen my_project/modules. Till exempel, för att bygga stm32-porten:
cd my_project/micropython/ports/stm32
make USER_C_MODULES=../../../modules
När du bygger med CMake bör micropython.cmake på toppnivå – som finns direkt i katalogen my_project/modules – include alla de moduler du vill ha tillgängliga:
include(${CMAKE_CURRENT_LIST_DIR}/example1/micropython.cmake) include(${CMAKE_CURRENT_LIST_DIR}/example2/micropython.cmake)
Bygg sedan med:
cd my_project/micropython/ports/rp2
make USER_C_MODULES=../../../modules/micropython.cmake
Du kan även ange absoluta sökvägar till USER_C_MODULES.
Alla moduler som anges av variabeln USER_C_MODULES (antingen de som hittas i denna katalog när Make används, eller de som läggs till via include när CMake används) kommer att kompileras, men endast de som är aktiverade kommer att vara tillgängliga för import. Användarmoduler är vanligtvis aktiverade som standard (detta avgörs av modulens utvecklare), och i så fall behöver du inte göra något mer än att sätta USER_C_MODULES enligt beskrivningen ovan.
Om en modul inte är aktiverad som standard måste motsvarande C-preprocessormakro aktiveras. Detta makronamn kan hittas genom att söka efter raden MP_REGISTER_MODULE i modulens källkod (den förekommer vanligtvis i slutet av den huvudsakliga källkodsfilen). Detta makro bör omges av ett par #if X / #endif, och konfigurationsalternativet X måste sättas till 1 med hjälp av CFLAGS_EXTRA för att göra modulen tillgänglig. Om det inte finns något par #if X / #endif är modulen aktiverad som standard.
Till exempel är modulen examples/usercmodule/cexample aktiverad som standard och har därför följande rad i sin källkod:
MP_REGISTER_MODULE(MP_QSTR_cexample, example_user_cmodule);
Alternativt, för att göra denna modul inaktiverad som standard men valbar via ett preprocessor-konfigurationsalternativ, skulle det vara:
#if MODULE_CEXAMPLE_ENABLED MP_REGISTER_MODULE(MP_QSTR_cexample, example_user_cmodule); #endif
I detta fall aktiveras modulen genom att lägga till CFLAGS_EXTRA=-DMODULE_CEXAMPLE_ENABLED=1 till make-kommandot, eller genom att redigera mpconfigport.h eller mpconfigboard.h för att lägga till
#define MODULE_CEXAMPLE_ENABLED (1)
Observera att den exakta metoden beror på porten eftersom de har olika strukturer. Om det inte görs korrekt kommer det att kompilera men importen kommer inte att hitta modulen.
Användning av modulen i MicroPython¶
När modulen väl är inbyggd i din kopia av MicroPython kan den nu nås i Python precis som vilken annan inbyggd modul som helst, t.ex.
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
Dynamisk minnesallokering i C¶
MicroPython använder sin egen ”Python-heap” för Minneshantering, vilket inte är samma sak som den ”C-heap” som används av C-bibliotekets funktioner malloc(), free() osv. Inte alla MicroPython-portar har överhuvudtaget en ”C-heap”.
Tier 1- och 2-portar har varierande stöd för dynamisk minnesallokering i C via en ”C-heap”:
Portarna unix, windows, esp32 och webassembly stöder dynamisk minnesallokering i C.
rp2-porten kommer att misslyckas med att allokera något minne vid körning om inte den fasta programvaran byggs med
MICROPY_C_HEAP_SIZE=nför att reserveranbyte minne för en C-heap. Detta minne kommer inte att vara tillgängligt för Python-kod att använda.Byggen för portarna alif, mimxrt, nrf, renesas-ra, samd och stm32 som inkluderar dynamisk C-allokering kommer att misslyckas vid länkning med fel som
undefined reference to `malloc'. MicroPython har inget inbyggt stöd för dynamisk C-allokering på dessa portar. Varje lösning kräver att man manuellt lägger till en C-heap-implementering i det anpassade bygget.zephyr-porten stöder för närvarande inte byggen med användarmoduler.
Python-heapen som C-heap¶
Det kan vara praktiskt för C-kod att i stället anropa dynamiska allokeringsfunktioner för ”Python-heapen”, såsom m_malloc(), m_malloc0() och m_free().
Se MicroPython-minne från C-kod för mer information om detta tillvägagångssätt.
C++-moduler¶
De flesta MicroPython-portar i Tier 1 och 2 (och vissa i Tier 3) stöder byggande av användarmoduler i C++, med hjälp av de C++-specifika miljövariabler som beskrivs ovan.
Att framgångsrikt integrera C++ och MicroPython innebär några ytterligare överväganden:
Dynamisk minnesallokering i C++¶
C++-program (liksom funktioner i C++-standardbiblioteket) använder vanligtvis dynamisk minnesallokering. C++:s standardminnesallokerare (dvs. operatorerna new och delete) implementeras vanligtvis som ett lager ovanpå Dynamisk minnesallokering i C.
För MicroPython-portar som inte inkluderar stöd för dynamisk minnesallokering i C kan dynamisk minnesallokering i C++ stödjas på ett av två sätt:
Implementera dynamisk minnesallokering i C i ditt anpassade bygge.
Implementera en anpassad C++-allokerare i ditt anpassade bygge.
Länkningsöverväganden¶
Eftersom MicroPython är ett C-baserat projekt måste alla symboler som länkar till eller från MicroPython kvalificeras med extern "C" i C++-kod.
Det rekommenderas starkt att följa mönstret som demonstreras i examples/usercmodule/cppexample, där Python-modulen implementeras i en minimal C-filomslutning runt C++-koden.