Các module C ngoài của MicroPython¶
Khi phát triển các module để sử dụng với MicroPython, bạn có thể gặp phải những hạn chế với môi trường Python, thường do không thể truy cập một số tài nguyên phần cứng nhất định hoặc do giới hạn tốc độ của Python.
Nếu các hạn chế của bạn không thể giải quyết bằng các gợi ý trong Tối đa hóa tốc độ MicroPython, thì việc viết một phần hoặc toàn bộ module bằng C (và/hoặc C++ nếu được hỗ trợ cho cổng của bạn) là một lựa chọn khả thi.
Nếu module của bạn được thiết kế để truy cập hoặc làm việc với phần cứng hoặc thư viện phổ biến, hãy cân nhắc triển khai nó trong cây mã nguồn MicroPython cùng với các module tương tự và gửi như một pull request. Tuy nhiên, nếu bạn đang nhắm đến các hệ thống hiếm gặp hoặc độc quyền, có thể sẽ hợp lý hơn khi giữ module này bên ngoài kho lưu trữ MicroPython chính.
Chương này mô tả cách biên dịch các module ngoài đó vào tệp thực thi hoặc ảnh firmware MicroPython. Cả hai công cụ xây dựng Make và CMake đều được hỗ trợ, và khi viết một module ngoài, nên thêm các tệp xây dựng cho cả hai công cụ này để module có thể được sử dụng trên tất cả các cổng. Nhưng khi biên dịch một cổng cụ thể, bạn chỉ cần sử dụng một phương pháp xây dựng, hoặc Make hoặc CMake.
Một cách tiếp cận thay thế là sử dụng Mã máy gốc trong các tệp .mpy, cho phép viết mã C tùy chỉnh được đặt trong tệp .mpy, có thể được nhập động vào hệ thống MicroPython đang chạy mà không cần biên dịch lại firmware chính.
Cấu trúc của một module C ngoài¶
Một module C người dùng MicroPython là một thư mục với các tệp sau:
Các tệp mã nguồn
*.c/*.cpp/*.hcho module của bạn.Các tệp này thường bao gồm chức năng cấp thấp được triển khai và các hàm liên kết MicroPython để hiển thị các hàm và module.
Hiện tại, tài liệu tham khảo tốt nhất để viết các hàm/module này là tìm các module tương tự trong cây MicroPython và sử dụng chúng làm ví dụ.
micropython.mkchứa đoạn Makefile cho module này.$(USERMOD_DIR)có sẵn trongmicropython.mknhư đường dẫn đến thư mục module của bạn. Vì nó được định nghĩa lại cho mỗi module C, nó nên được mở rộng trongmicropython.mkcủa bạn thành một biến make cục bộ, ví dụEXAMPLE_MOD_DIR := $(USERMOD_DIR)micropython.mkcủa bạn phải thêm các tệp nguồn module vào biếnSRC_USERMOD_ChoặcSRC_USERMOD_LIB_C. Tệp trước sẽ được xử lý cho các định nghĩaMP_QSTR_vàMP_REGISTER_MODULE, tệp sau thì không (ví dụ: các hàm trợ giúp và mã thư viện không dành riêng cho MicroPython). Các đường dẫn này nên bao gồm bản sao mở rộng của$(USERMOD_DIR)của bạn, ví dụ:SRC_USERMOD_C += $(EXAMPLE_MOD_DIR)/modexample.c SRC_USERMOD_LIB_C += $(EXAMPLE_MOD_DIR)/utils/algorithm.c
Tương tự, sử dụng
SRC_USERMOD_CXXvàSRC_USERMOD_LIB_CXXcho các tệp nguồn C++. Nếu bạn muốn bao gồm các tệp assembly, hãy sử dụngSRC_USERMOD_LIB_ASM.Nếu bạn có các tùy chọn trình biên dịch tùy chỉnh (như
-Iđể thêm thư mục tìm kiếm tệp header), chúng nên được thêm vàoCFLAGS_USERMODcho mã C và vàoCXXFLAGS_USERMODcho mã C++.micropython.cmakechứa cấu hình CMake cho module này.Trong
micropython.cmake, bạn có thể sử dụng${CMAKE_CURRENT_LIST_DIR}làm đường dẫn đến module hiện tại.micropython.cmakecủa bạn nên định nghĩa một thư việnINTERFACEvà liên kết các tệp nguồn, định nghĩa biên dịch và thư mục include với nó. Sau đó thư viện nên được liên kết đến targetusermod.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)
Xem bên dưới để biết ví dụ sử dụng đầy đủ.
Ví dụ cơ bản¶
Module cexample cung cấp các ví dụ về một hàm và một lớp. Hàm cexample.add_ints(a, b) cộng hai đối số nguyên với nhau và trả về kết quả. Kiểu cexample.Timer() tạo các bộ định thời có thể được dùng để đo thời gian đã trôi qua kể từ khi đối tượng được khởi tạo.
Module có thể được tìm thấy trong cây mã nguồn MicroPython trong thư mục examples và có một tệp nguồn và một đoạn Makefile với nội dung như mô tả ở trên:
micropython/
└──examples/
└──usercmodule/
└──cexample/
├── examplemodule.c
├── micropython.mk
└── micropython.cmake
Tham khảo các chú thích trong các tệp này để biết thêm giải thích. Bên cạnh module cexample còn có cppexample, hoạt động theo cách tương tự nhưng cho thấy một cách kết hợp mã C và C++ trong MicroPython.
Biên dịch cmodule vào MicroPython¶
Để xây dựng một module như vậy, hãy biên dịch MicroPython (xem getting started), áp dụng 2 thay đổi:
Đặt cờ thời gian xây dựng
USER_C_MODULESđể trỏ đến các module bạn muốn bao gồm. Đối với các cổng sử dụng Make, biến này nên là một thư mục được tìm kiếm tự động cho các module. Đối với các cổng sử dụng CMake, biến này nên là một tệp bao gồm các module cần xây dựng. Xem bên dưới để biết chi tiết.Kích hoạt các module bằng cách đặt macro tiền xử lý C tương ứng thành 1. Điều này chỉ cần thiết nếu các module bạn đang xây dựng không được kích hoạt tự động.
Để xây dựng các module ví dụ đi kèm với MicroPython, đặt USER_C_MODULES thành thư mục examples/usercmodule cho Make, hoặc thành examples/usercmodule/micropython.cmake cho CMake.
Ví dụ, đây là cách xây dựng cổng unix với các module ví dụ:
cd micropython/ports/unix
make USER_C_MODULES=../../examples/usercmodule
Bạn có thể cần chạy make clean một lần lúc đầu khi bao gồm các module người dùng mới trong quá trình xây dựng. Đầu ra xây dựng sẽ hiển thị các module được tìm thấy:
...
Including User C Module from ../../examples/usercmodule/cexample
Including User C Module from ../../examples/usercmodule/cppexample
...
Đối với cổng dựa trên CMake như rp2, điều này sẽ trông hơi khác một chút (lưu ý rằng CMake thực sự được gọi bởi make):
cd micropython/ports/rp2
make USER_C_MODULES=../../examples/usercmodule/micropython.cmake
Một lần nữa, bạn có thể cần chạy make clean trước để CMake có thể nhận ra các module người dùng. Đầu ra xây dựng CMake liệt kê các module theo tên:
...
Including User C Module(s) from ../../examples/usercmodule/micropython.cmake
Found User C Module(s): usermod_cexample, usermod_cppexample
...
Nội dung của micropython.cmake cấp cao nhất có thể được sử dụng để kiểm soát các module nào được kích hoạt.
Đối với các dự án của riêng bạn, sẽ thuận tiện hơn khi giữ mã tùy chỉnh bên ngoài cây mã nguồn MicroPython chính, vì vậy cấu trúc thư mục dự án điển hình sẽ trông như thế này:
my_project/
├── modules/
│ ├── example1/
│ │ ├── example1.c
│ │ ├── micropython.mk
│ │ └── micropython.cmake
│ ├── example2/
│ │ ├── example2.c
│ │ ├── micropython.mk
│ │ └── micropython.cmake
│ └── micropython.cmake
└── micropython/
├──ports/
... ├──stm32/
...
Khi xây dựng với Make, đặt USER_C_MODULES thành thư mục my_project/modules. Ví dụ, xây dựng cổng stm32:
cd my_project/micropython/ports/stm32
make USER_C_MODULES=../../../modules
Khi xây dựng với CMake, micropython.cmake cấp cao nhất -- nằm trực tiếp trong thư mục my_project/modules -- nên include tất cả các module bạn muốn có sẵn:
include(${CMAKE_CURRENT_LIST_DIR}/example1/micropython.cmake) include(${CMAKE_CURRENT_LIST_DIR}/example2/micropython.cmake)
Sau đó xây dựng với:
cd my_project/micropython/ports/rp2
make USER_C_MODULES=../../../modules/micropython.cmake
Bạn cũng có thể chỉ định đường dẫn tuyệt đối đến USER_C_MODULES.
Tất cả các module được chỉ định bởi biến USER_C_MODULES (được tìm thấy trong thư mục này khi sử dụng Make, hoặc được thêm qua include khi sử dụng CMake) sẽ được biên dịch, nhưng chỉ những module được kích hoạt mới có thể nhập. Các module người dùng thường được kích hoạt theo mặc định (điều này do nhà phát triển module quyết định), trong trường hợp đó không cần làm gì thêm ngoài việc đặt USER_C_MODULES như mô tả ở trên.
Nếu một module không được kích hoạt theo mặc định thì macro tiền xử lý C tương ứng phải được kích hoạt. Tên macro này có thể được tìm thấy bằng cách tìm kiếm dòng MP_REGISTER_MODULE trong mã nguồn của module (thường xuất hiện ở cuối tệp nguồn chính). Macro này nên được bao quanh bởi cặp #if X / #endif, và tùy chọn cấu hình X phải được đặt thành 1 bằng cách sử dụng CFLAGS_EXTRA để module sẵn sàng. Nếu không có cặp #if X / #endif thì module được kích hoạt theo mặc định.
Ví dụ, module examples/usercmodule/cexample được kích hoạt theo mặc định nên có dòng sau trong mã nguồn của nó:
MP_REGISTER_MODULE(MP_QSTR_cexample, example_user_cmodule);
Ngoài ra, để module này bị vô hiệu hóa theo mặc định nhưng có thể chọn thông qua tùy chọn cấu hình tiền xử lý, nó sẽ là:
#if MODULE_CEXAMPLE_ENABLED MP_REGISTER_MODULE(MP_QSTR_cexample, example_user_cmodule); #endif
Trong trường hợp này, module được kích hoạt bằng cách thêm CFLAGS_EXTRA=-DMODULE_CEXAMPLE_ENABLED=1 vào lệnh make, hoặc chỉnh sửa mpconfigport.h hoặc mpconfigboard.h để thêm
#define MODULE_CEXAMPLE_ENABLED (1)
Lưu ý rằng phương pháp chính xác phụ thuộc vào cổng vì chúng có cấu trúc khác nhau. Nếu không thực hiện đúng, nó sẽ biên dịch nhưng việc nhập sẽ không tìm thấy module.
Sử dụng module trong MicroPython¶
Sau khi được tích hợp vào bản sao MicroPython của bạn, module có thể được truy cập trong Python giống như bất kỳ module tích hợp nào khác, ví dụ
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ấp phát bộ nhớ động C¶
MicroPython sử dụng "Python heap" riêng của mình cho Quản lý Bộ nhớ, không giống với "C heap" được sử dụng bởi các hàm thư viện C như malloc(), free(), v.v. Không phải mọi cổng MicroPython đều có "C heap".
Các cổng Tier 1 & 2 có mức độ hỗ trợ khác nhau cho việc cấp phát bộ nhớ động C qua "C heap":
Các cổng unix, windows, esp32 và webassembly hỗ trợ cấp phát bộ nhớ động C.
Cổng rp2 sẽ không cấp phát được bất kỳ bộ nhớ nào trong thời gian chạy trừ khi firmware được xây dựng với
MICROPY_C_HEAP_SIZE=nđể dànhnbyte bộ nhớ cho C heap. Bộ nhớ này sẽ không có sẵn cho mã Python sử dụng.Các bản xây dựng cổng alif, mimxrt, nrf, renesas-ra, samd và stm32 bao gồm cấp phát C động sẽ thất bại tại thời điểm liên kết với các lỗi như
undefined reference to `malloc'. MicroPython không có hỗ trợ tích hợp cho cấp phát C động trên các cổng này. Bất kỳ giải pháp nào đều yêu cầu thêm thủ công một triển khai C heap vào bản xây dựng tùy chỉnh.Cổng zephyr hiện không hỗ trợ xây dựng với các module người dùng.
Python heap như là C heap¶
Có thể thực tế khi mã C gọi các hàm cấp phát động "Python heap" như m_malloc(), m_malloc0() và m_free() thay thế.
Xem Bộ nhớ MicroPython từ mã C để biết thêm thông tin về cách tiếp cận này.
Các module C++¶
Hầu hết các cổng MicroPython Tier 1 & 2 (và một số Tier 3) hỗ trợ xây dựng các module người dùng C++, sử dụng các biến môi trường dành riêng cho C++ được mô tả ở trên.
Tích hợp C++ và MicroPython thành công đòi hỏi thêm một số lưu ý:
Cấp phát bộ nhớ động C++¶
Các chương trình C++ (cũng như các tính năng Thư viện Chuẩn C++) thường sử dụng cấp phát bộ nhớ động. Bộ cấp phát bộ nhớ mặc định của C++ (tức là các toán tử new và delete) thường được triển khai như một lớp trên Cấp phát bộ nhớ động C.
Đối với các cổng MicroPython không bao gồm hỗ trợ cấp phát bộ nhớ động C, cấp phát bộ nhớ động C++ có thể được hỗ trợ theo một trong hai cách:
Triển khai cấp phát bộ nhớ động C trong bản xây dựng tùy chỉnh của bạn.
Triển khai một bộ cấp phát C++ tùy chỉnh trong bản xây dựng tùy chỉnh của bạn.
Các lưu ý về liên kết¶
Vì MicroPython là một dự án dựa trên C, bất kỳ ký hiệu nào liên kết đến hoặc từ MicroPython cần được khai báo extern "C" trong mã C++.
Rất nên làm theo mẫu được trình bày trong examples/usercmodule/cppexample, trong đó module Python được triển khai trong một tệp bao bọc C tối giản xung quanh mã C++.