ไฟล์ .mpy ของ MicroPython¶
MicroPython กำหนดแนวคิดของไฟล์ .mpy ซึ่งเป็นรูปแบบไฟล์คอนเทนเนอร์แบบไบนารีที่เก็บโค้ดที่คอมไพล์ล่วงหน้า และสามารถนำเข้าได้เหมือนโมดูล .py ทั่วไป ไฟล์ foo.mpy สามารถนำเข้าได้ผ่าน import foo ตราบใดที่ foo.mpy สามารถถูกค้นพบได้ตามปกติโดยกลไกการนำเข้า โดยทั่วไปแต่ละไดเรกทอรีที่ระบุใน sys.path จะถูกค้นหาตามลำดับ เมื่อค้นหาในไดเรกทอรีหนึ่ง ๆ จะมองหา foo.py ก่อน หากไม่พบจะมองหา foo.mpy จากนั้นการค้นหาจะดำเนินต่อในไดเรกทอรีถัดไปหากไม่พบทั้งสองไฟล์ ดังนั้น foo.py จะมีความสำคัญเหนือ foo.mpy
ไฟล์ .mpy เหล่านี้สามารถบรรจุ bytecode ซึ่งมักถูกสร้างขึ้นจากไฟล์ต้นฉบับ Python (.py) ผ่านโปรแกรม mpy-cross สำหรับบางสถาปัตยกรรม ไฟล์ .mpy ยังสามารถบรรจุโค้ดเครื่องแบบเนทีฟได้ ซึ่งสามารถสร้างได้หลายวิธี โดยเฉพาะอย่างยิ่งจากโค้ดต้นฉบับภาษา C
คอมไพเลอร์ mpy-cross¶
mpy-cross คือคอมไพเลอร์ข้ามแพลตฟอร์มที่แปลงไฟล์ต้นฉบับ .py ให้เป็นไฟล์คอนเทนเนอร์ไบนารี .mpy พร้อมสำหรับการนำเข้าบนกล้อง เป็นส่วนหนึ่งของซอร์สต้นไม้ MicroPython (ตัวเดียวกับที่ใช้สร้างเฟิร์มแวร์ของกล้อง) และยังเผยแพร่เป็นแพ็กเกจ pip สำหรับใช้งานฝั่งโฮสต์โดยไม่ต้องมีการเช็กเอาต์เฟิร์มแวร์แบบเต็ม:
$ pip install --user mpy-cross
หรือผ่าน pipx:
$ pipx install mpy-cross
เมื่อติดตั้งแล้ว ให้เรียกใช้กับไฟล์ต้นฉบับเดี่ยว:
$ mpy-cross foo.py
คำสั่งนี้จะสร้าง foo.mpy ในไดเรกทอรีปัจจุบัน พร้อมสำหรับการคัดลอกไปยังระบบไฟล์ของกล้องควบคู่กับโมดูลอื่น ๆ หรือนำไปใส่ในอิมเมจ ROMFS
ตัวเลือกบรรทัดคำสั่งที่มีประโยชน์ที่สุด:
-o <path>-- เส้นทางเอาต์พุตสำหรับ.mpyที่สร้างขึ้น (ค่าเริ่มต้นคือชื่อไฟล์อินพุตโดยแทนที่นามสกุล;-o -เขียนไปยัง stdout)-O<n>-- ระดับการปรับให้เหมาะสม0ถึง3ค่าเริ่มต้น0จะเก็บรักษา assertions และตำแหน่งต้นฉบับแบบเต็ม;3จะตัด assertions และ docstrings ออกและเขียนบล็อกif __debug__ใหม่ ระดับนี้ควบคุมพื้นผิวmicropython.opt_levelเดียวกันกับที่รันไทม์เปิดเผย-march=<arch>-- สถาปัตยกรรมเนทีฟเป้าหมายสำหรับฟังก์ชันที่ตกแต่งด้วย@nativeและ@viperจำเป็นต้องระบุเมื่อต้นฉบับใช้ตัวตกแต่งเหล่านั้น ค่าต้องตรงกับคลาส MCU ของกล้อง: เลือกจากรายการที่mpy-cross --helpแสดง หรืออ่านจากกล้องขณะรันไทม์ด้วยsys.implementation._mpy-s <path>-- สตริงเส้นทางต้นฉบับที่ฝังอยู่ในข้อมูลดีบักของ.mpyมีประโยชน์เมื่อเส้นทางบนดิสก์แตกต่างจากเส้นทางการนำเข้าที่ไฟล์ควรแสดงใน tracebacks-X emit=bytecode|native|viper-- เลือก emitter เริ่มต้นสำหรับทั้งโมดูล (ทางเลือกในระดับฟังก์ชันแทนตัวตกแต่ง@native/@viper)--version-- แสดงเวอร์ชันรูปแบบ.mpyที่ไบนารีนี้สร้างออกมา ตัวเลขนั้นต้องตรงกับเวอร์ชันที่รันไทม์ของกล้องรองรับ (ดูตารางรีลีสด้านล่าง) มิฉะนั้นการนำเข้าจะเกิดข้อผิดพลาดValueError('incompatible .mpy file')
รัน mpy-cross --help เพื่อดูรายการแฟล็กทั้งหมด
แพ็กเกจ pip ยังเปิดเผย Python module API ขนาดเล็กเพื่อให้สคริปต์สร้างสามารถขับเคลื่อนคอมไพเลอร์ในกระบวนการแทนที่จะ fork subprocess ด้วยตนเอง:
import mpy_cross
mpy_cross.compile('foo.py', dest='build/foo.mpy', opt=3,
march=mpy_cross.NATIVE_ARCH_ARMV7EMSP)
mpy_cross.compile, mpy_cross.run และ mpy_cross.mpy_version คือจุดเข้าทั้งสาม; mpy_cross.CrossCompileError รับ stderr ของคอมไพเลอร์เมื่อมีบางอย่างผิดพลาด ค่าคงที่สถาปัตยกรรม (NATIVE_ARCH_ARMV7EMSP, NATIVE_ARCH_ARMV7EMDP เป็นต้น) ตรงกับสตริงที่แฟล็ก -march ยอมรับ
การกำหนดเวอร์ชันและความเข้ากันได้ของไฟล์ .mpy¶
ไฟล์ .mpy ที่กำหนดอาจเข้ากันได้หรือไม่ได้กับระบบ MicroPython ที่กำหนด ความเข้ากันได้ขึ้นอยู่กับสิ่งต่อไปนี้:
เวอร์ชันของไฟล์ .mpy: เวอร์ชันของไฟล์ต้องตรงกับเวอร์ชันที่รองรับโดยระบบที่โหลดไฟล์นั้น
เวอร์ชันย่อยของไฟล์ .mpy: หากไฟล์ .mpy มีโค้ดเครื่องแบบเนทีฟ เวอร์ชันย่อยของไฟล์ต้องตรงกับเวอร์ชันที่รองรับโดยระบบที่โหลดไฟล์นั้น มิฉะนั้น หากไม่มีโค้ดเครื่องแบบเนทีฟในไฟล์ .mpy เวอร์ชันย่อยจะถูกละเว้นเมื่อโหลด
บิตจำนวนเต็มขนาดเล็ก: ไฟล์ .mpy จะต้องการจำนวนบิตขั้นต่ำใน small integer และระบบที่โหลดต้องรองรับบิตอย่างน้อยจำนวนนี้
สถาปัตยกรรมเนทีฟ: หากไฟล์ .mpy มีโค้ดเครื่องแบบเนทีฟ ไฟล์จะระบุสถาปัตยกรรมของโค้ดเครื่องนั้น และระบบที่โหลดต้องรองรับการรันโค้ดของสถาปัตยกรรมนั้น
หากระบบ MicroPython รองรับการนำเข้าไฟล์ .mpy ฟิลด์ sys.implementation._mpy จะมีอยู่และส่งคืนจำนวนเต็มซึ่งเข้ารหัสเวอร์ชัน (8 บิตล่าง) ลักษณะเด่น และสถาปัตยกรรมเนทีฟ
การพยายามนำเข้าไฟล์ .mpy ที่ล้มเหลวในการทดสอบสี่รายการแรกจะเกิดข้อผิดพลาด ValueError('incompatible .mpy file') การพยายามนำเข้าไฟล์ .mpy ที่ล้มเหลวในการทดสอบสถาปัตยกรรมเนทีฟ (หากมีโค้ดเครื่องแบบเนทีฟ) จะเกิดข้อผิดพลาด ValueError('incompatible .mpy arch')
หากการนำเข้าไฟล์ .mpy ล้มเหลว ให้ลองทำสิ่งต่อไปนี้:
ตรวจสอบเวอร์ชัน .mpy และแฟล็กที่รองรับโดยระบบ MicroPython ของคุณโดยรัน:
import sys sys_mpy = sys.implementation._mpy arch = [None, 'x86', 'x64', 'armv6', 'armv6m', 'armv7m', 'armv7em', 'armv7emsp', 'armv7emdp', 'xtensa', 'xtensawin', 'rv32imc', 'rv64imc'][(sys_mpy >> 10) & 0x0F] print('mpy version:', sys_mpy & 0xff) print('mpy sub-version:', sys_mpy >> 8 & 3) print('mpy flags:', end='') if arch: print(' -march=' + arch, end='') if (sys_mpy >> 16) != 0: print(' -march-flags=' + (sys_mpy >> 16), end='') print()
ตรวจสอบความถูกต้องของไฟล์ .mpy โดยตรวจสอบสองไบต์แรกของไฟล์ ไบต์แรกควรเป็นตัวอักษรพิมพ์ใหญ่ 'M' และไบต์ที่สองจะเป็นหมายเลขเวอร์ชัน ซึ่งควรตรงกับเวอร์ชันระบบจากด้านบน หากไม่ตรงกัน ให้สร้างไฟล์ .mpy ใหม่
ตรวจสอบว่าเวอร์ชัน .mpy ของระบบตรงกับเวอร์ชันที่ส่งออกโดย
mpy-crossที่ใช้สร้างไฟล์ .mpy ซึ่งพบได้โดยmpy-cross --versionหากไม่ตรงกัน ให้คอมไพล์mpy-crossใหม่จากที่เก็บ Git ที่เช็กเอาต์ที่แท็ก (หรือแฮช) ที่รายงานโดยmpy-cross --versionตรวจสอบให้แน่ใจว่าคุณใช้แฟล็ก
mpy-crossที่ถูกต้อง ซึ่งพบได้จากโค้ดด้านบน หรือโดยตรวจสอบตัวแปร MakefileMPY_CROSS_FLAGSสำหรับพอร์ตที่คุณใช้หากไบต์ที่สามของไฟล์ .mpy มีบิต #6 ถูกตั้งค่า ให้ตรวจสอบว่า vuint ของบิตแฟล็กเฉพาะสถาปัตยกรรมที่เข้ารหัสเข้ากันได้กับเป้าหมายที่คุณนำเข้าไฟล์
ตารางต่อไปนี้แสดงความสอดคล้องระหว่างรีลีส MicroPython และเวอร์ชัน .mpy
รีลีส MicroPython |
เวอร์ชัน .mpy |
|---|---|
v1.23.0 และสูงกว่า |
6.3 |
v1.22.x |
6.2 |
v1.20 - v1.21.0 |
6.1 |
v1.19.x |
6 |
v1.12 - v1.18 |
5 |
v1.11 |
4 |
v1.9.3 - v1.10 |
3 |
v1.9 - v1.9.2 |
2 |
v1.5.1 - v1.8.7 |
0 |
เพื่อความสมบูรณ์ ตารางถัดไปแสดง Git commit ของที่เก็บ MicroPython หลักซึ่งเวอร์ชัน .mpy ถูกเปลี่ยนแปลง
การเปลี่ยนแปลงเวอร์ชัน .mpy |
Git commit |
|---|---|
6.2 ถึง 6.3 |
bdbc869f9ea200c0d28b2bc7bfb60acd9d884e1b |
6.1 ถึง 6.2 |
6967ff3c581a66f73e9f3d78975f47528db39980 |
6 ถึง 6.1 |
d94141e1473aebae0d3c63aeaa8397651ad6fa01 |
5 ถึง 6 |
f2040bfc7ee033e48acef9f289790f3b4e6b74e5 |
4 ถึง 5 |
5716c5cf65e9b2cb46c2906f40302401bdd27517 |
3 ถึง 4 |
9a5f92ea72754c01cc03e5efcdfe94021120531e |
2 ถึง 3 |
ff93fd4f50321c6190e1659b19e64fef3045a484 |
1 ถึง 2 |
dd11af209d226b7d18d5148b239662e30ed60bad |
0 ถึง 1 |
6a11048af1d01c78bdacddadd1b72dc7ba7c6478 |
เวอร์ชัน 0 เริ่มต้น |
d8c834c95d506db979ec871417de90b7951edc30 |
การเข้ารหัสไบนารีของไฟล์ .mpy¶
ไฟล์ .mpy ของ MicroPython เป็นรูปแบบคอนเทนเนอร์ไบนารีที่มีออบเจกต์โค้ด (bytecode และโค้ดเครื่องแบบเนทีฟ) เก็บไว้ภายในในลำดับชั้นแบบซ้อน โค้ดสำหรับโมดูลภายนอกจะถูกเก็บก่อน จากนั้นตามด้วยลูก ๆ ของมัน แต่ละลูกอาจมีลูกเพิ่มเติม ตัวอย่างเช่นในกรณีที่คลาสมีเมธอด หรือฟังก์ชันกำหนด lambda หรือ comprehension เพื่อให้ไฟล์มีขนาดเล็กในขณะที่ยังคงให้ค่าที่เป็นไปได้หลากหลาย จึงใช้แนวคิดของจำนวนเต็มไม่มีเครื่องหมายที่เข้ารหัสแบบแปรผัน (vuint) ในหลายที่ คล้ายกับการเข้ารหัส UTF-8 การเข้ารหัสนี้เก็บ 7 บิตต่อไบต์โดยบิตที่ 8 (MSB) ถูกตั้งค่าหากมีหนึ่งไบต์หรือมากกว่าตามมา บิตของจำนวนเต็มไม่มีเครื่องหมายจะถูกเก็บใน vuint ในรูปแบบ LSB
ระดับบนสุดของไฟล์ .mpy ประกอบด้วยสามส่วน:
ส่วนหัว (header)
ตาราง qstr และค่าคงที่ส่วนกลาง
raw-code สำหรับขอบเขตภายนอกของโมดูล ขอบเขตภายนอกนี้จะถูกรันเมื่อนำเข้าไฟล์ .mpy
คุณสามารถตรวจสอบเนื้อหาของไฟล์ .mpy โดยใช้ mpy-tool.py ตัวอย่างเช่น (รันจากรากของที่เก็บ MicroPython หลัก):
$ ./tools/mpy-tool.py -xd myfile.mpy
ส่วนหัว (header)¶
ส่วนหัว .mpy คือ:
ขนาด |
ฟิลด์ |
|---|---|
ไบต์ |
ค่า 0x4d (ASCII 'M') |
ไบต์ |
หมายเลขเวอร์ชันหลัก .mpy |
ไบต์ |
แฟล็กลักษณะเด่น, arch เนทีฟ, หมายเลขเวอร์ชันรอง (เดิมเป็นแฟล็กลักษณะเด่นในเวอร์ชันเก่า) |
ไบต์ |
จำนวนบิตใน small int |
ไบต์ที่สามถูกแบ่งดังนี้ (MSB ก่อน):
บิต |
ความหมาย |
|---|---|
7 |
สำรอง ต้องเป็น 0 |
6 |
vuint ที่มีแฟล็กเฉพาะสถาปัตยกรรมตามหลังส่วนหัว |
5..2 |
หมายเลข arch เนทีฟ |
1..0 |
หมายเลขเวอร์ชันรอง |
แฟล็กเฉพาะสถาปัตยกรรม¶
หากบิต #6 ของไบต์แฟล็กลักษณะเด่นของส่วนหัวถูกตั้งค่า vuint ที่บรรจุข้อมูลเฉพาะสถาปัตยกรรมแบบเสริมจะตามหลังส่วนหัว เนื้อหาของจำนวนเต็มนี้ขึ้นอยู่กับสถาปัตยกรรมเนทีฟที่ไฟล์มุ่งหมายสำหรับ
ปัจจุบันใช้เพื่อเก็บว่าส่วนขยายโปรเซสเซอร์ RISC-V ใดที่ไฟล์ MPY ต้องการเพื่อทำงานได้อย่างถูกต้องนอกเหนือจาก I, M, C และ Zicsr ArmV7 รสชาติต่าง ๆ ถูกระบุโดยหมายเลข arch เนทีฟของพวกมัน แต่การนำกลไกนั้นมาใช้ซ้ำจะทำให้สิ่งต่าง ๆ ซับซ้อนขึ้นสำหรับ RV32 และ RV64
ไฟล์ MPY ที่มุ่งหมายสำหรับ RV32 หรือ RV64 ที่ไม่ต้องการส่วนขยายโปรเซสเซอร์เฉพาะใด ๆ ไม่จำเป็นต้องมีจำนวนเต็มแฟล็ก (พร้อมกับการตั้งค่าบิตที่เหมาะสมในส่วนหัว) การขาดค่าแฟล็กสำหรับไฟล์ MPY RV32 และ RV64 ใช้เพื่อระบุว่าไม่จำเป็นต้องมีส่วนขยายเฉพาะ และประหยัดหนึ่งไบต์ในไบนารีเอาต์พุตสุดท้าย
ดูเพิ่มเติมที่ตัวเลือกบรรทัดคำสั่ง -march-flags ทั้งใน mpy-tool.py และ mpy-cross และตัวเลือก --arch-flags ใน mpy_ld.py เพื่อตั้งค่านี้เมื่อสร้างไฟล์ MPY
ตาราง qstr และค่าคงที่ส่วนกลาง¶
ไฟล์ .mpy มีตาราง qstr หนึ่งตารางและตารางออบเจกต์คงที่หนึ่งตาราง สิ่งเหล่านี้เป็นแบบส่วนกลางของไฟล์ .mpy และถูกอ้างอิงโดยออบเจกต์ raw-code ที่ซ้อนอยู่ทั้งหมด ตาราง qstr แมปหมายเลข qstr ภายใน (ภายในไฟล์ .mpy) ไปยังหมายเลข qstr ที่แก้ไขแล้วของรันไทม์ที่นำเข้าไฟล์ .mpy สิ่งนี้เชื่อมโยงไฟล์ .mpy กับส่วนที่เหลือของระบบที่รันอยู่ ตารางออบเจกต์คงที่จะถูกเติมด้วยการอ้างอิงไปยังออบเจกต์คงที่ทั้งหมดที่ไฟล์ .mpy ต้องการ
ขนาด |
ฟิลด์ |
|---|---|
vuint |
จำนวน qstrs |
vuint |
จำนวนออบเจกต์คงที่ |
... |
ข้อมูล qstr |
... |
ออบเจกต์คงที่ที่เข้ารหัส |
องค์ประกอบ Raw code¶
องค์ประกอบ raw-code มีโค้ด ไม่ว่าจะเป็น bytecode หรือโค้ดเครื่องแบบเนทีฟ เนื้อหาประกอบด้วย:
ขนาด |
ฟิลด์ |
|---|---|
vuint |
ประเภท ขนาด และว่ามีองค์ประกอบ sub-raw-code หรือไม่ |
... |
โค้ด (bytecode หรือโค้ดเครื่อง) |
vuint |
จำนวนองค์ประกอบ sub-raw-code (เฉพาะเมื่อไม่ใช่ศูนย์) |
... |
องค์ประกอบ sub-raw-code |
vuint แรกในองค์ประกอบ raw-code เข้ารหัสประเภทของโค้ดที่เก็บในองค์ประกอบนี้ (สองบิตที่มีนัยสำคัญน้อยที่สุด) ว่า raw-code นี้มีลูกหรือไม่ (บิตที่สามที่มีนัยสำคัญน้อยที่สุด) และความยาวของโค้ดที่ตามมา (จำนวน RAM ที่จะจัดสรร)
ตาม vuint มาเป็นโค้ดเอง เว้นแต่ประเภทโค้ดจะเป็น viper code ที่มีการย้ายที่อยู่ โค้ดนี้เป็นข้อมูลคงที่และไม่จำเป็นต้องถูกแก้ไข
หาก raw-code นี้มีลูก (ตามที่ระบุโดยบิตใน vuint แรก) ตาม vuint มาเป็น vuint ที่นับจำนวนองค์ประกอบ sub-raw-code
สุดท้ายองค์ประกอบ sub-raw-code ใด ๆ จะถูกเก็บแบบเรียกซ้ำ