ESP32 Code Examples

ตัวอย่างโค้ดไมโครไพธอนเพื่อนำไปทดลองใช้งานกับบอร์ด ESP32

คำแนะนำ

  1. ผู้อ่านควรมีความรู้ภาษา Python เบื้องต้น พื้นฐานอิเล็กทรอนิกส์ และการเขียนโค้ดไมโครคอนโทรลเลอร์ในระดับพื้นฐานมาบ้าง เช่น Arduino

  2. บอร์ด ESP32 ที่เราสามารถเลือกมาใช้งานนั้น มีหลายรูปแบบ ความเหมาะสมของบอร์ดแต่ละชนิด ก็อาจแตกต่างกันไปขึ้นอยู่กับวัตถุประสงค์สำหรับการใช้งาน ดังนั้นขอแนะนำให้ใช้บอร์ดที่เสียบขา Pin Headers ลงบนเบรดบอร์ดได้ สำหรับการต่อวงจรใช้งานร่วมกับอุปกรณ์หรือวงจรอิเล็กทรอนิกส์พื้นฐานแบบต่าง ๆ

  3. ในกรณีที่ต้องการใช้ไมโครไพธอนสำหรับ ESP32 แนะนำให้ใช้บอร์ด ESP32 ที่มีชิป PSRAM หรือเรียกว่า SPIRAM (เช่น 4MB) เพื่อเพิ่มความจุของหน่วยความจำ นอกเหนือจาก RAM ที่มีอยู่ภายในชิป ESP32 SoC

  4. ประเด็นหนึ่งที่สำคัญเมื่อได้เลือกใช้บอร์ดไมโครคอนโทรลเลอร์ คือ การทราบตำแหน่งของขาต่าง ๆ ของบอร์ด หรือ Pin Layout (PinOut Map) แตกต่างกันไปขึ้นอยู่กับบอร์ดที่ใช้ ดังนั้นควรตรวจสอบให้ถูกต้องเมื่อนำไปต่อวงจร จะไม่ได้เกิดความผิดพลาดในการใช้งานขา GPIO หรือขาแรงดันไฟเลี้ยง (ขา 3.3V และ GND) จากบอร์ด ESP32

แหล่งข้อมูลเกี่ยวกับบอร์ด LilyGO TTGO T8 (V1.1 / V1.3 / V1.7) ที่ได้นำมาใช้งาน

ตัวอย่างแรกเป็นการทดลองใช้ขา GPIO ของ ESP32 เป็นเอาต์พุตหรืออินพุตแบบดิจิทัล ถ้าเป็นเอาต์พุต มีการกำหนดและเปลี่ยนสถานะค่า 0 หรือ 1 สำหรับเอาต์พุต และสลับค่าโดยเว้นระยะเวลาตามที่กำหนดไว้ ถ้านำไปขาเอาต์พุตนี้ไปต่อกับวงจร LED ก็จะทำให้มองเห็นการเปลี่ยนแปลงของสถานะลอจิกได้

อีกกรณีหนึ่งคือ การใช้ขา GPIO เป็นอินพุต ใช้ในการอ่านค่าจากวงจรปุ่มกด (Push Button) เช่น วงจรปุ่มกดที่ทำงานแบบ Active-Low คือ ถ้าไม่กดปุ่ม จะได้ค่าเป็น High (1) แต่ถ้ากดปุ่มจะได้ค่าเป็น Low (0)

คลาส Pin จากไลบรารี machine เกี่ยวข้องกับการใช้งาน GPIO (Digital I/O pins) ของฮาร์ดแวร์ เช่น ใช้งานเป็นขาดิจิทัล-อินพุต หรือเอาต์พุต การเปิดใช้งานขาอินพุตร่วมกับอินเทอร์รัพท์ (Interrupt) และการเปิดใช้งาน Internal Pull-Up หรือ Pull-Down สำหรับขาที่จะถูกใช้เป็นอินพุต เป็นต้น

ในตัวอย่างนี้ได้เลือกใช้ขา GPIO-21 สำหรับ LED และ GPIO-22 สำหรับปุ่มกด (ทำงานแบบ Active-Low และเปิดใช้งาน Pull-Up ภายในวงจรที่ขาดังกล่าว)

การทำงานของโปรแกรมจะทำให้ LED กระพริบ และมีการตรวจสอบสถานะอินพุตของปุ่มกด ถ้าพบว่า มีการกดปุ่ม (ได้ค่าเป็น 0) จะหยุดการทำงานของลูป while และจบการทำงานของโปรแกรม

from micropython import const
from machine import Pin
import utime as time

LED_GPIO = const(21) # use GPIO-21 for LED output
BTN_GPIO = const(22) # use GPIO-22 for push-button input

# create an object for the LED pin
led = Pin( LED_GPIO, mode=Pin.OUT ) 
# create an object for button pin (with pull-up enabled)
btn = Pin( BTN_GPIO, mode=Pin.IN, pull=Pin.PULL_UP )

state = False # used to keep the output state
try:
    while btn.value() != 0:  # check input button 
        state = not state    # toggle state
        led.value( state )   # write value to output pin
        time.sleep_ms(100)   # sleep for 0.1 seconds
except KeyboardInterrupt:
    pass
finally:
    led.value(0) # turn off the LED
print('Done')

จากตัวอย่างที่แล้ว เราได้ใช้วิธีวนซ้ำ (Polling) เพื่อคอยอ่านค่าอินพุตจากวงจรปุ่มกด และนำค่าอินพุตที่อ่านได้ในแต่ละครั้ง มากำหนดเงื่อนไขในการหยุดหรือออกจากลูป while ในตัวอย่างนี้ เราจะใช้วิธีที่เรียกว่า "อินเทอร์รัพท์" (Interrupt) สำหรับอินพุต-ปุ่มกด

เมื่อมีการกดปุ่ม จะเกิดการเปลี่ยนระดับลอจิก จาก High เป็น Low หรือที่เรียกว่า “ขอบขาลง” (Falling Edge) และเมื่อปล่อย จะเกิด “ขอบขาขึ้น” (Rising Edge) และอาจเกิดได้มากกว่าหนึ่งครั้ง ถ้ามีการกระเด้งของสวิตซ์ปุ่มกด (Switch Bouncing)

เราสามารถกำหนดให้ไมโครไพธอนเรียกฟังก์ชันที่เราสร้างขึ้นมา (Callback Function หรือ Interrupt Handler) เช่น ฟังก์ชัน btn_handler() ในโค้ดตัวอย่าง ให้ทำงานโดยอัตโนมัติเมื่อเกิดเหตุการณ์ดังกล่าว เช่น ขอบขาลง ในตัวอย่างนี้จะทำให้ตัวแปรภายนอก stop เปลี่ยนจาก False เป็น True ซึ่งใช้ในการตรวจสอบเงื่อนไขการออกจากลูป while

from micropython import const
from machine import Pin
import utime as time

LED_GPIO = const(21) # use GPIO-21 for LED output
BTN_GPIO = const(22) # use GPIO-22 for push-button input

# create objects from machine.Pin
led = Pin( LED_GPIO, mode=Pin.OUT )
btn = Pin( BTN_GPIO, mode=Pin.IN, pull=Pin.PULL_UP )

def btn_handler(pin): # callback function
    global stop
    args = (pin, pin.value())
    print( 'callback: {} value={}'.format(*args) )
    stop = True

# enable interrupt handler for Button pin
btn.irq( handler=btn_handler, trigger=Pin.IRQ_FALLING )

stop  = False # loop condition variable
state = False # LED state

try:
    while not stop:
        state = not state    # toggle state
        led.value( state )   # write value to output pin
        time.sleep_ms( 100 ) # sleep for 100 msec
except KeyboardInterrupt:
    pass
finally:
    btn.irq( handler=None ) # disable interrupt for button pin
    led.value(0) # turn off the LED
print('Done')

โค้ดตัวอย่างที่ 3: LED Toggle on Button Press

โค้ดตัวอย่างถัดไปสาธิตการทำให้ LED เปลี่ยนสถานะเอาต์พุตหนึ่งครั้งเมื่อมีการกดปุ่มการทำงานของโค้ดจะอาศัยการเปิดใช้งานอินเทอร์รัพท์ที่ขาอินพุต (ตรวจสอบเหตุการณ์ขอบขาลง) และมีการสร้างฟังก์ชัน btn_handler() ให้ทำหน้าที่เป็น Callback Function

เมื่อเกิดเหตุการณ์ดังกล่าว ฟังก์ชัน btn_handler() จะสลับสถานะของ LED หนึ่งครั้ง นอกจากนั้น ถ้ามีการกดปุ่มค้างไว้ (Long Press) ให้จบการทำงานของโปรแกรม

from micropython import const
from machine import Pin
import utime as time

BTN_GPIO = const(22) # use GPIO-22 for push-button input
LED_GPIO = const(21) # use GPIO-21 for LED output

# create objects from machine.Pin
led = Pin( LED_GPIO, mode=Pin.OUT )
btn = Pin( BTN_GPIO, mode=Pin.IN, pull=Pin.PULL_UP )

# global variable
btn_was_pressed = False

def btn_handler(pin): # callback function
    global btn_was_pressed
    state = led.value()     # get current state
    led.value( not state )  # update LED output
    btn_was_pressed = True
    btn.irq( handler=None ) # disable IRQ

# enable interrupt handler for Button pin
btn.irq( handler=btn_handler, trigger=Pin.IRQ_FALLING )

stop = False
try:
    while not stop:
        if btn_was_pressed:
            cnt = 0
            while btn.value() == 0: # wait for button release
                time.sleep_ms(20)
                cnt = cnt + 1
                if cnt > 100: # long pressed 
                    stop = True
                    break
            btn_was_pressed = False
            btn.irq( handler=btn_handler ) # re-enable IRQ
            time.sleep_ms(100)
except KeyboardInterrupt:
    pass
finally:
    btn.irq( handler=None ) # disable IRQ for button 
    led.value(0) # turn off LED
print('Done')

ตัวอย่างถัดไปเป็นการใช้ Timer จากคลาส Timer ในไลบรารี machine เพื่อทำคำสั่งของฟังก์ชัน led_toggle() ที่ทำหน้าที่เป็น Callback ตามระยะเวลาที่กำหนด (Periodic Mode) เช่น ทุก ๆ 500 มิลลิวินาที โดยอัตโนมัติ และทำให้เกิดการสลับสถานะลอจิกสำหรับ LED

ในตัวอย่างนี้มีการระบุหมายเลขของ Timer เป็น -1 ซึ่งหมายถึง การใช้งานไทม์เมอร์แบบซอฟต์แวร์ (FreeRTOS-based) และไม่ได้ใช้ Hardware Timer (หมายเลข 0,1,2,3) ของ ESP32

from micropython import const
from machine import Pin, Timer
import utime as time

BTN_GPIO = const(22) # use GPIO-22 for push-button input
LED_GPIO = const(21) # use GPIO-21 for LED output

led = Pin( LED_GPIO, mode=Pin.OUT )
btn = Pin( BTN_GPIO, mode=Pin.IN, pull=Pin.PULL_UP )

def led_toggle(timer): # callback function for timer
    global led
    # show elapsed system time in msec
    print( 'timer ticks: {} msec'.format(time.ticks_ms()) )
    led.value( not led.value() ) # toggle LED 

timer = Timer( -1 ) # create a Timer object
# use the timer in periodic mode (period = 500 msec)
timer.init( period=500, 
            mode=Timer.PERIODIC,
            callback=led_toggle )

# Press the button to stop and exit the loop
try:
    while True:
        # check whether button is pressed
        if btn.value() == 0:
           break
        time.sleep_ms(10)
except KeyboardInterrupt:
    pass
    
led.value(0)   # turn off LED
timer.deinit() # stop timer
print('Done')

ตัวอย่างถัดไป สาธิตการกระพริบของ LED ซึ่งเกิดจากการทำงานของ ‘เธรด’ (Thread) โดยใช้ไลบรารี _thread และกำหนดใช้ฟังก์ชัน led_toggle() สำหรับการทำงานที่เกี่ยวข้อง การกระพริบจะเกิดซ้ำไปเรื่อย ๆ จนกว่า ตัวแปรภายนอก stop จะมีค่าเป็น True เช่น เกิดขึ้นเมื่อมีการกดปุ่ม

นอกจากนั้น ยังมีการใช้วงจร Timer ของ ESP32 จากคลาส machine.Timer ในโค้ดตัวอย่างนี้ timer จะถูกตั้งเวลาให้ทำงานแบบ One-Shot เมื่อเวลาผ่านไปตามที่กำหนด เช่น มีคาบเท่ากับ 5000 มิลลิวินาที เมื่อถึงเวลา จะเรียกฟังก์ชัน stop_led_blink() ที่ทำหน้าที่เป็น Callback Function และทำคำสั่งเพียงหนึ่งครั้ง คือ การเปลี่ยนค่าของตัวแปรภายนอก stop จาก False ให้เป็น True ซึ่งจะถูกใช้ในการตรวจสอบเพื่อการจบการทำงานของโปรแกรม

from micropython import const
from machine import Pin, Timer
import utime as time
import _thread # for multi-threading

LED_GPIO = const(21) # use GPIO-21 for LED output
BTN_GPIO = const(22) # use GPIO-22 for push-button input

btn = Pin( BTN_GPIO, mode=Pin.IN, pull=Pin.PULL_UP )
led = Pin( LED_GPIO, mode=Pin.OUT )

stop = False # used as a global variable

def led_toggle(led): # thread function
    global stop
    state = 0
    while not stop:
        state = not state
        print( 'LED state: {}'.format( int(state) ))
        led.value(state)   # update LED output
        time.sleep_ms(100) # sleep for 0.1 seconds

# create and start a new thread
_thread.start_new_thread( led_toggle, (led,) )

def stop_led_blink(timer): # callback function for timer
    global stop
    stop = True

timer = Timer( 0 ) # create a Timer object
# start the timer in one-shot mode
timer.init( period=5000,
            mode=Timer.ONE_SHOT,
            callback=stop_led_blink )
# Press the button to stop and exit the loop
while not stop:
    # check whether button is pressed
    if btn.value() == 0: 
        stop = True
        break
    time.sleep_ms(10)

led.value(0)   # turn off LED
timer.deinit() # stop timer
print('Done')

โค้ดตัวอย่างที่ 6: PWM LED Dimming

ถัดไปเป็นตัวอย่างการสร้างสัญญาณประเภท PWM (Pulse Width Modulation) แล้วนำไปใช้เป็นสัญญาณเอาต์พุตสำหรับวงจร LED ถ้าปรับค่า Duty Cycle ของสัญญาณ PWM จาก 0 ไปจนถึงค่าสูงสุด 1023 สำหรับ ESP32 ซึ่งมีความละเอียดเท่ากับ 10 บิต (ช่วงความกว้างที่เป็น High ในแต่ละคาบ จะอยู่ระหว่าง 0% ถึง 100% ตามลำดับ) เราจะเห็น LED สว่างขึ้นตามลำดับ ความถี่ของ PWM เมื่อใช้ ESP32 สามารถเลือกได้ในช่วง 1 Hz ถึง 40 MHz แต่สำหรับตัวอย่างนี้ เลือกความถี่เท่ากับ 500 Hz

การสร้างสัญญาณ PWM สำหรับขา Pin จะใช้คลาส PWM ของไลบรารี machine ให้ลองสังเกตการใช้คำสั่ง เช่น duty() และ freq() ในการเขียนหรืออ่านค่าสำหรับค่า Duty Cycle และความถี่ของสัญญาณ

from micropython import const
from machine import Pin, PWM
import utime as time
import math

LED_GPIO = const(21) # use GPIO-21 for LED output
BTN_GPIO = const(22) # use GPIO-22 for push-button input

btn = Pin( BTN_GPIO, mode=Pin.IN, pull=Pin.PULL_UP )
led = Pin( LED_GPIO, mode=Pin.OUT )

# create an object from PWM for the LED pin
pwm = PWM( led, freq=500, duty=0 )
time.sleep_us(10)
print ('PWM freq: {} Hz'.format( pwm.freq() ))

N = const(32)
# create a list of precomputed duty-cyle values
values = [int(1023*math.sin(math.pi*i/N)) for i in range(N)]

try:
    i = 0
    while True:
        if btn.value() == 0: # is button pressed ?
            break
        value = values[i]
        pwm.duty( value )  # set the duty cycle value
        percent = 100*( pwm.duty()/1024.0 )
        print( 'Duty cycle: {:4d} ({:4.1f}%)'.format(value, percent) )
        i = (i+1) % N
        time.sleep_ms( 50 )
except KeyboardInterrupt:
    pass
pwm.duty(0)  # set duty cycle to 0
pwm.deinit() # important: turn off the PWM pin
print('Done')

ในตัวอย่างนี้ มีการคำนวณค่าคงตัวโดยใช้ฟังก์ชัน math.sin() และเก็บไว้ในอาร์เรย์ที่มีขนาดเท่ากับ N (N=32) แล้วนำไปใช้เพื่อกำหนดค่า Duty Cycle ทีละค่าตามลำดับในอาร์เรย์

เมื่อทดสอบการทำงานของโค้ดตัวอย่างกับอุปกรณ์จริง จะเห็นการปรับความสว่างของ LED ด้วยสัญญาณแบบ PWM

โค้ดตัวอย่างที่ 7: Neopixel RGB LED

เฟิร์มแวร์ของไมโครไพธอน มีไลบรารี neopixel เพื่อใช้ในการกำหนดสีให้แก่โมดูล RGB LED (WS2812B) หรือที่มักเรียกว่า Neopixel ซึ่งใช้สายสัญญาณดิจิทัลเป็นเอาต์พุต เพียงเส้นเดียว (ในตัวอย่างนี้ ได้เลือกใช้ขา GPIO-23)

ตัวอย่างโค้ดนี้ สาธิตการกำหนดสีให้ RGB LED จำนวน 1 ดวง โดยเปลี่ยนสี (ระบุค่าสีแบบ 24 บิต ประกอบด้วย 3 ไบต์แบบ 3-Tuple) จากแดง (Red), เขียว (Green) และน้ำเงิน (Blue) ไปตามลำดับ

ข้อสังเกต: เนื่องจากใช้โมดูลที่มี RGB LED เพียงดวงเดียว ดังนั้นเลือกจึงใช้ 3.3V จากบอร์ด ESP32 เป็นแหล่งจ่ายแรงดันคงที่ได้ แต่ถ้าใช้ RGB LED จำนวนหลายดวงและอาจใช้กระแสไฟมาก ควรใช้แหล่งจ่ายแรงดันคงที่จากภายนอก

from micropython import const
from machine import Pin
import utime as time
from neopixel import NeoPixel

GPIO_NUM = const(23)  # use GPIO-23
np_pin = Pin( GPIO_NUM, Pin.OUT ) # for Neopixel output

BTN_GPIO = const(22) # use GPIO-22 for push-button input
btn = Pin( BTN_GPIO, mode=Pin.IN, pull=Pin.PULL_UP )

stop = False
def btn_callback(pin):
    global stop
    stop = True

btn.irq( trigger=Pin.IRQ_FALLING,
         handler=btn_callback )

NUM_PIXELS = 1 # only one RGB LED in the strip
# create a Neopixel object
np = NeoPixel( np_pin, NUM_PIXELS )

# convert an integer to a 3-tuple object for RGB value
int2rgb = lambda x: ((x >> 16)&0xff,(x>>8)&0xff,(x)&0xff)

# a list of predefined RGB color values
colors  = [ (255,0,0), (0,255,0), (0,0,255) ]
colors += [ int2rgb(0xffff00),
            int2rgb(0xff00ff),
            int2rgb(0x00ffff) ]
colors += 3*[ (0,0,0) ] # RGB off, 3 times
print( colors)
try:
    while not stop:
        for color in colors:
            if stop:
                break
            # set color value to the first RGB LED
            np[0] = color
            # apply the color value
            np.write() 
            time.sleep_ms(1000)
except KeyboardInterrupt:
    pass
np[0] = (0,0,0) # turn off color (black)
np.write()
print('Done')

โค้ดตัวอย่างที่ 8: DHT22 Sensor Reading

เฟิร์มแวร์ของไมโครไพธอน มีไลบรารี dht สำหรับการอ่านค่าจากโมดูลเซ็นเซอร์ DHT22 มาให้แล้ว ดังนั้นเราสามารถลองใช้คำสั่ง เพื่ออ่านค่าอุณหภูมิ (Temperature) และความชื้นสัมพัทธ์ (Relative Humidity) ได้ไม่ยาก

การต่อวงจรเพื่อใช้งานโมดูล DHT22 ก็ใช้แรงดันไฟเลี้ยง 3.3V และ GND จากบอร์ด ESP32 และสายสัญญาณ Digital I/O เพียงเส้นเดียว ในตัวอย่างนี้ได้เลือกใช้ขา GPIO-19 (และจะต้องมีตัวต้านทานแบบ Pull-Up เช่น 4.7k หรือ 10k ต่ออยู่ด้วย)

การอ่านค่าจากโมดูล DHT22 จะทำทุก ๆ 2000 มิลลิวินาที โดยใช้ Software Timer ช่วยจัดการเพื่อให้ทำงานในลักษณะที่มีคาบ (Periodic Task)

from micropython import const
from machine import Pin, Timer
import utime as time
import dht

GPIO_NUM = const(19) # use GPIO-19
dht22 = dht.DHT22( Pin(GPIO_NUM) ) # create a DHT22 object

text = u'DHT22 reading: {:.1f} °C, {:.1f} %RH'

def read_dht22(timer):
    try:
        # start the measurement 
        dht22.measure()
        # read and show values from DHT22
        t,h = dht22.temperature(), dht22.humidity()
        print( text.format(t,h) )
    except OSError as ex:
        print('Sensor reading error!', ex)

timer = Timer( -1 ) # create a Timer object
# use the timer in periodic mode (period = 500 msec)
timer.init( period=2000, 
            mode=Timer.PERIODIC,
            callback=read_dht22 )
try:
    while True:
        pass
except KeyboardInterrupt:
    pass
timer.deinit()
print('Done')

โค้ดตัวอย่างที่ 9: ESP32 ADC Reading

การใช้คำสั่งของไมโครไพธอน เพื่ออ่านค่าจากวงจร ADC (Analog-to-Digital Converter) ของ ESP32 จะใช้ได้กับขาหมายเลข GPIO 32 ถึง 39 สำหรับ ADC1 ค่าที่ได้จะอยู่ในช่วง 0 ถึง 4095 (ความละเอียดสูงสุด 12 บิต) และแรงดันจะต้องอยู่ในช่วง 0V ถึง 1.1V และถ้าสูงกว่านั้น จะอ่านได้ค่า 4095

แต่ถ้าจะให้รับแรงดันอินพุตได้สูงกว่านั้น (แต่ต้องไม่เกิน 3.6V !!!) ต้องกำหนดค่าสำหรับ Attenuation (การลดทอนขนาดสัญญาณ) ให้เหมาะสม มีค่าให้เลือกได้ดังนี้

  • ADC.ATTN_0DB (ไม่มีการลดทอนสัญญาณ หรือ 0dB และอ่านแรงดันในช่วง 0V ถึง 1.1V โดยประมาณ)

  • ADC.ATTN_2_5DB (การลดทอนสัญญาณ -2.5dB)

  • ADC.ATTN_6DB (การลดทอนสัญญาณ -6dB)

  • ADC.ATTN_11DB (การลดทอนสัญญาณสูงที่สุด -11dB และอ่านแรงดันในช่วง 0V ถึง 3.3V โดยประมาณ)

นอกจากนั้นยังสามารถเลือกได้ว่า ต้องการจะอ่านข้อมูลที่มีความละเอียดกี่บิต ตั้งแต่ 9 บิต จนถึง 12 บิต

from micropython import const
from machine import Pin, ADC
import utime as time

ADC_GPIO = const(34) # use GPIO-34 for ADC input channel
adc = ADC(Pin(ADC_GPIO),unit=1) # create an ADC object
adc.atten(ADC.ATTN_11DB)   # set 11dB attenuation for input
adc.width(ADC.WIDTH_12BIT) # set 12 bit return values

NUM_SAMPLES = const(4)
for i in range(20): # repeat 20 times
    samples = []
    for j in range(NUM_SAMPLES):
        samples.append( adc.read() )
    # calcuate the average value from N samples
    value_avg = round(sum(samples)/NUM_SAMPLES)
    print( 'ADC: {:4d}'.format(value_avg))
    time.sleep_ms(200)
print('Done')

ข้อสังเกต: จากการทดลองอ่านค่าจากวงจรแบ่งแรงดัน และตั้งค่าแรงดันให้อยู่ในช่วง 0V ถึง 3.3 V ค่าที่อ่านได้จาก ADC ของ ESP32 อาจมีความแม่นยำไม่สูงนัก (ค่าไม่ค่อยนิ่ง)

โค้ดตัวอย่างที่ 10: ESP32 DAC Output

ESP32 มีวงจร DAC (Digital-to-Analog Converter) อยู่ภายใน แบ่งเป็น 2 ช่อง ซึ่งตรงกับขา GPIO-26 (DAC Channel 1) และ GPIO-25 (DAC Channel 2) ตามลำดับ สามารถกำหนดค่าตัวเลขขนาด 8 บิต สำหรับเอาต์พุตได้ ในช่วง 0 ถึง 255 ซึ่งหมายถึง 0V ถึง 3.3V

เราสามารถใช้คลาส machine.DAC เพื่อสร้างสัญญาณเอาต์พุตแบบแอนะล็อกโดยใช้ DAC ของ ESP32 ตามตัวอย่างดังนี้ ซึ่งได้เลือกใช้ DAC ช่อง 1 ที่ขา GPIO-26 เป็นเอาต์พุต แล้วใช้สายไฟต่อจากขานี้ไปยังขา GPIO-34 ซึ่งจะถูกใช้เป็นขาสำหรับ ADC เพื่ออ่านค่าอินพุตจากสัญญาณแอนะล็อก (เลือกขนาดข้อมูลที่อ่านได้เป็นแบบ 10 บิต)

from micropython import const
from machine import Pin, ADC, DAC
import utime as time

ADC_GPIO = const(34) # use GPIO-34 for ADC input channel
adc = ADC(Pin(ADC_GPIO))   # create an ADC object
adc.atten(ADC.ATTN_11DB)   # set 11dB attenuation for input
adc.width(ADC.WIDTH_10BIT) # set 10 bit return values

#  GPIO25 (Channel 2) and GPIO26 (Channel 1)
DAC_NUM = const(26)        # GPIO26 
dac = DAC( Pin(DAC_NUM) )  # 8-bit DAC

NUM_SAMPLES = const(4)
text = 'DAC:{:4d} -> ADC:{:5d}'
for value in range(255):
    dac.write( value )
    time.sleep_us(100)
    samples = []
    for j in range(NUM_SAMPLES):
        samples.append( adc.read() )
    # calcuate the average value from N samples
    value_avg = round(sum(samples)/NUM_SAMPLES)
    print( text.format(value, value_avg) )
print('Done')

โค้ดตัวอย่างที่ 11: Pulse Width Measurement

ตัวอย่างถัดไปสาธิตการวัดความกว้างของพัลส์ช่วงที่เป็น High โดยใช้คำสั่ง machine.time_pulse_us() และสร้างสัญญาณ PWM เพื่อใช้ในการทดสอบ

ในการทดสอบการทำงานของโค้ด ได้เลือกใช้ขา GPIO-19 เป็นขาเอาต์พุตสำหรับสัญญาณ PWM โดยเลือกความถี่เท่ากับ 1kHz และปรับค่า Duty Cycle ได้ในช่วง 10% ถึง 90% และสัญญาณเอาต์พุตจะถูกนำไปป้อนกลับให้ขา GPIO-23 ที่ใช้เป็นขาอินพุต-ดิจิทัล

from micropython import const
from machine import Pin, PWM, time_pulse_us
import utime as time

PWM_OUT_GPIO  = const(19)
PULSE_IN_GPIO = const(23)

freq = 1000 # in Hz
pwm_pin = Pin( PWM_OUT_GPIO, mode=Pin.OUT )
# create an object from PWM for output pin
pwm = PWM( pwm_pin, freq=freq, duty=0 )
time.sleep_us(10)

pwm_freq = pwm.freq()
pwm_period = 10**6//pwm_freq  # in usec
print ('PWM freq:   {:6d} Hz'.format( pwm_freq ))
print ('PWM period: {:6d} usec'.format( pwm_period ))

pulse_in = Pin( PULSE_IN_GPIO )
N = 10 # total steps for the PWM duty cycle
for i in range(1,N):
    p = pwm_period*i//N # pulse with in usec
    pwm.duty( 1023*p//pwm_period ) # set duty cycle
    time.sleep_us( 2*pwm_period )
    # wait until the input signal goes low
    while pulse_in.value()==1: pass
    # measure the next high pulse width
    t = time_pulse_us( pulse_in, 1, pwm_period )
    # show the set value against the measured value
    print( 'Pulse width: {:4d},{:4d} usec'.format(p,t) )
pwm.deinit()
print('Done')

ตัวอย่างข้อความเอาต์พุตเปรียบเทียบค่าที่ต้องการกับค่าที่วัดได้ สำหรับการตั้งค่า Duty Cycle ในแต่ละกรณี ตั้งแต่ 10% ถึง 90% หรือความกว้างของพัลส์ตั้งแต่ 100 usec ถึง 900 usec สำหรับสัญญาณ PWM ที่มีคาบเท่ากับ 1000 usec

โค้ดตัวอย่างที่ 12: Getting System Info

โค้ดตัวอย่างถัดไป สาธิตการอ่านข้อมูลเกี่ยวกับระบบ เช่น ข้อมูลเกี่ยวกับ Machine ID ซึ่งในกรณีของ ESP32 ก็คือ MAC Address ที่มี 6 ไบต์ โดยการใช้คำสั่ง machine.unique_id() หรือคำสั่ง network.WLAN().config('mac') ซึ่งจะได้ข้อมูลเป็น bytes และใช้คำสั่ง ubinascii.hexlify() แปลงข้อมูลดังกล่าวให้เป็นข้อความเลขฐานสิบหก

import machine
import ubinascii
import network

# get the machine ID (MAC address of ESP32)
machine_id = machine.unique_id()
hex_id = ubinascii.hexlify( machine_id,':' )
print( hex_id.decode('ascii') )

# get the MAC address of ESP32
mac_addr = network.WLAN().config('mac')
hex_addr = ubinascii.hexlify( mac_addr,':' )
print( hex_addr.decode('ascii') )

ถ้าต้องการตรวจสอบดู ขนาดของหน่วยความจำ Flash และขนาดของหน่วยความจำ RAM ที่ยังว่างอยู่ หรือถูกใช้ไปในขณะนั้น ก็สามารถทำคำสั่งตามโค้ดตัวอย่างนี้ได้

import esp
import gc

gc.collect() # run garbage collector

# get flash size
fs = esp.flash_size()//1024 # in KB
# get free memory
free_mem_kb = gc.mem_free()//1024 # in KB
# get allocated memory
alloc_mem_kb = gc.mem_alloc()//1024 # in KB

print( '      Flash size: {:4d} MB'.format(fs) )
print( '     Free memory: {:4d} KB'.format(free_mem_kb) )
print( 'Allocated memory: {:4d} KB'.format(alloc_mem_kb) )

ถ้าต้องการทราบข้อมูลเกี่ยวกับระบบ เช่น ชื่อของระบบ เวอร์ชันของเฟิร์มแวร์ของไมโครไพธอนที่กำลังใช้งานอยู่นั้น ก็สามารถทำคำสั่ง uos.uname() ตามโค้ดตัวอย่างต่อไปนี้ได้

import machine
import uos 

names = ['sysname','nodename',
         'release','version','machine']

sys_info = dict(zip(names,uos.uname()))
for n,v in sys_info.items():
    print( "{:>10s}: '{}'".format(n,v) )

โค้ดตัวอย่างที่ 13: SPI Loopback Test

บัส SPI เป็นวิธีการสื่อสารข้อมูลที่มักพบเห็นได้บ่อย โดยทั่วไปจะใช้สัญญาณ 3 เส้น คือ SCK (Serial Clock), MOSI (Master-Out-Slave-In) และ MISO (Master-In-Slave-Out)

โดยทั่วไปแล้ว เราจะใช้ไมโครคอนโทรลเลอร์ทำหน้าที่เป็น ‘มาสเตอร์’ (Master) สำหรับบัส SPI เพื่อเชื่อมต่อกับอุปกรณ์อื่นในระบบบัส ซึ่งทำหน้าที่เป็น ‘สเลฟ’ (Slave)

แต่ถ้ามีอุปกรณ์ ‘สเลฟ’ มากกว่าหนึ่งตัว จะต้องมีสัญญาณควบคุมสำหรับอุปกรณ์แต่ละตัว เรียกว่า Slave Select (SS) หรือ Chip Select (CS) ซึ่งทำงานแบบ Active-Low

ในการทำงานของบัส SPI การรับส่งข้อมูลจะเกิดขึ้นพร้อมกัน เมื่อส่งข้อมูลทีละบิตออกไปทาง MOSI ก็จะได้ข้อมูลทีละบิตจาก MISO เข้ามา

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

การเลือกใช้ขาสำหรับ Hardware SPI ของ ESP32 จะใช้ขา GPIO-14, GPIO-13 และ GPIO-12 สำหรับ SCK, MOSI และ MISO ตามลำดับ ซึ่งตรงกับขาสำหรับบัส HSPI ของ ESP32

โหมดการทำงานของ SPI มี 4 โหมด ในกรณีนี้ได้เลือกใช้โหมด (polarity=0, phase=0) ความถี่ของสัญญาณ SCK กำหนดให้เป็น 10 MHz (baudrate) ขนาดเฟรมข้อมูลเป็น 8 บิต ส่งข้อมูลบิตแบบ MSB First

โค้ดตัวอย่าง สาธิตการส่งข้อมูลคราวละ 5 ไบต์ โดยที่ 4 ไบต์แรกคือ 0xaa, 0xbb, 0xcc, 0xdd และไบต์สุดท้ายจะเป็นค่าของตัวนับขนาด 8 บิต

แต่ถ้าไม่มีการเชื่อมต่อระหว่างขา MISO และ MOSI เมื่อโค้ดทำงาน จะอ่านได้ค่า 0xff, 0xff, 0xff, 0xff, 0xff, …

from micropython import const
from machine import Pin, SPI
import utime

SCK  = const(14)
MOSI = const(13)
MISO = const(12)
hspi = SPI(1, baudrate=10000000, 
    polarity=0, phase=0, bits=8, firstbit=SPI.MSB,
    sck=Pin(SCK), mosi=Pin(MOSI), miso=Pin(MISO))
    
for i in range(256):
    rbuf = bytearray(5*[0]) # read buffer filled with zero
    wbuf = bytearray( [0xaa,0xbb,0xcc,0xdd, i] )
    hspi.write_readinto( wbuf, rbuf )
    for b in rbuf:
        print( hex(b), end=' ' )
    print('')
    
hspi.deinit() # disable the SPI bus

โค้ดตัวอย่างที่ 14: UART Data Transmission

ESP32 มีวงจร UART (Universal Asynchronous Receiver/Transmitter) สำหรับสื่อสารข้อมูลบิตแบบอนุกรม หรือที่เรียกว่า Hardware UART มีจำนวน 3 ชุด คือ UART0, UART1 และ UART2

แต่ UART0 จะถูกใช้สำหรับ MicroPython REPL และเชื่อมต่อกับไอซี USB-to-Serial ไว้แล้ว (เช่น CP2102 / CP2104 เป็นต้น) ดังนั้นถ้าเราต้องการจะส่งข้อมูลด้วย UART ก็สามารถเลือกใช้ UART1 หรือ UART2 แทนได้ เช่น เลือกใช้ขา GPIO สำหรับ UARTx ดังนี้ เหมือนในกรณีที่เขียนโค้ดด้วย Arduino-ESP32 แต่จะเลือกขา GPIO อื่นได้เช่นกัน

UARTx

RX GPIO

TX GPIO

CTS

RTS

UART0

GPIO3

GPIO1

N/A

N/A

UART1

GPIO9

GPIO10

GPIO6

GPIO11

UART2

GPIO16

GPIO17

GPIO8

GPIO7

ในกรณีที่เราจะใช้ UART ส่งข้อมูลไปยังคอมพิวเตอร์ อุปกรณ์ที่จำเป็นต้องใช้ร่วมกันคือ โมดูล USB-to-Serial และให้เลือกใช้แรงดันลอจิกที่ 3.3V เท่านั้น (ไม่ใช่ 5V)

ตัวอย่างต่อไปนี้ สาธิตการใช้งาน UART โดยเลือกขา TX / GPIO-2 (ทิศทางเอาต์พุตสำหรับ ESP32) และ RX / GPIO-15 (ทิศทางอินพุตสำหรับ ESP32) ตามลำดับ และให้นำไปเชื่อมต่อกับขา RXD และ TXD ของโมดูล USB-to-Serial ตามลำดับ อีกทั้งให้ต่อขา GND ร่วมกันด้วย

เมื่อเปิดใช้งาน UART ให้ id = 1 เลือกใช้ Baudrate เท่ากับ 115200 จากนั้นจะมีการส่งข้อความทีละบรรทัด ซึ่งได้จากการแปลงตัวเลขที่คำนวณได้จากฟังก์ชัน math.sin() โดยให้มีค่าอยู่ในช่วง -100 .. +100

การส่งข้อความ จะใช้คำสั่ง write() ของไลบรารี machine.UART

from micropython import const
from machine import UART
import math

TX = const(2)  # use GPIO-2  for TXD
RX = const(15) # use GPIO-15 for RXD
id = 1
uart = UART(id, tx=TX, rx=RX,
            baudrate=115200, timeout=1000)
N = 512
A = 100
for i in range(N): # repeat N times
    x = A*(math.sin(2*math.pi*i/N))
    uart.write('{}\n'.format( round(x)) )
uart.deinit()
print('Done')

ถ้าใช้โปรแกรมอย่างเช่น Arduino IDE และเปิดใช้งาน Serial Plotter จะเห็นการแสดงข้อมูลในรูปของกราฟ

ถ้าจะลองเขียนโค้ดที่คอยรับข้อความทีละบรรทัดจาก UART แล้วส่งกลับไปยังคอมพิวเตอร์ เพื่อทดสอบการทำงานในลักษณะ UART Loopback ก็มีตัวอย่างดังนี้

from micropython import const
from machine import UART
import math

TX = const(2)  # use GPIO-2  for TXD
RX = const(15) # use GPIO-15 for RXD
id = 2
uart = UART(id, tx=TX, rx=RX, 
            baudrate=115200, parity=None, stop=1,
            timeout=1000)
try:
    while True:
        line = uart.readline() # read next line
        if line: # if line is not None
            print( line.decode().strip() )
            uart.write( line ) # send back
except KeyboardInterrupt:
    pass
uart.deinit()

ลองเปรียบเทียบกับอีกโค้ดตัวอย่างหนึ่ง ซึ่งให้ผลการทำงานเหมือนกัน แต่ใช้คำสั่งของไลบรารี machine.UART ที่แตกต่างกันในการรับข้อมูล

from micropython import const
from machine import UART
import math

TX = const(2)  # use GPIO-2  for TXD
RX = const(15) # use GPIO-15 for RXD
id = 2
uart = UART(id, tx=TX, rx=RX, 
            baudrate=115200, parity=None, stop=1)

BUF_SIZE = const(64)

try:
    # create buffer for UART RX
    buf = bytearray( BUF_SIZE )
    # set timeout to 100 msec
    uart.init(timeout=100)
    
    while True:
        n = uart.readinto( buf )
        if n: # check the number of bytes received
            line = buf[:n].decode()
            print( line.strip() ) # show line
            uart.write( line )    # send back
            
except KeyboardInterrupt:
    pass
    
uart.deinit()

โค้ดตัวอย่างที่ 15: BH1750 I2C Light Sensor

ESP32 มีวงจรภายในที่สำหรับการสื่อสารข้อมูลด้วยบัส I2C จำนวน 2 ชุด (Hardware I2C) และจะต้องเลือกใช้ขา SCL (Clock) และ SDA (Data) ให้ถูกต้อง และการเขียนโค้ดด้วยไมโครไพธอน เราจะใช้คลาส machine.I2C

เริ่มต้นด้วยการต่ออุปกรณ์ที่ทำงานเป็น I2C Slave และให้ ESP32 ทำหน้าที่เป็น I2C Master ในตัวอย่างนี้ เราจะใช้โมดูลเซ็นเซอร์แสง BH1750 ซึ่งสามารถใช้แรงดันไฟเลี้ยง 3.3V และ GND จากบอร์ด ESP32 ได้ และเลือกใช้ขา SCL / GPIO-22 และ SDA / GPIO-21 ตามลำดับ

โค้ดต่อไปนี้ สาธิตการตรวจหาอุปกรณ์ที่เชื่อมต่อกับ I2C Bus โดยใช้ขา SCL และ SDA ตามที่กำหนดไว้ ถ้าตรวจพบอุปกรณ์ BH1750 จะแสดงข้อความที่ระบุหมายเลขแอดเดรส 0x23

โมดูล GY302 เป็น Breakout Board สำหรับ BH1750 ที่ได้เลือกมาใช้งาน มีขา VCC, GND , SCL, SDA, ADDR ตามลำดับ ถ้าไม่ต่อขา ADDR จะได้หมายเลขแอดเดรสเป็น 0x23 แต่ถ้าต่อขาดังกล่าวกับ VCC จะได้ 0x5c

import machine, utime

i2c_bus = 0 # use either I2C bus 0 or 1
i2c = machine.I2C( i2c_bus, freq=100000,
    scl=machine.Pin(22), sda=machine.Pin(21) )

devices = i2c.scan() # scan I2C devices

num_found = len(devices)
if num_found > 0:
    print('I2C device%s found: ' % ('s' if num_found > 1 else ''))
    cnt = 1
    for dev in devices:
        print('({}) addr: {}'.format(cnt, hex(dev)))
        cnt = cnt+1
    print('')
else:
    print('no I2C devices found')

ตัวอย่างถัดไปสาธิตการอ่านค่าความเข้มแสงจากโมดูล BH1750 ซึ่งจะได้ค่าตัวเลขจำนวนเต็มในช่วง 0 .. 65535 (16 บิต)

import machine, utime

i2c_bus = 0 # use bus 0 or 1
i2c = machine.I2C( i2c_bus, freq=100000,
    scl=machine.Pin(22), sda=machine.Pin(21) )

def bh1750_init(addr):
    try:
        # power on the BH1750
        i2c.writeto(addr, bytes([0x01]) )
        # reset the BH1750
        i2c.writeto(addr, bytes([0x07]) )
        utime.sleep_ms(200)
        # set mode to 1.0x high-resolution, continuous measurement
        i2c.writeto(addr, bytes([0x10]) )
        utime.sleep_ms(150)
        return True
    except OSError as ex:
        return False

def bh1750_read(addr):
    try:
        data  = i2c.readfrom(addr, 2) # read two bytes
        value = (data[0]<<8 | data[1])/(1.2)
        return value
    except OSError as ex:
        print('BH1750 reading error')
        return None

addr_found = i2c.scan()
bh1750_addr_list = []
for addr in [0x23,0x5c]:
    if addr in addr_found:
        if bh1750_init( addr ):
            bh1750_addr_list.append( addr )

text = 'Light ({0:02X}h) {1:>6.1f} lx'
try:
    while True:
        for addr in bh1750_addr_list:
            value = bh1750_read( addr )
            if value:
                print( text.format(addr,value) )
        utime.sleep_ms(500)
except KeyboardInterrupt:
    pass
print('Done')

โค้ดตัวอย่างที่ 16: TouchPad

ตัวอย่างถัดไปสาธิตการใช้งาน TouchPad สำหรับขา GPIO บางขาของ ESP32 โดยได้เลือกใช้ขา GPIO-4 (TOUCH0) และ GPIO-0 (TOUCH1) จำนวน 2 ขา

ในการตรวจสอบดูว่า มีการใช้นิ้วมือสัมผัสที่ลวดหรือแผ่นโลหะที่เชื่อมต่อกับขา TouchPad หรือไม่ ก็ใช้วิธีการอ่านค่าเหมือนกรณีแอนะล็อก-อินพุต และจะได้ค่าเป็นเลขจำนวนเต็มบวก ถ้ามีการสัมผัส จะได้ค่าตัวเลขค่อนข้างต่ำ ในโค้ดตัวอย่างได้กำหนดไว้ว่า ถ้าได้ค่าต่ำกว่า 200 แสดงว่า มีการสัมผัสด้วยปลายนิ้ว

โดยทั่วไป ขา GPIO ที่ใช้เป็น TouchPad Input ควรมีตัวต้านทาน Pull-Up เช่น 1 เมกกะโอห์ม ต่อเอาไว้ด้วย

from machine import TouchPad, Pin
import utime as time

THRESHOLD_LEVEL = 200

pins = [0,4] # use GPIO-0 and GPIO-4 as touchpad pins
touch_pads = [ TouchPad( Pin(p) ) for p in pins ]

stop = False
text = 'Touched, GPIO{}, value={}'

while not stop:
    for i,tp in enumerate(touch_pads):
        value = tp.read() # read touchpad input
        if value < THRESHOLD_LEVEL:
            pin = pins[i]
            print( text.format(pin,value) )
            stop = True
    time.sleep_ms(100)

print('Done')

โค้ดตัวอย่างที่ 17: การใช้ OLED I2C Display

ตัวอย่างถัดไปสาธิตการใช้งานโมดูแสดงผลแบบ OLED โดยเชื่อมต่อกับบัส I2C ของ ESP32 โมดูล OLED ที่คนทั่วไปนิยมนำมาใช้งานจะใช้ไอซีควบคุมคือ SSD1306 และ SH1106 ดังนั้นจึงต้องเลือกไลบรารีที่นำมาใช้งานสำหรับไมโครไพธอนให้ถูกต้อง

เราสามารถดาวน์โหลดไฟล์ .py ซึ่งเป็นไลบรารีสำหรับไมโครไพธอนได้จาก

โมดูล OLED อาจจะมีขนาดที่แตกต่างกันคือ ความกว้าง (Width) และความสูง (Height) เช่น 128 x 64 หรือ 128 x 32 เป็นต้น

ในบางกรณีจะพบว่า โมดูล OLED นั้นใช้งานแบบบัส SPI ซึ่งจะมีจำนวนขามากกว่า และสิ่งที่ควรระวังคือ โมดูล OLED ที่ใช้วิธีการเชื่อมต่อแบบ I2C มีขา 4 ขา ได้แก่ GND, VCC, SCL, SDA ตามลำดับ แต่ก็อาจพบกรณีที่มีการเรียงขาแตกต่างไป ดังนั้นตรวจสอบให้แน่ใจก่อนต่อใช้งานวงจร

โค้ดสำหรับสาธิตการใช้งานโมดูล SH1106-based OLED I2C 128x64 pixels (default address: 0x3C)

import machine, utime
from sh1106 import *
# https://github.com/robert-hh/SH1106/blob/master/sh1106.py
import time

i2c_bus = 0 # use either I2C bus 0 or 1
i2c = machine.I2C( i2c_bus, freq=400000,
    scl=machine.Pin(22), sda=machine.Pin(21) )

I2C_ADDR = 0x3C
W, H = 128,64
BLACK, WHITE = 0,1

# scan for I2C devices
if 0x3C not in i2c.scan():
    raise RuntimeError('OLED SH1106 not found!!!')

display = SH1106_I2C(W, H, i2c, addr=I2C_ADDR)
display.rotate(True)
# fill the entire display
display.fill( BLACK )
display.rect(0, 0, W, H, WHITE ) 
# write some text lines (using the default font)
xpos,ypos = 0, 4
text_lines = ["Hi!", "MicroPython", "ESP32"]
for line in text_lines:
    display.text( '{:^16s}'.format(line), xpos, ypos )
    ypos += 10
display.show() # update the display 
time.sleep_ms(1000)
display.poweroff()
print('Done')

เผยแพร่ภายใต้ลิขสิทธิ์ Attribution-ShareAlike 4.0 International (CC BY-SA 4.0)

Last updated