บทความนี้นำเสนอตัวอย่างการเขียนโค้ด MicroPython สำหรับ ESP32 และกล่าวถึง การสร้างสัญญาณควบคุมเพื่อโปรแกรมค่าสีให้โมดูล WS2812B RGB LEDs โดยใช้วงจรภายใน RMT ของ ESP32 ในการสร้างสัญญาณพัลส์เป็นเอาต์พุต
โมดูล WS2812 / WS2812 RGB
WS2812 ที่ผลิตโดยบริษัท WorldSemi เป็นวงจรรวมที่มีตัวถังแบบ SMD 5050 ได้รวม LED สามสี (แดง เขียว และน้ำเงิน) ไว้กับวงจรควบคุมการทำงานแบบดิจิทัล ทำให้สามารถโปรแกรมเพื่อเลือกและกำหนดสีได้ โดยใช้ข้อมูล 24 บิต แบ่งเป็น 8 บิตแรก (MSB) สำหรับสีเขียว 8 บิตถัดมาเป็นสีแดง และ 8 บิตสุดท้าย เป็นสีน้ำเงิน (GRB: Green Red Blue)
WS2812B ได้พัฒนาต่อจาก WS2812 มีการลดจำนวนขาจากเดิม 6 ขา ให้เลือก 4 ขา (VCC, DIN, DOUT, GND) โดยทั่วไปเรา ก็ใช้แรงดันไฟเลี้ยง +5V สำหรับ VCC และสำหรับสัญญาณควบคุมที่ขา DIN เราก็ใช้สัญญาณจากอุปกรณ์ +3.3V ได้ด้วย
WS2813B เป็นรุ่นที่พัฒนาต่อจาก WS2812B โดยเพิ่มสายสัญญาณควบคุมจากเดิม 1 เส้น เป็น 2 เส้น ถ้าสายสัญญาณเส้นหนึ่งชำรุด อีกเส้นก็ยังใช้งานได้
การส่งข้อมูลขนาด 24 บิต สำหรับ WS2812 หนึ่งดวงนั้น จะใช้วิธีสร้างสัญญาณพัลส์ ป้อนเข้าที่ขา DIN โดยความกว้างช่วงที่เป็น High และ Low จะเป็นตัวหนดค่าบิตว่าเป็น 1 หรือ 0 เช่น กำหนดให้ทั้ง 24 บิต เป็น 1 ก็จะทำให้ LED ให้แสงผสมเป็นสีขาว และมีความเข้มแสงสูงสุด (Max. Brightness) ใช้กระแสไฟฟ้า (Current Draw) แต่ละสีอาจสูงถึง 20 mA หรือ รวมทั้งหมด 3*20 mA = 60mA
สัญญาณพัลส์หนึ่งลูก จะเริ่มต้นด้วยช่วง High และตามด้วยช่วง Low ความกว้างจะเป็นตัวกำหนดค่าบิตดังนี้ และคลาดเคลื่อนได้ไม่เกิน +/-150 us (ไมโครวินาที)
- บิต 0: T0H= 0.4us, T0L= 0.85us
- บิต 1: T1H=0.85us, T1L= 0.4us
ถ้าสัญญาณเป็น Low อย่างน้อย 50 ไมโครวินาที หมายถึง RESET Code จบการส่งข้อมูลไปยัง WS2812B
เมื่อนำโมดูล WS2812B มาต่อกันแบบ Cascade โดยให้ขา DOUT ของตัวก่อนหน้าไปต่อกับขา DIN ของตัวถัดไป เราก็สามารถใช้วิธีส่งข้อมูลโดยใช้สัญญาณดิจิทัลเส้นเดียว ถ้ามีโมดูล WS2812B จำนวน N ชุด ต่อกัน เราจะต้องส่งข้อมูลทั้งหมด 24*N บิต หรือ 3*N ไบต์
ข้อมูลที่ถูกส่งไปยัง WS2812B ที่มีมากกว่าหนึ่งชุด เมื่อไปถึงตำแหน่งแรก จะถูกตัด 3 ไบต์แรกออกเพื่อใช้สำหรับ LED ในตำแหน่งดังกล่าว และข้อมูลที่เลือกจะถูกส่งออกทางขา DOUT ต่อไป และจะลดลงทีละ 3 ไบต์ เมื่อไปถึง LED ในตำแหน่งท้ายสุด จะได้รับข้อมูล 3 ไบต์สุดท้าย
ถ้าต้องการใช้อุปกรณ์ที่มี WS2812B หลายดวง ก็อาจเลือกโมดูล PCB ที่มีการจัดวาง LED เป็นแถวแบบมิติเดียว หรือเป็นแบบอาร์เรย์สองมิติ หรือเป็นวงกลม แต่ถ้ามี RGB LED จำนวนมาก ก็มีลักษณะเป็นแถบ (Strip) เช่น 30 หรือ 60 หรือ 144 ดวงต่อความยาวหนึ่งเมตร
ข้อควรระวัง: ในกรณีที่ใช้ RGB LED จำนวนมาก จะต้องมีแหล่งจ่ายไฟเอาต์พุต+5V ที่สามารถจ่ายกระแสได้สูง เช่น ถ้าคิดคำนวณที่ 60mA (0.06A) ต่อดวง และมี 150 ดวง จะต้องใช้กระแสถึง 150*0.06A = 9A
เนื่องจาก ESP32 มีวงจร RMT ที่สามารถสร้างสัญญาณพัลส์และมีความแม่นยำสูง เราจะใช้วงจรนี้ในการสร้างสัญญาณควบคุมให้ WS2812B
วงจร RMT ภายใน ESP32
RMT (Remote Control) เป็นวงจรภายใน (On-chip Peripheral Module) ของ ESP32 โดยทั่วไป จะใช้สำหรับสร้างสัญญาณพัลส์ (Pulse Generation) เช่น การสื่อสารข้อมูลด้วยแสงอินฟราเรด ในโหมดตัวส่ง (IR Transmitter) หรือตัวรับ (IR Receiver) หรือการสัญญาณควบคุมสำหรับโมดูล WS2812 RGB LEDs เป็นต้น
ข้อสังเกต: MicroPython v1.12 ยังไม่รองรับการใช้งาน RMT ในโหมดการส่งหรือรับสัญญาณอินฟราเรด
วงจร RMT ใช้สัญญาณ Clock ภายใน ความถี่ 80 MHz (default) เป็นฐานเวลาในการทำงาน สามารถกำหนดค่าของตัวหารความถี่ (Clock Divider) ได้ในช่วง 1..255 (8 บิต) เช่น ถ้าใช้ตัวหารความถี่เท่ากับ 80 จะได้ความละเอียดในการนับเท่ากับ 1 ไมโครวินาที หรืออัตราการนับที่ 80MHz/80 = 1MHz (Tick Rate) แต่ถ้าเลือกตัวหารความถี่เท่ากับ 8 ก็จะได้ความละเอียดในการนับ 0.1 ไมโครวินาที เป็นต้น
วงจร RMT มีช่องสัญญาณทั้งหมด 8 ช่อง (0..7) และมีหน่วยความจำ SRAM ในการเก็บข้อมูลได้ 512 x 32 บิต โดยใช้ร่วมกันทั้ง 8 ช่องสัญญาณ
การเขียนโค้ด MicroPython เพื่อใช้งาน RMT
ถ้าเราใช้ MicroPython สำหรับ ESP32 เราก็สามารถใช้ชุดคำสั่งในไลบรารี esp32
และใช้คลาส RMT
โดยจะต้องกำหนดหมายเลขช่องสัญญาณ (0..7) ขา GPIO ที่จะใช้งาน (ที่ไม่ใช่ขา สำหรับ ADC) และเลือกค่าตัวหารความถี่ เช่น หาร 8
ตัวอย่างโค้ดนี้ สาธิตการใช้ RMT เพื่อกำหนดค่าสีตัวอย่าง สีแดง สีเขียวและสีน้ำเงิน วนไปตามลำดับ และในการต่อวงจรได้เลือกใช้ขา GPIO27 ต่อกับโมดูล WS2812B (มีเพียงหนึ่งดวง) ที่ขา DIN และใช้แรงดันไฟเลี้ยง +5V จากขา VIN (USB) ของบอร์ด ESP32 และต่อขา GND ของระบบร่วมกัน
# File: ws2812b_rmt_demo-1.py
from machine import Pin
import esp32
import utime as timeWS2812B_PIN = Pin(27)# create an RMT object, use clock divider = 8
# => The resolution is 100ns or 0.1us
rmt = esp32.RMT(id=0, pin=WS2812B_PIN, clock_div=8)# define bit timings: pulse widths
BIT0 = (4,8) # T0H = 0.4us, T0L = 0.8us
BIT1 = (8,4) # T1H = 0.8us, T1L = 0.4us# test colors
RED_COLOR = 8*BIT0 + 8*BIT1 + 8*BIT0
GREEN_COLOR = 8*BIT1 + 8*BIT0 + 8*BIT0
BLUE_COLOR = 8*BIT0 + 8*BIT0 + 8*BIT1
COLORS = [RED_COLOR, GREEN_COLOR, BLUE_COLOR]try:
# press Ctrl+C to terminate
while True: # main loop
for bits in COLORS:
# send data to RMT
rmt.write_pulses( bits, start=1)
time.sleep(1.0)
except KeyboardInterrupt:
print('Terminated...')
finally:
rmt.deinit() # release the RMT channel
ในตัวอย่างนี้จะเห็นได้ว่า มีการใช้คำสั่ง write_pulses()
เพื่อกำหนดข้อมูลให้ RMT เป็นข้อมูลแบบ Tuple ซึ่งเป็นลำดับตัวเลขจำนวนเต็มบวก (ขนาด 15 บิต) ที่ระบุจำนวน Ticks ยกตัวอย่างเช่น (4,8,…) หมายถึง ให้เริ่มต้นด้วยความกว้างของพัลส์ High เท่ากับ 4*0.1 usec ตามด้วยช่วง Low อีก 8*0.1 usec เป็นต้น ดังนั้นจึงกำหนดบิตแรกให้มีค่าเป็น 0 และสำหรับค่าของบิตถัดไป ให้พิจารณาจากตัวเลข 2 ตัวถัดไป ตามลำดับ
ตัวอย่างการสร้างคลาส WS2812B
ลองมาดูตัวอย่างการเขียนโค้ด MicroPython เพื่อสร้างคลาสที่มีชื่อว่า WS2812B
และบันทึกลงในไฟล์ ws2812b.py
# File: ws2812b.py
import esp32
import utime as timeclass WS2812B:
"""An implementation of a WS2812B driver using ESP32-RMT"""
BIT0 = [4,8] # T0H = 0.4us, T0L = 0.8us
BIT1 = [8,4] # T1H = 0.8us, T1L = 0.4us
def __init__(self, pin, n=1):
self._n = n
self._data = n*[(0,0,0)]
self._rmt = esp32.RMT(0, pin=pin, clock_div=8)
def __setitem__(self, index, val):
if index < 0 or index >= self._n:
raise IndexError(’Index out of range’)
if isinstance( val, tuple ):
self._data[index] = val
elif isinstance( val, int):
r = (val >> 16) & 0xff
g = (val >> 8) & 0xff
b = val & 0xff
self._data[index] = (r,g,b)
def __getitem__(self, index):
if 0 <= index and index < self._n:
return self._data[index]
else:
return None
def clear(self):
self._data = self._n*[(0,0,0)]
self.update()
def shift_left(self, new_val=None):
first = self._data.pop(0)
self._data.append((0,0,0))
if new_val is not None:
self[self._n-1] = new_val
else:
self[self._n-1] = first
return first
def shift_right(self, new_val=None):
last = self._data.pop(self._n-1)
self._data.insert(0,(0,0,0))
if new_val is not None:
self[0] = new_val
else:
self[0] = last
return last
def update(self):
bits = []
for t in self._data:
r,g,b = t
v = (g << 16) | (r << 8) | b
for i in range(23,-1,-1):
if (v >> i) & 1:
bits += self.BIT1
else:
bits += self.BIT0
self._rmt.write_pulses(bits, start=1)
while not self._rmt.wait_done():
pass
def deinit(self):
self._rmt.deinit()
ตัวอย่างโค้ดสาธิตการใช้งานคลาส WS2812B สำหรับ 8-Pixel RGB LED Strip
# File: ws2812b_rmt_demo-2.py
from machine import Pin
import utime as time
from ws2812b import WS2812BWS2812B_PIN = Pin(27)
NUM_LEDS = 8# create WS2812B object for n-pixel RGB LED strip
strip = WS2812B(pin=WS2812B_PIN,n=NUM_LEDS)# define test colors
COLORS = [
(0x3f << 16), (0x3f << 8), 0x3f,
0x3f3f00, 0x3f003f, 0x003f3f,
0x7f7f7f,(127,20,20) ]try:
for i in range(NUM_LEDS):
strip[i] = COLORS[i]
strip.update()
time.sleep_ms(500)
# press Ctrl+C to terminate
while True: # main loop
for i in range(2*NUM_LEDS):
if i < NUM_LEDS:
# rotate shift left
strip.shift_left()
else:
# rotate shift right
strip.shift_right()
strip.update()
time.sleep_ms(1000)
except KeyboardInterrupt:
print('Terminated...')
finally:
strip.clear() # turn off all RGB LEDs
strip.deinit() # release the RMT channel
โดยสรุป เราได้เรียนรู้หลักการทำงานของโมดูล WS2812B RGB LED และเรียนรู้วิธีการเขียนโค้ดโดยใช้คำสั่งของ MicroPython ที่ใช้วงจร RMT ภายใน ESP32 สร้างสัญญาณพัลส์เพื่อควบคุมการทำงาน