- variant ที่ใช้ได้
- mtb-mpy เท่านั้น (ไม่มีการลิงก์ libbento_mpy.a เมื่อ BENTO_HAS_MPY=0)
โมดูล gpio คือ modgpio.c ซึ่งไม่อยู่ในแพ็กเกจ mtb-only และไม่มีสิ่งเทียบเท่าฝั่ง C ให้บันทึกไว้สำหรับ variant นั้น: บน mtb-only ให้ขับขาเหล่านี้ด้วย PDL โดยตรง (Cy_GPIO_Set / Cy_GPIO_Clr / Cy_GPIO_Read บน CYBSP_USER_LED* และ CYBSP_USER_BTN1) ส่วนตารางขาใน ขา (pin) บัส และแอดเดรส คือส่วนของบทนี้ที่ยังใช้ได้อยู่
เป้าหมายของหัวข้อนี้
เมื่อจบบทนี้จะขับ LED ทั้ง 5 ดวงและอ่านค่าปุ่มได้ และที่สำคัญกว่านั้นคือจะคาดเดาได้ว่า brightness(40) ทำอะไรจริง ๆ บน LED แต่ละดวง เพราะมันเป็น กลไก 2 แบบที่ต่างกัน ขึ้นกับว่าเลือกขาใด นี่คือหัวข้อที่ให้ความเข้าใจมากที่สุดในส่วนอุปกรณ์ต่อพ่วงทั้งหมด เพราะการเข้าใจกลไกผิดจะได้เดโมที่ดูเหมือนทำงานได้ แต่ไม่ได้วัดอะไรเลย
ลำดับการทำงานจริงของเฟิร์มแวร์
LED 5 ดวง ปุ่มหนึ่งปุ่ม led_table[] (modgpio.c:38-50) สร้างจากชื่อแทนของ BSP และมี #ifdef กำกับทีละดวง ดังนั้น NUM_LEDS (:51) จึงเท่ากับจำนวนที่บอร์ดนิยามไว้ บนบอร์ดนี้คือ 5 ดวง เรียงตามลำดับนี้:
| ดัชนี | name() | ชื่อแทนของ BSP | เส้นทาง TCPWM |
| 0 | LED1 | CYBSP_USER_LED1 | ไม่มี |
| 1 | LED2 | CYBSP_USER_LED2 | ไม่มี |
| 2 | RGB_RED | CYBSP_USER_LED3 | P20.6, line 265 |
| 3 | RGB_BLUE | CYBSP_USER_LED4 | P20.5, line 264 |
| 4 | RGB_GREEN | CYBSP_USER_LED5 | P20.4, line 263 |
btn_table[] มีรายการเดียวพอดี คือ NUM_BTNS 1 (modgpio.c:53-59)
ชื่อของปุ่มเป็นการแก้ให้ถูก และคอมเมนต์ก็ระบุไว้เช่นนั้น รายการนี้รายงานว่า "SW2" และคอมเมนต์เหนือมันบันทึกเหตุผลไว้:
Physical silkscreen says SW2 on both the AI Kit and the Eva Kit; the BSP alias chain is CYBSP_USER_BTN1 = CYBSP_SW1, which is the DEFINE's name, not the button's. Reported as "SW1" until 2026-08-20.
หากมีเอกสารรุ่นเก่าที่เรียกปุ่มนี้ว่า SW1 เอกสารนั้นกำลังเรียกชื่อมาโคร ไม่ใช่ชื่อชิ้นส่วน ตัวบอร์ดระบุว่า SW2
มีเส้นทาง PWM สามเส้น และมีเพียงสามเส้น s_led_pwm_routes[] (modgpio.c:159-166) จับคู่ดัชนี LED 2, 3 และ 4 เข้ากับตัวนับ 265, 264 และ 263 ของ TCPWM0 กลุ่ม 1 โดยใช้ตัวหาร CY_SYSCLK_DIV_16_BIT #3 และคาบ 1000 (:147-149) — 100 MHz / 100 = 1 MHz คาบ 1000 จึงได้ PWM ที่ 1 kHz ส่วน LED1 และ LED2 ไม่มี รายการอยู่เลย คอมเมนต์ที่ :143-146 บันทึกไว้ว่าเคยลองใช้ตัวหาร #4 ก่อนแล้วพบว่าไม่มีอยู่จริง และความล้มเหลวนั้นเงียบเพราะมีการละเลยรหัสที่คืนกลับมา — ตอนนี้ไม่ละเลยแล้ว
การเปลี่ยนสถานะทุกครั้งแจ้ง CM55 on(), off(), toggle(), value(v), brightness() และ hold() ทั้งหมดจบด้วย gpio_ipc_send_led_state() หน้า GPIO บนจอจึงสะท้อนสิ่งที่ทำจาก REPL บัฟเฟอร์ที่ทั้งหมดใช้ส่งคือ gpio_ipc_msg ซึ่ง ไม่ใช่ static และใช้ร่วมกับ modoptiga.c — ดู กับดัก
เรื่องจริงของ brightness
ส่วนนี้คุ้มค่าที่จะอ่าน 2 รอบ led.brightness(pct) (modgpio.c:375) เดินได้หนึ่งใน 3 เส้นทาง:
- 0 < pct < 100 บนขาที่มีเส้นทาง led_pwm_set() (:211) โปรแกรมตัวนับ TCPWM แล้วคืนค่า true การหรี่ทำด้วย ฮาร์ดแวร์: ไม่บล็อก ไม่กะพริบ และ คงระดับความสว่างไว้ จนกว่าจะเรียกครั้งถัดไป นี่คือสิ่งที่ได้บน RGB_RED, RGB_BLUE, RGB_GREEN
- pct == 0 หรือ pct == 100 เส้นทางถูกปล่อยคืน และขาถูกขับให้เป็นลอจิกต่ำหรือสูงตรง ๆ (:391-403) ต้นทุนต่ำ และกรณีที่พบบ่อยไม่มีค่าใช้จ่ายเลย
- 0 < pct < 100 บนขาที่ไม่มีเส้นทาง — คือ LED1 และ LED2 จะย้อนไปใช้การ bit-bang: BRIGHT_PERIOD_US 200 µs (5 kHz) โดยเปิดเป็นสัดส่วน pct% ของคาบทำซ้ำ BRIGHT_CYCLES 60 ครั้ง (:372-373) วิธีนี้ บล็อก VM ไว้ประมาณ 12 ms และเมื่อจบแล้ว จบลงในสภาพดับ (:427)
การที่เส้นทางที่ 3 จบลงในสภาพดับเป็นความตั้งใจ และคอมเมนต์ที่ modgpio.c:414-426 เป็นบันทึกของการแก้ข้อบกพร่องอย่างตรงไปตรงมาแทนที่จะกลบเกลื่อน โค้ดเดิมจบด้วย Cy_GPIO_Set แล้วอ้างว่าวิธีนั้นทำให้ยังเห็นระดับที่หรี่ไว้ ผลกลับตรงกันข้าม — ขาค้างอยู่ที่ลอจิกสูง ระหว่างการเรียกแต่ละครั้ง LED จึงสว่างเต็ม 100% และทุกระดับดูเหมือนกันหมด ตัวอย่างที่เขียนขึ้นเพื่ออธิบายเรื่อง duty cycle (สัดส่วนเวลาที่สัญญาณเป็นลอจิกสูง) โดยตั้ง 20 แล้วตั้ง 60 และเทียบด้วยตาเปล่า แสดงความสว่างเดียวกันทั้ง 2 ค่า ซึ่งเป็นวิธีที่ทำให้พบข้อบกพร่องนี้ การจบลงในสภาพดับทำให้ความจริงมองเห็นได้: บนขาที่ไม่มีเส้นทางการเรียก brightness() หนึ่งครั้งคือพัลส์ยาว 12 ms และการคงระดับความสว่างไว้หมายถึงต้องเรียกซ้ำในลูป
hold() คือลูปนั้น ที่ทำอย่างถูกต้อง led.hold(pct, ms=500) (modgpio.c:444) ทำซ้ำพัลส์เดิมจนกว่าจะครบ ms และคืนคิวผ่าน MICROPY_EVENT_POLL_HOOK ระหว่างพัลส์ (:474) เพื่อให้ Ctrl-C และปุ่ม Stop ของ IDE ยังทำงานอยู่ — ส่วน brightness() เพียงตัวเดียวหูหนวกตลอด 12 ms ของมัน และมันก็จบลงในสภาพดับเช่นกัน
- Note
- hold() ใช้เส้นทาง bit-bang เสมอ มันไม่ปรึกษา led_pwm_set() เลยการเรียกมันบนขา RGB จึงได้การคงระดับที่จับเวลาด้วยซอฟต์แวร์ บนขาที่ทำด้วยฮาร์ดแวร์ได้อยู่แล้ว สำหรับระดับความสว่างที่นิ่งบน RGB_* การเรียก brightness() ครั้งเดียวทั้งประหยัดกว่าและดีกว่า ส่วน hold() เป็นเครื่องมือสำหรับ LED1 และ LED2
duty() รายงานสิ่งที่ร้องขอไป ไม่ใช่สิ่งที่ขากำลังทำอยู่ led.duty() (modgpio.c:485) คืนค่า led_duty[index] ซึ่งเป็นเปอร์เซ็นต์ค่าสุดท้ายที่ส่งให้ brightness() หรือ hold() บนขาที่ไม่มีเส้นทาง LED ดับไปแล้วตั้งแต่ก่อนที่จะอ่านค่า และ duty() ก็ยังบอกว่า 40 นั่นคือข้อกำหนดการเรียกใช้ที่ตั้งใจไว้ — มันเป็นบันทึกของคำขอ — แต่ไม่ใช่ผลการวัด
ทีละขั้น
ขั้นที่ 1 — ดูว่าบอร์ดคิดว่าตัวเองมีอะไรบ้าง
- ตัวอย่าง (เขียนขึ้นเอง — ไม่มี call site ในของที่ส่งมอบจริง)
import gpio
info = gpio.board_info()
print(info['name'], info['leds'], info['buttons'])
print(info['led_names'])
print(info['btn_names'])
- สิ่งที่ควรสังเกต
- ได้ PSoC Edge AI Dev Kit 5 1 (modgpio.c:61-65 และ :625) แล้วตามด้วย ['LED1', 'LED2', 'RGB_RED', 'RGB_BLUE', 'RGB_GREEN'] แล้วจึง ['SW2'] ให้เขียนลูปโดยอิงกับ gpio.num_leds() (:613) แทนการฝังเลข 5 ไว้ตายตัว — ตารางมี #ifdef กำกับ และบอร์ดที่มี LED น้อยกว่านี้เป็น build ที่ถูกต้อง
ขั้นที่ 2 — เปิด ปิด สลับ
- ตัวอย่าง (เขียนขึ้นเอง — ไม่มี call site ในของที่ส่งมอบจริง)
import gpio, time
led = gpio.led(0)
led.on(); time.sleep_ms(300)
led.off(); time.sleep_ms(300)
led.toggle()
print(led.name(), led.value())
- สิ่งที่ควรสังเกต
- LED ทำตาม และ value() อ่านค่ากลับจากตัวขาจริง ไม่ใช่จากค่าเงา (modgpio.c:322-325) บนหน้าจอ หน้า GPIO และ RGB ปรับค่าตามทุกการเรียก — นั่นคือ gpio_ipc_send_led_state() ที่อยู่ท้ายทุกเมท็อด ดัชนีที่อยู่นอกช่วง 0..num_leds()-1 จะโยน ValueError พร้อมระบุช่วงที่ถูกต้อง (modgpio.c:591-594)
ขั้นที่ 3 — เห็นกลไก brightness ทั้ง 2 แบบในเซสชันเดียว
ขั้นนี้คือขั้นที่แสดงสาระของบทนี้ ให้รันบนช่อง RGB ก่อน แล้วจึงรันบน LED1
- ตัวอย่าง (เขียนขึ้นเอง — ไม่มี call site ในของที่ส่งมอบจริง)
import gpio, time
rgb = gpio.led(2)
rgb.brightness(20); time.sleep(2)
rgb.brightness(80); time.sleep(2)
rgb.brightness(0)
led1 = gpio.led(0)
led1.brightness(20)
time.sleep(1)
led1.hold(20, 2000)
led1.hold(80, 2000)
- สิ่งที่ควรสังเกต
- บน RGB_RED: ได้ระดับความสว่างที่นิ่ง 2 ระดับซึ่งต่างกันชัดเจน แต่ละระดับคงอยู่จนกว่าจะเรียกครั้งถัดไป ไม่มีการกะพริบ และ REPL ไม่หยุดค้าง ส่วนบน LED1: การเรียก brightness(20) ครั้งแรกเป็นการกะพริบที่อาจมองไม่ทันด้วยซ้ำ และหลังจากนั้น LED ดับ — ไม่ใช่หรี่ แต่ดับ จากนั้นการเรียก hold() 2 ครั้งจะให้ระดับความสว่างที่นิ่งและแยกออกจากกันได้ ครั้งละสองวินาที หากระดับจาก hold() ทั้งสองดูเหมือนกัน เป็นไปได้ว่า LED อยู่ใกล้ยอดของเส้นโค้งการตอบสนอง ให้ลองใช้ 10 กับ 90
- สิ่งที่ไม่ควรสรุป
- อย่าสรุปว่า brightness() เสียบน LED1 มันทำสิ่งที่พัลส์ยาว 12 ms หน้าตาเป็นอย่างนั้นพอดี ความต่างอยู่ที่กลไก ไม่ใช่ที่ตัวการเรียก
ขั้นที่ 4 — duty() คือบันทึก ไม่ใช่ค่าที่อ่านได้
- ตัวอย่าง (เขียนขึ้นเอง — ไม่มี call site ในของที่ส่งมอบจริง)
import gpio
led1 = gpio.led(0)
led1.brightness(40)
print(led1.duty())
- สิ่งที่ควรสังเกต
- ได้ 40 ส่วนขาอยู่ที่ลอจิกต่ำ ทั้งสองข้อเป็นจริง และข้อที่สองคือเหตุผลที่ hold() มีอยู่
ขั้นที่ 5 — อ่านค่าปุ่ม
- ตัวอย่าง (เขียนขึ้นเอง — ไม่มี call site ในของที่ส่งมอบจริง)
import gpio, time
btn = gpio.button(0)
print(btn.name())
while True:
if btn.is_pressed():
print("pressed, raw =", btn.value())
time.sleep_ms(200)
time.sleep_ms(20)
- สิ่งที่ควรสังเกต
- ได้ SW2 แล้วตามด้วยหนึ่งบรรทัดต่อการกดหนึ่งครั้ง is_pressed() เป็นแบบ active-low — มันเทียบกับ CYBSP_BTN_PRESSED (modgpio.c:520-526) — ดังนั้น value() จึงอ่านได้ 0 ขณะที่กดปุ่มค้างอยู่ โมดูลนี้ไม่มี debounce (การกรองสัญญาณเด้งของหน้าสัมผัส) การหน่วง 200 ms ข้างบนทำหน้าที่นั้นแทน (ส่วนปุ่มของ QWA309 ใน J5 — บอร์ดฐาน QWA309: pot ปุ่ม RGB matrix และ header มี debounce ด้วยซอฟต์แวร์ โดยใช้หน้าต่างค่านิ่ง 50 ms — API ของปุ่ม 2 ชุดบนบอร์ดนี้ทำงานไม่เหมือนกัน และควรรู้ไว้ก่อนย้ายโค้ดข้ามไปมาระหว่างทั้งสอง)
กับดัก
- Warning
- brightness() บน LED1 หรือ LED2 บล็อก VM ไว้ประมาณ 12 ms และจบลงในสภาพดับ มันไม่ใช่ระดับความสว่าง แต่เป็นพัลส์ ให้ใช้ hold() เมื่อต้องการระดับหรือย้ายไปใช้ช่อง RGB ที่ฮาร์ดแวร์คงระดับให้ได้
-
hold() ไม่ใช้ PWM ของฮาร์ดแวร์เลย แม้บนขาที่มีเส้นทาง บน RGB_* ให้ใช้ brightness() แทน
-
duty() คือคำขอครั้งล่าสุด ไม่ใช่สถานะของขา
-
gpio_ipc_msg ใช้ร่วมกับ modoptiga.c และไม่ใช่ static modgpio.c:104-107 ระบุไว้ตรง ๆ ว่า: ทั้งสองโมดูลส่งข้อมูลจาก MicroPython task เท่านั้น จึงไม่มีทางทำงานพร้อมกัน และหน่วยความจำร่วมบนบางรุ่นเล็กเกินกว่าจะจัดบัฟเฟอร์ให้โมดูลละหนึ่งชุดได้ มันใช้ได้เพราะสมมติฐานว่ามี task เดียว หากส่งการปรับสถานะ LED จากที่อื่นที่ไม่ใช่ MicroPython task ก็จะทำให้การหยุดพักการสัมผัสของ OPTIGA ที่กำลังส่งอยู่เสียหายได้ บันทึกไว้เป็นภาคผนวก X #24 — ภาคผนวก X — กับดักและ anti-pattern
-
ห้ามเรียกปุ่มนี้ว่า SW1 CYBSP_USER_BTN1 = CYBSP_SW1 เป็นชื่อแทนระดับมาโคร ไม่ใช่ตัวพิมพ์บนแผ่นวงจร โมดูลรายงานว่า "SW2" และตัวบอร์ดก็ตรงกัน (modgpio.c:53-58)
-
ห้ามฝังจำนวน LED ไว้ตายตัวที่ 5 led_table[] มี #ifdef กำกับทีละดวง ให้ถาม gpio.num_leds()
กล่อง variant
| mtb-mpy | mtb-only |
| โมดูล | gpio จาก modgpio.c | ไม่มีในแพ็กเกจ |
| การควบคุม LED | gpio.led(n) และเมท็อดของมัน | Cy_GPIO_Set/Clr/Inv บน CYBSP_USER_LED* |
| การหรี่ด้วยฮาร์ดแวร์ | brightness() บน RGB_* | ต้องตั้งค่า TCPWM เอง เส้นทางใน modgpio.c:159-166 มีไว้อ้างอิงเท่านั้น |
| ปุ่ม | gpio.button(0).is_pressed() | Cy_GPIO_Read(CYBSP_USER_BTN1_PORT, ...) แบบ active low |
| การสะท้อนบน UI | อัตโนมัติ ผ่าน gpio_ipc_msg | ไม่มี — ไม่มีสิ่งใดดันสถานะ LED ไปยัง CM55 |