> For the complete documentation index, see [llms.txt](https://think-embedded.gitbook.io/micropython/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://think-embedded.gitbook.io/micropython/micropython-for-esp32/blinking-leds-in-different-ways.md).

# How To Blink LEDs with ESP32

เนื้อหาในส่วนนี้ มีโจทย์ง่าย ๆ เพื่อทดลองเขียนโค้ดที่จะทำให้ **LED** เช่น หนึ่งดวงที่อยู่บนบอร์ด **ESP32 (**&#xE2B;รือจะเลือกใช้ขา **GPIO** ขาใดขาหนึ่งแล้วต่อวงจร **LED** เพิ่มเองก็ได&#xE49;**)** สามารถกระพริบได้ (**LED Blink**)

คำถามคือ แล้วมีวิธีการเขียนโค้ดอย่างไรได้บ้างโดยใช้ภาษาไมโครรไพธอน ลองมาดูตัวอย่างและศึกษาการทำงานของโค้ด อาจมองว่าเป็นการทบทวนการใช้ภาษาไพธอน สำหรับผู้ที่เคยเขียนโค้ดในภาษานี้มาบ้างแล้ว แต่อาจจะยังไม่เคยใช้งานไพธอนแบบสัมผัสฮาร์ดแวร์ (**Python for Microcontrollers / Physical Computing**)

## **LED Blink: Demo 1** <a href="#id-3040" id="id-3040"></a>

จากเนื้อหาที่ผ่านมา  เราได้เรียนรู้การใช้คลาส `Pin` จากไลบรารีหรือโมดูล `machine` เพื่อใช้งานขา **GPIO** ของบอร์ด **ESP32**&#x20;

ตัวอย่างนี้ได้เลือกใช้ขา **GPIO-5** และกำหนดทิศทางให้เป็นเอาต์พุต ฟังก์ชัน `value()` ที่ของอ็อปเจกต์ที่สร้างมาจากคลาส `Pin` มีไว้สำหรับอ่านหรือเขียนค่าลอจิก (0 หรือ 1)

เมื่อกำหนดสถานะของขา **GPIO** ได้แล้ว จะต้องมีการหน่วงเวลา เราก็ใช้คำสั่งจากโมดูล `time` เช่น `time.sleep()` , `time.sleep_ms()` หรือ `time.sleep_us()` ก็ได้ ซึ่งแตกต่างกันตรงที่หน่วยของเวลาสำหรับอาร์กิวเมนต์ เช่น วินาที มิลลิวินาที และไมโครวินาที ตามลำดับ

```python
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**&#x20;

ข้อสังเกต: ในตัวอย่างนี้ **LED** บนบอร์ด **ESP32** ที่ได้นำมาใช้งาน จะสว่าง (**ON**) ก็ต่อเมื่อเขียนสถานะลอจิกเป็น 0 (`False`) และจะดับ (**OFF**) เมื่อเป็น 1 (`True`)

## LED Blink: Demo 2 <a href="#cdda" id="cdda"></a>

จากตัวอย่างแรก ตัวอย่างที่สองนี้ มีการแก้ไขจากเดิมเล็กน้อย โดยเปลี่ยนจากการใช้คำสั่ง `time.sleep_ms()` มาเป็นการอ่านค่าเวลาของระบบ  `time.tick_ms()` ในหน่วยเป็นมิลลิวินาที แล้วคอยตรวจสอบซ้ำดูว่า เวลาผ่านไป 100 มิลลิวินาที แล้วหรือไม่ ถ้าเป็นเช่นนั้น ก็ให้ทำการสลับสถานะของ **LED** หนึ่งครั้ง แล้วอัปเดตเวลาการเปลี่ยนแปลงครั้งล่าสุด

คำสั่ง `time.ticks_diff()` ใช้สำหรับการหาผลต่างของตัวเลขเวลาที่ได้บันทึกไว้ในตัวแปร `t2` และ `t1`

```python
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 <a href="#id-32d3" id="id-32d3"></a>

ตัวอย่างที่สาม เป็นการสาธิตการใช้งาน **Python Generator Function** โดยสร้างฟังก์ชันชื่อ `next_state_generator()` ภายในมีการใช้คำสั่ง `yield` ดังนั้นจึงนำไปใช้เป็น **Generator Iterator** ได้ เมื่อเรียกฟังก์ชันนี้ในแต่ละครั้ง จะได้สถานะลอจิก (0 หรือ 1) ถัดไป เช่น สลับ 0 และ 1 ไปเรื่อย ๆ และมีการหน่วงเวลาด้วยคำสั่ง `time.sleep_ms()` ด้วยเช่นกัน

```python
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 <a href="#id-4519" id="id-4519"></a>

ตัวอย่างถัดไปสาธิตการใช้ไทม์เมอร์ หรือตัวนับเวลา  `Timer` ของโมดูล `machine` ซึ่งมีสองประเภทให้ใช้งานคือ **Hardware Timer** และ **Software Timer** แต่เนื่องจาก **Hardware Timer** สำหรับ **ESP32** มีจำนวนจำกัด&#x20;

โดยทั่วไปเราก็จะใช้ **Software Timer** (ระบุหมายเลขเป็น -1) เหมาะสำหรับกรณีที่มีความถี่หรืออัตราการนับจังหวะไม่สูงมากนัก แต่ถ้าจะใช้กับความถี่สูง ก็ให้ใช้ **Hardware Timer** (ระบุหมายเลขเป็น 0 – 3) สำหรับ **ESP32**

ในตัวอย่างนี้ เราใช้งาน **Software Timer** และกำหนดคาบในการทำงาน (**Period**) เท่ากับ 100 มิลลิวินาที และเลือกโหมดแบบทำซ้ำ (`Timer.PERIODIC`) แต่ถ้าเลือกเป็น `Timer.ONE_SHOT` จะทำเพียงครั้งเดียวแล้วจบการทำงานของไทม์เมอร์

นอกจากนั้นจะต้องกำหนดฟังก์ชันที่เรียกว่า **Callback Function** ซึ่งจะถูกเรียกให้ทำงานเมื่อเวลาผ่านไปครบหนึ่งคาบในแต่ละครั้ง ในตัวอย่างนี้ได้ระบุฟังก์ชันเป็นแบบ `lambda` ซึ่งจะทำหน้าที่สลับสถานะของ **LED**

```python
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 <a href="#id-0bb8" id="id-0bb8"></a>

ตัวอย่างถัดไปสาธิตการสร้างสัญญาณแบบ **PWM** (**Pulse Width Modulation**) ให้มีความถี่ต่ำ (**5 Hz** หรือมีคาบเท่ากับ **200 msec**) สำหรับนำไปใช้กับ **LED** และกำหนดค่า **Duty Cycle** ให้เท่ากับ **50%** โดยระบุค่าเป็น **511** ซึ่งเป็นค่ากลางในช่วง **0..1023** ดังนั้นจึงได้สัญญาณเอาต์พุตแบบมีคาบ ในช่วงที่เป็น 0 (**Low**) และ 1 (**High**) มีความกว้างช่วงละ 100 มิลลิวินาที

```python
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 <a href="#id-3b39" id="id-3b39"></a>

ตัวอย่างถัดไปสาธิตการสร้าง  'เธรด' (**Thread**) ที่ทำงานได้อิสระจาก **Main Thread** โดยใช้คำสั่งของโมดูลหรือไลบรารี `_thread`

เธรดที่ถูกสร้างขึ้นมานั้น ทำหน้าที่คอยสลับสถานะลอจิกของ **LED** และเว้นระยะเวลา 100 มิลลิวินาที แล้วทำขั้นตอนซ้ำ

ในตัวอย่างนี้ ฟังก์ชัน `led_thread_func()` จะถูกกำหนดให้เป็น **Thread entry function** (ฟังก์ชันที่ทำงานโดยเธรดที่ถูกสร้างขึ้นใหม่)  โดยใช้คำสั่ง `_thread.start_new_thread()`

ตัวแปรภายนอก `finished` แบบบูลีน (**Boolean**) จะถูกใช้ในการตรวจสอบเงื่อนไขการออกจากลูปการทำงานของเธรด ซึ่งจะจบการทำงานเมื่อมีการกดคีย์ **Ctrl+C** ใน **REPL Shell**&#x20;

```python
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 <a href="#id-800b" id="id-800b"></a>

ตัวอย่างถัดไปเป็นการใช้เธรด เหมือนตัวอย่างแล้ว แต่สาธิตการสร้างเธรด 2 ชุด (หมายเลข `id` คือ 0 และ 1) โดยให้แต่ละเธรดทำหน้าที่กำหนดสถานะลอจิกตาม `id` ของตัวเอง กล่าวคือ ถ้า `id` เป็น 0 ให้เขียนสถานะเป็น 0 แต่ถ้า `id` เป็น 1 ให้เขียนสถานะเป็น 1 แล้วเว้นระยะเวลา 100 มิลลิวินาที

แต่เนื่องจากว่า เธรดทั้งสองชุดใช้ฟังก์ชันเดียวกันคือ `led_thread_func()` ดังนั้นจึงใช้ `id` เป็นตัวระบุการทำงานของแต่ละเธรด&#x20;

ทั้งสองเธรดนั้น ต้องเข้าถึงอ็อปเจกต์ที่เป็น `Pin` สำหรับ **LED** ร่วมกัน ดังนั้นจึงมีการสร้างและใช้ `lock` (**Mutex Lock**) ในการป้องกันและจัดลำดับการเข้าถึงทรัพยากรที่ใช้ร่วมกัน

```python
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 <a href="#c0e6" id="c0e6"></a>

ตัวอย่างนี้สาธิตการใช้วงจร **RMT** **(**&#xE41;ละใช้ได้เฉพาะกับ **ESP32** เท่านั้&#xE19;**)** เหมาะสำหรับสร้างสัญญาณพัลส์เป็นชุด (**Pulse Train Generation**) เช่น การสร้างสัญญาณควบคุมสำหรับรีโมทแสงอินฟราเรด (**Infrared Remote Control**) หรือใช้ควบคุมหรือกำหนดค่าสีให้กับโมดูล **WS2812B Neopixel RGB LED**

เราสามารถใช้ **RMT** มาสร้างสัญญาณพัลส์ เพื่อทำให้ **LED** กระพริบได้เช่นกัน (แต่มีข้อจำกัดเรื่องช่วงความถี่ที่สามารถใช้งานได้)  วงจร **RMT** ภายใน **ESP32** รับสัญญาณ **Clock** ที่มีความถี่ **80 MHz** ไปผ่านวงจรหารความถี่ ขนาด 8 บิต (`clock_div`) ซึ่งเลือกค่าได้ในช่วง 0..255 แล้วนำไปสร้างสัญญาณพัลส์&#x20;

ความกว้างของพัลส์จะถูกกำหนดโดยค่าที่เป็นเลขจำนวนเต็มขนาด 15 บิต (0..32,767) เช่น ถ้าเลือกตัวหารความถี่เป็น 250 และเลือกตัวเลข 32,000 สำหรับความกว้างของพัลส์ (**Bit Width**) เราจะได้ความกว้างเท่ากับ **(1/80 MHz) \* 250 \* 32000 = 100 msec**

$$
\mbox{Pulse Width} = \frac{32000}{80 MHz / 250} = 0.1,sec
$$

```python
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 <a href="#a864" id="a864"></a>

ตัวอย่างถัดไปเป็นการสร้างฟังก์ชันแบบ [**Coroutine**](https://www.python.org/dev/peps/pep-0492/) ซึ่งมีความคล้ายกับ **Python Generator** ที่ภายในมีคำสั่ง `yield` และเราสามารถนำมาใช้ร่วมกับคำสั่ง `async` และ `await` ได้เช่นกัน

```python
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 <a href="#a864" id="a864"></a>

ตัวอย่างนี้สาธิตการใช้คำสั่งจากไลบรารี `uasyncio`  และลองสร้างฟังก์ชันที่มีชื่อว่า `led_toggle()` ให้ทำหน้าที่เป็น **Coroutine** (โดยการเขียนคำว่า `async` ไว้นำหน้าเมื่อประกาศและสร้างฟังก์ชันดังกล่าว) และจะต้องมีคำสั่ง `await` อยู่ภายในฟังก์ชัน&#x20;

ในกรณีนี้ การทำงานของฟังก์ชัน เป็นการรอให้เวลาผ่านไป โดยใช้คำสั่ง `asyncio.sleep_ms()` เช่น 100 มิลลิวินาที ก่อนที่จะสลับสถานะเอาต์พุตของ **LED**&#x20;

ฟังก์ชันดังกล่าวจะถูกนำไปใช้ในการสร้างทาสก์ (**Task**) ที่ทำงานต่อเนื่องไปเรื่อย ๆ และถูกจัดการโดย **Event Loop** ของ `asyncio`

```python
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** ซึ่งในความเป็นจริงไม่ได้ใช้งานขาทั้งสองกับวงจรใด ๆ ภายนอก

```python
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** กระพริบได้&#x20;

{% hint style="info" %}
**เผยแพร่ภายใต้ลิขสิทธิ์**\
**Attribution-ShareAlike 4.0 International (**[**CC BY-SA 4.0**](https://creativecommons.org/licenses/by-sa/4.0/)**)**
{% endhint %}
