ทบทวนสั้น ๆ ก่อนลืม เพราะท้ายบทเรียนเราต้องใช้ทุกขั้น
wifi.connect() บล็อกได้นานราว 85 วินาทีถ้าวงนั้นไม่มีในห้อง ป้าย "กำลังต่อ" จึงต้องขึ้นจอก่อนบรรทัดนี้wifi.ip() ที่ยังได้ "0.0.0.0" แปลว่ามีลิงก์แต่ยังไม่มีที่อยู่ ส่งอะไรออกไม่ได้mqtt.publish() ตอนสายหลุดไม่ได้คืน False มันโยน OSError · get_message() มีช่องเดียว ใบใหม่ทับใบเก่าบอร์ดยังต่อ Hotspot มือถือของทีม เหมือนบทเรียน 1.4–1.6 (WiFi ขององค์กรต้อง login บอร์ดใช้ไม่ได้) WIFI_SSID WIFI_PASS ในไฟล์ 07 กับ 08 ใช้ชื่อกับรหัสชุดเดิม · ชุดบทเรียนนี้ใช้ broker สาธารณะ broker.hivemq.com พอร์ต 1883 และชื่อทีมที่ผู้สอนแจก (team01 ถึง team19) ไฟล์ท้ายบทเรียนตั้งต้นเป็น TEAM = "teamXX" และไม่ยอมรันจนกว่าจะแก้ ใช้ชื่อทีมคนอื่นเมื่อไร บอร์ดของทีมนั้นถูกเตะหลุดทันที
วันนี้ครึ่งแรกเป็นเรื่องบนโต๊ะล้วน ๆ ไฟ ปุ่ม และเวลา พอนับปุ่มได้ตรงแล้ว เราค่อยส่งมันออกไป
| คำถาม | คำตอบของชุดบทเรียนนี้ | อยู่ช่วงไหน | |
|---|---|---|---|
| Why | ทำไมต้องคุมขาสัญญาณเอง ในเมื่อจอก็แสดงผลได้แล้ว | เพราะ IoT ไม่ได้จบที่การแสดงค่า มันต้องสั่งของจริงให้ขยับ ทุกเครื่องจักรที่คุณเคยเห็นเปิดปิดเอง มีบรรทัดแบบนี้อยู่ข้างใน | ครึ่งแรก · ทฤษฎีกระแสสองเส้นทาง |
| What | มีอะไรให้ใช้บ้าง | โมดูล gpio ทั้ง 18 ชื่อ — ตัวไหนตอบ "สิ่งที่วัดได้" ตัวไหนตอบแค่ "สิ่งที่เราสั่งไป" · บวก time.ticks_ms() ticks_diff() สำหรับคุมจังหวะ |
สไลด์บัญชี 18 ชื่อ |
| How | ประกอบยังไงให้ใช้งานได้จริง | ลูป polling ที่อ่านปุ่ม กันสัญญาณเด้ง แล้วสั่งไฟตามจังหวะที่ทีมตั้งเอง | หกไฟล์ตัวอย่าง + ไฟล์ฝึก · ท้ายบทเรียนอีกสองไฟล์ที่ส่งขึ้น broker (07 งานประยุกต์ · 08 เกมทั้งห้อง) |
ปลายทางที่จับต้องได้ — ไฟทุกดวงบนบอร์ดวิ่งไล่กันตามจังหวะที่ทีมตั้ง (Eva 3 ดวง · Dev Kit 5 ดวง — โค้ดชุดเดียวกัน) และทุกครั้งที่กดปุ่มบนบอร์ด ตัวเลขบนจอเพิ่มขึ้นหนึ่ง
บทเรียน 1.1–1.3 เห็นไฟติดจากโค้ดที่เราเขียนให้ · ชุดบทเรียนนี้เป็นคนเขียนลูปที่สั่งมันเอง
gpio.led(n) ได้ทั้ง เปิด ปิด สลับสถานะ และสั่งด้วยตัวเลขที่คำนวณได้gpio.button(0).is_pressed() แล้วบอกได้ว่า polling คืออะไรtime.sleep_ms() และ time.ticks_ms()/ticks_diff() เป็นgpio มีอะไรอยู่ทั้งโมดูล และตัวไหนตอบ "สิ่งที่วัดได้" ตัวไหนตอบแค่ "สิ่งที่เราสั่งไป"ปลายทางของวันนี้: ไฟทุกดวงบนบอร์ดวิ่งไล่กันตามจังหวะที่ทีมตั้งเอง และทุกครั้งที่กดปุ่มบนบอร์ด ตัวเลขบนจอเพิ่มขึ้นหนึ่ง
บทเรียน 1.1–1.3 เราเห็นไฟติดจากตัวอย่างที่เขียนมาให้ · ชุดบทเรียนนี้เราเป็นคนเขียนลูปที่สั่งมันเอง และรู้ว่าทุกคำสั่งในโมดูลนี้ทำอะไร

s03_led_button.py ของบทเรียน 2.3USER Button 1 คือชื่อที่เฟิร์มแวร์ตอบ ไม่ใช่ป้ายที่พิมพ์บนแผ่นวงจร และบอร์ดสองรุ่นพิมพ์ต่างกัน (สไลด์ "กับดักชื่อ" เล่าว่าทำไม)ผลลัพธ์จริงอยู่ที่ LED บนตัวบอร์ด ที่ไล่ดวงอยู่ จอบอกแค่ว่าโค้ดสั่งอะไร และนับการกดได้กี่ครั้ง
บทเรียน 1.1–1.3 เป็นบทเรียนเดินชมของ เราไล่ดูว่าไลบรารีชุดนี้มีอะไรบ้าง และหนึ่งในนั้นคือ 11_lights_and_a_button.py ที่ สั่งหลอดไฟจริงและอ่านปุ่มจริงไปแล้ว — ไฟไล่ทีละดวง หรี่ความสว่างด้วย hold() แล้วรอปุ่ม
บทเรียน 1.4–1.6 เราส่งค่าจากบอร์ดออกไปถึงเครื่องอื่น และรับคำสั่งกลับมาจุดไฟบนบอร์ดนี้ ระหว่างทางได้อ่านเลข IP ของมัน แยก "ต่อติดตอนนั้น" ออกจาก "ยังต่ออยู่ตอนนี้" แล้วเขียนกฎของเราเองว่าลิงก์แบบไหนถึงเรียกว่าใช้งานได้
พูดให้ตรง: วันนี้ไม่ใช่ครั้งแรกที่เราสั่งไฟ ครั้งแรกอยู่ในบทเรียน 1.1–1.3 แล้ว สิ่งที่ต่างคือบทเรียนนั้นเรา รันของที่เขียนมาให้ เพื่อดูว่าบอร์ดทำอะไรได้บ้าง วันนี้เราเปิดฝาโมดูล gpio ออกดูทั้งใบ แล้วเขียนลูปควบคุมของทีมเราเอง
สามอย่างที่บทเรียน 1.1–1.3 ให้เห็นผ่าน ๆ แล้ววันนี้จะรู้จริง: เมื่อไร hold() ถึงจำเป็น · ทำไม duty() ถึงเชื่อไม่ได้ · และ ทำไม ปุ่มที่กดครั้งเดียวถึงนับได้หลายครั้ง
เครื่องมือเดิมที่ยังใช้ต่อ: lcd.print() กับ markup, time.sleep_ms(), ลูป for และหน้า Playground
ทุกบทเรียนต่อจากนี้จะมีลูปหลักเป็นแกน — วันนี้คือลูปแรกของเรา
import gpio
gpio.led(0).on() # ติดค้าง
gpio.led(0).off() # ดับ
gpio.led(1).toggle() # สลับสถานะจากเดิม
gpio.led(2).value(1) # สั่งด้วยตัวเลข 1 = ติด, 0 = ดับ
gpio.led(2).brightness(50) # หรี่เป็นเปอร์เซ็นต์ของกำลังไฟ 0-100
pressed = gpio.button(0).is_pressed() # True ตอนที่นิ้วกดอยู่
gpio.led(n) ไม่ต้อง init ไม่ต้องบอกขา ไม่ต้องตั้งโหมด — เฟิร์มแวร์จัดการให้ตอนเรียกครั้งแรก · .name() ขอชื่อของหลอดตามที่เฟิร์มแวร์รู้จัก — เดี๋ยวจะเจอว่าชื่อนี้มีเรื่อง
n ใช้ได้ตั้งแต่ 0 ถึง gpio.num_leds() - 1 — Eva Kit 3 ดวง (0-2) · Dev Kit 5 ดวง (0-4) นอกช่วงได้ ValueError ทันที · ดวงไหนสีอะไร และดวงไหนมองเห็นบนบอร์ดที่ประกอบแล้ว ให้สั่ง on() ทีละดวงแล้วดูที่บอร์ดของทีม (ในบันทึกการเรียน) — บน Dev Kit หลอด LED1/LED2 อยู่บนโมดูล ดวง RGB คือดวง 2-4


.brightness(pct) ไม่ได้ลดแรงดันที่ขา แต่ สลับติด-ดับเร็วกว่าที่ตาจับได้ แล้วปล่อยให้ตาเห็นเป็นค่าเฉลี่ย ภาพนิ่งอธิบายเรื่องนี้ไม่ได้ ต้องดูสองภาพซ้ายตอนมันเคลื่อน — ซ้าย สัดส่วนเวลาติดต่อหนึ่งคาบคือสิ่งเดียวที่เปลี่ยน · ขวา เส้นค่าเฉลี่ยไต่ตามความกว้างพัลส์ "ความสว่างที่ตาเห็น" คือค่าเฉลี่ย ไม่ใช่ค่าที่ขาส่งออกจริง (ภาพ: "PWM duty cycle with label" สาธารณสมบัติ · "PWM gemiddelde waarde" โดย Jef daems CC0 1.0 — Wikimedia Commons)
on()/off()เมื่อรู้ว่าอยากได้สถานะไหน ·toggle()เมื่อแค่อยากกลับด้าน ·brightness(pct)ค่ากลาง 1-99 เลือกทางตามขาของหลอด: ดวงที่มีเส้น PWM ของฮาร์ดแวร์ (Eva 0-2 · Dev Kit ดวง RGB 2-4) ค้างระดับไว้ ไม่บล็อก · ดวงอื่นได้พัลส์ราว 12 ms แล้วจบด้วยหลอดดับ · ค้างได้ทุกดวงด้วยhold(pct, ms)ซึ่งบล็อกจนครบแล้วจบด้วยหลอดดับ — แต่ถ้าเพิ่งสั่งbrightness()ค่ากลางบนดวง PWM ต้องเรียกoff()คั่นก่อน ไม่งั้นขายังถูก PWM ถืออยู่hold()ไม่เห็นผลและหลอดไม่ดับตอนจบ (03_led_brightness.pyทำแบบนั้น) ·brightness(0)/100ไม่บล็อก และ100ทิ้งหลอดติดค้าง · มองหลอดจริงเป็นคนตัดสิน
สไลด์นี้ไม่ได้ให้ท่อง แต่ให้ รู้ว่าอะไรมีอยู่ จะได้ไม่ไปเขียนของที่ไม่มี แล้วนั่งงงว่าทำไม error
ระดับโมดูล — ห้าฟังก์ชัน สองชนิด (import gpio แล้วเรียกได้เลย)
| ชื่อ | คืนอะไร | ใช้ตอนไหน |
|---|---|---|
gpio.board_info() |
dict ห้าช่อง: name leds buttons led_names btn_names |
เปิดโปรแกรมมา อยากรู้ว่าบอร์ดนี้มีอะไร |
gpio.num_leds() |
int — Eva Kit ได้ 3 · Dev Kit ได้ 5 |
เขียน range() ให้ย้ายบอร์ดแล้วยังถูก — คอร์สนี้ย้ายจริง |
gpio.num_buttons() |
int — ได้ 1 เสมอ เพราะเฟิร์มแวร์ตรึงไว้ที่ 1 (ทั้งสองบอร์ด) |
เช็กก่อนวนอ่านปุ่ม |
gpio.led(n) |
อ็อบเจกต์ LED · n นอกช่วง 0..num_leds()-1 โยน ValueError |
ทุกครั้งที่จะสั่งไฟ |
gpio.button(n) |
อ็อบเจกต์ Button · n ต้องเป็น 0 เท่านั้น |
ทุกครั้งที่จะอ่านปุ่ม |
gpio.LED · gpio.Button |
ชนิด ไม่ใช่ฟังก์ชัน เรียกสร้างเองไม่ได้ | isinstance(x, gpio.LED) ตอนเขียนฟังก์ชันที่รับได้ทั้งไฟและปุ่ม |
เมธอดของ LED — แปดตัว · เมธอดของ Button — สามตัว
LED |
ทำอะไร | กับดัก |
|---|---|---|
.on() .off() |
บังคับเป็น 1 / 0 | — |
.toggle() |
กลับด้านจากเดิม | ไม่ไปแก้ตัวเลขของ duty() พอ on() แล้ว toggle() หลอดดับแต่ duty() ยังตอบ 100 |
.value() |
อ่านระดับขาจริงกลับมา | หลัง hold() ได้ 0 เสมอ (และหลัง brightness() ค่ากลางบนดวงที่ไม่มีเส้น PWM) — ตอบระดับขา ไม่ตอบความสว่าง |
.value(n) |
ตัวเดียวกันแต่ใส่อาร์กิวเมนต์ = สั่ง 1/0 | ใช้ตอนค่ามาจากการคำนวณ |
.name() |
ชื่อในตารางเฟิร์มแวร์ | Eva: ดวงที่ 2 ตอบ "RGB_RED" แต่ติดเป็นน้ำเงิน · Dev Kit: มี RGB_RED RGB_BLUE RGB_GREEN ครบ — หาดวงจากชื่อได้ |
.brightness(pct) |
1-99: ดวงที่มีเส้น PWM ของฮาร์ดแวร์ (Eva 0-2 · Dev Kit 2-4) ค้างระดับไว้ ไม่บล็อก · ดวงอื่นได้พัลส์ 12 ms หนึ่งชุด (200 µs × 60 รอบ) แล้วจบด้วยหลอดดับ | 0 กับ 100 เป็นทางลัด สั่งขาตรง ๆ แล้วคืนทันที — 100 จบด้วยหลอดติดค้าง ไม่ได้ดับ · จะ hold() ต่อจาก brightness() ค่ากลางบนดวง PWM ให้ off() คั่นก่อน (03_led_brightness.py ทำแบบนั้น) |
.hold(pct, ms) |
ย้ำพัลส์นั้นจนครบ ms · ms ไม่ใส่ได้ ปริยาย 500 · ใช้ได้ทุกดวง จบด้วยหลอดดับ |
บล็อกจนครบ ระหว่างนั้นปุ่มไม่ถูกอ่าน · 100 ก็ค้างติดเหมือนกัน · ไม่ปลดเส้น PWM ให้ — ถ้าเพิ่ง brightness() ค่ากลางบนดวง PWM แล้วไม่ off() คั่น hold() จะไม่เห็นผลและหลอดไม่ดับตอนจบ |
.duty() |
เปอร์เซ็นต์ที่ เราสั่งไปครั้งล่าสุด | ไม่ได้ไปวัดหลอด · on() off() value(n) brightness() hold() เขียนตัวเลขนี้ทั้งหมด แต่ toggle() ไม่เขียน มันจึงเก่าได้ |
เมธอดของ Button — สามตัว
Button |
ทำอะไร | กับดัก |
|---|---|---|
.is_pressed() |
True ตอนกดอยู่ — แปลความหมายให้แล้ว |
ใช้ตัวนี้เป็นหลัก |
.value() |
ระดับไฟฟ้าดิบ กด = 0 | if btn.value(): ทำงานกลับด้านทั้งหมด |
.name() |
ชื่อในตารางเฟิร์มแวร์ | ตอบ "USER Button 1" ทั้งสองบอร์ด — จงใจไม่ใช้ป้ายบนแผ่นวงจร (สไลด์กับดักชื่อเล่าว่าทำไม) |
ของที่เฟิร์มแวร์ตัวนี้ไม่มีให้เรียก — เอกสาร MicroPython ต้นทางมีครบ แต่ที่นี่ไม่มี เรียกไปได้ AttributeError เหมือนกันหมด แม้เหตุผลจะไม่เหมือนกัน
| ชื่อ | สถานะจริง |
|---|---|
machine.PWM · machine.ADC · machine.SPI |
พอร์ตนี้ไม่ได้เขียนไว้เลย ไม่มีไฟล์ ไม่มีชื่อ |
machine.Timer |
มีโค้ดอยู่ในพอร์ต แต่เฟิร์มแวร์ชุดนี้ไม่ได้เปิดสวิตช์คอมไพล์ให้ (เครื่องเกมของคอร์สอื่นเปิด) ผลที่เราเจอจึงเหมือนกันคือเรียกไม่ได้ |
machine.I2C |
อันนี้มี แต่ยังไม่ใช่เรื่องของชุดบทเรียนนี้ |
ไม่มี machine.PWM ให้เรียกจาก Python ก็จริง แต่ฝั่งเฟิร์มแวร์ brightness() ต่อดวง RGB เข้ากับ TCPWM ในตัวชิปให้แล้ว (ตาราง s_led_pwm_routes ใน modgpio.c) ดวงที่ไม่มีเส้นนั้นเท่านั้นที่ต้องกะพริบขาเองด้วยซอฟต์แวร์ — ดังนั้นการหรี่ไฟทำผ่าน gpio ไม่ใช่ผ่าน machine
เห็นคำตอบใน Stack Overflow ที่ขึ้นต้นด้วย
machine.PWM(...)เมื่อไร ให้รู้ทันทีว่าคำตอบนั้นไม่ใช่ของบอร์ดเรา
info = gpio.board_info()
...
n = gpio.num_leds() # 3 หรือ 5 แล้วแต่บอร์ด
...
for i in range(n):
gpio.led(i).off()
...
print("board_info() =", info)
# Eva Kit: {'name': 'PSoC Edge Eval Kit', 'leds': 3, 'buttons': 1,
# 'led_names': ['LED1', 'LED2', 'RGB_RED'], 'btn_names': ['USER Button 1']}
# Dev Kit: {'name': 'PSoC Edge AI Dev Kit', 'leds': 5, 'buttons': 1,
# 'led_names': ['LED1', 'LED2', 'RGB_RED', 'RGB_BLUE', 'RGB_GREEN'], 'btn_names': ['USER Button 1']}
นิสัยที่ดีของงาน embedded คือ ถามอุปกรณ์ ไม่ใช่เดาจากความจำ — for i in range(3) จะพังเงียบ ๆ เมื่อย้ายไปบอร์ดที่มีห้าดวง (ซึ่งคือ Dev Kit ในห้องนี้) แต่ for i in range(n) ย้ายไปแล้วยังถูก (gpio.num_buttons() ตอบ 1 ทั้งสองบอร์ด) · สังเกตชื่อในรายการให้ดี 'RGB_RED' บน Eva กับ 'USER Button 1' บนทั้งสองบอร์ด — สองชื่อนี้ไม่ตรงกับสิ่งที่ตาเห็นหรือป้ายที่พิมพ์ไว้ คนละเหตุผลกัน

board_info() รายงานเป็น 3 ดวงกับ 1 ปุ่ม — Dev Kit รายงาน 5 ดวงกับ 1 ปุ่ม และคำตอบที่บอร์ดพูดเองคือคำตอบที่ย้ายบอร์ดแล้วยังถูก · ภาพหน้าจอของ 01_board_info.py ถอดออกชั่วคราว ไฟล์ถูกลดจำนวน widget ลงเมื่อ 14 ส.ค. รอถ่ายใหม่เขียนโค้ดให้ถามบอร์ด แล้วโค้ดจะรอดตอนย้ายบอร์ด
| สิ่งที่โค้ดบอก | ความจริงบนบอร์ด | ต้องทำอย่างไร |
|---|---|---|
gpio.led(2).name() คืน "RGB_RED" |
Eva Kit: หลอดที่ติดจริงเป็นสี น้ำเงิน (ชื่อตกทอดจากตารางร่วม) · Dev Kit: ตารางเฟิร์มแวร์ (modgpio.c) บอกว่าดวง 2 คือแดง และมี RGB_BLUE RGB_GREEN เป็นดวง 3-4 — ยังไม่มีใครยืนยันด้วยตาบนบอร์ดที่ประกอบแล้ว ทีมเป็นคนแรก |
เชื่อสายตา — สั่ง on() ทีละดวงแล้วดู ไม่ต้องเชื่อชื่อ |
gpio.button(0).name() คืน "USER Button 1" |
ไม่ใช่ป้ายบนแผ่นวงจร — Eva Kit พิมพ์ว่า SW2 · Dev Kit: บนฐาน QWA309 สวิตช์ที่พิมพ์ว่า SW หลายตัวเป็นสวิตช์ตัดไฟเลี้ยง (ในผังวงจร SW2/SW3/SW4/SW11 คือสวิตช์ไฟของกล้อง/mikroBUS/ADU/RGB) ห้ามโยกสวิตช์ใดบนฐานที่บทเรียนไม่ได้สั่ง | ตั้งใจ ให้เป็นแบบนี้ — เรียกปุ่มด้วยชื่อจาก .name() เสมอ ห้ามบอกเพื่อนว่า "กด SW2" |
บนบอร์ดมีปุ่มอื่นอีก (Eva: SW4 · Dev Kit: ปุ่มกดสองปุ่มของฐาน ใช้ผ่านโมดูล buttons เท่านั้น — ชื่อบนแผ่นรุ่นผลิต SW4/SW5) |
gpio.button(1) โยน ValueError ทั้งสองบอร์ด |
ชุดบทเรียนนี้ใช้ปุ่มเดียวคือ gpio.button(0) |
gpio.led(2).value() คืน 0 ทั้งที่หลอดเพิ่งสว่าง |
อ่านกลับได้จริง แต่ hold() (และ brightness() ค่ากลางบนดวงที่ไม่มี PWM) จบด้วยขาต่ำ |
จำสถานะไว้ในตัวแปรของเราเอง |
duty() ตอบ 100 ทั้งที่หลอดดับอยู่ |
เพิ่งเรียก toggle() ซึ่ง ไม่ไปแก้ตัวเลขของ duty() |
duty() คือ "สั่งอะไรไปล่าสุด" ไม่ใช่ "หลอดเป็นอย่างไรตอนนี้" |
Eva Kit: led(0) แดง · led(1) เขียว · led(2) น้ำเงิน — จำจากสายตา ไม่ใช่จากชื่อ · Dev Kit: ดวง RGB คือ 2-4 ตามชื่อใน led_names ส่วน LED1/LED2 อยู่บนโมดูล — ยืนยันด้วยสายตาบนบอร์ดของทีมเหมือนกัน
สองชื่อนี้หลอกด้วยเหตุผลคนละแบบ RGB_RED มาจากตารางร่วมของเฟิร์มแวร์ที่บอร์ดหลายรุ่นใช้ด้วยกัน ดวงที่สามของ Eva จึงได้ชื่อของดวงแดงบนบอร์ดอื่น · USER Button 1 ตรงกันข้าม มันถูกเลือกโดยตั้งใจให้ไม่ตรงกับป้ายบนแผ่นวงจร เพราะบน Dev Kit คำว่า "SW2" ชี้ไปที่สวิตช์ตัดไฟบนฐาน — ชื่อที่มีไว้ให้คนหาปุ่มด้วยนิ้ว ต้องไม่พาไปกดของผิดชิ้น (เหตุผลเขียนไว้ใน modgpio.c ตรงตารางปุ่ม) · สิ่งที่วิศวกรทำคือ ทดลองหนึ่งครั้งแล้วจดไว้ ไม่ใช่เถียงกับเอกสาร
อย่าให้ชื่อตัวแปรของทีมเราสืบทอดความสับสนต่อ — หาดัชนีดวงน้ำเงินครั้งเดียว (ตาเห็น หรือชื่อ
RGB_BLUEถ้าบอร์ดมี) แล้วตั้งชื่อว่าLED_BLUEไปเลย · ส่วนปุ่ม ให้ป้ายบนจอพิมพ์btn.name()ออกมาตรง ๆ คนก้มดูบอร์ดกับคนมองโค้ดจะได้เรียกปุ่มเดียวกันด้วยชื่อเดียวกัน