Izvorni strojni kod u .mpy datotekama¶
Ovaj odjeljak opisuje kako izgraditi i raditi s .mpy datotekama koje sadrže izvorni strojni kod napisan u jeziku različitom od Pythona. To vam omogućuje da napišete kod u jeziku poput C-a, kompilirate ga i povežete u .mpy datoteku, a zatim tu datoteku uvezete poput uobičajenog Python modula. To se može koristiti za implementaciju funkcionalnosti koja je kritična za izvedbu ili za uključivanje postojeće biblioteke napisane u drugom jeziku.
Jedna od glavnih prednosti korištenja izvornih .mpy datoteka jest to što skripta može dinamički uvesti izvorni strojni kod, bez potrebe za ponovnom izgradnjom glavnog MicroPython ugrađenog programa (firmware). To je u suprotnosti s MicroPython vanjski C moduli, koji također omogućuje definiranje prilagođenih modula u C-u, ali oni moraju biti kompilirani u glavnu sliku ugrađenog programa.
Naglasak je ovdje na korištenju C-a za izgradnju izvornih modula, no u načelu se bilo koji jezik koji se može kompilirati u samostalni strojni kod može staviti u .mpy datoteku.
Izvorni .mpy modul izgrađuje se pomoću alata mpy_ld.py, koji se nalazi u direktoriju tools/ projekta. Taj alat uzima skup objektnih datoteka (.o datoteka) i povezuje ih zajedno kako bi stvorio izvornu .mpy datoteku. Zahtijeva CPython 3 i biblioteku pyelftools v0.25 ili noviju.
Podržane značajke i ograničenja¶
.mpy datoteka može sadržavati MicroPython bajtkod i/ili izvorni strojni kod. Ako sadrži izvorni strojni kod, tada .mpy datoteka ima pridruženu određenu arhitekturu. Trenutno podržane arhitekture su (ovo su valjane opcije za varijablu ARCH, vidi niže):
x86(32-bitni)x64(64-bitni x86)armv6m(ARM Thumb, npr. Cortex-M0)armv7m(ARM Thumb 2, npr. Cortex-M3)armv7emsp(ARM Thumb 2, jednostruka preciznost s pomičnim zarezom, npr. Cortex-M4F, Cortex-M7)armv7emdp(ARM Thumb 2, dvostruka preciznost s pomičnim zarezom, npr. Cortex-M7)xtensa(bez prozora, npr. ESP8266)xtensawin(s prozorima veličine 8, npr. ESP32, ESP32S3)rv32imc(RISC-V 32-bitni s komprimiranim instrukcijama, npr. ESP32C3, ESP32C6)rv64imc(RISC-V 64-bitni s komprimiranim instrukcijama)
Ako odabrana platforma podržava eksplicitne zastavice arhitekture i želite da izlazna .mpy datoteka nosi vrijednost tih zastavica, morate ih proslijediti varijabli zastavica ARCH_FLAGS prilikom izgradnje .mpy datoteke.
Pri kompiliranju i povezivanju izvorne .mpy datoteke mora se odabrati arhitektura, a odgovarajuća datoteka može se uvesti samo na toj arhitekturi (i, ako su prisutne zastavice arhitekture, samo ako odgovaraju mogućnostima cilja). Više pojedinosti o .mpy datotekama potražite u MicroPython .mpy datoteke.
Izvorni kod mora biti kompiliran kao pozicijski neovisan kod (PIC) i koristiti globalnu tablicu pomaka (GOT), iako se pojedinosti o tome razlikuju od arhitekture do arhitekture. Pri uvozu .mpy datoteka s izvornim kodom mehanizam uvoza sposoban je izvršiti osnovnu relokaciju izvornog koda. To uključuje relokaciju text, rodata i BSS sekcija.
Podržane značajke poveznika (linker) i dinamičkog učitavača su:
izvršni kod (text)
podaci samo za čitanje (rodata), uključujući stringove i konstantne podatke (polja, strukture itd.)
podaci postavljeni na nulu (BSS)
pokazivači u text na text, rodata i BSS
pokazivači u rodata na text, rodata i BSS
Poznata ograničenja su:
data sekcije nisu podržane; zaobilazno rješenje: koristite BSS podatke i eksplicitno inicijalizirajte vrijednosti podataka
statičke BSS varijable nisu podržane; zaobilazno rješenje: koristite globalne BSS varijable
varijable lokalne pohrane dretve (thread-local storage) nisu podržane na rv32imc; zaobilazno rješenje: koristite globalne BSS varijable ili alocirajte nešto prostora na gomili (heap) za njihovu pohranu
Dakle, ako vaš C kod ima podatke koji se mogu pisati, pobrinite se da su podaci definirani globalno, bez inicijalizatora, i da se u njih piše samo unutar funkcija.
Izvorni modul nije automatski povezan sa standardnim statičkim bibliotekama poput libm.a i libgcc.a, što može dovesti do pogrešaka undefined symbol. Runtime biblioteke možete povezati postavljanjem LINK_RUNTIME = 1 u svojem Makefileu. Prilagođene statičke biblioteke također se mogu povezati dodavanjem MPY_LD_FLAGS += -l path/to/library.a. Imajte na umu da se one povezuju u izvorni modul i neće se dijeliti s drugim modulima ili sustavom.
Ograničenje poveznika (linker): izvorni modul nije povezan s tablicom simbola cijelog MicroPython ugrađenog programa. Umjesto toga, povezan je s eksplicitnom tablicom izvezenih simbola koja se nalazi u mp_fun_table (u py/nativeglue.h), a koja je fiksirana u trenutku izgradnje ugrađenog programa. Stoga nije moguće jednostavno pozvati neku proizvoljnu HAL/OS/RTOS/sistemsku funkciju, na primjer, osim ako se ona ne nalazi na fiksnoj adresi. U tom se slučaju putanja do linkerskripte koja sadrži niz imena simbola i njihovih fiksnih adresa može proslijediti alatu mpy_ld.py putem argumenta naredbenog retka --externs. Na taj će način simboli koji se pojavljuju u linkerskripti imati prednost nad onim što pružaju objektne datoteke, ali u ovom trenutku implementacija objektnih datoteka i dalje će ostati u konačnoj MPY datoteci. Parser linkerskripte ograničen je u svojim mogućnostima i trenutno se koristi samo za parsiranje popisa ROM simbola ESP8266 porta (vidi ports/esp8266/boards/eagle.rom.addr.v6.ld).
Novi simboli mogu se dodati na kraj tablice i ugrađeni program ponovno izgraditi. Simboli se također moraju dodati u rječnik fun_table datoteke tools/mpy_ld.py na istom mjestu. To omogućuje da mpy_ld.py može pokupiti nove simbole i pružiti relokacije za njih kada se mpy uvozi. Konačno, ako je simbol funkcija, makro ili stub treba dodati u py/dynruntime.h kako bi se olakšalo pozivanje funkcije.
Definiranje izvornog modula¶
Izvorni .mpy modul definiran je skupom datoteka koje se koriste za izgradnju .mpy datoteke. Raspored datotečnog sustava sastoji se od dva glavna dijela, izvornih datoteka i Makefilea:
U najjednostavnijem slučaju potrebna je samo jedna C izvorna datoteka, koja sadrži sav kod koji će biti kompiliran u .mpy modul. Taj C izvorni kod mora uključivati datoteku
py/dynruntime.hza pristup dinamičkom MicroPython API-ju i mora barem definirati funkciju pod nazivommpy_init. Ta funkcija bit će ulazna točka modula, pozvana kada se modul uvozi.Modul se po želji može podijeliti na više C izvornih datoteka. Dijelovi modula također se mogu implementirati u Pythonu. Sve izvorne datoteke trebale bi biti navedene u Makefileu, dodavanjem u varijablu
SRC(vidi niže). To uključuje i C izvorne datoteke kao i sve Python datoteke koje će biti uključene u rezultirajuću .mpy datoteku.Makefilesadrži konfiguraciju izgradnje za modul i navodi izvorne datoteke koje se koriste za izgradnju .mpy modula. Trebao bi definiratiMPY_DIRkao lokaciju MicroPython repozitorija (za pronalaženje datoteka zaglavlja, odgovarajućeg Makefile fragmenta i alatampy_ld.py),MODkao naziv modula,SRCkao popis izvornih datoteka, opcionalno odrediti arhitekturu stroja putemARCH, zajedno s opcionalnim zastavicama arhitekture stroja navedenim putemARCH_FLAGS, a zatim uključitipy/dynruntime.mk.
Minimalni primjer¶
Ovaj odjeljak pruža potpuno funkcionalan primjer jednostavnog modula pod nazivom factorial. Ovaj modul pruža jednu funkciju factorial.factorial(x) koja izračunava faktorijel ulaza i vraća rezultat.
Raspored direktorija:
factorial/
├── factorial.c
└── Makefile
Datoteka factorial.c sadrži:
// Include the header file to get access to the MicroPython API
#include "py/dynruntime.h"
// Helper function to compute factorial
static mp_int_t factorial_helper(mp_int_t x) {
if (x == 0) {
return 1;
}
return x * factorial_helper(x - 1);
}
// This is the function which will be called from Python, as factorial(x)
static mp_obj_t factorial(mp_obj_t x_obj) {
// Extract the integer from the MicroPython input object
mp_int_t x = mp_obj_get_int(x_obj);
// Calculate the factorial
mp_int_t result = factorial_helper(x);
// Convert the result to a MicroPython integer object and return it
return mp_obj_new_int(result);
}
// Define a Python reference to the function above
static MP_DEFINE_CONST_FUN_OBJ_1(factorial_obj, factorial);
// This is the entry point and is called when the module is imported
mp_obj_t mpy_init(mp_obj_fun_bc_t *self, size_t n_args, size_t n_kw, mp_obj_t *args) {
// This must be first, it sets up the globals dict and other things
MP_DYNRUNTIME_INIT_ENTRY
// Make the function available in the module's namespace
mp_store_global(MP_QSTR_factorial, MP_OBJ_FROM_PTR(&factorial_obj));
// This must be last, it restores the globals dict
MP_DYNRUNTIME_INIT_EXIT
}
Datoteka Makefile sadrži:
# Location of top-level MicroPython directory
MPY_DIR = ../../..
# Name of module
MOD = factorial
# Source files (.c or .py)
SRC = factorial.c
# Architecture to build for (x86, x64, armv6m, armv7m, xtensa, xtensawin, rv32imc, rv64imc)
ARCH = x64
# Include to get the rules for compiling and linking the module
include $(MPY_DIR)/py/dynruntime.mk
Kompiliranje modula¶
Preduvjetni alati potrebni za izgradnju izvorne .mpy datoteke su:
MicroPython repozitorij (barem direktoriji
py/itools/).CPython 3 i biblioteka pyelftools (npr.
pip install 'pyelftools>=0.25').GNU make.
C kompilator za ciljnu arhitekturu (ako se koristi C izvorni kod).
Opcionalno
mpy-cross, izgrađen iz MicroPython repozitorija (ako se koristi .py izvorni kod).
Pazite da odaberete ispravan ARCH za cilj na kojem ćete pokretati. Zatim izgradite pomoću:
$ make
Bez izmjene Makefilea možete odrediti ciljnu arhitekturu putem:
$ make ARCH=armv7m
Isto vrijedi za opcionalne zastavice arhitekture putem:
$ make ARCH=rv32imc ARCH_FLAGS=zba
Korištenje modula u MicroPythonu¶
Nakon što je modul izgrađen, trebala bi postojati datoteka pod nazivom factorial.mpy. Kopirajte je tako da bude dostupna na datotečnom sustavu vašeg MicroPython sustava i da se može pronaći u putanji uvoza. Modulu se sada može pristupiti u Pythonu poput svakog drugog modula, na primjer:
import factorial
print(factorial.factorial(10))
# should display 3628800
Korištenje Picolibc-a pri izgradnji modula¶
Korištenje Picolibc kao vaše C standardne biblioteke nije samo podržano, već je zapravo zadano za platforme rv32imc i rv64imc. Međutim, postoji nekoliko stvari koje vrijedi spomenuti kako biste bili sigurni da kasnije nećete naići na probleme pri izgradnji koda.
Neke unaprijed izgrađene verzije Picolibc-a (na primjer, one koje pruža Ubuntu Linux kao pakete picolibc-arm-none-eabi, picolibc-riscv64-unknown-elf i picolibc-xtensa-lx106-elf) pretpostavljaju da je lokalna pohrana dretve (TLS) dostupna tijekom izvođenja, ali nažalost MicroPython moduli to ne podržavaju na nekim arhitekturama (naime rv32imc i rv64imc). To znači da će neke funkcionalnosti koje pruža Picolibc prema zadanim postavkama koristiti TLS, vraćajući pogrešku bilo tijekom kompilacije bilo tijekom povezivanja.
Za primjer kako vas to može pogoditi, primjer modula examples/natmod/btree sadrži zaobilazno rješenje koje osigurava da errno radi (potražite __PICOLIBC_ERRNO_FUNCTION u Makefileu i slijedite trag odande).
Daljnji primjeri¶
Pogledajte examples/natmod/ za daljnje primjere koji prikazuju mnoge od dostupnih značajki izvornih .mpy modula. Takve značajke uključuju:
korištenje više C izvornih datoteka
uključivanje Python koda uz C kod
rodata i BSS podaci
alokacija memorije
korištenje pomičnog zareza
rukovanje iznimkama
uključivanje vanjskih C biblioteka