From the previous ulab article, it was found that Micropython can implement the same dataset processing instructions as used in Numpy through the previous ulab library v.0.54.0 which is the older version of ulab (currently v.3.0.1) brought up this article. This article describes how to create a Micropython that integrates the ulab library and uses it with SPIRAM versions of esp32.
ulab3
From Figure 1, it can be seen that the structure of the ulab library has changed from the original. This causes the programming from the previous example to have to be modified. Under ulab there are libraries of numpy and scipy. The details of numpy that are supported are as follows.
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>
Under numpy, there is an fft module available. This module has the following functions:
object <module 'fft'> is of type module
__name__ -- fft
fft -- <function>
ifft -- <function>
And under the linalg module which works on linear algebra the following functions are available.
object <module 'linalg'> is of type module
__name__ -- linalg
cholesky -- <function>
det -- <function>
eig -- <function>
inv -- <function>
norm -- <function>
Supported scipy functions are
object <module 'scipy'> is of type module
__name__ -- scipy
linalg -- <module 'linalg'>
optimize -- <module 'optimize'>
signal -- <module 'signal'>
special -- <module 'special'>
Supported linalg module commands are
object <module 'linalg'> is of type module
__name__ -- linalg
solve_triangular -- <function>
cho_solve -- <function>
The commands in the optimize module are:
object <module 'optimize'> is of type module
__name__ -- optimize
bisect -- <function>
fmin -- <function>
newton -- <function>
The commands under the signal module are:
object <module 'signal'> is of type module
__name__ -- signal
spectrogram -- <function>
sosfilt -- <function>
The commands under the special module are
object <module 'special'> is of type module
__name__ -- special
erf -- <function>
erfc -- <function>
gamma -- <function>
gammaln -- <function>
The commands in the ulab utisl group are:
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>
Create ulab library
Creating a ulab library to integrate into Micropython requires the esp-idf tool mentioned earlier. Steps to create ulab library with board esp32 with SPIRAM and when ordering the following code will get the result as shown in Figure 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 download
First thing you need to do is download the source code for ulab and Micropython, compile mpy-cross and prepare the esp32 submodules.
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
Create partition information
Create a partitions_ulab.csv file using nano and stored in ~/src/micropython/ports/esp32 by typing the following command
cd ~/src/micropython/ports/esp32
nano partitions_ulab.csv
Type the following code and save with Ctrl+O and exit nano with 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,
Edit sdkconfig
When the partition master file is obtained, the next step is to edit the sdkconfig file for the required board which is stored in the folder ~/src/micropython/ports/esp32/boards. In addition to the board that our team chooses to use as a board with SPIRAM, we have to edit the file sdkconfig.spiram by adding the following 2 lines to it. If it’s on a board without SPIRAM, edit it in the file sdkconfig.base.
CONFIG_PARTITION_TABLE_CUSTOM=y
CONFIG_PARTITION_TABLE_CUSTOM_FILENAME="partitions_ulab.csv"
Complie
Compiling requires two things:
- We choose GENERIC_SPIRAM Because the board esp32 used has more RAM than the normal model. But if used with normal version, change to GENERIC which can compile as well.
- Functional modules written in c or ulab.
The command is as follows and when it starts to compile, it will look like Figure 4, and when the compilation process is complete (and no errors) are displayed as in Figure 5.
cd ~/src/micropython/ports/esp32
make BOARD=GENERIC_SPIRAM USER_C_MODULES=~/src/ulab/code/micropython.cmake
Installation
The last step is to install it to the board. We have connected the board to the machine successfully. Therefore, delete and write the firmware from the command line by running the following commands: If it is used with board esp32 without SPIRAM, choose BOARD as GENERIC. The writing process on the board is as shown in Figure 6 and 7.
make erase
make deploy BOARD=GENERIC_SPIRAM
Or the reader uses the thonny program to write the file ~/src/micropython/ports/esp32/build-GENERIC_SPIRAM/micropython.bin. instead of commands with the command line as well
Testing
Tested by modifying code18-1.py to use with new ulab and the result is as shown in Figure 8.
Rotate square
From the example program in the article ESP32 : Display of rotation squares with application ulab. It is an application of ulab in calculating the rotation of squares because ulab 3 has been changed. The sample code for this version of ulab is as follows:
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()
Conclusion
Through this article, readers can compile and install the ulab library that is integrated with Micropython for use with esp32 boards with SPIRAM. Henceforth, coding must be adjusted since there’s some change. However, we believe that readers will be able to adapt the code from the ulab article that the team has written before for sure. Or if there is a suitable opportunity, we will write an article for the initial use of ulab 3 as a guideline for further use. But from testing, it was found that the added SPIRAM or PSRAM caused the performance to be slower up to 50%, but the amount of storage was increased. Finally, have fun programming.
If you want to talk with us, feel free to leave comments below!!
Reference
(C) 2021, By Jarut Busarathid and Danai Jedsadathitikul
Updated 2021-11-18