บทความนี้นำเสนอตัวอย่างการเขียนโค้ด MicroPython (v1.12) สำหรับ ESP32 และกล่าวถึง การใช้งานโครงสร้างข้อมูลแบบ Frame Buffer เพื่อช่วยในการจัดการข้อมูลสำหรับจอแสดงผล และสาธิตการนำไปใช้งานร่วมกับโมดูล MAX7219 8x8 LED Matrix Display แบบ Monochrome
การจัดเก็บข้อมูลด้วย Frame Buffer
คลาส FrameBuffer
ในไลบรารี framebuf
ของ MicroPython ช่วยในการจัดการหน่วยความจำและเก็บข้อมูลสำหรับการแสดงผลบนสกรีน (Screen) สามารถกำหนดสี (Color) หรือค่าสำหรับแต่ละพิกเซล (Pixel) ในพิกัด (x,y) สำหรับแกน x หรือแนวนอน (Horizontal) และแกน y หรือแนวตั้ง (Vertical) ของสกรีน
การกำหนดพิกัด (x,y) ของสกรีนเป็นดังนี้: ให้พิกัด (0,0) คือ มุมบนซ้ายของสกรีน ถ้าขนาดของสกรีนมีความกว้าง W และสูง H (นับเป็นพิกเซล) มุมล่างขวาของสกรีนจะมีพิกัด (W-1,H-1) ดังนั้นค่า y เพิ่มขึ้นในแนวตั้งจากบนลงล่าง และค่า x เพิ่มขึ้นในแนวนอนจากซ้ายไปขวา
รูปแบบของค่าสี (Color Format) ที่สามารถเลือกใช้ได้ แบ่งเป็น 2 ประเภทคือ แบบ Monochrome (ใช้ข้อมูล 1 บิต สำหรับแต่ละพิกเซล มีค่า 0 หรือ 1) และแบบ Color ที่ใช้ข้อมูลมากกว่าหนึ่งบิตสำหรับแต่ละพิกเซล
framebuf.MONO_VLSB
: แสดงผลแบบ Monochrome เก็บข้อมูล 8 บิต ในหนึ่งไบต์สำหรับกลุ่มพิกเซลในแนวตั้ง ถ้านับจากบิต 7..0 ของไบต์ จะเป็นไปตามลำดับของค่า y ที่เพิ่มขึ้นframebuf.MONO_HLSB
: แสดงผลแบบ Monochrome เก็บข้อมูล 8 บิต ในหนึ่งไบต์สำหรับกลุ่มพิกเซลในแนวนอน ถ้านับจากบิต 7..0 ของไบต์ จะเป็นไปตามลำดับของค่า x ที่เพิ่มขึ้นframebuf.MONO_HMSB
: แสดงผลแบบ Monochrome เก็บข้อมูล 8 บิต ในหนึ่งไบต์สำหรับกลุ่มพิกเซลในแนวนอน ถ้านับจากบิต 0..7 ของไบต์ จะเป็นไปตามลำดับของค่า x ที่เพิ่มขึ้นframebuf.RGB565
: แสดงผลแบบ 16 บิต สำหรับสีแดง เขียว และน้ำเงิน (Red, Green, Blue) แบ่งเป็น 5, 6, 5 บิต ตามลำดับframebuf.GS2_HMSB
: แสดงผลโดยใช้ 2 บิต ต่อหนึ่งพิกเซลframebuf.GS4_HMSB
: แสดงผลโดยใช้ 4 บิต ต่อหนึ่งพิกเซลframebuf.GS8
: แสดงผลโดยใช้ 8 บิต ต่อหนึ่งพิกเซล
นอกจากนั้นยังมีคำสั่งสำหรับกำหนดค่าให้พิกเซลที่พิกัด (x,y) การวาดเส้นตรงในแนวนอนหรือแนวตั้ง การวาดรูปกรอบสี่เหลี่ยม การระบายสีพื้นที่สี่เหลี่ยม และการเขียนข้อมูลตัวหนังสือภาษาอังกฤษ (ตัวอักขระมีความสูง 7 พิกเซล ขนาด 8 x 8 พิกเซล) เป็นต้น
การใช้งาน FrameBuffer
จะต้องใช้คู่กับชนิดข้อมูล bytearray
ยกตัวอย่าง ถ้าต้องการใช้งานกับสกรีนแบบ Monochrome ขนาดแนวแนว 16 พิกเซล แนวตั้ง 8 พิกเซล และจัดเก็บข้อมูลแบบ framebuf.MONO_HLSB
ก็เขียนโค้ดดังนี้
import framebuf
import ubinascii as binasciiw = 16 # width in pixels
h = 8 # height in pixels
buf = bytearray(w * h // 8) # create a bytearray objectcolor_format = framebuf.MONO_HLSB# create a Framebuffer object
fbuf = framebuf.FrameBuffer( buf, w, h, color_format )print( 'buffer size: {} bytes'.format( len(buf) ) )
fbuf.fill(0) # fill all pixels with 0 (clear all pixels)# convert a bytearray to a hexadecimal string
hex_str = lambda x: binascii.hexlify(x).decode().upper()+'h'# show the content of buf as a hex string
print( 'output:', hex_str(buf) )
# output: 00000000000000000000000000000000h
สกรีนมีขนาด 16x8 พิกเซล ดังนั้นจะใช้ bytearray
ที่มีขนาด (16*8)/8 = 16 ไบต์ ในการเก็บข้อมูลภายใน และการเรียกใช้คำสั่ง fill(0)
เป็นการกำหนดค่าเริ่มต้นให้พิกเซลทั้งหมดเป็น 0
ในตัวอย่างนี้เราได้สร้างฟังก์ชัน hex_str()
แบบ lambda สำหรับเอาไว้แสดงค่าของ bytearray
ในเลขฐานสิบหก (Hexadecimal String)
ถัดไป ถ้าเราลองกำหนดค่าให้พิกเซลต่อไปนี้เป็น 1 แล้วดูว่ามีการเปลี่ยนแปลงอย่างไรกับข้อมูลไบต์ใน bytearray
ก็ให้เขียนโค้ดดังนี้
pixels = [ (0,0),(7,0),(8,0),(14,0),(15,0) ] # list of test pixels
for pixel in pixels:
x,y = pixel
fbuf.pixel(x,y,1) # set pixel at (x,y) to 1
print( 'set pixel({:2d},{:2d}): {}'.format(x,y,hex_str(buf)) )
ตัวอย่างข้อความที่เป็นเอาต์พุต
set pixel( 0, 0): 80000000000000000000000000000000h
set pixel( 7, 0): 81000000000000000000000000000000h
set pixel( 8, 0): 81800000000000000000000000000000h
set pixel(14, 0): 81820000000000000000000000000000h
set pixel(15, 0): 81830000000000000000000000000000h
จากผลลัพธ์ที่ได้ จะเห็นว่า เป็นการกำหนดค่าพิกเซลในตำแหน่งของ x ที่แตกต่างกัน แต่มีค่า y=0 เหมือนกัน
เนื่องจากได้กำหนดความกว้างไว้ 16 พิกเซล ดังนั้นจึงใช้ข้อมูล 2 ไบต์ สำหรับหนึ่งแถวแนวนอน (Horizontal Line) จากตัวอย่างการทำงานในข้างต้น จะเห็นได้ว่า 0x8183
เป็นข้อมูลสองไบต์สำหรับแถวแนวนอนที่ y=0
สมมุติว่า เราเคลียร์ข้อมูลให้พิกเซลเป็น 0 ทั้งหมด แล้วกำหนดให้แถวแนวตั้งที่ x=7 และพิกเซลในแถวดังกล่าวเป็น 1 ทั้งหมด (สำหรับ y=0..7) เราก็สามารถเขียนโค้ดได้ดังนี้
fbuf.fill(0) # clear all pixels
pixels = [ (7,y) for y in range(8) ] # list of test pixels
for pixel in pixels:
x,y = pixel
fbuf.pixel(x,y,1) # set pixel at (x,y) to 1
print( 'set pixel({:2d},{:2d}): {}'.format(x,y,hex_str(buf)) )
และได้ข้อความเอาต์พุตดังนี้
set pixel( 7, 0): 01000000000000000000000000000000h
set pixel( 7, 1): 01000100000000000000000000000000h
set pixel( 7, 2): 01000100010000000000000000000000h
set pixel( 7, 3): 01000100010001000000000000000000h
set pixel( 7, 4): 01000100010001000100000000000000h
set pixel( 7, 5): 01000100010001000100010000000000h
set pixel( 7, 6): 01000100010001000100010001000000h
set pixel( 7, 7): 01000100010001000100010001000100h
ถัดไป มาลองใช้คำสั่ง hline()
สำหรับการวาดเส้นตรงในแนวนอน (เช่น ความกว้าง 16 พิกเซล) ตามตัวอย่างต่อไปนี้
fbuf.fill(0)
fbuf.hline(0,0,w,1) # draw a horizontal line: y=0
print( '{}'.format(hex_str(buf)) )
fbuf.hline(0,h-1,w,1) # draw a horizontal line: y=7
print( '{}'.format(hex_str(buf)) )
และได้ข้อความเอาต์พุตดังนี้
FFFF0000000000000000000000000000h
FFFF000000000000000000000000FFFFh
คำถาม: ถ้าเปลี่ยนจาก MONO_HLSB
เป็น MONO_HMSB
แล้วลองรันโค้ดตัวอย่างนี้ จะให้ผลลัพธ์แตกต่างจากเดิมอย่างไร ? และถ้าลองเปลี่ยนขนาดของสกรีน (w,h) ที่มีขนาดใหญ่ขึ้น (เลือกค่าที่เป็นเลขจำนวนเต็มบวกและหารด้วย 8 ลงตัว) จะให้ผลอย่างไร ?
การใช้งานโมดูล MAX7219 สำหรับ 8x8 LED Matrix Display
หลักการทำงานของ MAX7219 ได้มีการอธิบายไว้ในบทความที่ได้เคยเขียนไว้แล้วในตอนที่ [8] และไม่ขอกล่าวในรายละเอียด แต่จะใช้ตัวอย่างโค้ด MicroPython สำหรับใช้งานโมดูล MAX7219 มานำเสนอดังนี้ (คลาส MAX7219
ในไฟล์ชื่อ max7219.py
)
# File: max7219.py
from micropython import const
from machine import Pin, SPI
import utime as timeclass MAX7219():
REG_DIGIT_BASE = const(0x1)
REG_DECODE_MODE = const(0x9)
REG_INTENSITY = const(0xA)
REG_SCAN_LIMIT = const(0xB)
REG_SHUTDOWN = const(0xC)
REG_DISP_TEST = const(0xF)
def __init__( self, spi, cs, n=1 ):
self._spi = spi # spi bus
self._cs = cs # cs pin
self._n = n # number of blocks
self.init()
def init( self ):
# decode mode: no decode for digits 0-7
self.write( REG_DECODE_MODE, self._n*[0] )
# set intensity: 0x7 = 15/32, 0xf = 31/32
self.write( REG_INTENSITY, self._n*[0xf] )
# scan limit: display digits 0-7
self.write( REG_SCAN_LIMIT, self._n*[7] )
# display test: normal (no display test)
self.write( REG_DISP_TEST, self._n*[0] )
# shutdown: normal operation (no shutdown)
self.write( REG_SHUTDOWN, self._n*[1] )
def write( self, reg, data ):
if isinstance(data, int):
data = [data]
n = len(data)
buf = []
for i in range(n):
buf += [reg, data[i]]
self._cs.value(0) # assert CS pin
self._spi.write( bytearray(buf) ) # write SPI data
self._cs.value(1) # deassert CS pin
def clear( self ):
for i in range(8):
self.write( REG_DIGIT_BASE+i, self._n*[0] )
def on( self ):
self.write( REG_SHUTDOWN, self._n*[1] )
def off( self ):
self.write( REG_SHUTDOWN, self._n*[0] )
def flashing( self, times, delay_ms=100 ):
for i in range(times):
self.write( REG_DISP_TEST, self._n*[1] )
time.sleep_ms( delay_ms )
self.write( REG_DISP_TEST, self._n*[0] )
time.sleep_ms( delay_ms )
def deinit( self ):
self._spi.deinit()
การใช้งานคลาส max7219.MAX7219
จะต้องใช้ร่วมกับบัส SPI โดยทั่วไปก็สามารถใช้บัส HSPI ของ ESP32 ได้
โมดูล MAX7219 จะมีขาสำหรับเชื่อมต่อกับไมโครคอนโทรลเลอร์ดังนี้
- VCC และ GND สำหรับป้อนแรงดันไฟเลี้ยง (สามารถใช้ +3.3V ได้)
- DIN เป็นขาสำหรับรับข้อมูลเข้าทีละบิต ให้นำไปต่อกับขา MOSI ของบัส SPI
- CS เป็นขาสัญญาณ Chip Select ทำงานแบบ Active-Low
- CLK เป็นขาสัญญาณ Clock ให้นำไปต่อกับขา SCK ของบัส SPI
ลองมาดูตัวอย่างการใช้งาน โดยให้แสดงข้อความว่า “Hello” บนโมดูล 8x8 LED Matrix Display ที่มีจำนวน 4 บล็อก (หรือ 4 หลัก) นำมาต่อกันแบบอนุกรม (Cascading) ซึ่งจะได้สกรีนที่มีขนาด 32 x 8 พิกเซล และสาธิตการใช้คำสั่ง เช่น text()
สำหรับแสดงข้อความภาษาอังกฤษ และคำสั่งscroll()
เพื่อเลื่อนข้อความบนสกรีนไปทางซ้ายทีละหนึ่งตำแหน่ง
# file: max7219_framebuf_demo.py
from micropython import const
from machine import Pin, SPI
import utime as time
from max7219 import MAX7219
import framebufn = 4 # number of 8x8 LED blocks
w = 8*n # screen width in pixels
h = 8 # screen height in pixels
buf = bytearray(w*h//8) # create a bytearray object
fbuf = framebuf.FrameBuffer(buf, w, h, framebuf.MONO_HLSB)SCK = const(14)
MOSI = const(13)
MISO = const(12) # note: not used for MAx7219
CS = const(27)SPI(1).deinit()
spi = SPI(1, baudrate=1000000,
polarity=0, phase=0, bits=8, firstbit=SPI.MSB,
sck=Pin(SCK,Pin.OUT),
mosi=Pin(MOSI,Pin.OUT),
miso=Pin(MISO,Pin.IN))disp = MAX7219(spi,Pin(CS, Pin.OUT, value=1),n)
disp.clear() # clear display
disp.on() # turn on the displaydef update():
for i in range(8):
data = buf[i*(w//8):i*(w//8)+n] # n bytes per line
disp.write( MAX7219.REG_DIGIT_BASE+i, data )def hello():
fbuf.text('H',0,0,1)
fbuf.text('e',7,0,1)
fbuf.text('l',13,0,1)
fbuf.text('l',18,0,1)
fbuf.text('o',24,0,1)
fbuf.fill(0) # clear framebuffer (filled with 0)
hello() # write hello text to framebuffer
update() # send data from framebuffer to MAX7219s
time.sleep(2.0)for i in range(w):
fbuf.scroll(-1,0) # shift text to the left
update() # send data from framebuffer to MAX7219s
time.sleep_ms(150)disp.deinit()
del disp
spi.deinit()
ข้อสังเกต: ในตัวอย่างนี้ เราได้กำหนดขนาดของ FrameBuffer
ให้มีขนาดเท่ากับจำนวนพิกเซลของโมดูล MAX7217 32 x 8 แต่เราก็สามารถกำหนดให้ขนาดของสกรีน มีขนาดใหญ่กว่าได้
ยกตัวอย่างในกรณีที่ต้องการแสดงข้อความที่มีความยาวมากกว่า 32 พิกเซล เราก็สามารถทำได้ เช่น กำหนดขนาดสกรีนเป็น 128 x 8 ซึ่งกว้างเป็น 16 เท่าของ 32 x 8 แสดงข้อความได้สูงสุด 16 ตัวอักขระ (มีความกว้างตัวละ 8 พิกเซล) และใช้วิธีเลื่อนข้อมูลใน FrameBuffer
เพื่อแสดงผลให้เลื่อนไปทางซ้ายทีละตำแหน่ง
โดยสรุป เราได้เรียนรู้หลักการทำงานของ framebuf.FrameBuffer
เพื่อนำมาใช้ในการเขียนโค้ดในภาษา MicroPython และใช้จัดการข้อมูลสำหรับการแสดงผลบนสกรีนแบบ Monochrome (0/1) และได้ทดลองกับโมดูล MAX7219 ที่มีจำนวน 4 บล็อก LED Matrix และมีขนาด 32 x 8 พิกเซล