โมดูล C ภายนอกสำหรับ MicroPython¶
เมื่อพัฒนาโมดูลสำหรับใช้งานกับ MicroPython คุณอาจพบข้อจำกัดของสภาพแวดล้อม Python ซึ่งมักเกิดจากการไม่สามารถเข้าถึงทรัพยากรฮาร์ดแวร์บางอย่าง หรือข้อจำกัดด้านความเร็วของ Python
หากข้อจำกัดของคุณไม่สามารถแก้ไขได้ด้วยคำแนะนำใน การเพิ่มความเร็วสูงสุดใน MicroPython การเขียนโมดูลบางส่วนหรือทั้งหมดใน C (และ/หรือ C++ หากมีการรองรับสำหรับพอร์ตของคุณ) ถือเป็นทางเลือกที่ทำได้
หากโมดูลของคุณออกแบบมาเพื่อเข้าถึงหรือทำงานกับฮาร์ดแวร์หรือไลบรารีทั่วไป โปรดพิจารณานำไปติดตั้งในซอร์สโค้ด MicroPython ควบคู่กับโมดูลที่คล้ายกัน และส่งเป็น pull request หากอย่างไรก็ตามคุณกำหนดเป้าหมายเป็นระบบที่คลุมเครือหรือระบบที่เป็นกรรมสิทธิ์ การเก็บไว้ภายนอก MicroPython repository หลักอาจเหมาะสมกว่า
บทนี้อธิบายวิธีคอมไพล์โมดูลภายนอกดังกล่าวเข้าไปในไฟล์ปฏิบัติการ MicroPython หรือ firmware image รองรับทั้ง Make และ CMake และเมื่อเขียนโมดูลภายนอก ควรเพิ่มไฟล์ build สำหรับเครื่องมือทั้งสองนี้เพื่อให้โมดูลสามารถใช้งานได้กับทุกพอร์ต แต่เมื่อคอมไพล์พอร์ตใดพอร์ตหนึ่ง คุณจะต้องใช้เพียงวิธีเดียว ไม่ว่าจะเป็น Make หรือ CMake
อีกแนวทางหนึ่งคือการใช้ โค้ดเครื่องแบบ native ในไฟล์ .mpy ซึ่งช่วยให้เขียนโค้ด C แบบกำหนดเองที่วางในไฟล์ .mpy และสามารถนำเข้าแบบไดนามิกเข้าสู่ระบบ MicroPython ที่กำลังทำงานอยู่ โดยไม่ต้องคอมไพล์ firmware ใหม่
โครงสร้างของโมดูล C ภายนอก¶
โมดูล C สำหรับผู้ใช้ MicroPython คือไดเรกทอรีที่ประกอบด้วยไฟล์ดังต่อไปนี้:
ไฟล์ซอร์สโค้ด
*.c/*.cpp/*.hสำหรับโมดูลของคุณโดยทั่วไปไฟล์เหล่านี้จะรวมถึงฟังก์ชันระดับล่างที่นำมาใช้งานและฟังก์ชัน binding ของ MicroPython เพื่อเปิดเผยฟังก์ชันและโมดูล
ปัจจุบันแหล่งอ้างอิงที่ดีที่สุดสำหรับการเขียนฟังก์ชัน/โมดูลเหล่านี้คือการค้นหาโมดูลที่คล้ายกันใน MicroPython tree และใช้เป็นตัวอย่าง
micropython.mkประกอบด้วย Makefile fragment สำหรับโมดูลนี้$(USERMOD_DIR)มีให้ใช้งานในmicropython.mkในฐานะเส้นทางไปยังไดเรกทอรีโมดูลของคุณ เนื่องจากมีการกำหนดใหม่สำหรับโมดูล C แต่ละตัว ควรขยายในmicropython.mkของคุณเป็นตัวแปร make ระดับท้องถิ่น เช่นEXAMPLE_MOD_DIR := $(USERMOD_DIR)micropython.mkของคุณต้องเพิ่มไฟล์ซอร์สของโมดูลไปยังตัวแปรSRC_USERMOD_CหรือSRC_USERMOD_LIB_Cส่วนแรกจะถูกประมวลผลสำหรับMP_QSTR_และMP_REGISTER_MODULEส่วนหลังจะไม่ถูกประมวลผล (เช่น helpers และโค้ดไลบรารีที่ไม่ได้เฉพาะเจาะจงกับ MicroPython) เส้นทางเหล่านี้ควรรวมสำเนาที่ขยายแล้วของ$(USERMOD_DIR)ของคุณ เช่น:SRC_USERMOD_C += $(EXAMPLE_MOD_DIR)/modexample.c SRC_USERMOD_LIB_C += $(EXAMPLE_MOD_DIR)/utils/algorithm.c
ในทำนองเดียวกัน ใช้
SRC_USERMOD_CXXและSRC_USERMOD_LIB_CXXสำหรับไฟล์ซอร์ส C++ หากต้องการรวมไฟล์ assembly ให้ใช้SRC_USERMOD_LIB_ASMหากคุณมีตัวเลือก compiler แบบกำหนดเอง (เช่น
-Iเพื่อเพิ่มไดเรกทอรีในการค้นหาไฟล์ header) ควรเพิ่มไว้ในCFLAGS_USERMODสำหรับโค้ด C และCXXFLAGS_USERMODสำหรับโค้ด C++micropython.cmakeประกอบด้วยการกำหนดค่า CMake สำหรับโมดูลนี้ใน
micropython.cmakeคุณสามารถใช้${CMAKE_CURRENT_LIST_DIR}เป็นเส้นทางไปยังโมดูลปัจจุบันmicropython.cmakeของคุณควรกำหนดไลบรารีINTERFACEและเชื่อมโยงไฟล์ซอร์ส คำจำกัดความการคอมไพล์ และไดเรกทอรี include เข้ากับมัน จากนั้นควรเชื่อมโยงไลบรารีกับ targetusermodadd_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)
ดูด้านล่างสำหรับตัวอย่างการใช้งานเต็มรูปแบบ
ตัวอย่างพื้นฐาน¶
โมดูล cexample ให้ตัวอย่างสำหรับฟังก์ชันและคลาส ฟังก์ชัน cexample.add_ints(a, b) บวกอาร์กิวเมนต์จำนวนเต็มสองตัวและส่งคืนผลลัพธ์ ประเภท cexample.Timer() สร้างตัวจับเวลาที่สามารถใช้วัดเวลาที่ผ่านไปตั้งแต่สร้างออบเจ็กต์
โมดูลนี้สามารถพบได้ใน MicroPython source tree ในไดเรกทอรี examples และมีไฟล์ซอร์สและ Makefile fragment พร้อมเนื้อหาตามที่อธิบายไว้ข้างต้น:
micropython/
└──examples/
└──usercmodule/
└──cexample/
├── examplemodule.c
├── micropython.mk
└── micropython.cmake
โปรดดูความคิดเห็นในไฟล์เหล่านี้สำหรับคำอธิบายเพิ่มเติม ถัดจากโมดูล cexample ยังมี cppexample ซึ่งทำงานในลักษณะเดียวกัน แต่แสดงวิธีหนึ่งในการผสม C และ C++ ใน MicroPython
การคอมไพล์ cmodule เข้าสู่ MicroPython¶
ในการ build โมดูลดังกล่าว ให้คอมไพล์ MicroPython (ดู การเริ่มต้นใช้งาน) โดยใช้การแก้ไข 2 อย่าง:
ตั้งค่าแฟล็ก build-time
USER_C_MODULESให้ชี้ไปยังโมดูลที่คุณต้องการรวม สำหรับพอร์ตที่ใช้ Make ตัวแปรนี้ควรเป็นไดเรกทอรีที่ถูกค้นหาโดยอัตโนมัติสำหรับโมดูล สำหรับพอร์ตที่ใช้ CMake ตัวแปรนี้ควรเป็นไฟล์ที่รวมโมดูลที่จะ build ดูรายละเอียดด้านล่างเปิดใช้งานโมดูลโดยตั้งค่า C preprocessor macro ที่สอดคล้องกันเป็น 1 ซึ่งจำเป็นเฉพาะเมื่อโมดูลที่คุณ build ไม่ได้เปิดใช้งานโดยอัตโนมัติ
สำหรับการ build โมดูลตัวอย่างที่มาพร้อมกับ MicroPython ให้ตั้งค่า USER_C_MODULES เป็นไดเรกทอรี examples/usercmodule สำหรับ Make หรือ examples/usercmodule/micropython.cmake สำหรับ CMake
ตัวอย่างเช่น นี่คือวิธี build พอร์ต unix พร้อมโมดูลตัวอย่าง:
cd micropython/ports/unix
make USER_C_MODULES=../../examples/usercmodule
คุณอาจต้องรัน make clean หนึ่งครั้งเมื่อเริ่มต้นเมื่อรวมโมดูลผู้ใช้ใหม่ใน build ผลลัพธ์ของ build จะแสดงโมดูลที่พบ:
...
Including User C Module from ../../examples/usercmodule/cexample
Including User C Module from ../../examples/usercmodule/cppexample
...
สำหรับพอร์ตที่ใช้ CMake เช่น rp2 สิ่งนี้จะดูแตกต่างออกไปเล็กน้อย (โปรดทราบว่า CMake ถูกเรียกใช้จริงโดย make):
cd micropython/ports/rp2
make USER_C_MODULES=../../examples/usercmodule/micropython.cmake
อีกครั้ง คุณอาจต้องรัน make clean ก่อนเพื่อให้ CMake รับโมดูลผู้ใช้ ผลลัพธ์ CMake build แสดงโมดูลตามชื่อ:
...
Including User C Module(s) from ../../examples/usercmodule/micropython.cmake
Found User C Module(s): usermod_cexample, usermod_cppexample
...
เนื้อหาของ micropython.cmake ระดับบนสุดสามารถใช้เพื่อควบคุมว่าโมดูลใดถูกเปิดใช้งาน
สำหรับโครงการของคุณเอง สะดวกกว่าที่จะเก็บโค้ดแบบกำหนดเองไว้นอก MicroPython source tree หลัก ดังนั้นโครงสร้างไดเรกทอรีโครงการทั่วไปจะมีลักษณะดังนี้:
my_project/
├── modules/
│ ├── example1/
│ │ ├── example1.c
│ │ ├── micropython.mk
│ │ └── micropython.cmake
│ ├── example2/
│ │ ├── example2.c
│ │ ├── micropython.mk
│ │ └── micropython.cmake
│ └── micropython.cmake
└── micropython/
├──ports/
... ├──stm32/
...
เมื่อ build ด้วย Make ให้ตั้งค่า USER_C_MODULES เป็นไดเรกทอรี my_project/modules ตัวอย่างเช่น การ build พอร์ต stm32:
cd my_project/micropython/ports/stm32
make USER_C_MODULES=../../../modules
เมื่อ build ด้วย CMake ไฟล์ micropython.cmake ระดับบนสุด ที่อยู่โดยตรงในไดเรกทอรี my_project/modules ควร include โมดูลทั้งหมดที่คุณต้องการให้พร้อมใช้งาน:
include(${CMAKE_CURRENT_LIST_DIR}/example1/micropython.cmake) include(${CMAKE_CURRENT_LIST_DIR}/example2/micropython.cmake)
จากนั้น build ด้วย:
cd my_project/micropython/ports/rp2
make USER_C_MODULES=../../../modules/micropython.cmake
คุณยังสามารถระบุเส้นทางสัมบูรณ์ไปยัง USER_C_MODULES ได้
โมดูลทั้งหมดที่ระบุโดยตัวแปร USER_C_MODULES (ไม่ว่าจะพบในไดเรกทอรีนี้เมื่อใช้ Make หรือเพิ่มผ่าน include เมื่อใช้ CMake) จะถูกคอมไพล์ แต่เฉพาะโมดูลที่เปิดใช้งานเท่านั้นที่จะพร้อมสำหรับการนำเข้า โมดูลผู้ใช้มักเปิดใช้งานโดยค่าเริ่มต้น (ซึ่งผู้พัฒนาโมดูลเป็นผู้ตัดสินใจ) ในกรณีนี้ไม่มีอะไรต้องทำเพิ่มเติมนอกจากตั้งค่า USER_C_MODULES ตามที่อธิบายข้างต้น
หากโมดูลไม่ได้เปิดใช้งานโดยค่าเริ่มต้น จะต้องเปิดใช้งาน C preprocessor macro ที่สอดคล้องกัน ชื่อ macro นี้สามารถค้นหาได้โดยการค้นหาบรรทัด MP_REGISTER_MODULE ในซอร์สโค้ดของโมดูล (ปกติจะปรากฏที่ท้ายไฟล์ซอร์สหลัก) macro นี้ควรล้อมรอบด้วยคู่ #if X / #endif และตัวเลือกการกำหนดค่า X ต้องตั้งค่าเป็น 1 โดยใช้ CFLAGS_EXTRA เพื่อให้โมดูลพร้อมใช้งาน หากไม่มีคู่ #if X / #endif แสดงว่าโมดูลเปิดใช้งานโดยค่าเริ่มต้น
ตัวอย่างเช่น โมดูล examples/usercmodule/cexample เปิดใช้งานโดยค่าเริ่มต้น จึงมีบรรทัดต่อไปนี้ในซอร์สโค้ด:
MP_REGISTER_MODULE(MP_QSTR_cexample, example_user_cmodule);
หรือเพื่อทำให้โมดูลนี้ปิดใช้งานโดยค่าเริ่มต้น แต่สามารถเลือกได้ผ่านตัวเลือกการกำหนดค่า preprocessor จะเป็น:
#if MODULE_CEXAMPLE_ENABLED MP_REGISTER_MODULE(MP_QSTR_cexample, example_user_cmodule); #endif
ในกรณีนี้โมดูลจะเปิดใช้งานโดยการเพิ่ม CFLAGS_EXTRA=-DMODULE_CEXAMPLE_ENABLED=1 ไปยังคำสั่ง make หรือแก้ไข mpconfigport.h หรือ mpconfigboard.h เพื่อเพิ่ม
#define MODULE_CEXAMPLE_ENABLED (1)
โปรดทราบว่าวิธีการที่แน่นอนขึ้นอยู่กับพอร์ตเนื่องจากมีโครงสร้างที่แตกต่างกัน หากไม่ได้ทำอย่างถูกต้อง จะคอมไพล์ได้แต่การนำเข้าจะหาโมดูลไม่พบ
การใช้งานโมดูลใน MicroPython¶
เมื่อ build เข้าไปในสำเนา MicroPython ของคุณแล้ว โมดูลสามารถเข้าถึงได้ใน Python เหมือนกับโมดูล builtin อื่นๆ เช่น
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¶
MicroPython ใช้ "Python heap" ของตัวเองสำหรับ การจัดการหน่วยความจำ ซึ่งไม่เหมือนกับ "C heap" ที่ใช้โดยฟังก์ชันไลบรารี C เช่น malloc(), free() เป็นต้น ไม่ใช่ทุกพอร์ต MicroPython ที่มี "C heap" เลย
พอร์ต Tier 1 & 2 มีการสนับสนุนการจัดสรรหน่วยความจำแบบไดนามิก C ผ่าน "C heap" ที่แตกต่างกัน:
พอร์ต unix, windows, esp32 และ webassembly รองรับการจัดสรรหน่วยความจำแบบไดนามิก C
พอร์ต rp2 จะล้มเหลวในการจัดสรรหน่วยความจำใดๆ ในขณะรันไทม์ เว้นแต่ firmware จะถูก build ด้วย
MICROPY_C_HEAP_SIZE=nเพื่อสำรองnไบต์ของหน่วยความจำสำหรับ C heap หน่วยความจำนี้จะไม่พร้อมใช้งานสำหรับโค้ด Pythonการ build พอร์ต alif, mimxrt, nrf, renesas-ra, samd และ stm32 ที่รวม dynamic C allocation จะล้มเหลวในขั้นตอน link-time พร้อมข้อผิดพลาด เช่น
undefined reference to `malloc'MicroPython ไม่มีการสนับสนุน dynamic C allocation ในตัวสำหรับพอร์ตเหล่านี้ โซลูชันใดๆ ต้องเพิ่ม C heap implementation ด้วยตนเองในการ build แบบกำหนดเองพอร์ต zephyr ปัจจุบันไม่รองรับการ build ด้วยโมดูลผู้ใช้
Python heap ในฐานะ C heap¶
สำหรับโค้ด C อาจเป็นประโยชน์ที่จะเรียกฟังก์ชันการจัดสรรหน่วยความจำแบบไดนามิกของ "Python heap" เช่น m_malloc(), m_malloc0() และ m_free() แทน
ดู หน่วยความจำ MicroPython จากโค้ด C สำหรับข้อมูลเพิ่มเติมเกี่ยวกับแนวทางนี้
โมดูล C++¶
พอร์ต MicroPython Tier 1 & 2 ส่วนใหญ่ (และบาง Tier 3) รองรับการ build โมดูลผู้ใช้ C++ โดยใช้ตัวแปรสภาพแวดล้อมเฉพาะ C++ ที่อธิบายไว้ข้างต้น
การผสาน C++ และ MicroPython อย่างประสบความสำเร็จเกี่ยวข้องกับข้อควรพิจารณาเพิ่มเติมบางประการ:
การจัดสรรหน่วยความจำแบบไดนามิก C++¶
โปรแกรม C++ (รวมถึงลักษณะเด่นของ C++ Standard Library) มักใช้การจัดสรรหน่วยความจำแบบไดนามิก ตัวจัดสรรหน่วยความจำเริ่มต้นของ C++ (เช่น operators new และ delete) มักถูกนำมาใช้งานเป็นชั้นบน การจัดสรรหน่วยความจำแบบไดนามิก C
สำหรับพอร์ต MicroPython ที่ไม่รวม C dynamic memory allocation คุณสามารถรองรับ C++ dynamic memory allocation ได้ด้วยวิธีใดวิธีหนึ่งในสองวิธี:
นำ C dynamic memory allocation ไปติดตั้งใน build แบบกำหนดเองของคุณ
นำ C++ allocator แบบกำหนดเองไปติดตั้งใน build แบบกำหนดเองของคุณ
ข้อควรพิจารณาเกี่ยวกับ Linkage¶
เนื่องจาก MicroPython เป็นโครงการบน C ดังนั้นสัญลักษณ์ใดๆ ที่เชื่อมโยงไปหรือมาจาก MicroPython จำเป็นต้องระบุ extern "C" ในโค้ด C++
แนะนำอย่างยิ่งให้ปฏิบัติตามรูปแบบที่แสดงใน examples/usercmodule/cppexample ซึ่ง Python module ถูกนำมาใช้งานในไฟล์ wrapper C ขนาดเล็กที่ล้อมรอบโค้ด C++