How To Blink LEDs with ESP32
ตัวอย่างสาธิตการเขียนโค้ดไมโครไพธอนสำหรับ ESP32 เพื่อทำให้ LED กระพริบ โดยใช้วิธีที่แตกต่างกันไป
เนื้อหาในส่วนนี้ มีโจทย์ง่าย ๆ เพื่อทดลองเขียนโค้ดที่จะทำให้ LED เช่น หนึ่งดวงที่อยู่บนบอร์ด ESP32 (หรือจะเลือกใช้ขา GPIO ขาใดขาหนึ่งแล้วต่อวงจร LED เพิ่มเองก็ได้) สามารถกระพริบได้ (LED Blink)
คำถามคือ แล้วมีวิธีการเขียนโค้ดอย่างไรได้บ้างโดยใช้ภาษาไมโครรไพธอน ลองมาดูตัวอย่างและศึกษาการทำงานของโค้ด อาจมองว่าเป็นการทบทวนการใช้ภาษาไพธอน สำหรับผู้ที่เคยเขียนโค้ดในภาษานี้มาบ้างแล้ว แต่อาจจะยังไม่เคยใช้งานไพธอนแบบสัมผัสฮาร์ดแวร์ (Python for Microcontrollers / Physical Computing)
LED Blink: Demo 1
จากเนื้อหาที่ผ่านมา เราได้เรียนรู้การใช้คลาส Pin
จากไลบรารีหรือโมดูล machine
เพื่อใช้งานขา GPIO ของบอร์ด ESP32
ตัวอย่างนี้ได้เลือกใช้ขา GPIO-5 และกำหนดทิศทางให้เป็นเอาต์พุต ฟังก์ชัน value()
ที่ของอ็อปเจกต์ที่สร้างมาจากคลาส Pin
มีไว้สำหรับอ่านหรือเขียนค่าลอจิก (0 หรือ 1)
เมื่อกำหนดสถานะของขา GPIO ได้แล้ว จะต้องมีการหน่วงเวลา เราก็ใช้คำสั่งจากโมดูล time
เช่น time.sleep()
, time.sleep_ms()
หรือ time.sleep_us()
ก็ได้ ซึ่งแตกต่างกันตรงที่หน่วยของเวลาสำหรับอาร์กิวเมนต์ เช่น วินาที มิลลิวินาที และไมโครวินาที ตามลำดับ
from micropython import const
from machine import Pin
import utime as time
LED_OFF_ON = [1,0] # active-low LED: OFF=1, ON=0
LED_GPIO = const(5) # use GPIO5 for LED output
def main_loop( led ):
while True:
# toggle LED: read, modify, write LED state
led.value( not led.value() )
# delay for 0.1 seconds
time.sleep_ms( 100 )
try:
# create a LED object for LED output
led = Pin( LED_GPIO, Pin.OUT )
# enter main loop (press Ctrl+C to stop)
main_loop( led )
except KeyboardInterrupt:
pass
led.value( LED_OFF_ON[0] ) # turn off LED
คำสั่งในฟังก์ชัน main_loop()
จะทำให้ LED กระพริบซ้ำไปเรื่อย ๆ โดยเว้นระยะเวลาในการสลับสถานะทุก ๆ 100 วินาที ถ้าทดลองรันโค้ดนี้ผ่าน REPL Shell ใน Thonny IDE และต้องการจะหยุดการทำงาน ก็ให้กดคีย์ Ctrl+C
ข้อสังเกต: ในตัวอย่างนี้ LED บนบอร์ด ESP32 ที่ได้นำมาใช้งาน จะสว่าง (ON) ก็ต่อเมื่อเขียนสถานะลอจิกเป็น 0 (False
) และจะดับ (OFF) เมื่อเป็น 1 (True
)
LED Blink: Demo 2
จากตัวอย่างแรก ตัวอย่างที่สองนี้ มีการแก้ไขจากเดิมเล็กน้อย โดยเปลี่ยนจากการใช้คำสั่ง time.sleep_ms()
มาเป็นการอ่านค่าเวลาของระบบ time.tick_ms()
ในหน่วยเป็นมิลลิวินาที แล้วคอยตรวจสอบซ้ำดูว่า เวลาผ่านไป 100 มิลลิวินาที แล้วหรือไม่ ถ้าเป็นเช่นนั้น ก็ให้ทำการสลับสถานะของ LED หนึ่งครั้ง แล้วอัปเดตเวลาการเปลี่ยนแปลงครั้งล่าสุด
คำสั่ง time.ticks_diff()
ใช้สำหรับการหาผลต่างของตัวเลขเวลาที่ได้บันทึกไว้ในตัวแปร t2
และ t1
from micropython import const
from machine import Pin
import utime as time
LED_OFF_ON = [1,0] # for active-low LED
LED_GPIO = const(5) # use GPIO5 for LED output
def main_loop( led ):
t1 = time.ticks_ms() # read time ticks (in msec)
try:
while True:
t2 = time.ticks_ms()
if time.ticks_diff(t2, t1) >= 100:
# update the timestamp
t1 = t2
# toggle LED state
led.value( not led.value() )
except KeyboardInterrupt:
pass
led = Pin( LED_GPIO, Pin.OUT )
main_loop( led ) # enter main loop
led.value( LED_OFF_ON[0] ) # turn off LED
LED Blink: Demo 3
ตัวอย่างที่สาม เป็นการสาธิตการใช้งาน Python Generator Function โดยสร้างฟังก์ชันชื่อ next_state_generator()
ภายในมีการใช้คำสั่ง yield
ดังนั้นจึงนำไปใช้เป็น Generator Iterator ได้ เมื่อเรียกฟังก์ชันนี้ในแต่ละครั้ง จะได้สถานะลอจิก (0 หรือ 1) ถัดไป เช่น สลับ 0 และ 1 ไปเรื่อย ๆ และมีการหน่วงเวลาด้วยคำสั่ง time.sleep_ms()
ด้วยเช่นกัน
from micropython import const
from machine import Pin
import utime as time
LED_OFF_ON = [1,0] # for active-low LED
LED_GPIO = const(5) # use GPIO5 for LED output
def next_state_generator(sleep_interval):
while True:
for i in range(2):
yield i
time.sleep_ms( sleep_interval )
def main_loop( led ):
for state in next_state_generator(100):
led.value( state )
try:
led = Pin( LED_GPIO, Pin.OUT )
main_loop( led )
except KeyboardInterrupt:
pass
led.value( LED_OFF_ON[0] ) # turn off LED
LED Blink: Demo 4
ตัวอย่างถัดไปสาธิตการใช้ไทม์เมอร์ หรือตัวนับเวลา Timer
ของโมดูล machine
ซึ่งมีสองประเภทให้ใช้งานคือ Hardware Timer และ Software Timer แต่เนื่องจาก Hardware Timer สำหรับ ESP32 มีจำนวนจำกัด
โดยทั่วไปเราก็จะใช้ Software Timer (ระบุหมายเลขเป็น -1) เหมาะสำหรับกรณีที่มีความถี่หรืออัตราการนับจังหวะไม่สูงมากนัก แต่ถ้าจะใช้กับความถี่สูง ก็ให้ใช้ Hardware Timer (ระบุหมายเลขเป็น 0 – 3) สำหรับ ESP32
ในตัวอย่างนี้ เราใช้งาน Software Timer และกำหนดคาบในการทำงาน (Period) เท่ากับ 100 มิลลิวินาที และเลือกโหมดแบบทำซ้ำ (Timer.PERIODIC
) แต่ถ้าเลือกเป็น Timer.ONE_SHOT
จะทำเพียงครั้งเดียวแล้วจบการทำงานของไทม์เมอร์
นอกจากนั้นจะต้องกำหนดฟังก์ชันที่เรียกว่า Callback Function ซึ่งจะถูกเรียกให้ทำงานเมื่อเวลาผ่านไปครบหนึ่งคาบในแต่ละครั้ง ในตัวอย่างนี้ได้ระบุฟังก์ชันเป็นแบบ lambda
ซึ่งจะทำหน้าที่สลับสถานะของ LED
from micropython import const
from machine import Pin, Timer
import utime as time
LED_OFF_ON = [1,0] # for active-low LED
LED_GPIO = const(5) # use GPIO5 for LED output
def main_loop( ):
while (True):
print( 'Main loop' )
time.sleep(-1) # wait forever
try:
# create a Pin object for LED
led = Pin( LED_GPIO, Pin.OUT )
# create a software-based (virtual) timer
timer = Timer(-1)
# start the timer in periodic mode (100msec period)
timer.init( mode=Timer.PERIODIC, period=100,
callback=lambda t: led.value(not led.value()) )
main_loop( ) # enter main loop
except KeyboardInterrupt:
pass
led.value( LED_OFF_ON[0] ) # turn off the LED
timer.deinit() # disable timer
ข้อสังเกต: ในตัวอย่างนี้ ขณะที่ Timer กำลังทำงานเป็นอิสระ การทำงานของลูปภายในฟังก์ชัน main_loop()
เป็นเพียงการรอให้จบการทำงานของโปรแกรม โดยการกดคีย์ Ctrl+C
LED Blink: Demo 5
ตัวอย่างถัดไปสาธิตการสร้างสัญญาณแบบ PWM (Pulse Width Modulation) ให้มีความถี่ต่ำ (5 Hz หรือมีคาบเท่ากับ 200 msec) สำหรับนำไปใช้กับ LED และกำหนดค่า Duty Cycle ให้เท่ากับ 50% โดยระบุค่าเป็น 511 ซึ่งเป็นค่ากลางในช่วง 0..1023 ดังนั้นจึงได้สัญญาณเอาต์พุตแบบมีคาบ ในช่วงที่เป็น 0 (Low) และ 1 (High) มีความกว้างช่วงละ 100 มิลลิวินาที
from micropython import const
from machine import Pin, PWM
import utime as time
LED_OFF_ON = [1,0] # for active-low LED
LED_GPIO = const(5) # use GPIO5 for LED output
def main_loop():
while True:
time.sleep(-1) # wait forever
try:
led = Pin( LED_GPIO, Pin.OUT )
# create a PWM object for the specified pin
pwm = PWM( led )
pwm.duty(511) # 50% (10-bit resolution: 0..1023)
pwm.freq(5) # note: 1Hz is the lowest frequency
main_loop( ) # enter main loop
except KeyboardInterrupt:
pass
led.value( LED_OFF_ON[0] ) # turn off LED
pwm.deinit() # turn off PWM output
LED Blink: Demo 6
ตัวอย่างถัดไปสาธิตการสร้าง 'เธรด' (Thread) ที่ทำงานได้อิสระจาก Main Thread โดยใช้คำสั่งของโมดูลหรือไลบรารี _thread
เธรดที่ถูกสร้างขึ้นมานั้น ทำหน้าที่คอยสลับสถานะลอจิกของ LED และเว้นระยะเวลา 100 มิลลิวินาที แล้วทำขั้นตอนซ้ำ
ในตัวอย่างนี้ ฟังก์ชัน led_thread_func()
จะถูกกำหนดให้เป็น Thread entry function (ฟังก์ชันที่ทำงานโดยเธรดที่ถูกสร้างขึ้นใหม่) โดยใช้คำสั่ง _thread.start_new_thread()
ตัวแปรภายนอก finished
แบบบูลีน (Boolean) จะถูกใช้ในการตรวจสอบเงื่อนไขการออกจากลูปการทำงานของเธรด ซึ่งจะจบการทำงานเมื่อมีการกดคีย์ Ctrl+C ใน REPL Shell
from micropython import const
from machine import Pin
import utime as time
import _thread
LED_OFF_ON = [1,0] # for active-low LED
LED_GPIO = const(5) # use GPIO5 for LED output
# global variable
finished = False
def main_loop( ):
global finished
print( 'Thread "main": id=%d' % _thread.get_ident() )
try:
while not finished:
pass
except KeyboardInterrupt:
finished = True
led.value( LED_OFF_ON[0] ) # turn off LED
_thread.exit() # force thread to exit
def led_thread_func(led, delay):
global finished
print( 'Thread "led": id=%d' % _thread.get_ident() )
try:
while not finished:
led.value( not led.value() )
time.sleep_ms( delay )
except KeyboardInterrupt:
finished = True
_thread.exit() # force thread to exit
led = Pin( LED_GPIO, Pin.OUT )
_thread.start_new_thread( led_thread_func, (led,100,) )
main_loop() # enter main loop
LED Blink: Demo 7
ตัวอย่างถัดไปเป็นการใช้เธรด เหมือนตัวอย่างแล้ว แต่สาธิตการสร้างเธรด 2 ชุด (หมายเลข id
คือ 0 และ 1) โดยให้แต่ละเธรดทำหน้าที่กำหนดสถานะลอจิกตาม id
ของตัวเอง กล่าวคือ ถ้า id
เป็น 0 ให้เขียนสถานะเป็น 0 แต่ถ้า id
เป็น 1 ให้เขียนสถานะเป็น 1 แล้วเว้นระยะเวลา 100 มิลลิวินาที
แต่เนื่องจากว่า เธรดทั้งสองชุดใช้ฟังก์ชันเดียวกันคือ led_thread_func()
ดังนั้นจึงใช้ id
เป็นตัวระบุการทำงานของแต่ละเธรด
ทั้งสองเธรดนั้น ต้องเข้าถึงอ็อปเจกต์ที่เป็น Pin
สำหรับ LED ร่วมกัน ดังนั้นจึงมีการสร้างและใช้ lock
(Mutex Lock) ในการป้องกันและจัดลำดับการเข้าถึงทรัพยากรที่ใช้ร่วมกัน
from micropython import const
from machine import Pin
import utime as time
import _thread
LED_OFF_ON = [1,0] # for active-low LED
LED_GPIO = const(5) # use GPIO5 for LED output
# global variable
finished = False
def main_loop( ):
global finished
try:
while not finished:
pass
except KeyboardInterrupt:
finished = True
led.value( LED_OFF_ON[0] )
_thread.exit()
def led_thread_func(id, led, delay, lock):
global finished
try:
while not finished:
lock.acquire() # try to acquire lock
led.value( (id==1) ) # set LED value
time.sleep_ms( delay )
lock.release() # release lock
except KeyboardInterrupt:
finished = True
_thread.exit()
# create a pin object for LED output
led = Pin( LED_GPIO, Pin.OUT )
# create a mutex lock
lock = _thread.allocate_lock()
# create and start two new threads
_thread.start_new_thread( led_thread_func, (0,led,100,lock,) )
_thread.start_new_thread( led_thread_func, (1,led,100,lock,) )
# enter the main loop
main_loop()
LED Blink: Demo 8
ตัวอย่างนี้สาธิตการใช้วงจร RMT (และใช้ได้เฉพาะกับ ESP32 เท่านั้น) เหมาะสำหรับสร้างสัญญาณพัลส์เป็นชุด (Pulse Train Generation) เช่น การสร้างสัญญาณควบคุมสำหรับรีโมทแสงอินฟราเรด (Infrared Remote Control) หรือใช้ควบคุมหรือกำหนดค่าสีให้กับโมดูล WS2812B Neopixel RGB LED
เราสามารถใช้ RMT มาสร้างสัญญาณพัลส์ เพื่อทำให้ LED กระพริบได้เช่นกัน (แต่มีข้อจำกัดเรื่องช่วงความถี่ที่สามารถใช้งานได้) วงจร RMT ภายใน ESP32 รับสัญญาณ Clock ที่มีความถี่ 80 MHz ไปผ่านวงจรหารความถี่ ขนาด 8 บิต (clock_div
) ซึ่งเลือกค่าได้ในช่วง 0..255 แล้วนำไปสร้างสัญญาณพัลส์
ความกว้างของพัลส์จะถูกกำหนดโดยค่าที่เป็นเลขจำนวนเต็มขนาด 15 บิต (0..32,767) เช่น ถ้าเลือกตัวหารความถี่เป็น 250 และเลือกตัวเลข 32,000 สำหรับความกว้างของพัลส์ (Bit Width) เราจะได้ความกว้างเท่ากับ (1/80 MHz) * 250 * 32000 = 100 msec
from micropython import const
from machine import Pin
import utime as time
import esp32 # for RMT module
LED_OFF_ON = [1,0] # for active-low LED
LED_GPIO = const(5) # use GPIO5 for LED output
led = Pin( LED_GPIO, Pin.OUT )
# Use the RMT module to generate pulses
# The freq. of input clock to RMT is always 80MHz.
# 80MHz/250/32000 = 10Hz
rmt = esp32.RMT(id=0, pin=led, clock_div=250)
# Generate pulses repeatedly
rmt.loop(True)
# Send 0 first, followed by 1
# The pulse width for 1 and 0 is 32000 (15-bit value).
rmt.write_pulses( [32000,32000], start=0 )
try:
while True:
time.sleep(-1)
except KeyboardInterrupt:
pass
rmt.loop(False) # stop RMT loop
rmt.deinit() # turn off RMT
led.init(mode=Pin.IN, value=LED_OFF_ON[0]) # turn off LED
LED Blink: Demo 9
ตัวอย่างถัดไปเป็นการสร้างฟังก์ชันแบบ Coroutine ซึ่งมีความคล้ายกับ Python Generator ที่ภายในมีคำสั่ง yield
และเราสามารถนำมาใช้ร่วมกับคำสั่ง async
และ await
ได้เช่นกัน
from micropython import const
from machine import Pin
import utime as time
LED_OFF_ON = [1,0] # for active-low LED
LED_GPIO = const(5) # use GPIO5 for LED output
async def next_state(led, delay):
state = 0
try:
while True:
state = not state
yield state
time.sleep_ms( delay )
except KeyboardInterrupt:
print('Terminated')
async def coro(): # coroutine
return await next_state(led, 100)
led = Pin( LED_GPIO, Pin.OUT )
c = coro() # create a Coroutine object
try:
while True:
led.value( c.send(None) )
except:
c.close() # stop coroutine
led.value( LED_OFF_ON[0] ) # turn off LED
LED Blink: Demo 10
ตัวอย่างนี้สาธิตการใช้คำสั่งจากไลบรารี uasyncio
และลองสร้างฟังก์ชันที่มีชื่อว่า led_toggle()
ให้ทำหน้าที่เป็น Coroutine (โดยการเขียนคำว่า async
ไว้นำหน้าเมื่อประกาศและสร้างฟังก์ชันดังกล่าว) และจะต้องมีคำสั่ง await
อยู่ภายในฟังก์ชัน
ในกรณีนี้ การทำงานของฟังก์ชัน เป็นการรอให้เวลาผ่านไป โดยใช้คำสั่ง asyncio.sleep_ms()
เช่น 100 มิลลิวินาที ก่อนที่จะสลับสถานะเอาต์พุตของ LED
ฟังก์ชันดังกล่าวจะถูกนำไปใช้ในการสร้างทาสก์ (Task) ที่ทำงานต่อเนื่องไปเรื่อย ๆ และถูกจัดการโดย Event Loop ของ asyncio
from micropython import const
from machine import Pin
import utime as time
import uasyncio as asyncio
LED_OFF_ON = [1,0] # for active-low LED
LED_GPIO = const(5) # use GPIO5 for LED output
led = Pin( LED_GPIO, Pin.OUT ) # LED pin object
async def led_toggle(led,duration):
while True:
await asyncio.sleep_ms( duration )
led.value( not led.value() )
try:
loop = asyncio.get_event_loop()
# create a task for LED blink
led_task = led_toggle(led, 100)
loop.run_until_complete( led_task )
except:
led_task.close() # close LED task
led.value( LED_OFF_ON[0] ) # turn off LED
LED Blink: Demo 11
ตัวอย่างโค้ดถัดไปสาธิตการใช้ SoftSPI (ไม่ใช่ Hardware SPI หรือ HSPI ของ ESP32) ในการเลื่อนบิตข้อมูลออกไปที่ขา GPIO ที่ได้เลือกมาเป็นขาสำหรับส่งข้อมูลขาออก MOSI ของบัส SPI
การส่งข้อมูลไบต์ มีจำนวน 2 ไบต์ ได้แก่ 0xff
(High Pulse) และ 0x00
(Low Pulse) ตามลำดับ วนซ้ำไปเรื่อย ๆ และถ้าใช้ความถี่ของ SCK ต่ำ และใช้ขา MOSI ต่อกับวงจร LED ก็จะเห็นแสงไฟ LED กระพริบ
ในตัวอย่างนี้ได้เลือกขา GPIO-5 เป็นขาสำหรับสัญญาณ MOSI ซึ่งนำไปต่อกับวงจร LED และใช้ขา GPIO-0 สำหรับ SCK และ GPIO-4 สำหรับ MISO ซึ่งในความเป็นจริงไม่ได้ใช้งานขาทั้งสองกับวงจรใด ๆ ภายนอก
from machine import Pin, SPI
# use GPIO-5 pin for LED output / MOSI signal
led_pin = Pin(5, Pin.OUT)
# use Soft-SPI
spi = SPI(baudrate=80,
polarity=0, phase=0,
sck=Pin(0), mosi=led_pin, miso=Pin(4))
try:
while True:
spi.write( bytes([0x00,0xff]) )
except KeyboardInterrupt:
pass
spi.deinit() # disable SPI
กล่าวสุรป
โดยสรุป จากตัวอย่างที่ได้นำเสนอไปนั้น เราได้เห็นรูปแบบการเขียนโค้ดภาษาไมโครไพธอนสำหรับ ESP32 โดยใช้เทคนิคที่แตกต่างกัน เพื่อวัตถุประสงค์เดียวกันคือ ทำให้ LED กระพริบได้
Last updated
Was this helpful?