จากบทความ ulab ก่อนหน้านี้จะพบว่า Micropython สามารถใช้งานคำสั่งเกี่ยวกับการประมวลผลชุดข้อมูลเหมือนกับใช้ใน Numpy ได้ผ่านทางไลบรารี ulab ซึ่งก่อนหน้านี้ทีมผู้เขียนใช้งานรุ่น 0.54.0 ซึ่งเก่ากว่ารุ่นปัจจุบัน คือ 3.0.1 ทำให้เกิดบทความนี้ขึ้นมา โดยบทความกล่าวถึงวิธีการสร้าง Micropython ที่ผนวกไลบรารี ulab เข้าไป และใช้งานกับ esp32 รุ่นที่มี SPIRAM
ulab3
จากภาพที่ 1 จะพบว่า โครงสร้างไลบรารีของ ulab เปลี่ยนแปลงไปจากเดิม ทำให้การเขียนโปรแกรมจากตัวอย่างก่อนหน้านี้ต้องมีการปรับเปลี่ยน ซึ่งภายใต้ ulab จะมีไลบรารีของ numpy และ scipy เข้ามา ซึ่งรายละเอียดของ numpy ที่รองรับเป็นดังนี้
object <module 'numpy'> is of type module
__name__ -- numpy
ndarray -- <class 'ndarray'>
array -- <function>
frombuffer -- <function>
e -- 2.718282
inf -- inf
nan -- nan
pi -- 3.141593
bool -- 63
uint8 -- 66
int8 -- 98
uint16 -- 72
int16 -- 104
float -- 102
fft -- <module 'fft'>
linalg -- <module 'linalg'>
set_printoptions -- <function>
get_printoptions -- <function>
ndinfo -- <function>
arange -- <function>
concatenate -- <function>
diag -- <function>
empty -- <function>
eye -- <function>
interp -- <function>
trapz -- <function>
full -- <function>
linspace -- <function>
logspace -- <function>
ones -- <function>
zeros -- <function>
clip -- <function>
equal -- <function>
not_equal -- <function>
isfinite -- <function>
isinf -- <function>
maximum -- <function>
minimum -- <function>
where -- <function>
convolve -- <function>
all -- <function>
any -- <function>
argmax -- <function>
argmin -- <function>
argsort -- <function>
cross -- <function>
diff -- <function>
dot -- <function>
trace -- <function>
flip -- <function>
max -- <function>
mean -- <function>
median -- <function>
min -- <function>
roll -- <function>
sort -- <function>
std -- <function>
sum -- <function>
polyfit -- <function>
polyval -- <function>
acos -- <function>
acosh -- <function>
arctan2 -- <function>
around -- <function>
asin -- <function>
asinh -- <function>
atan -- <function>
atanh -- <function>
ceil -- <function>
cos -- <function>
cosh -- <function>
degrees -- <function>
exp -- <function>
expm1 -- <function>
floor -- <function>
log -- <function>
log10 -- <function>
log2 -- <function>
radians -- <function>
sin -- <function>
sinh -- <function>
sqrt -- <function>
tan -- <function>
tanh -- <function>
vectorize -- <function>
ภายใต้ numpy มีโมดูล fft ให้ใช้งาน ซึ่งโมดูลลนี้มีฟังก์ชันสำหรับใช้งานได้แก่
object <module 'fft'> is of type module
__name__ -- fft
fft -- <function>
ifft -- <function>
และภายใต้โมดูล linalg ซึ่งทำงานเกี่ยวกับ linear algebra มีฟังก์ชันต่อไปนี้ให้ใช้งาน
object <module 'linalg'> is of type module
__name__ -- linalg
cholesky -- <function>
det -- <function>
eig -- <function>
inv -- <function>
norm -- <function>
การทำงานของ scipy ที่รองรับคือ
object <module 'scipy'> is of type module
__name__ -- scipy
linalg -- <module 'linalg'>
optimize -- <module 'optimize'>
signal -- <module 'signal'>
special -- <module 'special'>
คำสั่งของโมดูล linalg ที่รองรับคือ
object <module 'linalg'> is of type module
__name__ -- linalg
solve_triangular -- <function>
cho_solve -- <function>
คำสั่งในโมดูล optimize คือ
object <module 'optimize'> is of type module
__name__ -- optimize
bisect -- <function>
fmin -- <function>
newton -- <function>
คำสั่งภายใต้โมดูล signal ได้แก่
object <module 'signal'> is of type module
__name__ -- signal
spectrogram -- <function>
sosfilt -- <function>
คำสั่งภายใต้โมดูล special คือ
object <module 'special'> is of type module
__name__ -- special
erf -- <function>
erfc -- <function>
gamma -- <function>
gammaln -- <function>
คำสั่งในกลุ่ม utisl ของ ulab ได้แก่
object <module 'utils'> is of type module
__name__ -- utils
from_int16_buffer -- <function>
from_uint16_buffer -- <function>
from_int32_buffer -- <function>
from_uint32_buffer -- <function>
สร้างไลบรารี ulab
การสร้างไลบรารี ulab ให้รวมในตัว Micropython จะต้องมีเครื่องมือ esp-idf ที่ได้กล่าวไปก่อนหน้านี้ ขั้นตอนการสร้างไลบรารี ulab กับบอร์ด esp32 ที่มี SPIRAM และเมื่อสั่งแสดงผลตามโค้ดต่อไปนี้จะได้ผลลัพธ์ดังภาพที่ 2
import sys
if sys.platform != 'esp32':
print("esp32 only!")
sys.exit(0)
import gc
import os
import esp
import esp32
import time
import machine as mc
import ulab
def show_hw_info():
uname = os.uname()
mem_total = gc.mem_alloc()+gc.mem_free()
free_percent = "("+str((gc.mem_free())/mem_total*100.0)+"%)"
alloc_percent = "("+str((gc.mem_alloc())/mem_total*100.0)+"%)"
stat = os.statvfs('/flash')
block_size = stat[0]
total_blocks = stat[2]
free_blocks = stat[3]
rom_total = (total_blocks * block_size)/1024
rom_free = (free_blocks * block_size)/1024
rom_usage = (rom_total-rom_free)
rfree_percent = "("+str(rom_free/rom_total*100.0)+"%)"
rusage_percent = "("+str(rom_usage/rom_total*100.0)+"%)"
print("ID ............:",mc.unique_id())
print("Platform ......:",sys.platform)
print("Version .......:",sys.version)
print("Memory")
print(" total ......:",mem_total/1024,"KB")
print(" usage ......:",gc.mem_alloc()/1024,"KB",alloc_percent)
print(" free .......:",gc.mem_free()/1024,"KB",free_percent)
print("ROM")
print(" total ......:", rom_total,"KB" )
print(" usage ......:", rom_usage,"KB",rfree_percent )
print(" Free .......:", rom_free,"KB",rusage_percent )
print("system name ...:",uname.sysname)
print("node name .....:",uname.nodename)
print("release .......:",uname.release)
print("version .......:",uname.version)
print("machine .......:",uname.machine)
def show_ulab():
print("ulab version {}.".format(ulab.__version__))
if __name__=="__main__":
show_hw_info()
show_ulab()
ดาวน์โหลด source code
สิ่งแรกที่ต้องทำคือดาวน์โหลดโคดต้นฉบับของ ulab และ Micropython แล้วคอมไพล์ mpy-cross และเตรียมการโมดูลย่อยของ esp32 ก่อน โดยสคริปต์คำสั่งเป็นดังนี้
mkdir ~/src
cd ~/src
git clone https://github.com/v923z/micropython-ulab.git ulab
git clone https://github.com/micropython/micropython.git
cd micropython
git submodule update --init
cd mpy-cross
make
cd ../ports/esp32
make submodules
สร้างข้อมูลพาร์ทิชัน
ให้สร้างไฟล์ partitions_ulab.csv โดยใช้โปรแกรม nano และเก็บไว้ใน ~/src/micropython/ports/esp32 ด้วยการพิมพ์คำสั่งดังนี้
cd ~/src/micropython/ports/esp32
nano partitions_ulab.csv
ให้พิมพ์โค้ดต่อไปนี้ลงไป และบันทึกด้วย Ctrl+O และออกจาก nano ด้วย Ctrl+X
# Notes: the offset of the partition table itself is set in
# $ESPIDF/components/partition_table/Kconfig.projbuild and the
# offset of the factory/ota_0 partition is set in makeimg.py
# Name, Type, SubType, Offset, Size, Flags
nvs, data, nvs, 0x9000, 0x6000,
phy_init, data, phy, 0xf000, 0x1000,
factory, app, factory, 0x10000, 0x200000,
vfs, data, fat, 0x220000, 0x180000,
แก้ไข sdkconfig
เมื่อได้ไฟล์ต้นแบบของพาร์ทิชัน ขั้นตอนถัดไปคือทำการแก้ไขไฟล์ sdkconfig สำหรับบอร์ดที่ต้องใช้งาน ซึ่งจัดเก็บอยู่ในโฟลเดอร์ ~/src/micropython/ports/esp32/boards ประกอบกับบอร์ดที่ทีมงานเราเลือกใช้เป็นบอร์ดที่มี SPIRAM จึงต้องแก้ไขไฟล์ sdkconfig.spiram โดยเพิ่ม 2 บรรทัดต่อไปนี้ลงไป แต่ถ้าใช้กับบอร์ดที่ไม่มี SPIRAM ให้แก้ไขในไฟล์ sdkconfig.base
CONFIG_PARTITION_TABLE_CUSTOM=y
CONFIG_PARTITION_TABLE_CUSTOM_FILENAME="partitions_ulab.csv"
คอมไพล์
การคอมไพล์ต้องกำหนด 2 สิ่ง คือ
- ประเภทของบอร์ด ซึ่งในที่นี้เลือกใช้ GENERIC_SPIRAM เนื่องจากบอร์ด esp32 ที่ใช้นั้นมีหน่วยความแรมเพิ่มจากรุ่นปกติ แต่ถ้าใช้กับรุ่นปกติให้เปลี่ยนเป็น GENERIC ซึ่งสามารถคอมไพล์ได้เหมือนกัน
- โมดูลเสริมการทำงานที่เขียนจากภาษา c หรือ ulab
คำสั่งเป็นดังนี้ และเมื่อเริ่มมีการคอมไพล์จะเป็นดังตัวอย่างภาพที่ 4 และเมื่อเสร็จกระบวนการคอมไพล์ (และไม่มีข้อผิดพลาด) จะแสดงผลตามตัวอย่างในภาพที่ 5
cd ~/src/micropython/ports/esp32
make BOARD=GENERIC_SPIRAM USER_C_MODULES=~/src/ulab/code/micropython.cmake
ติดตั้ง
ขั้นตอนสุดท้ายคือทำการติดตั้งลงบอร์ด โดยทีมงานเราได้เชื่อมต่อบอร์ดเข้ากับเครื่องเรียบร้อย จึงสั่งลบและเขียนเฟิร์มแวร์จากบรรทัดคำสั่งโดยสั่งงานดังนี้ ซึ่งถ้าใช้กับบอร์ด esp32 ที่ไม่มี SPIRAM ให้เลือก BOARD เป็น GENERIC โดยกระบวนการเขียนลงบอร์ดจะเป็นตามตัวอย่างในภาพที่ 6 และ 7
make erase
make deploy BOARD=GENERIC_SPIRAM
หรือผู้อ่านใช้โปรแกรม thonny เขียนไฟล์ ~/src/micropython/ports/esp32/build-GENERIC_SPIRAM/micropython.bin แทนการสั่งด้วยบรรทัดคำสั่งได้เช่นกัน
ทดสอบ
ทดสอบด้วยการปรับแก้ code18-1.py ให้ใช้กับ ulab ตัวใหม่ และผลลัพธ์เป็นดังภาพที่ 8
หมุนสี่เหลี่ยม
จากตัวอย่างโปรแกรมในบทความ ESP32 : Display of rotation squares with application ulab. เป็นการประยุกต์ใช้ ulab ในการคำนวณการหมุนของสี่เหลี่ยม เนื่องจาก ulab 3 มีความเปลี่ยนแปลงไปจากเดิม ทำให้ได้โค้ดตัวอย่างสำหรับ ulab ในรุ่นนี้เป็นดังนี้
import gc
import os
import sys
import ulab
import time
import math
import machine as mc
from ulab import numpy as np
from st7735 import TFT
from sysfont import sysfont
from machine import SPI,Pin
gc.enable()
gc.collect()
mc.freq(240000000)
minX = -10.0
maxX = 10.0
minY = -5.0
maxY = 5.0
scrWidth = const(160)
scrHeight = const(80)
ratioX = float(scrWidth)/(math.fabs(minX)+math.fabs(maxX)+1)
ratioY = float(scrHeight)/(math.fabs(minY)+math.fabs(maxY)+1)
centerX = const(scrWidth >> 1)
centerY = const(scrHeight >> 1)
spi = SPI(2, baudrate=27000000,
sck=Pin(14), mosi=Pin(12),
polarity=0, phase=0)
# dc, rst, cs
tft=TFT(spi,15,13,2)
tft.init_7735(tft.GREENTAB80x160)
tft.fill(TFT.BLACK)
def rotate(pX,pY,angle):
rad = math.radians(angle)
xCos = pX*np.cos(rad)
ySin = pY*np.sin(rad)
xSin = pX*np.sin(rad)
yCos = pY*np.cos(rad)
newX = xCos - ySin
newY = xSin + yCos
return (newX, newY)
def draw(pX, pY,aColor=tft.WHITE):
newPx = np.array(pX*ratioX+centerX,dtype=np.uint16)
newPy = np.array(pY*ratioY+centerY,dtype=np.uint16)
tft.line((newPx[0],newPy[0]),(newPx[1],newPy[1]),aColor)
tft.line((newPx[1],newPy[1]),(newPx[2],newPy[2]),aColor)
tft.line((newPx[2],newPy[2]),(newPx[3],newPy[3]),aColor)
tft.line((newPx[3],newPy[3]),(newPx[0],newPy[0]),aColor)
def show_hw_info():
uname = os.uname()
mem_total = gc.mem_alloc()+gc.mem_free()
free_percent = "("+str((gc.mem_free())/mem_total*100.0)+"%)"
alloc_percent = "("+str((gc.mem_alloc())/mem_total*100.0)+"%)"
stat = os.statvfs('/flash')
block_size = stat[0]
total_blocks = stat[2]
free_blocks = stat[3]
rom_total = (total_blocks * block_size)/1024
rom_free = (free_blocks * block_size)/1024
rom_usage = (rom_total-rom_free)
rfree_percent = "("+str(rom_free/rom_total*100.0)+"%)"
rusage_percent = "("+str(rom_usage/rom_total*100.0)+"%)"
print("ID ............:",mc.unique_id())
print("Platform ......:",sys.platform)
print("Version .......:",sys.version)
print("Memory")
print(" total ......:",mem_total/1024,"KB")
print(" usage ......:",gc.mem_alloc()/1024,"KB",alloc_percent)
print(" free .......:",gc.mem_free()/1024,"KB",free_percent)
print("ROM")
print(" total ......:", rom_total,"KB" )
print(" usage ......:", rom_usage,"KB",rusage_percent )
print(" Free .......:", rom_free,"KB",rfree_percent )
print("system name ...:",uname.sysname)
print("node name .....:",uname.nodename)
print("release .......:",uname.release)
print("version .......:",uname.version)
print("machine .......:",uname.machine)
def show_ulab():
print("ulab version {}.".format(ulab.__version__))
# main program
if __name__=="__main__":
show_hw_info()
show_ulab()
tft.rotation(1)
tft.fill(tft.BLACK)
t0 = time.ticks_us()
pX = np.array([-2,2,2,-2],dtype=np.float)
pY = np.array([2,2,-2,-2],dtype=np.float)
for degree in range(360):
newP = rotate(pX,pY,degree)
draw(newP[0],newP[1],tft.WHITE)
#time.sleep_ms(100)
tft.fill(0)
for degree in range(360):
newP = rotate(pX,pY,-degree)
draw(newP[0],newP[1],tft.CYAN)
#time.sleep_ms(100)
tft.fill(0)
print("ulab: Delta = {} usec".format(time.ticks_us()-t0))
# endof program
time.sleep_ms(2000)
tft.on(False)
spi.deinit()
สรุป
จากบทความนี้ ผู้อ่านสามารถคอมไพล์และติดตั้งไลบรารี ulab ที่ผนวกเข้ากับ Micropython เพื่อใช้งานกับบอร์ด esp32 ที่มี SPIRAM ได้ ซึ่งหลังจากนี้การเขียนโปรแกรมจะต้องมีการปรับแก้ไขโค้ดเนื่องจากไลบรารี ulab ได้เปลี่ยนแปลงโครงสร้างของการใช้งานไปจากเดิม แต่อย่างไรก็ดี ทีมงานเชื่อว่าผู้อ่านสามารถประยุกต์ปรับปรุงโค้ดจากบทความที่เกี่ยวกับ ulab ที่ทีมงานได้เขียนไว้ก่อนหน้านี้ได้แย่างแน่นอน หรือถ้ามีโอกาสที่เหมาะสม ทางทีมงานจะเขียนบทความสำหรับใช้งาน ulab 3 เบื้องต้นเพื่อเป็นแนวทางการใช้งานต่อไป แต่จากการทดสอบพบว่า SPIRAM หรือ PSRAM ที่เพิ่มเข้ามานั้นทำให้ประสิทธิภาพการทำงานช้าลงกว่า 50% แต่ได้ปริมาณที่เก็บข้อมูลปริมาณที่มากขึ้น สุดท้าย ขอให้สนุกกับการเขียนโปรแกรมครับ
ท่านใดต้องการพูดคุยสามารถคอมเมนท์ไว้ได้เลยครับ
แหล่งอ้างอิง
(C) 2021, โดย อ.ดนัย เจษฎาฐิติกุล/อ.จารุต บุศราทิจ
ปรับปรุงเมื่อ 2021-06-20, 2021-08-11, 2021-11-17