Teaching Basic Data Structures with MicroPython and WS2812B RGB LEDs

<rawat.s>
7 min readJun 6, 2020

--

‍‍‍‍‍‍ ‍‍ ‍‍‍‍‍‍ ‍‍‍‍‍‍ ‍‍

บทความนี้นำเสนอแนวทางการใช้โมดูล WS2812(B) ซึ่งเป็น RGB LEDs ที่มีการจัดวาง LEDs แบบอาร์เรย์มิติเดียวและสองมิติ มาเป็นอุปกรณ์เสริมในการเรียนโค้ดดิ้งในภาษาไพธอน (Python) ในบทความนี้ ได้เลือกใช้บอร์ด STM32F411CEU6 ซึ่งเป็นบอร์ดไมโครคอนโทรลเลอร์ราคาถูก และติดตั้งเฟิร์มแวร์ของ MicroPython v1.12 สำหรับการเขียนโค้ด

คำแนะนำ: ถ้าผู้อ่านที่ยังไม่เคยใช้งาน MicroPython สำหรับบอร์ด STM32F411CEU6 Black Pill สามารถอ่านและศึกษาขั้นตอนการเริ่มต้นใช้งานได้จากบทความ “MicroPython for STM32F411CEU Black Pill: Embedded Programming Style

RGB LED Display

‍‍‍‍‍‍ ‍‍ ‍‍‍‍‍‍ ‍‍‍‍‍‍ ‍‍

การเขียนโค้ดเช่น ภาษาไพธอน เราก็มักแสดงข้อความเอาต์พุตที่ได้จากการทำงานของโค้ดในส่วนต่าง ๆ ของโปรแกรม โดยใช้คำสั่ง print() ซึ่งเป็นคำสั่งพื้นฐาน เช่น แสดงข้อความว่า “Hello World” แต่ในทางฮาร์ดแวร์หรืออิเล็กทรอนิกส์ ถ้าเราเลือกใช้ไมโครคอนโทรลเลอร์เป็นตัวประมวลผล และมีการนำอุปกรณ์แสดงผลมาต่อเพิ่ม เราก็สามารถนำฮาร์ดแวร์มาใช้แสดงสถานะการทำงานของโปรแกรม เช่น ทำให้ LED “ติดหรือดับ” ตามสถานะลอจิกของตัวแปรตัวหนึ่งในขณะนั้น อาจเป็นเพียงการแสดงสถานะลอจิกที่ไม่ใช่ข้อความก็ได้ ยกตัวอย่างเช่น

  • LED Matrix Module เช่น มีขนาด 8x8 หรือ 16x8 (หรือขนาดใหญ่กว่านั้น) ที่แสดงผลด้วย LEDs สีเดียวกัน เช่น สีแดง (R) สีเขียว (G) หรือสีฟ้า (B) เป็นต้น และก็มีหลายวิธีในการควบคุมการทำงาน กำหนดสถานะติดหรือดับ มีสถานะแบบไบนารี (ON/OFF) ของ LED แต่ดวง
  • RGB LED Matrix Module เป็นโมดูลที่แสดงผลโดยใช้ RGB LEDs และที่นิยมใช้งานกัน ก็คือ โมดูล WS2812B อาจมีการจัดวางแบบเชิงเส้นหรือแถวเดียว หรือแบบเมตริกซ์
  • Alpha-numberic Display Module เป็นโมดูลที่เป็นจอแสดงผลข้อความภาษาอังกฤษ เช่น ขนาด 16x2 หรือ 20x4 เป็นต้น
  • Graphic LCD Display เป็นโมดูลที่แสดงผลแบบกราฟิก อาจเป็นแบบ Monochrome หรือแบบสี RGB ก็ได้ มีความละเอียดค่อนข้างมาก (จำนวนพิกเซล)

ถ้าเราเลือกใช้โมดูลแสดงผลที่มีขนาดใหญ่ขึ้น ซึ่งหมายถึงจำนวน LEDs มากขึ้น ก็จะต้องใช้ปริมาณกรแสไฟฟ้ามากขึ้น และราคาของอุปกรณ์ก็สูงขึ้นตาม

โมดูล WS2812B RGB LEDs

‍‍‍‍‍‍ ‍‍ ‍‍‍‍‍‍ ‍‍‍‍‍‍ ‍‍

โมดูล WS2812B มีจุดเด่นคือ สามารถใช้สายสัญญาณเพียงเส้นเดียวในการส่งข้อมูลเข้าไปข้างใน เพื่อกำหนดค่าสี R,G,B แต่ละสีใช้ข้อมูลจำนวน 8 บิต รวม 24 บิต (หรือ 3 ไบต์) ต่อ RGB LED หนึ่งตำแหน่ง หรือจะเรียกว่า พิกเซล ก็ได้

เราอาจจะเลือกใช้ RGB LEDs จำนวน 8 ดวงที่มีการจัดเรียงแบบเชิงเส้นหรือแถวเดียว หรือเป็นโมดูลแบบ 2 มิติ เช่น ขนาด 4x4 เป็นต้น

WS2812B: Bar / Ring-Form Arrangement
WS2812B: Matrix-Form Arrangement

‍‍‍‍‍‍ ‍‍ ‍‍‍‍‍‍ ‍‍‍‍‍‍ ‍‍

การกำหนดค่าสีของ RGB LED แต่ละดวง จะต้องใช้ 3 ไบต์ (24 บิต) ต่อหนึ่งดวง อาจจะกำหนดเป็นค่าคงที่ โดยใช้โครงสร้างข้อมูลแบบ Tuple (ทูเพิล) ในภาษาไพธอน และกำหนดให้มีข้อมูลสมาชิกเป็นีเลขจำนวนเต็มแบบ Unsigned ขนาด 8 บิต จำนวน 3 ค่าคงที่ เช่น (0,0,0) หรือ (255,255,255) เป็นต้น (255 หมายถึง ระดับความสว่างสูดสุง) หรืออาจจะเป็นอาร์เรย์ที่มีจำนวนสมาชิกเท่ากับ 3 เช่น [0,0,0] และ [255,255,255] แล้วก็สามารถแปลงเป็น Tuple ได้เช่นกัน

โค้ดอย่างต่อไปนี้ สาธิตการใช้ตัวแปรชื่อ rgb ให้อ้างอิงข้อมูลแบบทูเพิล เช่น เริ่มต้นให้มีค่าเป็น (10,20,30) และแสดงค่าของทูเพิลดังกล่าวด้วยคำสั่ง print()

rgb = (10,20,30)
print( 'rgb = ({},{},{})'.format(*rgb) )
print( 'rgb = ({},{},{})'.format(rgb[0],rgb[1],rgb[2]) )

ให้สังเกตว่า การเข้าถึงข้อมูลสมาชิกของทูเพิล ทำได้โดยใช้ตัวดำเนินการหรือโอเปอร์เรเตอร์ […] พร้อมระบุหมายเลขลำดับของข้อมูลสมาชิก ที่ต้องการเข้าถึง (เริ่มต้นนับที่ 0)

ข้อจำกัดของการใช้ทูเพิลคือ เราไม่สามารถเปลี่ยนแปลงข้อมูลสมาชิกได้หลังจากที่ได้สร้างขึ้นมาแล้ว เช่น ถ้าเราเขียนโค้ดแบบนี้ จะเกิด Runtime Error (TypeError)

rgb = (10,20,30) # variable assignment with a 3-tuple object
# try to change the member of a tuple object
rgb[0] = 255
# TypeError: 'tuple' object doesn't support item assignment

แต่ถ้าเราใช้อาร์เรย์ (Array) หรือ ลิสต์ (List) แทนทูเพิล ก็จะเปลี่ยนแปลงแก้ไขข้อมูลสมาชิกได้

โครงสร้างข้อมูลสำหรับ WS2812B RGB LEDs

‍‍‍‍‍‍ ‍‍ ‍‍‍‍‍‍ ‍‍‍‍‍‍ ‍‍

ถ้าเรามีจำนวน RGB LEDs มากกว่าหนึ่ง ก็จะใช้อาร์เรย์ หรือ ลิสต์ ในภาษาไพธอน เพื่อเก็บข้อมูลในการกำหนดค่าสี (Color Values) เช่น ถ้าอาร์เรย์มีขนาดเท่ากับ 4 ก็เขียนโค้ดเพื่อกำหนดค่าให้ตัวแปรชื่อ values เป็นตัวอย่างดังนี้

# create a list object with four 3-tuple objects
values = [ (255,0,0), (0,255,9), (0,0,255), (127,127,127) ]

ในกรณีนี้ ตัวแปร values อ้างอิงโครงสร้างข้อมูลแบบลิสต์ที่ภายในมีข้อมูลสมาชิกแบบ 3-ทูเพิล (3-Tuple) หรือเราจะเขียนอีกแบบก็ได้

# create a list object with four sublists, each with 3 elements
values = [ [255,0,0], [0,255,0], [0,0,255], [127,127,127] ]

ในกรณีนี้ ตัวแปร values อ้างอิงโครงสร้างข้อมูลแบบลิสต์ที่ภายในมีข้อมูลสมาชิกเป็นลิสต์ที่มีข้อมูลจำนวนสมาชิกเท่ากับ 3 หรือเรียกว่า “ลิสต์ซ้อนลิสต์” (Nested List) หรือจะมองว่าเป็น อาร์เรย์แบบสองมิติ (Two-dimensional Array) ก็ได้

ถ้าเราอยากจะแสดงข้อมูลในอาร์เรย์แบบสองมิติ เช่น ขนาด 4 x 3 (มี 4 แถว หรือ rows และแต่ละแถวมี 3 คอลัมน์ หรือ columns) ก็สามารถเข้าถึงได้ตามตัวอย่างนี้

values = [
[255,0,0], # the first row
[0,255,0], # the second row
[0,0,255], # the third row
[127,127,127] # the fourth row
]
num_rows = len(values) # number of rows
num_cols = len(values[0]) # number of columns
for i in range(num_rows):
for j in range(num_cols):
print('{:3d}'.format(values[i][j]), end=' ')
print('')

ตัวอย่างเอาต์พุตที่ได้จะเป็นดังนี้

255   0   0 
0 255 0
0 0 255
127 127 127

ในภาษาไพธอน เราสามารถแปลงอาร์เรย์ให้เป็นทูเพิล โดยใช้คำสั่ง tuple() ยกตัวอย่างเช่น เราต้องการแปลงข้อมูลสมาชิกในอาร์เรย์หนึ่ง ซึ่งแต่ละตัวเป็นอาร์เรย์เช่นกัน ให้กลายเป็นทูเพิล เราก็เขียนโค้ดได้ดังนี้

values = [ [255,0,0],[0,255,0],[0,0,255],[127,127,127] ]
print( values ) # a list of sublists, each with 3 numbers
tuple_values = [ tuple(v) for v in values ]
print( tuple_values ) # a list of 3-tuples

ตัวอย่างเอาต์พุตจากคำสั่ง print() เป็นดังนี้

[[255, 0, 0], [0, 255, 0], [0, 0, 255], [127, 127, 127]]
[(255, 0, 0), (0, 255, 0), (0, 0, 255), (127, 127, 127)]

เมื่อเราได้เรียนรู้ตัวอย่างโครงสร้างข้อมูลในภาษาไพธอนไปบ้างแล้ว เพื่อนำมาใช้กำหนดค่าสีสำหรับ RGB LEDs ถัดไป เราจะเรียนรู้เทคนิคการส่งข้อมูลจากไมโครคอนโทรลเลอร์ (STM32) ไปยังอุปกรณ์จริง โดยสื่อสารข้อมูลผ่านบัส SPI

การใช้งานโมดูล WS2812B RGB LED Bar

‍‍‍‍‍‍ ‍‍ ‍‍‍‍‍‍ ‍‍‍‍‍‍ ‍‍

โมดูล WS2812B ใช้สัญญาณข้อมูลเพียงเส้นเดียว ในการส่งข้อมูลจากไมโครคอนโทรลเลอร์ไปยังขา DIN ของโมดูลดังกล่าว การส่งข้อมูลเป็นส่งข้อมูลแบบทีละบิตตามลำดับ (Bit Serial)

ถ้าใช้ WS2812B จำนวนหลายตำแหน่ง ข้อมูลบิตจะถูกส่งเข้ามาที่ขา DIN และสามารถส่งต่อไปยังตำแหน่งถัดไปที่ขา DOUT ในลักษณะ Daisy Chain (Cascading)

WS2812B Cascading

จำนวนบิตสำหรับค่าสี RGB ของ LED หนึ่งดวงหรือหนึ่งตำแหน่ง จะเท่ากับ 24 บิต ดังนั้นถ้ามีจำนวน N ตำแหน่ง ก็จะต้องใช้ข้อมูล (N*24) บิต หรือ (N*3) ไบต์

ข้อมูลที่ถูกส่งออกไป ไบต์ที่ 0,1,2 จะใช้สำหรับตำแหน่งแรก ไบต์ที่ 3,4,5 จะใช้สำหรับตำแหน่งที่สอง เป็นอย่างนี้ ไปตามลำดับจนครบทุกตำแหน่ง

คำถามคือ ถ้าใช้สายสัญญาณข้อมูลเพียงหนึ่งเส้น แล้วส่งข้อมูลจากไมโครคอนโทรลเลอร์ไปทีละบิต แล้วฝ่ายรับซึ่งเป็นโมดูล WS2812B จะทราบได้อย่างไรว่า ข้อมูลแต่ละบิตนั้นเป็น 0 หรือ 1 แล้วมีขอบเขตของสัญญาณบิตแต่ละบิตอย่างไร ?

บริษัท WorldSemi ผู้ออกแบบและพัฒนาไอซี WS2812B ได้กำหนดวิธีการส่งข้อมูลหรือโพรโทคอล (Protocol) เอาไว้แล้ว ซึ่งจะต้องเป็นไปตามนั้น มิเช่นนั้น การรับข้อมูลอาจไม่ถูกต้อง หรือไม่ได้รับข้อมูลใด ๆ

อ้างอิงตามเอกสาร Datasheet การจำแนกข้อมูลบิตแต่ละบิต จะใช้ความกว้างของสัญญาณพัลส์ (Pulse) เป็นตัวกำหนด (หน่วยเป็นไมโครวินาที) ดังนี้

บิตที่มีค่าเป็น 1 (ช่วง High กว้างกว่าช่วง Low)

  • T1H: เริ่มต้นด้วยช่วง High กว้างประมาณ 0.8us และ
  • T1L: ตามด้วยช่วง Low กว้างประมาณ 0.45us

บิตที่มีค่าเป็น 0 (ช่วง Low กว้างกว่าช่วง High)

  • T0H: เริ่มต้นด้วยช่วงช่วง High กว้างประมาณ 0.4us และ
  • T0L: ตามด้วยช่วง Low กว้างประมาณ 0.85us

และสามารถมีความคลาดเคลื่อนได้ +/- 150us

ความกว้างของช่วง High และ Low ของแต่ละบิต

‍‍‍‍‍‍ ‍‍ ‍‍‍‍‍‍ ‍‍‍‍‍‍ ‍‍

ลำดับของบิตในแต่ละไบต์ จะเป็นแบบ MSB First และลำดับข้อมูลไบต์ที่จะถูกส่งออกไป เป็นแบบ GRB Format คือ ไบต์แรกสำหรับสีเขียว (G) ไบต์ที่สองสำหรับสีแดง (R) และไบต์ที่สามสำหรับสีเขียว (B) เพื่อใช้กับ RGB LED ในตำแหน่งแรก

ในการส่งข้อมูลไบต์เหล่านี้ เราจะใช้วงจร SPI ที่มีให้เลือกใช้มากกว่าหนึ่งชุด (เช่น เลือกใช้ SPI Bus 1 หรือ SPI Bus 2) ของ STM32F4 เป็นตัวดำเนินการ และข้อมูลจะถูกส่งออกไปผ่านขา SPI MOSI

การส่งข้อมูลโดยใช้ SPI จะต้องกำหนดความถี่ SCLK และเป็นตัวกำหนดความกว้างของเวลาสำหรับแต่ละบิตในหนึ่งไบต์ ถ้ามีข้อมูลหลายไบต์ ก็จะถูกส่งออกไปตามลำดับแบบต่อเนื่องจนกว่าจะครบ

เราจะใช้ข้อมูล 4 บิต สำหรับเป็นตัวกำหนดค่าบิต 0 หรือ 1 ของค่าสีแต่ละบิต ดังนี้

  • 1110” (ช่วง High กว้างกว่าช่วง Low) หมายถึง บิต 1
  • 1000” (ช่วง Low กว้างกว่าช่วง High) หมายถึง บิต 0

ถ้าเรามีค่าสี เช่น 3 ไบต์ หรือ 24 บิต (เรียกว่า Color Bits) ต่อหนึ่งตำแหน่ง เราจะต้องสร้างสัญญาณบิตสำหรับ SPI ที่ขา MOSI ทั้งหมด 24*4 = 96 บิต (เรียกว่า SPI Data Bits) หรือ 12 ไบต์

คำถามคือ แต่ละบิตที่ถูกส่งออกไปทาง SPI จะต้องมีความกว้างเท่าไหร่ ? ถ้าเราลองเลือกความถี่ SCK ให้เท่ากับ 3.2 MHz จะได้ความกว้างของบิตเท่ากับ 1/3.2MHz = 0.3125us และจะได้ความกว้างดังนี้

  • 1110” (บิต 1): T1H=3*0.3125=0.9375us, T1L=0.3125us
  • 1000” (บิต 0): T0H=0.3125us, T0L=3*0.3125 = 0.9375us

ซึ่งยังถือว่า ค่าเหล่านี้ยังอยู่ในช่วงที่รับได้ (เมื่อพิจารณา +/-150us แล้ว)

ลองมาดูโค้ดสาธิตการทำงาน โดยเราจะเลือกใช้บัส SPI หมายเลข 1 และจะต้องใช้ขา A7 ของ STM32F411 เป็นขาเอาต์พุต นำไปต่อกับขา DIN ของโมดูล WS2812B เลือกใช้แบบ RGB LED Bar มีทั้งหมด 8 ดวง (8 ตำแหน่ง)

ในตัวอย่างนี้ได้กำหนดค่าสี 6 สีที่แตกต่างกัน สำหรับ 6 ตำแหน่งแรก และสองตำแหน่งท้ายที่เหลือนั้น ให้อยู่ในสถานะ OFF หรือ (0,0,0)

import pyb
import utime as time
# use SPI bus 1, use "A7" pin as SPI1 MOSI pin
# use SCK freq. = 3.2MHz
spi_bus = 1
spi = pyb.SPI(spi_bus, pyb.SPI.MASTER,
baudrate=3200000,
polarity=0, phase=1)
# set the number of RGB LEDs
NUM_LEDS = 8
# lookup table for bit pattern conversion
bit_patterns = (0x88, 0x8e, 0xe8, 0xee)
# SPI data buffer
buf = bytearray( NUM_LEDS * 3 * len(bit_patterns) )
def ws2812b_write( colors, start=0 ):
i = start
for r,g,b in colors:
for c in (g,r,b): # for GRB format
# convert 2 color bits into 8 SPI data bits
buf[i+0] = bit_patterns[c >> 6 & 0b11]
buf[i+1] = bit_patterns[c >> 4 & 0b11]
buf[i+2] = bit_patterns[c >> 2 & 0b11]
buf[i+3] = bit_patterns[c & 0b11]
i += 4

def ws2812b_update():
spi.send(buf) # send data buffer to the SPI bus
# show only 6 different colors on the WS2812B strip
data = [ (20,0,0), (0,20,0), (0,0,20) ]
data += [ (20,20,0), (0,20,20), (20,0,20) ]
data += (NUM_LEDS-len(data)) *[(0,0,0)] # the rest is off
ws2812b_write( data )
ws2812b_update()
time.sleep(2.0)
try:
while True:
# rotate color values
c = data.pop(-1)
data.insert( 0, c )
ws2812b_write( data )
ws2812b_update()
time.sleep(0.5)
except KeyboardInterrupt:
pass
finally:
ws2812b_write( NUM_LEDS*[(0,0,0)] )
ws2812b_update()
print('Done')
ตัวอย่างการใช้ Thonny IDE เขียนโค้ด MicroPython

‍‍‍‍‍‍ ‍‍ ‍‍‍‍‍‍ ‍‍‍‍‍‍ ‍‍

ฟังก์ชัน ws2812b_write() รับอาร์กิวเมนต์ colors ที่เป็นอาร์เรย์ของข้อมูลแบบ 3-ทูเพิล เพื่อใช้สำหรับกำหนดค่าสี RGB ในแต่ละตำแหน่ง จากนั้นจะแปลงข้อมูลที่เป็นค่าสี ทีละ 2 บิต ให้เป็นข้อมูล 8 บิตสำหรับ SPI แล้วนำไปใส่ลงอาร์เรย์ของไบต์ buf ตามลำดับ

ตัวอย่างการทดสอบการทำงานโดยใช้อุปกรณ์จริง

‍‍‍‍‍‍ ‍‍ ‍‍‍‍‍‍ ‍‍‍‍‍‍ ‍‍

คำแนะนำ: วิธีการสร้างคลาสในภาษา MicroPython สำหรับ WS2812B เพื่อนำไปใช้กับ PyBoard (pyb) สามารถศึกษาได้จากตัวอย่างโค้ดนี้

การใช้งานโมดูล WS2812B 4x4 Matrix

‍‍‍‍‍‍ ‍‍ ‍‍‍‍‍‍ ‍‍‍‍‍‍ ‍‍

เราได้ลองใช้โมดูล WS2812B แบบแถวเดียวไปแล้ว คราวนี้ลองมาใช้โมดูล WS2812B แบบ 2 มิติ เป็นลำดับถัดไป ซึ่งมีการจัดเรียงแบบเมตริกซ์ (2 มิติ)

WS2812B: Matrix-Arrangement (Back and Front Sides)

ถ้าใช้โมดูลขนาด 4x4 ตามรูปภาพประกอบ เราก็กำหนดให้มุมบนซ้ายมีพิกัด (x=0,y=0) และมุมบนล่างขวามีพิกัด (x=3,y=3) แต่ละลำดับของ RGB LEDs จะเป็นแบบเชิงเส้น เนื่องจากมีรูปแบบการนำมาต่อกันแบบ Cascading

โมดูลที่ได้เลือกมาใช้งาน มีลำดับการนับของ LEDs ดังนี้ ให้เริ่มนับจากแถวแรก (แนวนอน) นับจากซ้ายไปขวา แต่ในแถวที่สองจะนับจากขวาไปซ้าย (reverse) สลับไปมาอย่างนี้ในแถวต่อไป

โจทย์ฝึกคิดและเขียนโค้ดมีดังนี้: ถ้าเราต้องการจะแสดงสีบนโมดูล 4x4 ตามรูปตัวอย่าง จะเขียนโค้ดอย่างไร ?

เราลองมากำหนดค่าของสีตามรูปตัวอย่าง โดยใช้โครงสร้างข้อมูลแบบอาร์เรย์สองมิติให้กับตัวแปร m ดังนี้

# W is the width and H is the height of the matrix
W = H = 4
R = (255,0,0) # Red color
G = (0,255,0) # Green color
B = (0,0,255) # Blue color
O = (0,0,0) # OFF
# define a WxH matrix of 3-tuple objects
m = [ [R,G,O,O],
[B,R,G,O],
[O,B,R,G],
[O,O,B,R] ]

ถ้าเราต้องการนำข้อมูลที่อ้างอิงโดยตัวแปร m ไปใช้กับฟังก์ชัน ws2812b_write() ก็จะต้องแปลงอาร์เรย์สองมิติให้เป็นอาร์เรย์มิติเดียว และจะต้องมีลำดับข้อมูลในแต่ละตำแหน่งที่ถูกต้องด้วย

โค้ดต่อไปนี้สาธิตการแปลงโครงสร้างข้อมูลเพื่อนำไปใช้กับโมดูล 4x4 WS2812B

data = sum( m[r][::1-2*(r%2)] for r in range(H-1,-1,-1),[])
ws2812b_write( data )
ws2812b_update()

ถ้านำไปทดสอบการทำงาน ก็จะได้ผลตามรูปภาพประกอบต่อไปนี้

ตัวอย่างการทดสอบการทำงานโดยใช้อุปกรณ์จริง

โดยสรุป เราเห็นตัวอย่างการใช้งานโมดูล WS2812B ที่มีให้เลือกหลายแบบ เช่น เป็นแถวตรง หรือเป็นแบบวงกลม หรือเป็นแบบเมตริกซ์ เราได้เรียนรู้การใช้โครงสร้างข้อมูลในภาษาไพธอน เช่น ลิสต์หรืออาร์เรย์มิติเดียว อาร์เรย์สองมิติ หรือลิสต์ซ้อน การใช้โครงสร้างข้อมูลแบบทูเพิล อาร์เรย์ของทูเพิล เป็นต้น เพื่อใช้ในการกำหนดรูปแบบข้อมูลสำหรับโมดูล WS2812B และแปลงข้อมูลให้อยู่ในรูปแบบที่นำไปใช้กับบัส SPI ของไมโครคอนโทรลเลอร์

--

--

<rawat.s>
<rawat.s>

Written by <rawat.s>

I'm Thai and working in Bangkok/Thailand.

No responses yet