- variant ที่ใช้ได้
- mtb-mpy เท่านั้น (ไม่มีการลิงก์ libbento_mpy.a เมื่อ BENTO_HAS_MPY=0)
โมดูลทั้ง 5 ตัวในบทนี้อยู่ใน bento_libs/claw/kit-tesaiot-pse84-ai/mpy/ ซึ่งเป็นไดเรกทอรีที่ไม่มีอยู่ในแพ็กเกจ mtb-only ตัวบอร์ดฐานเองเป็นฮาร์ดแวร์เดียวกันบนทั้งสอง variant บน mtb-only ให้เข้าถึงมันผ่าน PDL และผ่าน cm55_sensor_poll (จุดเข้าฝั่ง C (ทั้ง 2 แพ็กเกจ))
เป้าหมายของหัวข้อนี้
เมื่อจบบทนี้จะอ่านค่าจากลูกบิดทั้งสี่ ปุ่มทั้งสอง และ RGB matrix ได้ และขับคอนเนกเตอร์ header สำหรับต่อขยายได้ ข้อเท็จจริง 2 ข้อในบทนี้เป็นสาระ และทั้ง 2 ข้อเดาจาก API ไม่ได้: การจับคู่ลูกบิดกับช่องสัญญาณ ไม่ใช่ การจับคู่แบบตรงตัว และหนึ่งใน 2 ปุ่มใช้ขาร่วมกับไฟเลี้ยง USB host หากเข้าใจผิดข้อใดข้อหนึ่ง โค้ดจะคอมไพล์ผ่าน รันได้ แล้วรายงานลูกบิดผิดตัว หรือทำให้จอยสติ๊กใช้ไม่ได้
ลำดับการทำงานจริงของเฟิร์มแวร์
ทุกอย่างอยู่หลัง flag ตัวเดียว pots, buttons, rgbmatrix, header, can และ shield แต่ละตัวห่อไว้ด้วย #if BSP_HAS_QWA309_BASEBOARD ซึ่งบอร์ดนี้ตั้งไว้เป็น 1 (bsp_features.mk:25) บน AI Kit SoM เปล่า ๆ จะไม่มีตัวใดลงทะเบียนเลยและ import pots จะโยน ImportError — ไม่ใช่ error ขณะทำงานที่เกิดจากอุปกรณ์ที่ไม่มีอยู่
2 คอร์แบ่งบอร์ดฐานกันตามความสามารถ ไม่ใช่ตามอุปกรณ์ ค่า pot อ่านจาก CM33_NS ตรงจากรีจิสเตอร์ผลลัพธ์ของ AutAnalog SAR — ไม่ผ่าน IPC หน่วงระดับไมโครวินาที ซึ่งเป็นสิ่งที่ทำให้ตัวพาย (paddle) ที่ 60 fps เป็นไปได้ (mod_qwa309_pots.c:5-15) ส่วน RGB matrix อยู่บนบัส I2C ของ header ที่ CM55 เป็นเจ้าของ การเรียก rgbmatrix.* ทุกครั้งจึงเป็นข้อความ IPC แบบส่งแล้วไม่รอผล และ CM33 ไม่เคยแตะบัสนั้นเลย (mod_rgbmatrix.c:6-13) CapSense ก็เป็นของ CM55 เช่นกัน (ทางแยกของ CapSense: หนึ่ง API สอง backend) ส่วนปุ่มและ header เป็น GPIO ของ CM33_NS ล้วน ๆ
การจับคู่ลูกบิดไม่ใช่การจับคู่แบบตรงตัว
s_pots_knob_ch[] มีค่า { 5, 4, 6, 7 } (mod_qwa309_pots.c:55) ดัชนีเชิงตรรกะ 0 (VR1) อ่านช่อง SAR GPIO ที่ 5 และดัชนี 1 (VR2) อ่านช่อง 4 เหตุผลตามที่ซอร์สระบุ: แผ่นวงจร QWA309 เดินลาย P15.4 (ช่อง 4) ไปที่ลูกบิด VR2 และ P15.5 (ช่อง 5) ไปที่ VR1 — ลายสองเส้นแรกสลับกันบนบอร์ด ยืนยันบนฮาร์ดแวร์แล้ว ส่วน VR3 และ VR4 เดินลายตรง
ตารางนี้ต้องเดินคู่กันไปตลอดกับ s_pot_adc_ch[] ใน proj_cm55/modules/cm55_sensor_poll/cm55_sensor_poll.c:80 ซึ่งมีค่า { 5, 4, 6, 7 } เหมือนกันและคอมเมนต์เหมือนกัน หน้าจอฝั่ง CM55 ชดเชยค่าตั้งแต่ 2026-07-31 ส่วนโมดูลนี้ส่งมอบไปโดยยังไม่ได้ชดเชย pots.read(0) จึงคืนค่าของลูกบิดที่พิมพ์ว่า VR2 จนกระทั่งแก้ไขเมื่อ 2026-08-20 หากจะแก้ตารางใดตารางหนึ่ง ต้องแก้ทั้ง 2 ตาราง มิฉะนั้นหน้าจอกับ REPL จะไม่ตรงกันว่าผู้ใช้กำลังหมุนลูกบิดตัวใด — และแต่ละฝั่งจะสอดคล้องกันเองภายในซึ่งเป็นสิ่งที่ทำให้มองเห็นได้ยาก
CM33_NS เป็นผู้เริ่ม SAR ตอนบูต ไม่ใช่โมดูลนี้ init_potentiometer_adc() (proj_cm33_ns/main.c:142) รัน Cy_AutAnalog_Init(&autonomous_analog_init) และ Cy_AutAnalog_StartAutonomousControl() โดยมี BSP_HAS_POTENTIOMETER เป็น gate (ด่านกั้น) และมีการเรียกที่ main.c:224 ไฟล์ design.modus ของ BSP จัดหา struct ตั้งค่ามาให้แต่ init_cycfg_peripherals() ไม่ได้เรียก API สำหรับ init — ดังนั้นหากตัดขั้นตอนนี้ออก ทุกช่องจะอ่านได้ 0 ตลอดไป และไม่มีสิ่งใดรายงาน error ลำดับการบูตที่ทำให้สิ่งนี้ทำงานได้อยู่ที่ B1 — เดินดูลำดับการบูตของ CM33_NS ไม่ต้องอธิบายซ้ำที่นี่
ค่าศูนย์ไม่ได้พิสูจน์อะไรเลย ช่องที่ยังไม่ได้ตั้งค่าก็อ่านได้ 0 เช่นกัน จึงเป็นเหตุผลที่ pots.ready() มีอยู่: มันเปิดเผย gate ของสถานะผลลัพธ์รายช่อง และทั้ง 4 ช่องต้องผลิตผลลัพธ์ออกมาแล้วในการกวาดรอบล่าสุด (mod_qwa309_pots.c:56-60) ให้ใช้ตอน bring-up ไม่ใช่ใช้ทุกเฟรม
มีตัวกรองสัญญาณกระโดด ตัว SAR บางครั้งให้ค่าตัวอย่างผิดพลาดจนเกือบเป็นศูนย์ pots_read_channel() ปฏิเสธการตกลงมามากกว่า 200 นับจนต่ำกว่า 50 หนึ่งครั้ง และยอมรับค่าที่ต่ำติดต่อกันเป็นครั้งที่สองว่าผู้เล่นหมุนลูกบิดไปสุดปลายจริง ๆ (mod_qwa309_pots.c:66-89) หน้าจอฝั่ง CM55 ใช้กฎเดียวกัน เพื่อให้ทั้ง 2 เส้นทางตรงกัน
ปุ่ม 2 ปุ่ม ซึ่งปุ่มหนึ่งเป็นไฟเลี้ยง USB ด้วย
s_btns[] (mod_qwa309_buttons.c:67-68): ดัชนี 0 คือ SW4 บน P17.5 ดัชนี 1 คือ SW5 บน P17.7 ทั้งคู่เป็นแบบ active-low พร้อม pull-up ภายใน MCU และทั้งคู่ผ่าน debounce ด้วยซอฟต์แวร์โดยใช้หน้าต่างค่านิ่ง 50 ms
มี 2 เรื่องที่คอมเมนต์ส่วนหัวของไฟล์ (mod_qwa309_buttons.c:8-22) บันทึกไว้ แต่ API บอกไม่ได้:
- ชื่อของปุ่มมีความเป็นมา ชื่อเหล่านี้คือชื่อที่พิมพ์บนแผ่นวงจรรุ่นผลิตจริงเอกสารรุ่นเก่าเรียกเน็ตเดียวกันนี้ว่า SW9/SW10 และหน้า GPIO และ RGB ของระบบเรียกว่า SW5/SW6 สามระบบการตั้งชื่อ ต่อปุ่มหนึ่งคู่ ให้ยึด buttons.name(n) เป็นเกณฑ์
- P17.5 ยังเป็นขาเปิดใช้ VBUS ของ USB host ด้วย (usbh_config.c) และเน็ตเหล่านี้ใช้ร่วมกับสัญญาณกล้อง DVP การใช้ปุ่มพร้อมกับจอยสติ๊ก USB บนบอร์ดฐานนี้ ไม่รองรับ: การเรียก buttons_hw_init() ครั้งแรกยึด P17.5 ไปเป็นขาอินพุต นี่เป็นการแลกที่ยอมรับแล้ว ไม่ใช่ความหลงลืม — แต่หมายความว่าคำสั่ง import buttons เพียงคำสั่งเดียวทำให้จอยสติ๊กหยุดทำงานได้ และจะไม่มีสิ่งใดแจ้งเตือน
ทีละขั้น
ขั้นที่ 1 — พิสูจน์ว่า SAR ยังมีชีวิตก่อนจะเชื่อค่าที่อ่านได้
- ตัวอย่าง (เขียนขึ้นเอง — ไม่มี call site ในของที่ส่งมอบจริง)
import pots
print(pots.count(), pots.ready())
print(pots.read_all())
- สิ่งที่ควรสังเกต
- ได้ 4 True แล้วตามด้วยทูเพิล 4 ตัวของจำนวนเต็มในช่วง 0..4095 การที่ ready() เป็น false หมายความว่า SAR ยังกวาดช่อง 4..7 ไม่ครบ — ให้ตรวจว่า BSP_HAS_POTENTIOMETER=1 และ init_potentiometer_adc() ยังทำงานอยู่ใน main() ทูเพิลที่เป็นศูนย์ทั้ง 4 ตัว พร้อมกับ ready() เป็น true หมายถึงลูกบิดทั้งสี่อยู่ที่ปลายสุดข้างเดียวกัน ส่วนทูเพิลศูนย์ทั้งสี่พร้อม ready() เป็น false หมายถึงตัวแปลงสัญญาณไม่เคยเริ่มทำงาน การแยก 2 กรณีนี้ออกจากกันคือเหตุผลทั้งหมดที่ ready() มีอยู่
ขั้นที่ 2 — ยืนยันการจับคู่ลูกบิดด้วยมือตัวเอง
- ตัวอย่าง (เขียนขึ้นเอง — ไม่มี call site ในของที่ส่งมอบจริง)
import pots, time
while True:
print(["%.2f" % pots.norm(i) for i in range(4)])
time.sleep_ms(200)
- สิ่งที่ควรสังเกต
- หมุนลูกบิดที่พิมพ์ว่า VR1 แล้วตัวเลข ตัวแรก จะขยับ นั่นคือการแก้ไขที่ทำงานถูกต้อง หากตัวเลขตัวที่สองขยับแทน แสดงว่ากำลังรัน build ที่ s_pots_knob_ch[] เป็นการจับคู่แบบตรงตัว — และหน้า pot บน CM55 จะไม่ตรงกับที่เห็น ซึ่งคือสัญญาณบอกเหตุ
ขั้นที่ 3 — อ่านค่าปุ่มทั้งสอง
- ตัวอย่าง (เขียนขึ้นเอง — ไม่มี call site ในของที่ส่งมอบจริง)
import buttons, time
print(buttons.count(), buttons.name(0), buttons.name(1))
while True:
sw4, sw5 = buttons.read()
if sw4 or sw5:
print("SW4" if sw4 else "", "SW5" if sw5 else "")
time.sleep_ms(50)
- สิ่งที่ควรสังเกต
- ได้ 2 SW4 SW5 แล้วตามด้วยหนึ่งบรรทัดต่อการกดหนึ่งครั้งโดยไม่มีสัญญาณเด้ง — หน้าต่างค่านิ่ง 50 ms อยู่ภายใน buttons_sample() จึงไม่ต้องเพิ่มของตัวเอง buttons.pressed(n) เป็นรูปแบบสำหรับปุ่มเดียว ดัชนีที่อยู่นอกช่วง 0..1 จะโยน ValueError
- Warning
- การรันโค้ดนี้ทำให้จอยสติ๊ก USB หยุดทำงานจนกว่าจะตัดไฟแล้วจ่ายไฟใหม่ P17.5 คือขาเปิดใช้ VBUS และโค้ดนี้ยึดมันไปเป็นขาอินพุต ให้ตัดสินใจว่าจะใช้บอร์ดนี้เพื่ออะไร
ขั้นที่ 4 — แสดงบางอย่างบน RGB matrix
- ตัวอย่าง (เขียนขึ้นเอง — ไม่มี call site ในของที่ส่งมอบจริง)
import rgbmatrix as m, time
m.clear()
m.pixel(0, 0, m.RED)
m.pixel(15, 7, m.CYAN)
time.sleep(1)
m.score(42, m.GREEN)
time.sleep(1)
m.bar(3, 5, m.YELLOW)
time.sleep(1)
m.scroll("BENTO", m.WHITE, 80)
time.sleep(3)
m.scroll("")
m.clear()
- สิ่งที่ควรสังเกต
- พาเนล DFR0522 ทำตามทุกการเรียก pixel() รับ x 0..15 และ y 0..7 score() แสดง 0..9999 ส่วน bar() วาดค่า value เทียบกับ max และ scroll() ส่งข้อความให้ timer บน CM55 ซึ่งรันตัววิ่งข้อความในภาษา C — มันคืนค่าทันทีและวิ่งข้อความต่อไปจนกว่าจะเรียก scroll("")
การเรียกทุกตัวเหล่านี้เป็นแบบ ส่งแล้วไม่รอผล ผ่าน IPC_CMD_UI_RGB_MATRIX และคืนค่า None ไม่มีการตอบรับและไม่มี error หากไม่ได้เสียบพาเนลไว้ การเรียกก็ยังสำเร็จและไม่มีอะไรสว่างขึ้น เป็นโมดูลที่ไม่มีผู้เรียก — ตัวอย่างทุกชิ้นในหัวข้อนี้เขียนขึ้นเอง
ขั้นที่ 5 — ขับคอนเนกเตอร์ header สำหรับต่อขยาย
- ตัวอย่าง (เขียนขึ้นเอง — ไม่มี call site ในของที่ส่งมอบจริง)
import header, time
header.gpio_out(0, 1)
print(header.name(0), header.gpio_in(1))
header.pwm(3, 25, 1000)
header.uart_write(b"hello\r\n")
time.sleep_ms(100)
if header.uart_any():
print(header.uart_read(header.uart_any()))
- สิ่งที่ควรสังเกต
- ดัชนีเชิงตรรกะ 0..5 จับคู่กับ P13.0, P13.3, P13.4, P13.5, P13.6, P13.7 (mod_qwa309_header.c:8-13) pwm() ใช้ตัวนับที่ 5 ของ TCPWM0 บน P13.3 โดยมี P13.4 ถูกขับเป็นสัญญาณเติมเต็มของฮาร์ดแวร์ — ดังนั้น PWM บนขา 3 จึงพาขา 4 ไปด้วย และการเรียก gpio_out(2, ...) หลังจากนั้นจะไปชนกับอุปกรณ์ต่อพ่วง ส่วน UART คือ SCB9 ที่ 115200 8N1 และเป็น UART คนละตัว กับคอนโซล: สิ่งที่เขียนที่นี่จะไม่ปรากฏในเทอร์มินัล เป็นโมดูลที่ไม่มีผู้เรียก ตัวอย่างเขียนขึ้นเอง
โมดูล 2 ตัวที่บทนี้เพียงชี้ให้เห็น
- can (modcan.c:479) — init, send, recv, deinit, stats
- shield.motor_v2 (mod_shield_motor_v2.c:313) — speed, run, stop_all และกลุ่มฟังก์ชัน stepper ได้แก่ stepper_attach, stepper_detach, stepper_rpm, stepper_step, stepper_stop, stepper_release พร้อมค่าคงที่ FORWARD/BACKWARD/BRAKE/RELEASE = 1..4 และ SINGLE/DOUBLE/INTERLEAVE/MICROSTEP = 1..4
ทั้งสองลงทะเบียนอยู่ใต้ flag ของบอร์ดฐานตัวเดียวกัน และทั้งสองเป็นเรื่อง ข้างเคียง ของอุปกรณ์ต่อพ่วง — มันขับสิ่งที่เสียบเข้ากับบอร์ด ไม่ใช่สิ่งที่อยู่บนบอร์ด หน้า Motor Shield ที่คู่กับทั้งสองปิดไว้เป็นค่าเริ่มต้น (bsp_features.mk:77) ด้วยเหตุผลว่าบอร์ดส่วนใหญ่ไม่ได้ติดตั้ง shield ไว้ และเมนูที่ขับฮาร์ดแวร์ที่ไม่มีอยู่คือสิ่งรบกวน ให้เปิดกลับได้เป็นราย build ด้วย ENABLE_PAGE_MOTOR_CTRL=1 ทั้ง 2 โมดูลไม่มีผู้เรียกที่ใดเลยในซอร์สที่ส่งมอบ
กับดัก
- Warning
- การจับคู่ลูกบิดคือ {5, 4, 6, 7} และมันอยู่ใน 2 ไฟล์ mod_qwa309_pots.c:55 และ cm55_sensor_poll.c:80 การแก้ไฟล์หนึ่งโดยไม่แก้อีกไฟล์จะทำให้ REPL และหน้าจอต่างผิดไปคนละทิศทางอย่างสอดคล้องกันเอง
-
การที่ pots.read(n) คืนค่า 0 นั้นกำกวม จนกว่า pots.ready() จะยืนยันว่าการกวาดเสร็จสิ้นแล้ว
-
import buttons แลกมาด้วยจอยสติ๊ก USB P17.5 คือขาเปิดใช้ VBUS และ buttons_hw_init() ยึดมันไป
-
ห้ามเชื่อชื่อปุ่ม 3 ชุดที่ต่างกัน SW4/SW5 คือชื่อที่พิมพ์บนแผ่นวงจรรุ่นผลิตจริง ส่วน SW9/SW10 และ SW5/SW6 ปรากฏในเอกสารรุ่นเก่าและในหน้า GPIO ของระบบให้ถาม buttons.name(n)
-
rgbmatrix ล้มเหลวไม่ได้ ทุกการเรียกเป็น IPC แบบส่งแล้วไม่รอผลพาเนลที่ไม่ได้เสียบให้ผลเป็นความเงียบ ไม่ใช่ข้อยกเว้น
-
header.pwm(3, ...) ยึด P13.4 ไปด้วย เอาต์พุตสัญญาณเติมเต็มเป็นเรื่องของฮาร์ดแวร์ ไม่ใช่ข้อตกลงในการเขียนโค้ด
-
ห้ามเพิ่มเจ้าของ SAR รายที่สอง CM33_NS เป็นผู้เริ่มตัวแปลงสัญญาณ และ CM55 อ่านรีจิสเตอร์ผลลัพธ์ชุดเดียวกัน ทั้งสองเป็นผู้อ่านอย่างเดียวของการแปลงที่วิ่งอิสระ จึงอยู่ร่วมกันได้โดยไม่ต้องมีการจัดสรร (mod_qwa309_pots.c:11-15) การเรียก Cy_AutAnalog_Init() ซ้ำจากที่อื่นทำลายสมดุลนั้น
กล่อง variant
| mtb-mpy | mtb-only |
| โมดูล | pots, buttons, rgbmatrix, header, can, shield | ไม่มี — ไดเรกทอรี mpy/ ของชุดคิตไม่ได้จัดลงแพ็กเกจ |
| การอ่านค่า pot | pots.read_all() บน CM33_NS | cm55_pot_read_all() บน CM55 หรือ Cy_AutAnalog_SAR_ReadResult() พร้อมการจับคู่ {5,4,6,7} ชุดเดียวกัน |
| ปุ่ม | buttons.read() ผ่าน debounce แล้ว | Cy_GPIO_Read ที่เขียนเองบน P17.5 / P17.7 พร้อม debounce ของตัวเอง |
| RGB matrix | rgbmatrix.* ผ่าน IPC | โค้ดฝั่ง CM55 บนบัส I2C ของ header |
| I/O ของ header | header.* | PDL: Cy_GPIO, Cy_TCPWM, Cy_SCB_UART |
| การเริ่ม SAR | main.c:224 ทั้งสอง variant | main.c:224 ทั้งสอง variant |