MicroPython harici C modülleri

MicroPython ile kullanılacak modüller geliştirirken, çoğunlukla belirli donanım kaynaklarına erişememe veya Python hız sınırlamaları nedeniyle Python ortamının kısıtlamalarıyla karşılaşabilirsiniz.

Kısıtlamalarınız MicroPython hızını en üst düzeye çıkarmak bölümündeki önerilerle çözülemiyorsa, modülünüzün bir kısmını veya tamamını C ile (ve/veya port’unuz için uygulanmışsa C++ ile) yazmak uygun bir seçenektir.

Modülünüz yaygın olarak bulunabilen donanım veya kütüphanelerle çalışacak ya da bunlara erişecek şekilde tasarlandıysa, lütfen onu benzer modüllerin yanında MicroPython kaynak ağacının içinde uygulamayı ve bir pull request olarak göndermeyi düşünün. Ancak belirsiz veya tescilli sistemleri hedefliyorsanız, bunu ana MicroPython deposunun dışında tutmak daha mantıklı olabilir.

Bu bölüm, bu tür harici modüllerin MicroPython çalıştırılabilir dosyasına veya aygıt yazılımı (firmware) görüntüsüne nasıl derleneceğini açıklar. Hem Make hem de CMake derleme araçları desteklenir ve harici bir modül yazarken, modülün tüm port’larda kullanılabilmesi için her iki araca yönelik derleme dosyalarını da eklemek iyi bir fikirdir. Ancak belirli bir port’u derlerken yalnızca tek bir derleme yöntemini, Make veya CMake’i kullanmanız gerekir.

Alternatif bir yaklaşım, bir .mpy dosyasına yerleştirilen özel C kodu yazmaya olanak tanıyan .mpy dosyalarındaki yerel makine kodu kullanmaktır; bu kod, ana aygıt yazılımını yeniden derlemeye gerek kalmadan çalışan bir MicroPython sistemine dinamik olarak içe aktarılabilir.

Harici bir C modülünün yapısı

Bir MicroPython kullanıcı C modülü, aşağıdaki dosyaları içeren bir dizindir:

  • Modülünüze ait *.c / *.cpp / *.h kaynak kodu dosyaları.

    Bunlar tipik olarak uygulanan düşük seviyeli işlevselliği ve işlevleri ve modül(ler)i açığa çıkaran MicroPython bağlama işlevlerini içerir.

    Şu anda bu işlevleri/modülleri yazmak için en iyi başvuru kaynağı, MicroPython ağacında benzer modülleri bulmak ve onları örnek olarak kullanmaktır.

  • micropython.mk bu modüle ait Makefile parçasını içerir.

    $(USERMOD_DIR), modül dizininize giden yol olarak micropython.mk içinde kullanılabilir. Her C modülü için yeniden tanımlandığından, micropython.mk dosyanızda yerel bir make değişkenine genişletilmelidir, örneğin EXAMPLE_MOD_DIR := $(USERMOD_DIR)

    micropython.mk dosyanız, modüllerinizin kaynak dosyalarını SRC_USERMOD_C veya SRC_USERMOD_LIB_C değişkenlerine eklemelidir. İlki MP_QSTR_ ve MP_REGISTER_MODULE tanımları için işlenir, ikincisi işlenmez (örneğin yardımcılar ve MicroPython’a özgü olmayan kütüphane kodu). Bu yollar, $(USERMOD_DIR) değişkeninin genişletilmiş kopyanızı içermelidir, örneğin:

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

    Benzer şekilde, C++ kaynak dosyaları için SRC_USERMOD_CXX ve SRC_USERMOD_LIB_CXX kullanın. Assembly dosyaları eklemek isterseniz SRC_USERMOD_LIB_ASM kullanın.

    Özel derleyici seçenekleriniz varsa (başlık dosyalarını aramak için dizin ekleyen -I gibi), bunlar C kodu için CFLAGS_USERMOD değişkenine ve C++ kodu için CXXFLAGS_USERMOD değişkenine eklenmelidir.

  • micropython.cmake bu modüle ait CMake yapılandırmasını içerir.

    micropython.cmake içinde, geçerli modüle giden yol olarak ${CMAKE_CURRENT_LIST_DIR} değişkenini kullanabilirsiniz.

    micropython.cmake dosyanız bir INTERFACE kütüphanesi tanımlamalı ve kaynak dosyalarınızı, derleme tanımlarınızı ve içerme dizinlerinizi bununla ilişkilendirmelidir. Kütüphane daha sonra usermod hedefine bağlanmalıdır.

    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)
    

    Tam kullanım örneği için aşağıya bakın.

Temel örnek

cexample modülü, bir işlev ve bir sınıf için örnekler sağlar. cexample.add_ints(a, b) işlevi, iki tamsayı argümanını birbirine ekler ve sonucu döndürür. cexample.Timer() türü, nesnenin örneklendiği andan itibaren geçen süreyi ölçmek için kullanılabilen zamanlayıcılar oluşturur.

Modül, MicroPython kaynak ağacında örnekler dizininde bulunabilir ve yukarıda açıklandığı gibi içeriğe sahip bir kaynak dosyası ve bir Makefile parçasına sahiptir:

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

Ek açıklama için bu dosyalardaki yorumlara bakın. cexample modülünün yanında ayrıca cppexample da bulunur; bu aynı şekilde çalışır ancak MicroPython’da C ve C++ kodunu karıştırmanın bir yolunu gösterir.

cmodule’ün MicroPython’a derlenmesi

Böyle bir modül oluşturmak için, 2 değişiklik uygulayarak MicroPython’u derleyin (bkz. başlangıç):

  1. Derleme zamanı bayrağı olan USER_C_MODULES değişkenini, dahil etmek istediğiniz modüllere işaret edecek şekilde ayarlayın. Make kullanan port’lar için bu değişken, modüller için otomatik olarak aranan bir dizin olmalıdır. CMake kullanan port’lar için bu değişken, oluşturulacak modülleri içeren bir dosya olmalıdır. Ayrıntılar için aşağıya bakın.

  2. İlgili C ön işlemci makrosunu 1 olarak ayarlayarak modülleri etkinleştirin. Bu yalnızca, oluşturduğunuz modüller otomatik olarak etkinleştirilmiyorsa gereklidir.

MicroPython ile birlikte gelen örnek modülleri oluşturmak için, USER_C_MODULES değişkenini Make için examples/usercmodule dizinine veya CMake için examples/usercmodule/micropython.cmake dosyasına ayarlayın.

Örneğin, unix port’unun örnek modüllerle nasıl oluşturulacağı aşağıda gösterilmiştir:

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

Derlemeye yeni kullanıcı modülleri dahil ederken başlangıçta bir kez make clean çalıştırmanız gerekebilir. Derleme çıktısı bulunan modülleri gösterecektir:

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

rp2 gibi CMake tabanlı bir port için bu biraz farklı görünecektir (CMake’in aslında make tarafından çağrıldığını unutmayın):

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

Yine, CMake’in kullanıcı modüllerini alması için önce make clean çalıştırmanız gerekebilir. CMake derleme çıktısı, modülleri adlarıyla listeler:

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

Üst düzey micropython.cmake dosyasının içeriği, hangi modüllerin etkinleştirileceğini kontrol etmek için kullanılabilir.

Kendi projeleriniz için, özel kodu ana MicroPython kaynak ağacının dışında tutmak daha kullanışlıdır, bu nedenle tipik bir proje dizin yapısı şöyle görünür:

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

Make ile derlerken USER_C_MODULES değişkenini my_project/modules dizinine ayarlayın. Örneğin, stm32 port’unu oluşturma:

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

CMake ile derlerken, doğrudan my_project/modules dizininde bulunan üst düzey micropython.cmake dosyası, kullanılabilir olmasını istediğiniz tüm modülleri include etmelidir:

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

Ardından şununla derleyin:

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

USER_C_MODULES değişkenine mutlak yollar da belirtebilirsiniz.

USER_C_MODULES değişkeniyle belirtilen tüm modüller (Make kullanılırken bu dizinde bulunanlar veya CMake kullanılırken include ile eklenenler) derlenecektir, ancak yalnızca etkinleştirilenler içe aktarılabilir olacaktır. Kullanıcı modülleri genellikle varsayılan olarak etkinleştirilir (buna modülün geliştiricisi karar verir); bu durumda yukarıda açıklandığı gibi USER_C_MODULES değişkenini ayarlamaktan başka yapılacak bir şey yoktur.

Bir modül varsayılan olarak etkinleştirilmemişse, ilgili C ön işlemci makrosu etkinleştirilmelidir. Bu makro adı, modülün kaynak kodundaki MP_REGISTER_MODULE satırı aranarak bulunabilir (genellikle ana kaynak dosyasının sonunda görünür). Bu makro bir #if X / #endif çiftiyle çevrelenmelidir ve modülü kullanılabilir hale getirmek için X yapılandırma seçeneği CFLAGS_EXTRA kullanılarak 1 olarak ayarlanmalıdır. Eğer bir #if X / #endif çifti yoksa modül varsayılan olarak etkinleştirilmiştir.

Örneğin, examples/usercmodule/cexample modülü varsayılan olarak etkinleştirildiğinden kaynak kodunda aşağıdaki satır bulunur:

MP_REGISTER_MODULE(MP_QSTR_cexample, example_user_cmodule);

Alternatif olarak, bu modülü varsayılan olarak devre dışı ancak bir ön işlemci yapılandırma seçeneği aracılığıyla seçilebilir hale getirmek için şu şekilde olur:

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

Bu durumda modül, make komutuna CFLAGS_EXTRA=-DMODULE_CEXAMPLE_ENABLED=1 eklenerek veya mpconfigport.h ya da mpconfigboard.h düzenlenerek şu eklenir:

#define MODULE_CEXAMPLE_ENABLED (1)

Tam yöntemin port’a bağlı olduğunu unutmayın, çünkü farklı yapılara sahiptirler. Doğru yapılmazsa derlenir ancak içe aktarma modülü bulamaz.

MicroPython’da modül kullanımı

MicroPython kopyanıza derlendikten sonra, modüle artık tıpkı diğer yerleşik modüller gibi Python’da erişilebilir, örneğin

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 Dinamik Bellek Ayırma

MicroPython, Bellek Yönetimi için kendi “Python yığını”nı kullanır; bu, malloc(), free() gibi C kütüphane işlevleri tarafından kullanılan “C yığını” ile aynı değildir. Her MicroPython port’unda bir “C yığını” bulunmaz.

Tier 1 ve 2 port’ları, bir “C yığını” aracılığıyla C dinamik bellek ayırmaya farklı düzeylerde destek sağlar:

  • unix, windows, esp32 ve webassembly port’ları C dinamik bellek ayırmayı destekler.

  • rp2 port’u, aygıt yazılımı bir C yığını için n bayt bellek ayırmak üzere MICROPY_C_HEAP_SIZE=n ile oluşturulmadıkça çalışma zamanında herhangi bir bellek ayırmada başarısız olur. Bu bellek, Python kodunun kullanımına açık olmayacaktır.

  • Dinamik C ayırma içeren alif, mimxrt, nrf, renesas-ra, samd ve stm32 port derlemeleri, undefined reference to `malloc' gibi hatalarla bağlama zamanında başarısız olur. MicroPython’un bu port’larda dinamik C ayırma için yerleşik desteği yoktur. Herhangi bir çözüm, özel derlemeye manuel olarak bir C yığını uygulaması eklemeyi gerektirir.

  • zephyr port’u şu anda kullanıcı modülleriyle derlemeyi desteklemez.

Python yığını C yığını olarak

C kodunun bunun yerine m_malloc(), m_malloc0() ve m_free() gibi “Python yığını” dinamik ayırma işlevlerini çağırması pratik olabilir.

Bu yaklaşım hakkında daha fazla bilgi için C Kodundan MicroPython Belleği bölümüne bakın.

C++ Modülleri

Çoğu Tier 1 ve 2 MicroPython port’u (ve bazı Tier 3 port’ları), yukarıda açıklanan C++’a özgü ortam değişkenlerini kullanarak C++ kullanıcı modülleri oluşturmayı destekler.

C++ ve MicroPython’u başarıyla entegre etmek bazı ek hususları içerir:

C++ Dinamik Bellek Ayırma

C++ programları (ve C++ Standart Kütüphanesi özellikleri) tipik olarak dinamik bellek ayırma kullanır. Varsayılan C++ bellek ayırıcısı (yani new ve delete operatörleri) tipik olarak C Dinamik Bellek Ayırma üzerinde bir katman olarak uygulanır.

C dinamik bellek ayırma desteği içermeyen MicroPython port’ları için, C++ dinamik bellek ayırma iki yoldan biriyle desteklenebilir:

  • Özel derlemenizde C dinamik bellek ayırmayı uygulayın.

  • Özel derlemenizde özel bir C++ ayırıcısı uygulayın.

Bağlama Hususları

MicroPython C tabanlı bir proje olduğundan, MicroPython’a veya MicroPython’dan bağlanan tüm semboller C++ kodunda extern "C" olarak nitelenmelidir.

Python modülünün, C++ kodu çevresinde minimal bir C dosyası sarmalayıcısı içinde uygulandığı examples/usercmodule/cppexample örneğinde gösterilen kalıbı izlemeniz şiddetle önerilir.