MicroPython Programming for ESP32 [17]

<rawat.s>
5 min readMay 27, 2020

--

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

บทความนี้นำเสนอตัวอย่างการเขียนโค้ด 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 SMD 5050 ของบริษัท WorldSemi
WS2812B RGB LED (SMD 5050)

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 ไบต์สุดท้าย

ข้อกำหนดเชิงเวลาของสัญญาณพัลส์ ช่วงกว้างที่เป็น High และ Low สำหรับบิตที่มีค่า 0 และ 1
ตารางระบุค่าความกว้างของสัญญาณพัลส์ในแต่ละกรณี
ลำดับของข้อมูลบิตที่มีทั้งหมด 24 บิตสำหรับ WS2812B แต่ละดวง
รูปแบบการต่ออนุกรมแบบ Cascading
รูปแสดงลำดับการส่งข้อมูลไปยัง WS2812B (4 ชุด ต่อกันแบบ Cascade)
ตารางแสดงข้อมูลปริมาณกระแสของแต่ละสี (อ้างอิงตามเอกสาร Datasheet)

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

ถ้าต้องการใช้อุปกรณ์ที่มี 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 time
WS2812B_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 time
class 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 WS2812B
WS2812B_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
ตัวอย่างแผนผังแสดงตำแหน่งขาต่าง ๆ ของบอร์ด NodeMCU DevKit ESP32
ตัวอย่างอุปกรณ์ที่ได้นำมาทดลอง
โมดูล 8-Pixel WS2812B RGB LED Strip ที่ให้แสงสีเมื่อทดสอบการทำงานของโค้ด

โดยสรุป เราได้เรียนรู้หลักการทำงานของโมดูล WS2812B RGB LED และเรียนรู้วิธีการเขียนโค้ดโดยใช้คำสั่งของ MicroPython ที่ใช้วงจร RMT ภายใน ESP32 สร้างสัญญาณพัลส์เพื่อควบคุมการทำงาน

--

--

<rawat.s>
<rawat.s>

Written by <rawat.s>

I'm Thai and working in Bangkok/Thailand.

Responses (1)