บทความ › ไมโครคอนโทรลเลอร์และโค้ด
รีโมตของเล่นด้วย ESP-NOW บอร์ดคุยกันตรง ไม่ต้องมี เราเตอร์
ใช้ ESP-NOW ส่งคำสั่งจาก ESP32 ตัวหนึ่งไปอีกตัวโดยไม่ผ่าน Wi-Fi บ้าน ข้อความไม่เกิน 250 ไบต์ โค้ด MicroPython ตัวส่งตัวรับ ระบบหยุดเมื่อสัญญาณหาย และโหมด long range
- ESP-NOW ส่งข้อความระหว่างบอร์ด ESP ตรง ๆ ด้วย MAC address ไม่ต้องมีเราเตอร์ ลงทะเบียน peer ได้ถึง 20 ตัว
- ข้อความยาวได้ 250 ไบต์ (ESP-NOW v1) ถ้าเก็บข้อความให้ไม่เกินนี้ ใช้ได้กับบอร์ดทุกรุ่น รวมถึง ESP8266
- ทั้งสองบอร์ดต้องอยู่ช่อง Wi-Fi เดียวกัน และฝั่งรับต้องหยุดมอเตอร์เองเมื่อไม่ได้รับข้อความเกินเวลาที่กำหนด
- โหมด long range (RATE_LORA_250K/500K) ต้องตั้ง PROTOCOL_LR ทั้งสองฝั่ง แลกความเร็วกับระยะ
ESP-NOW คือโปรโตคอลของ Espressif ที่ให้บอร์ด ESP32 ส่งข้อความหากันตรง ๆ ผ่านวิทยุ Wi-Fi โดยไม่ต้องมีเราเตอร์ ไม่ต้องจับคู่แบบ Bluetooth และเริ่มส่งได้เกือบทันทีที่เปิดเครื่อง เหมาะกับรีโมตรถ หุ่นยนต์ หรือปุ่มกดเกมที่พกไปเล่นนอกบ้าน บทนี้ทำรีโมต 4 ปุ่มด้วย MicroPython พร้อมระบบหยุดรถเมื่อสัญญาณหาย
ถ้ายังไม่เคยต่อปุ่ม อ่านต่อปุ่มกดกับ ESP32 ให้นิ่ง ก่อน ส่วนไฟเลี้ยงมอเตอร์หรือเซอร์โวดูบทเซอร์โว ทุกอย่างในบทนี้ใช้ไฟต่ำจาก USB หรือแบตก้อนเล็ก
ESP-NOW เทียบกับทางเลือกอื่น
| ESP-NOW | Wi-Fi ผ่านเราเตอร์ | Bluetooth LE | |
|---|---|---|---|
| ต้องมีเราเตอร์ | ไม่ต้อง | ต้อง | ไม่ต้อง |
| เริ่มส่งได้ | เกือบทันที | ต้องรอต่อเครือข่าย | ต้องจับคู่/เชื่อมต่อ |
| ขนาดข้อความ | 250 ไบต์ (v1) | ใหญ่กว่ามาก | ขึ้นกับการตั้งค่า |
| คุยกับมือถือ | ไม่ได้ | ได้ | ได้ |
| เหมาะกับ | บอร์ดคุยบอร์ด | ของที่ต้องเข้าเน็ต | ของที่คุยกับแอป |
ข้อจำกัดหลักของ ESP-NOW คือมือถือคุยด้วยไม่ได้ อีกฝั่งต้องเป็นบอร์ด ESP เสมอ
ขนาดข้อความ: 250 กับ 1470 ไบต์
ESP-NOW รุ่นแรก (v1) รับข้อความได้ไม่เกิน 250 ไบต์ ตั้งแต่ ESP-IDF 5.4 บน ESP32 มี v2 ที่ยาวได้ถึง 1470 ไบต์ เช็กได้จาก espnow.MAX_DATA_LEN แต่บอร์ด v1 (รวม ESP8266) จะตัดหรือทิ้งข้อความที่ยาวเกิน 250 ไบต์ รีโมตของเล่นใช้แค่ไม่กี่ไบต์ จึงไม่ต้องกังวล แต่ถ้าออกแบบโปรโตคอลเอง เก็บไว้ไม่เกิน 250 ไบต์จะใช้ได้ทุกรุ่น
ตัวอย่าง: ข้อความรีโมตใหญ่แค่ไหน
รีโมต 4 ปุ่ม: หน้า หลัง ซ้าย ขวา
- ปุ่ม 4 ปุ่มเก็บเป็นบิตใน 1 ไบต์ (bit 0 = หน้า, bit 1 = หลัง, bit 2 = ซ้าย, bit 3 = ขวา)
- เพิ่มเลขลำดับ 1 ไบต์ วน 0–255 ไว้ดูว่าข้อความหายไหม รวม 2 ไบต์ต่อข้อความ
- ส่งทุก 20 ms = 50 ข้อความต่อวินาที: 2 × 50 = 100 ไบต์ต่อวินาที
- กดหน้ากับซ้ายพร้อมกัน ไบต์แรกเป็น 0b0101 = 5
เทียบกับงานหนักกว่า วิทยุสื่อสารในบทความของ Elektor ส่งเสียง 8,000 ตัวอย่างต่อวินาที ใส่ข้อความละ 250 ไบต์ ได้ 8,000 ÷ 250 = 32 ข้อความต่อวินาที นับขนาดรวม 255 ไบต์ต่อแพ็กเก็ต เท่ากับ 32 × 255 × 8 = 65,280 บิตต่อวินาที อัตราส่งเริ่มต้นของ ESP-NOW คือ 1 Mbit/s รีโมตของเราใช้น้อยกว่านั้นมาก
หา MAC ของบอร์ดรับ
ESP-NOW ส่งหากันด้วย MAC address แฟลช MicroPython ลงบอร์ดรถ แล้วรันใน REPL
import network
sta = network.WLAN(network.WLAN.IF_STA)
sta.active(True)
print(sta.config('mac'))
จะได้ค่าแบบ b'\xaa\xbb\xcc\xdd\xee\xff' จดไว้ใส่ในโค้ดรีโมต
โค้ดตัวส่ง (รีโมต)
เลขขาอิงผังขาของ ESP32-C3 บอร์ดอื่นเปลี่ยนตามผังขา และอย่าใช้ขา strapping กับขาที่ต่อ flash
import network, espnow, time
from machine import Pin
sta = network.WLAN(network.WLAN.IF_STA)
sta.active(True)
sta.config(channel=6) # ต้องตรงกับฝั่งรับ
e = espnow.ESPNow()
e.active(True)
CAR = b'\xaa\xbb\xcc\xdd\xee\xff' # MAC ของบอร์ดรถ
e.add_peer(CAR)
pins = [Pin(n, Pin.IN, Pin.PULL_UP) for n in (4, 5, 6, 7)] # หน้า หลัง ซ้าย ขวา
seq = 0
while True:
buttons = 0
for i, p in enumerate(pins):
if p.value() == 0: # pull-up: กดแล้วเป็น 0
buttons |= 1 << i
e.send(CAR, bytes([buttons, seq]), False)
seq = (seq + 1) & 0xFF
time.sleep_ms(20)
send(..., False) คือไม่รอคำตอบ ส่งต่อเนื่องได้ไว ถ้าใช้ค่าเริ่มต้น sync=True จะรอให้ฝั่งรับตอบรับ และคืนค่า False ถ้าไม่ได้คำตอบ
โค้ดตัวรับ (รถ) พร้อมระบบหยุดเมื่อสัญญาณหาย
import network, espnow, time
from machine import Pin
sta = network.WLAN(network.WLAN.IF_STA)
sta.active(True)
sta.config(channel=6)
e = espnow.ESPNow()
e.active(True)
REMOTE = b'\x11\x22\x33\x44\x55\x66' # MAC ของรีโมต
leds = [Pin(n, Pin.OUT) for n in (4, 5, 6, 7)] # แทนมอเตอร์ไปก่อน
def drive(buttons):
for i, led in enumerate(leds):
led.value((buttons >> i) & 1)
last = time.ticks_ms()
while True:
host, msg = e.recv(100) # รอสูงสุด 100 ms
if msg and host == REMOTE and len(msg) == 2:
drive(msg[0])
last = time.ticks_ms()
elif time.ticks_diff(time.ticks_ms(), last) > 300:
drive(0) # ไม่ได้ข้อความเกิน 300 ms ให้หยุด
ทดลองกับ LED ก่อน พอทำงานถูกค่อยเปลี่ยน drive() ไปคุมไดรเวอร์มอเตอร์ เช็ก host == REMOTE ไว้ เพื่อไม่ให้รีโมตของเพื่อนที่เล่นอยู่ใกล้ ๆ มาบังคับรถเราได้
ของที่ขยับได้ต้องหยุดเองเมื่อสัญญาณหาย อย่าให้รถวิ่งต่อด้วยคำสั่งสุดท้ายที่ได้รับ
โหมด long range
ESP32 (ยกเว้น ESP32-C2) มีโหมด long range ของ Espressif ที่ส่งช้าลงเพื่อให้ไปได้ไกลขึ้น ต้องตั้งทั้งสองฝั่ง
sta.config(channel=6, protocol=network.WLAN.PROTOCOL_LR) # ทั้งสองฝั่ง
e.config(rate=espnow.RATE_LORA_250K) # ฝั่งส่งเท่านั้น
เลือกได้ RATE_LORA_250K หรือ RATE_LORA_500K เอกสารบอกว่าอัตราต่ำมักนิ่งกว่าในระยะไกล ข้อควรรู้: การตั้ง rate ใช้ไม่ได้บน ESP32-C6 และโหมดนี้ไม่เข้ากับอุปกรณ์ Wi-Fi ทั่วไป ใช้เฉพาะบอร์ด ESP ด้วยกัน
ข้อผิดพลาดที่เจอบ่อย
- ช่องไม่ตรงกัน ฝั่งหนึ่งต่อเราเตอร์อยู่ ช่องจึงย้ายตามเราเตอร์ อีกฝั่งยังอยู่ช่อง 6 ข้อความไม่ถึง ตั้งให้ตรงกันทุกบอร์ด
- ลืม
sta.active(True)ต้องเปิด WLAN interface ก่อน send/recv แม้ไม่ต่อเครือข่าย - ลืม
add_peer()ก่อนส่งบน ESP32 - ต่อ Wi-Fi บ้านแล้วข้อความหายเป็นช่วง ๆ โหมดประหยัดไฟเปิดอยู่ ปิดด้วย
sta.config(pm=sta.PM_NONE) - soft reset แล้วงง MicroPython ไม่รีเซ็ต Wi-Fi ตอน soft reset ให้ปิด STA และ AP ก่อนตั้งค่าใหม่ตอนเริ่มโปรแกรม
- ไม่มีระบบหยุดเมื่อสัญญาณหาย รถวิ่งออกนอกระยะแล้วไม่หยุด
- มอเตอร์ใช้ไฟเส้นเดียวกับบอร์ด แรงดันตกจนบอร์ดรีเซ็ต แยกไฟมอเตอร์แล้วต่อกราวด์ร่วม
แหล่งข้อมูล
คำถามที่พบบ่อย
ESP-NOW ไกลแค่ไหน
เอกสาร MicroPython บอกว่าใช้สื่อสารได้ระดับหลายร้อยเมตร ในบทความของ Elektor ทดสอบวิทยุสื่อสาร ESP-NOW ได้ราว 150 m ในที่โล่ง ระยะจริงขึ้นกับเสาอากาศ ความสูงที่วาง และสิ่งกีดขวาง
ใช้ ESP-NOW พร้อมกับต่อ Wi-Fi บ้านได้ไหม
ได้ แต่บอร์ดจะย้ายไปช่องของเราเตอร์ ทุกบอร์ดต้องตั้งช่องตามนั้น และควรปิดโหมดประหยัดไฟด้วย sta.config(pm=sta.PM_NONE) ไม่อย่างนั้นข้อความหายได้
ต้องเข้ารหัสไหม
ของเล่นทั่วไปไม่จำเป็น ถ้าต้องการ ESP-NOW เข้ารหัสได้ด้วยคีย์ 16 ไบต์ (PMK และ LMK) สูงสุด 6 peer ส่วนข้อความ broadcast เข้ารหัสไม่ได้
วงจรและโค้ดในบทความทดสอบกับบอร์ดตัวอย่าง บอร์ดต่างรุ่นอาจใช้ขาต่างกัน ตรวจ pinout ของบอร์ดที่ใช้ก่อนต่อไฟเสมอ