ภาพพื้นหลังปกบทเรียน

บทเรียน 2.9 — ลงมือทำ: เกจลูกบิดกับแถบสัมผัส

อนาล็อกและสัมผัส · Potentiometer + CapSense + กรองสัญญาณให้อ่านรู้เรื่อง

โมดูล 2 — จากจอสู่ฮาร์ดแวร์

ต่อจากบทเรียน 2.8 — กรองสัญญาณ: EMA กับ Median แล้วแกะโค้ดเกจ

TESA Open Knowledge · © 2026 สมาคมสมองกลฝังตัวไทย (TESA) · ดัดแปลงจาก AIoT in Action (AIC มหาวิทยาลัยบูรพา) · CC BY-NC 4.0

MVP checkpoint — ผ่านชุดบทเรียนนี้เมื่อ

หมุน pot คุม ui.Bar บน ui.Scale + ตั้งเกณฑ์ด้วย ui.Spinbox + ui.Led บอกสถานะ + นิ้วเลื่อน CapSense คุม ui.Bar + แสดง raw/filtered สองเส้นเทียบกัน

หน้าตาของคำว่า "ผ่าน" 0 50 100 62.4 % ค่าดิบ 40915 โวลต์ ?.??? V 70 เกณฑ์ แถบสัมผัส ดิบ 62.43 % กรอง 62.21 % คุณภาพของค่า อ่านค่าปกติ ทั้ง 33 ชิ้นทำงานพร้อมกัน

แปลเป็นสิ่งที่ตรวจได้จริง

  • [ ] หมุนลูกบิดแล้วแถบเดินตามได้ตลอดช่วง 0 ถึง 100 และตัวเลขบนไม้บรรทัดใต้แถบอ่านออกทุกขีด
  • [ ] ค่าดิบ / เปอร์เซ็นต์ / โวลต์ ขึ้นครบและตรงกันเชิงตรรกะ (สุดขวา ≈ 65535 และ 100% ส่วนโวลต์ให้จดค่าที่อ่านได้จริงไว้เทียบกับมัลติมิเตอร์)
  • [ ] กดปุ่มเพิ่ม/ลด แล้วเลขในช่องเกณฑ์เปลี่ยนตาม และหยุดที่ขอบพิสัย 10 กับ 95 เอง
  • [ ] หมุนลูกบิดข้ามเกณฑ์แล้วไฟสลับกันติด ทีละดวงเท่านั้น ไม่ติดพร้อมกันสองดวง
  • [ ] ลากนิ้วบนแถบสัมผัสแล้วแถบล่างเดินตามตำแหน่งนิ้ว และแตะปุ่มทองแดงแล้วไฟสองดวงติดถูกดวง (ไม่กลับด้าน)
  • [ ] สองบรรทัดขวาล่างแสดงค่าดิบกับค่ากรองพร้อมกัน และเห็นชัดว่าบรรทัดค่ากรองนิ่งกว่า
  • [ ] ตัวเลขบนจอเปลี่ยนวินาทีละครั้ง ไม่ใช่ห้าครั้งต่อวินาที (จ้องดูสิบวินาทีแล้วนับ)
  • [ ] ทีมตอบได้ว่า ถ้าเปลี่ยน alpha เป็น 0.05 กับ 0.8 ผลต่างกันอย่างไร
  • [ ] ถ่ายรูปหน้าจอตอนหมุนลูกบิดค้างไว้ แนบในบันทึกการเรียน

ข้อที่ยากที่สุดไม่ใช่ข้อที่โค้ดยาวที่สุด แต่คือข้อสุดท้ายที่ต้อง อธิบายพฤติกรรมได้

TESA Open Knowledge · © 2026 สมาคมสมองกลฝังตัวไทย (TESA) · ดัดแปลงจาก AIoT in Action (AIC มหาวิทยาลัยบูรพา) · CC BY-NC 4.0

กับดักที่เจอบ่อย (1/3)

อาการ สาเหตุที่แท้จริง วิธีแก้
จอขึ้นครบแต่ทุกค่าค้างที่ 0 ยังไม่ได้เติมจุดอ่านค่าในลูป เติม sensors.pot.percent() / capsense.slider()
TypeError ตอนสร้างฟิลเตอร์ เขียน dsp.EMA(0.2) keyword-only: dsp.EMA(alpha=0.2)
บรรทัด EMA เท่ากับ RAW ทุกรอบ สร้าง dsp.EMA() ไว้ในลูป ความจำถูกล้าง ย้ายไปสร้างครั้งเดียวนอกลูป
ปุ่มสัมผัสรายงานกลับด้าน วางนิ้วค้างตอน บอร์ดบูต ยกนิ้วออกให้หมดแล้ว ถอด USB เสียบกลับ (Dev Kit: ห้ามโยกสวิตช์บนฐาน นั่นคือสวิตช์ไฟ)
OSError ที่บรรทัด sensors.init() บน Eva Kit เฟิร์มแวร์ปฏิเสธคำสั่งนี้ (บน Dev Kit ผ่านเงียบ ๆ แต่ไม่จำเป็น) ลบบรรทัดนั้นทิ้ง ไม่ต้องมี init เลย ทั้งสองบอร์ด
OSError: ... CM55 did not answer ที่บรรทัดแรก ๆ เพิ่งรีเซ็ต คอร์จอยังไม่ตอบสายเซนเซอร์ รอสักครู่แล้วรันซ้ำ ถ้ายังซ้ำค่อยแจ้งผู้สอน
widget หายไปเป็นวินาที จอกระตุก ลืม ui.poll() ในลูป ใส่ ui.poll() ทุกรอบก่อน sleep_ms
ui.Bar ไม่ขยับทั้งที่ค่าเปลี่ยน ส่งค่าทศนิยมเข้าไป ครอบด้วย int() และคุมช่วง 0-100
TESA Open Knowledge · © 2026 สมาคมสมองกลฝังตัวไทย (TESA) · ดัดแปลงจาก AIoT in Action (AIC มหาวิทยาลัยบูรพา) · CC BY-NC 4.0

กับดักที่เจอบ่อย (2/3)

อาการ สาเหตุที่แท้จริง วิธีแก้
ui.Scale ไม่ขยับเลยสักครั้ง มันคือไม้บรรทัด ไม่รับ .value() ตัวที่ต้องขยับคือ ui.Bar ที่วางทับ (แบบวงกลมมีเข็มจริงผ่าน .prop(ui.PROP_SCALE_NEEDLE, ...) — fw 2026-08-20 ขึ้นไป ดูบทเรียน 3.1–3.3)
ตัวเลขบนไม้บรรทัดเบียดกันจนอ่านไม่ออก ขีดเยอะเกินไปสำหรับความกว้างที่มี .ticks(ทั้งหมด, ใส่เลขทุกกี่ขีด) เช่น (11, 2)
ui.Spinbox แตะแล้วค่าไม่เปลี่ยน จอสัมผัสไม่มีลูกบิดหมุน การแตะแค่เลือกตำแหน่งหลัก ต้องมี ui.Button เพิ่ม/ลดข้าง ๆ เสมอ
ช่องเกณฑ์ขึ้น 0070 ทั้งที่ตั้ง 70 ค่าตั้งต้นของ spinbox คือสี่หลัก sp.digits(2, 0) = สองหลัก ไม่มีจุดทศนิยม
ไฟดับแล้วยังเห็นเป็นวงจาง ๆ ตั้งใจ .value(0) คือหรี่ ไม่ใช่หาย ไฟที่หายไปทำให้แยกไม่ออกว่าดับหรือจอเสีย
เปลี่ยนสีแถบตอนเกินเกณฑ์แล้วไม่ได้ผล .color() ของ ui.Bar ไปลงที่ "ราง" ไม่ใช่แถบค่า บอกสถานะด้วยไฟกับตัวหนังสือแทน
ค่าเซนเซอร์ค้างเป็นค่าเดียวตลอด ใช้ ui.* แล้ว sensor auto-task หยุด อ่านเซนเซอร์เองในลูปทุกรอบ
OSError ที่บรรทัด sensors.scan() บน Eva Kit เฟิร์มแวร์ปฏิเสธเพื่อกันบัสชนกัน (บน Dev Kit ผ่าน) ห้ามใช้คำสั่งนี้ในคอร์สนี้ทั้งสองบอร์ด ใช้ sensors.snapshot() แทน
TESA Open Knowledge · © 2026 สมาคมสมองกลฝังตัวไทย (TESA) · ดัดแปลงจาก AIoT in Action (AIC มหาวิทยาลัยบูรพา) · CC BY-NC 4.0

กับดักที่เจอบ่อย (3/3)

อาการ สาเหตุที่แท้จริง วิธีแก้
AttributeError ตอนอ่าน pot รันบน AI Kit เปล่า ๆ ที่ไม่มีฐาน ลูกบิดและ CapSense มีบน Eva Kit และ Dev Kit
AttributeError: sht40 / dps368 สองชิปนี้ไม่มีบน Eva Kit ธง BSP ตั้งไว้ 0 (Dev Kit มี) บน Eva ไม่มีทางแก้ด้วยโค้ด ใช้ตัวเลขที่พิมพ์เองป้อน dsp.dew_point() แทน
Median(window=4) แล้วได้ 5 หนีบ 3-15 และบังคับเป็นเลขคี่ เงียบ ๆ ไม่ใช่บั๊ก · print(med) บอกค่าที่ได้จริง
SMA(window=200) แล้วนิ่งน้อยกว่าที่คิด หนีบเพดานไว้ 64 เงียบ ๆ เหมือนกัน ขอเกินได้ แต่ไม่ได้ตามขอ ตรวจด้วย print()
HPF บรรทัดแรกได้ 0.0 ทุกครั้ง ยังไม่มีค่าก่อนหน้าให้ลบ ปกติ ทิ้งค่าแรกไปหนึ่งรอบ
LPF(cutoff=2, fs=100) แต่กรองไม่เหมือนที่คำนวณ ลูปจริงเดิน 5 Hz ไม่ใช่ 100 Hz fs ต้องเท่ากับคาบลูปจริง ไม่ใช่ค่าตั้งต้น
sensors.auto_rate(5) แล้วไม่มีอะไรเปลี่ยน หนีบไว้ 20 ms และบน Eva Kit งานเบื้องหลังก็ไม่ได้เดินอยู่แล้ว "เรียกได้" ไม่ได้แปลว่า "มีผล"

เจ็ดในยี่สิบสามข้อนี้ไม่ได้เกิดจากโค้ดผิด แต่เกิดจาก ลำดับการทำงานกับฮาร์ดแวร์ อีกสี่ข้อเกิดจาก ค่าที่ถูกหนีบเงียบ ๆ และแถวส่วนใหญ่ของตารางที่ 2 เกิดจาก การเข้าใจ widget ผิดตัว — ทั้งสามกลุ่มไม่มี error ให้เห็นสักบรรทัด

TESA Open Knowledge · © 2026 สมาคมสมองกลฝังตัวไทย (TESA) · ดัดแปลงจาก AIoT in Action (AIC มหาวิทยาลัยบูรพา) · CC BY-NC 4.0

ลงมือทำ — เติมช่องว่างในไฟล์ฝึก

เปิด s05_pot_capsense.py มีช่องว่างให้เติม 6 จุด

# เติม: ema = dsp.EMA(alpha=0.2)
pass

while True:
    try:
        # เติม: pct = sensors.pot.percent()
        pass

        # เติม: slider = sensors.capsense.slider()
        pass
    except OSError:
        fresh = False

    # เติม: pot_bar.value(int(max(0, min(100, pct))))
    pass

    # เติม: ema_pct = ema.update(pct)
    pass

    # เติม: events = ui.poll()
    events = []
    pass
เติมแล้วจอ "ตื่น" ทีละส่วน จุดที่ 2 เสร็จ ตัวเลขเปอร์เซ็นต์เริ่มขยับ จุดที่ 3 เสร็จ แถบเดินไปตามไม้บรรทัด จุดที่ 5 เสร็จ ไฟสองดวงเริ่มสลับกันติด จุดที่ 6 เสร็จ ปุ่มเกณฑ์กดติด จอไม่กระตุก
TESA Open Knowledge · © 2026 สมาคมสมองกลฝังตัวไทย (TESA) · ดัดแปลงจาก AIoT in Action (AIC มหาวิทยาลัยบูรพา) · CC BY-NC 4.0

ลงมือทำ (ต่อ) — เติมทีละจุด รันทีละครั้ง แล้วดูจอ "ตื่น" ทีละส่วน

ไฟล์ฝึกตั้งค่าเริ่มต้นไว้ให้แล้ว (pct = 0.0, slider = 0, events = []) โปรแกรมจึง รันได้ตั้งแต่ยังไม่เติมอะไรเลย แต่ตัวเลขเปอร์เซ็นต์ แถบ และค่ากรองจะนิ่งสนิท (ค่าดิบกับโวลต์ขยับตั้งแต่แรก เพราะสองบรรทัดนั้นไม่มีช่องว่าง) ใช้ตรงนี้เป็นเครื่องมือ: เติมทีละจุด รันทีละครั้ง แล้วดูว่าจอ "ตื่น" ขึ้นทีละส่วน

หน้าจอทั้ง 33 ชิ้นเขียนไว้ให้ครบแล้ว ไม่ต้องแตะ งานของเราคือทำให้ค่าไหลเข้าไปในนั้น จุดที่ 6 คือจุดที่คนข้ามบ่อยที่สุด เพราะไฟล์รันได้อยู่แล้วโดยไม่มีมัน อาการที่ตามมาคือปุ่มเพิ่ม/ลดเกณฑ์กดไม่ติด ซึ่งไม่มี error ให้เห็นเลยสักบรรทัด

ตัวอย่างของบทเรียน 2.7–2.9 — สามไฟล์แรกทำในบทเรียนให้จบ ที่เหลือเปิดตามอาการ

ต้องทำในบทเรียน · เปิดตามลำดับนี้ ทั้งชุดราว 40 นาที

ลำดับ · เรื่อง · เวลา ไฟล์ ลงมือทำอะไร แล้วจะเข้าใจอะไร
1 · เลขจาก ADC ไม่ใช่โวลต์ · 10 นาที 05_adc_counts_to_volts.py ลดจำนวนบิตทีละท่าแล้วดูบันไดหยาบขึ้น · จะแปลงเลขดิบเป็นโวลต์ได้เอง เพราะรู้แล้วว่าต้องรู้แรงดันเต็มสเกลกับจำนวนบิตเสมอ
2 · แถบสัมผัสคุมความสว่าง · 15 นาที 01_capsense_dimmer.py ลากนิ้วแล้วปล่อย แล้วดูว่าเส้นแดงค้างอยู่ตอนไม่มีนิ้ว · จะรู้ว่า slider() เท่ากับ 0 ไม่ได้แปลว่าปล่อยนิ้ว ต้องดูว่าค่าหยุดเปลี่ยนแทน
3 · alpha แปลเป็นวินาทีได้ · 15 นาที 06_ema_time_constant.py เดินทีละท่าเปลี่ยน alpha แล้วอ่านค่า tau ที่ไฟล์คำนวณให้ · จะเลือก alpha ของ MVP จากตัวเลข ไม่ใช่จากการเดา
TESA Open Knowledge · © 2026 สมาคมสมองกลฝังตัวไทย (TESA) · ดัดแปลงจาก AIoT in Action (AIC มหาวิทยาลัยบูรพา) · CC BY-NC 4.0

ตัวอย่างของบทเรียน 2.7–2.9 (ต่อ) — เปิดตามอาการ และอ่านเสริมนอกเวลา

ติดตรงไหน เปิดอันนี้

อาการที่เจอ ไฟล์ที่ตอบอาการนั้น
แตะแถบสัมผัสแล้วไม่แน่ใจว่าบอร์ดรับไปหรือยัง โดยเฉพาะตอนมือเปียก 02_capsense_menu_wet_hand.py — ปุ่มสัมผัสไม่มีแรงต้านให้นิ้วรู้สึก จึงต้องตอบกลับด้วยจอ ไฟ หรือเสียงทุกครั้ง
กราฟที่กรองแล้วยังกระโดดตามค่าหลุดค่าเดียวอยู่ดี 07_median_beats_mean.py — ค่าเฉลี่ยเอาค่าที่หลุดมาบวกด้วย ส่วน median เรียงแล้วหยิบตัวกลาง ค่าหลุดจึงไม่มีสิทธิ์ถูกหยิบ · หน้าต่าง N ทน spike ที่ติดกันได้ไม่เกิน (N-1)//2 ตัว
ปล่อยมือจากลูกบิดแล้ว ค่าที่รายงานยังกระดิกไม่หยุด 03_pot_setpoint_deadband.py — dead-band ตัดการกระดิกทิ้งโดยผู้ใช้ไม่รู้สึกว่าเสียอะไร นิสัยนี้ใช้ยาวไปถึงบทเรียน 4.4–4.6 ตอนต้องลดจำนวนข้อความที่ส่งออก

อ่านเสริมนอกเวลา — เรื่องนี้อยู่นอกเกณฑ์ผ่านของบทเรียน 2.7–2.9 แต่ตอบคำถามที่ทีมเสียงมักติด: 04_pot_taper_volume.py ครึ่งทางของลูกบิดไม่ใช่ครึ่งหนึ่งของความดังที่หูได้ยิน หูตอบสนองเป็นลอการิทึม การแมปตรง ๆ จึงรู้สึกว่าดังพรวดตั้งแต่ยังหมุนไม่ถึงไหน

โปรแกรมที่รันได้แต่ยังไม่ถูกต้อง ดีบักง่ายกว่าโปรแกรมที่รันไม่ขึ้นเสมอ

TESA Open Knowledge · © 2026 สมาคมสมองกลฝังตัวไทย (TESA) · ดัดแปลงจาก AIoT in Action (AIC มหาวิทยาลัยบูรพา) · CC BY-NC 4.0

เชื่อมโยงรากฐาน — วันนี้เราแตะอะไรไปบ้าง

อิเล็กทรอนิกส์และการวัด อนาล็อก → ดิจิทัล · ความละเอียด แรงดันอ้างอิง · baseline ประมวลผลสัญญาณ noise มาจากไหน · EMA · Median แลกความนิ่งกับความหน่วง Python และการออกแบบ ออบเจกต์ที่จำสถานะข้ามรอบ ตรวจขอบเขตค่าจากภายนอก สามเสาที่วันนี้แตะพร้อมกัน เสาแรกคือเสาที่ทำให้เราตอบได้ว่า "ตัวเลขนี้เชื่อได้แค่ไหน" ไม่ใช่แค่ "ตัวเลขนี้คืออะไร"

ฝั่งอิเล็กทรอนิกส์และการวัด
การแปลงอนาล็อกเป็นดิจิทัล · ความละเอียด (บิต) กับช่วงการวัด · แรงดันอ้างอิงและผลของมันต่อค่าที่อ่านได้ · การตรวจจับแบบ capacitive · แนวคิด baseline และการวัดแบบส่วนต่าง

ฝั่งการประมวลผลสัญญาณ
noise มาจากไหนและมีกี่ชั้น · ค่าเฉลี่ยถ่วงน้ำหนักแบบมีความจำ (EMA) · ตัวกลางแบบหน้าต่างเลื่อน (Median) · การแลกกันระหว่างความนิ่งกับความหน่วง

ฝั่ง Python และการออกแบบระบบ
ออบเจกต์ที่เก็บสถานะข้ามรอบลูป · keyword-only arguments · การตรวจขอบเขตค่าจากภายนอก · การออกแบบหน้าจอให้หนึ่งค่าเล่าได้หลายหน่วย

เรื่อง trade-off ระหว่าง "นิ่ง" กับ "ไว" จะกลับมาหาผู้เรียนอีกในบทเรียน 3.1–3.3 และ 3.4–3.6 แต่คราวนั้นข้อมูลจะมาจาก IMU

TESA Open Knowledge · © 2026 สมาคมสมองกลฝังตัวไทย (TESA) · ดัดแปลงจาก AIoT in Action (AIC มหาวิทยาลัยบูรพา) · CC BY-NC 4.0

งานทำเอง 30% + สรุปบทเรียน

อ่านลูกบิด raw · % · โวลต์ อ่านสัมผัส ปุ่ม + แถบเลื่อน รู้ที่มาของ noise สามชั้นซ้อนกัน กรองแล้วอธิบายได้ EMA เทียบ RAW ชุดบทเรียนถัดไป IMU + dsp.tilt สี่อย่างที่ติดมือไปชุดบทเรียนถัดไป ฟิลเตอร์ตัวเดิมจะได้ใช้ทันทีกับข้อมูลจาก IMU ที่สั่นกว่านี้อีก

วันนี้เราได้:
อ่านค่าอนาล็อกจากลูกบิดครบสามหน่วยและอธิบายที่มาของแต่ละหน่วยได้ · เข้าใจว่า ADC ปัดค่าอย่างไรและทำไมบิตล่างถึงกระพริบ · อ่านปุ่มสัมผัสและแถบเลื่อนพร้อมรู้เรื่อง baseline · ใช้ dsp.EMA และ dsp.Median แล้วเห็นผลเทียบกันบนจอเดียว

การบ้านของทีม: เลือกทำ 1 ข้อจากสี่ข้อในสไลด์ถัดไป จดลงบันทึกการเรียน

ชุดบทเรียนถัดไป: เราจะเปลี่ยนจากลูกบิดที่มีคนหมุน ไปเป็นเซนเซอร์ที่วัดโลกจริง — sensors.bmi270.motion() กับ dsp.tilt() แล้วสร้างเครื่องวัดระดับดิจิทัลที่บอกองศาการเอียงของบอร์ดได้จริง ฟิลเตอร์ที่เรียนวันนี้จะได้ใช้ทันทีในชุดบทเรียนถัดไป

เก็บค่า alpha ที่ทีมชอบไว้ให้ดี ชุดบทเรียนถัดไปจะได้ไม่ต้องลองใหม่ตั้งแต่ศูนย์

TESA Open Knowledge · © 2026 สมาคมสมองกลฝังตัวไทย (TESA) · ดัดแปลงจาก AIoT in Action (AIC มหาวิทยาลัยบูรพา) · CC BY-NC 4.0

เฉลย s05_pot_capsense.py — ส่วนที่หนึ่ง: ตั้งเวที

อ่านให้เข้าใจ แล้วพิมพ์เอง อย่าคัดลอกวาง

import ui
ui.screen()
import time
import sensors
import dsp

ui.clear()
time.sleep_ms(200)

COL_TEXT, COL_DIM = 0xE8EAED, 0x9AA3AF
COL_CARD = 0x171B22
COL_OK, COL_WARN, COL_BAD = 0x30A46C, 0xF5A623, 0xE5484D

TH_MIN, TH_MAX, TH_STEP = 10, 95, 5
th = 70              # เกณฑ์ตั้งต้น หน่วยเปอร์เซ็นต์

# ไม่มี sensors.init() ในไฟล์นี้ และไม่ควรมี (ทั้งสองบอร์ด)
try:
    sensors.pot.read()   # อุ่นเครื่อง รอบแรกหลังรีเซ็ตอาจต้องรอคอร์จอตอบ
except OSError:
    print("อ่านเซนเซอร์รอบแรกยังไม่ได้ - ลองใหม่ในลูป")
สีคือ "สถานะ" ไม่ใช่การตกแต่ง ขาว - ค่าที่ต้องอ่าน เทา - ป้ายกำกับ อ่านทีหลังได้ สงวนให้สถานะเท่านั้น สถานะปกติต้องเงียบ สีจัดที่ใช้กับของธรรมดา ทำให้สีเตือนหมดความหมาย
TESA Open Knowledge · © 2026 สมาคมสมองกลฝังตัวไทย (TESA) · ดัดแปลงจาก AIoT in Action (AIC มหาวิทยาลัยบูรพา) · CC BY-NC 4.0

เฉลย — ส่วนที่หนึ่ง (ต่อ): ทำไมจานสีถึงเปลี่ยน

เฉลยรุ่นนี้เปลี่ยนจานสีทั้งชุด จากเดิมที่ให้ สีบอกหน่วย (เขียว = ค่าดิบ, ฟ้า = เปอร์เซ็นต์, เหลือง = โวลต์) มาเป็นจานสีของแผงควบคุมจริง ที่ สีบอกสถานะ เท่านั้น

เหตุผลไม่ใช่ความชอบ: สีเหลืองในหน้าจออุตสาหกรรมแปลว่า "เฝ้าระวัง" ตายตัว การเอาไปใช้กับบรรทัดโวลต์ที่ปกติดีทุกอย่าง คือการสอนให้ตาคนชินกับสีเตือน แล้ววันที่มันเตือนจริง จะไม่มีใครเห็น หน่วยของแต่ละบรรทัดบอกด้วย คำ ได้อยู่แล้ว ไม่ต้องยืมสีมาบอก

ค่าสีถูกตั้งเป็นค่าคงที่ชื่ออ่านรู้เรื่องไว้บนสุด ไม่ได้ใส่เลข 0x30A46C ลงไปกลางโค้ดตรง ๆ เพราะวันหนึ่งที่อยากเปลี่ยนธีมทั้งจอ เราจะแก้ที่เดียวจบ — ค่าทั้งชุดคือจานสีของหลักสูตร (พื้น 0x0E1116 · การ์ด 0x171B22 · ปุ่มรอง 0x3A4150 · ตัวหนังสือ 0xE8EAED · จาง 0x9AA3AF · ปกติ/เตือน/เสีย 0x30A46C 0xF5A623 0xE5484D) · การอุ่นเครื่องอยู่ใน try/except เพราะ OSError รอบแรกหลังรีเซ็ตเป็นเรื่องปกติทั้งสองบอร์ด ไม่ใช่ความผิดพลาด

เลขฐานสิบหกที่โผล่กลางโค้ดโดยไม่มีชื่อ คือหนี้ที่คนอ่านคนถัดไปต้องมาจ่าย

TESA Open Knowledge · © 2026 สมาคมสมองกลฝังตัวไทย (TESA) · ดัดแปลงจาก AIoT in Action (AIC มหาวิทยาลัยบูรพา) · CC BY-NC 4.0

เฉลย — ส่วนที่สอง: วางหน้าจอสี่การ์ด รวม 33 ชิ้น

ui.Label("แผงคุมลูกบิดกับแถบสัมผัส", x=16, y=8, color=COL_TEXT, value=24)

# การ์ดบนซ้าย - ค่าที่วัดได้ พร้อมพิสัยของมันเอง
ui.Panel(x=12, y=40, w=468, h=200, color=COL_CARD, min=COL_DIM, max=12, value=1)
ui.Label("ลูกบิดเทียบพิสัย 0-100", x=28, y=48, color=COL_DIM, value=20)
pot_bar = ui.Bar(x=28, y=80, w=428, h=16, color=0x4A9EFF, min=0, max=100, value=0)
pot_scale = ui.Scale(x=28, y=92, w=428, h=44, color=COL_TEXT, min=0, max=100)
pot_scale.ticks(11, 2)
...
lbl_pct = ui.Label("0.0 %", x=28, y=180, color=COL_TEXT, value=28)
lbl_raw = ui.Label("ค่าดิบ 0", x=252, y=148, color=COL_DIM, value=16)
lbl_volt = ui.Label("โวลต์ 0.000 V", x=252, y=172, color=COL_DIM, value=16)

# การ์ดบนขวา - เกณฑ์ที่ผู้ใช้ตั้งเอง กับไฟสองดวง
ui.Panel(x=492, y=40, w=288, h=224, color=COL_CARD, min=COL_DIM, max=12, value=1)
...
sp_th = ui.Spinbox(x=508, y=76, w=256, h=88, color=COL_TEXT,
                   min=TH_MIN, max=TH_MAX, value=th)
sp_th.digits(2, 0)
btn_up = ui.Button("เพิ่ม", x=644, y=172, w=120, h=88, color=0x3A4150, value=16)
btn_dn = ui.Button("ลด", x=508, y=172, w=120, h=88, color=0x3A4150, value=16)
...
led_ok = ui.Led(x=492, y=304, w=48, h=48, color=COL_OK, value=1)
...
led_bad = ui.Led(x=492, y=348, w=48, h=48, color=COL_BAD, value=0)
ผังหน้าจอ สี่การ์ด แผงคุมลูกบิดกับแถบสัมผัส ค่า + พิสัย 55.4 % เกณฑ์ + ไฟ 70 แถบสัมผัส + ไฟปุ่ม คุณภาพของค่า ดิบ เทียบ กรอง ดิบ 55.43 กรอง 54.99 33 ชิ้น - เกินงบคอร์ส 32 ไปหนึ่ง
TESA Open Knowledge · © 2026 สมาคมสมองกลฝังตัวไทย (TESA) · ดัดแปลงจาก AIoT in Action (AIC มหาวิทยาลัยบูรพา) · CC BY-NC 4.0

เฉลย — ส่วนที่สอง (ต่อ): นับ widget และจองความกว้างไว้ตั้งแต่แรก

นับ widget ให้ครบทุกครั้งก่อนรัน: 33 ชิ้น เกินงบของคอร์ส 32 ไปหนึ่งชิ้น (ยังต่ำกว่าเพดานเฟิร์มแวร์ 64) งานต่อยอดจึงต้องเอาชิ้นเดิมออกก่อนเพิ่มชิ้นใหม่ · ข้อเสนอ (ไม่บังคับ): ชิ้นที่ตัดได้ก่อนคือป้ายนิ่ง "อัลฟา 0.2" ทั้งในไฟล์ฝึกและเฉลย เพราะเป็นข้อความตายตัวที่ซ้ำกับ dsp.EMA(alpha=0.2) ในโค้ด และจะแสดงค่าผิดทันทีที่ลองเปลี่ยน alpha ตัดแล้วเหลือ 32 พอดีงบ · ui.Panel สี่ใบไม่ได้มีไว้ให้สวย มันคือเส้นที่บอกตาว่า "ของกลุ่มนี้เกี่ยวกันนะ" หน้าจอที่ของทุกชิ้นลอยอยู่บนพื้นเดียวกันหมด คนดูต้องจัดกลุ่มเองด้วยสายตาทุกครั้งที่มอง

ทุกตัวถูกสร้างด้วยข้อความตั้งต้นที่มีรูปแบบเหมือนตอนมีค่าจริง เช่น "โวลต์ 0.000 V" ไม่ใช่ "..." เพราะการจองความกว้างไว้ตั้งแต่แรกทำให้ข้อความไม่กระตุกเวลาความยาวเปลี่ยน

ข้อควรรู้เรื่องความยาวข้อความ: คำสั่งสร้าง widget มีท่อกว้าง 95 ไบต์ ส่วน .text() กว้าง 126 ไบต์ เฟิร์มแวร์ 2026-08-20 ขึ้นไปส่งส่วนที่เกิน 95 ไบต์ซ้ำทาง .text() ให้เอง (modui.c) ป้ายจึงพาได้ 126 ไบต์ทั้งตอนสร้างและตอน .text() ตรงกับบทเรียน 1.2 ภาษาไทยตัวละ 3 ไบต์ แปลว่าราว 42 ตัวอักษร ยาวกว่านั้น ถูกตัดเงียบ ๆ ไม่มี error ให้เห็น · เฟิร์มแวร์รุ่นก่อนตัดที่ 95 ไบต์ (ราว 31 ตัวอักษรไทย) ตั้งแต่ตอนสร้าง ถ้าป้ายยาวขาดกลางคำ ให้ตั้งสั้นตอนสร้างแล้วเขียนเต็มด้วย .text()

ออกแบบหน้าจอโดยคิดถึง "ตอนมีค่าจริง" เสมอ ไม่ใช่ตอนที่ยังว่างเปล่า

TESA Open Knowledge · © 2026 สมาคมสมองกลฝังตัวไทย (TESA) · ดัดแปลงจาก AIoT in Action (AIC มหาวิทยาลัยบูรพา) · CC BY-NC 4.0

เฉลย — ส่วนที่สาม: ลูปที่ทำงานจริง

ema = dsp.EMA(alpha=0.2)        # สร้างนอกลูป
last_sec = -1
while True:
    fresh = True
    try:
        raw = sensors.pot.read()
        volts = sensors.pot.voltage()
        pct = sensors.pot.percent()
        slider = sensors.capsense.slider()
        b0, b1 = sensors.capsense.buttons()
    except OSError:
        fresh = False
        b0, b1 = 0, 0

    pot_bar.value(int(max(0, min(100, pct))))
    touch_bar.value(max(0, min(100, slider)))
    ema_pct = ema.update(pct)

    over = ema_pct >= th            # หนึ่งดวงติดเท่านั้น
    led_ok.value(0 if over else 1)
    led_bad.value(1 if over else 0)
    led_b0.value(1 if b0 else 0)
    led_b1.value(1 if b1 else 0)

    for ev in ui.poll():
        if ev.get("type") != "clicked":
            continue
        if ev.get("handle") == btn_up.id():
            th = min(TH_MAX, th + TH_STEP)
            sp_th.value(th)
        elif ev.get("handle") == btn_dn.id():
            th = max(TH_MIN, th - TH_STEP)
            sp_th.value(th)

    sec = time.ticks_ms() // 1000   # ตัวเลขเปลี่ยนวินาทีละครั้ง
    if sec != last_sec:
        last_sec = sec
        lbl_pct.text("{:.1f} %".format(pct))
        lbl_raw.text("ค่าดิบ {}".format(raw))
        lbl_volt.text("โวลต์ {:.3f} V".format(volts))
        raw_line.text("ดิบ    {:.2f} %".format(pct))
        ema_line.text("กรอง  {:.2f} %".format(ema_pct))

    time.sleep_ms(200)
สองจังหวะในลูปเดียว 1 - อ่านทั้งชุดใน try เดียว 2 - แถบ + ไฟ ทุกรอบ 5 ครั้งต่อวินาที 3 - poll แล้วรับปุ่มเกณฑ์ 4 - ตัวเลข วินาทีละครั้ง if sec != last_sec ของที่ตาอ่านเป็นตำแหน่ง เดินเร็วได้
TESA Open Knowledge · © 2026 สมาคมสมองกลฝังตัวไทย (TESA) · ดัดแปลงจาก AIoT in Action (AIC มหาวิทยาลัยบูรพา) · CC BY-NC 4.0

เฉลย — ส่วนที่สาม (ต่อ): try เดียว และเกณฑ์จากค่าที่กรองแล้ว

การอ่านทั้งห้าบรรทัดอยู่ใน try เดียวกันโดยตั้งใจ: ถ้ารอบนี้คอร์จอไม่ตอบ เราไม่อยากได้ครึ่งชุดเก่าครึ่งชุดใหม่ปนกันบนจอ ค่าทั้งหน้าจะเป็นชุดเดียวกันเสมอ และธง fresh จะไปบอกบรรทัดคุณภาพของค่าว่าตัวเลขที่เห็นตอนนี้เป็นของรอบก่อน

over = ema_pct >= th ใช้ค่าที่ กรองแล้ว ไม่ใช่ค่าดิบ ถ้าใช้ค่าดิบ ไฟจะกระพริบสลับดวงตอนค่าอยู่คาบเกี่ยวกับเกณฑ์พอดี ซึ่งเป็นอาการที่แผงควบคุมจริงเรียกว่า alarm chattering และเป็นเหตุผลหนึ่งที่คนหน้างานปิดเสียงเตือนทิ้ง

ลองแก้ alpha เป็น 0.05 แล้ว 0.8 ดู แล้วจ้องสองบรรทัดขวาล่างสิบวินาที — บทเรียนทั้งบทเรียนอยู่ในความต่างนั้น

TESA Open Knowledge · © 2026 สมาคมสมองกลฝังตัวไทย (TESA) · ดัดแปลงจาก AIoT in Action (AIC มหาวิทยาลัยบูรพา) · CC BY-NC 4.0

เฉลย · ทำไมเรียงห้าท่าแบบนี้ ไม่ใช่สุ่มเรียง

1 ตั้งจอ + อุ่นเครื่อง 2 ลูกบิด + เกณฑ์ 3 CapSense 4 ฟิลเตอร์ 5 จังหวะลูป ของที่ไม่ต้องพึ่งใคร เพิ่มตัวแปรใหม่ทีละหนึ่ง ทำให้อยู่ได้นาน พังตอนไหนก็รู้ทันทีว่าพังที่ของที่เพิ่งเพิ่ม ไม่ใช่ที่ของเดิม

ท่า 1 ตั้งจอ แล้วรอให้คอร์จอตอบ มาก่อน เพราะถ้าจอไม่ขึ้นหรือ sensors.pot.read() รอบอุ่นเครื่องยังขึ้น OSError ท่าที่เหลือไม่มีทางถูกต้องได้เลย

ท่า 2 ลูกบิดกับเกณฑ์ มาก่อน CapSense เพราะลูกบิดต่ออยู่กับ ADC ในชิปหลักโดยตรง ไม่ต้องพึ่งชิปตัวที่สองและไม่ต้องพึ่งบัส I2C — ถ้าท่านี้ผ่าน แปลว่าเส้นทาง sensor → Python → จอ ใช้ได้แล้วทั้งเส้น

ท่านี้พ่วง พิสัย (ui.Scale ใต้แถบ) กับ เกณฑ์ (ui.Spinbox และไฟสองดวง) ไว้ด้วยกัน เพราะมันคือคำถามเดียวกัน: ค่านี้เทียบกับอะไร — แยกไปคนละท่า ผู้เรียนจะจำได้แค่ว่า "มี widget อีกตัว" ไม่ใช่ว่า "ค่าที่วัดได้ห้ามอยู่ลำพัง"

ท่า 3 CapSense มาทีหลัง เพราะมันเพิ่มตัวแปรใหม่เข้ามาสองอย่าง (ชิป 4000T และบัส I2C) ถ้าพังตอนนี้ เรารู้ทันทีว่าพังที่ของใหม่ ไม่ใช่ที่ของเก่า

ท่า 4 ฟิลเตอร์กับบรรทัดคุณภาพของค่า ต้องมาหลังจากเห็นค่าดิบสั่นด้วยตาตัวเองแล้ว ไม่งั้นจะไม่มีวันรู้ว่ามันแก้ปัญหาอะไรให้ · สองอย่างนี้อยู่ท่าเดียวกันเพราะตอบคำถามเดียวกัน: "ตัวเลขที่เห็นเชื่อได้แค่ไหน"

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

TESA Open Knowledge · © 2026 สมาคมสมองกลฝังตัวไทย (TESA) · ดัดแปลงจาก AIoT in Action (AIC มหาวิทยาลัยบูรพา) · CC BY-NC 4.0

เชื่อมจุดให้เห็นภาพ — วันนี้อยู่ตรงไหนของเส้นทาง

บทเรียน 2.1–2.6 อินพุตแบบมี/ไม่มี ปุ่มกด สวิตช์บนจอ วันนี้ · บทเรียน 2.7–2.9 อินพุตแบบมีระดับ และการทำให้มันเชื่อถือได้ บทเรียน 3.1–3.9 เซนเซอร์วัดโลกจริง ฟิลเตอร์ตัวเดิมได้ใช้ต่อ บทเรียน 4.1–5.3 ส่งค่าที่กรองแล้ว ขึ้นเครือข่าย จาก 0/1 → มีระดับ → มาจากโลกจริง → ส่งออกไปให้คนอื่นใช้ต่อ

คำถามคิดต่อ: ถ้าต้องส่งค่าลูกบิดขึ้นคลาวด์ทุก 5 วินาที จะส่งค่าดิบหรือค่าที่กรองแล้ว · การส่งค่าที่กรองแล้วทำให้เราสูญเสียข้อมูลอะไรไปบ้าง · ถ้าปลายทางเป็นระบบแจ้งเตือน ความหน่วงจากฟิลเตอร์มีราคาเท่าไร

TESA Open Knowledge · © 2026 สมาคมสมองกลฝังตัวไทย (TESA) · ดัดแปลงจาก AIoT in Action (AIC มหาวิทยาลัยบูรพา) · CC BY-NC 4.0

ใช้จริงที่ไหน — สี่มุมของงานอนาล็อกในสนามจริง

โรงงาน · วาล์วและตัวปรับความเร็ว ลูกบิดหน้าตู้คุมความเร็วมอเตอร์ผ่าน ADC เดียวกันนี้ ถ้าไม่กรอง มอเตอร์จะกระตุกตามบิตล่างที่กระพริบ ฟิลเตอร์ที่ใช้: EMA alpha ต่ำ เพราะยอมให้หน่วงได้ เครื่องมือแพทย์ · หัววัดชีพจร สัญญาณระดับมิลลิโวลต์ปนกับ noise จากไฟ 50 Hz ต้องนิ่งพอให้อ่าน แต่ห้ามหน่วงจนแจ้งเตือนช้า ฟิลเตอร์ที่ใช้: Median กัน spike + EMA ปรับให้ลื่น เครื่องใช้ไฟฟ้า · แผงสัมผัสไร้ปุ่ม เตาไฟฟ้า ลิฟต์ ตู้เย็น ใช้ CapSense แบบเดียวกับบอร์ดนี้ ไม่มีรูให้ฝุ่นและน้ำเข้า ทำความสะอาดง่าย อายุยาว โจทย์จริง: baseline ต้องขยับตามความชื้นและอุณหภูมิ เกษตร · วัดความชื้นดิน หัววัดในดินให้แรงดันอนาล็อกที่แกว่งตลอดเวลา กรองแล้วค่อยตัดสินใจเปิดปั๊ม ไม่งั้นปั๊มจะเปิด-ปิดรัว โจทย์จริง: ค่าที่กรองแล้วยังต้องมี hysteresis อีกชั้น

ทั้งสี่มุมนี้ใช้ความรู้ชุดเดียวกับที่เพิ่งเขียนไป ต่างกันแค่ว่าค่าที่ผิดพลาดไปหนึ่งเปอร์เซ็นต์ ราคาของมันแพงแค่ไหน

TESA Open Knowledge · © 2026 สมาคมสมองกลฝังตัวไทย (TESA) · ดัดแปลงจาก AIoT in Action (AIC มหาวิทยาลัยบูรพา) · CC BY-NC 4.0

ต่อยอด — คิดต่อเอง (เลือกทำ 1 ข้อ)

1 · สนาม alpha RAW · 0.05 · 0.5 จับเวลาที่ตามทัน 2 · Median ปะทะ spike เคาะบอร์ดสร้าง spike บันทึกว่าใครกันได้ 3 · ลูกบิดสั่งไฟ ช่วงเท่ากันตาม num_leds() กันกะพริบด้วยช่วงเผื่อ 4 · ปุ่มสลับหน้าที่ BTN0 สลับหน่วย BTN1 เปิด-ปิดฟิลเตอร์ ทั้งสี่ข้อต่อยอดจากไฟล์เฉลยเดิม ไม่ต้องเขียนใหม่ทั้งไฟล์

ข้อ 1 · สนามทดลองค่า alpha — ทำสามบรรทัดเทียบกัน: RAW, EMA(0.05), EMA(0.5) บนจอเดียว หมุนเร็ว ๆ แล้วปล่อยนิ่ง จดว่าแต่ละเส้นตามทันต่างกันกี่วินาที

ข้อ 2 · Median ปะทะ spike — ใส่ dsp.Median(window=5) เพิ่มอีกบรรทัด แล้วจงใจสร้าง spike ด้วยการเคาะบอร์ด บันทึกว่า EMA กับ Median รับมือต่างกันอย่างไร

ข้อ 3 · ลูกบิดสั่งไฟจริง — ใช้ gpio.led() จากบทเรียน 2.1–2.3 คู่กับค่าเปอร์เซ็นต์: ยิ่งหมุนมากยิ่งติดหลายดวง — แบ่ง 0-100 % เป็นช่วงเท่ากันตาม gpio.num_leds() (Eva 3 ดวง = ดวงละ 33 % · Dev Kit 5 ดวง = ดวงละ 20 %) ไม่พิมพ์ 3 ตายตัว · กันกะพริบตรงรอยต่อด้วยช่วงเผื่อ ไม่ใช่จุดเดียว · ดวงที่ต้องเป็นสีให้หาตามชื่อใน gpio.board_info()["led_names"] แบบ led_named() ใน 01_imu_vibration_monitor.py

ข้อ 4 · ปุ่มสัมผัสสลับหน้าที่จอ — BTN0 สลับหน่วยของเกจ · BTN1 เปิด-ปิดฟิลเตอร์ · ตรวจขอบขาขึ้นแบบบทเรียน 2.1–2.3 กันแตะครั้งเดียวสลับหลายรอบ

เขียนคำตอบลงบันทึกการเรียน แล้วเอามาเล่าให้เพื่อนฟังต้นชุดบทเรียนถัดไป

TESA Open Knowledge · © 2026 สมาคมสมองกลฝังตัวไทย (TESA) · ดัดแปลงจาก AIoT in Action (AIC มหาวิทยาลัยบูรพา) · CC BY-NC 4.0

เลือกด้วยนิ้ว ไม่ใช่กดวน — ui.Roller

หน้าจอของ 10_roller_picks_the_filter.py: เลือกด้วยการปัดนิ้ว ไม่ใช่กดวนทีละครั้ง

ภาพจากตัวจำลอง bento_sim ซึ่งเรนเดอร์ด้วยโค้ด CM55 ชุดเดียวกับที่รันบนบอร์ด ที่ 800x480 เท่าจอของ Eva Kit และ Dev Kit — แสดงหน้าจอที่ 10_roller_picks_the_filter.py สร้าง สัญญาณบนกราฟไฟล์นั้นสร้างขึ้นเอง ไม่ได้มาจากลูกบิดหรือแผ่นสัมผัส

ชุดบทเรียนนี้มีตัวกรองหกตัวให้เลือก และวิธีเลือกก็เป็นบทเรียนของมันเอง — 08_six_filters_one_signal.py ใช้ปุ่ม "ตัวถัดไป" ซึ่งแปลว่าจะไปดู Kalman ต้องกดห้าครั้ง และตลอดเวลานั้นไม่มีใครเห็นว่ามีอะไรให้เลือกบ้าง

ตัวเลือกยาว ๆ ทำด้วยอะไรได้บ้าง ข้อเสียบนจอสัมผัส 4.3 นิ้ว
ปุ่ม "ตัวถัดไป" ที่กดวน ซ่อนรายการทั้งชุด และไปตัวที่ห้าต้องกดห้าครั้ง
ui.Dropdown ต้องแตะเปิดก่อนถึงจะเห็นตัวเลือก และรายการไปทับของอื่น
ui.Roller กางตัวเลือกค้างไว้ เลื่อนด้วยนิ้วรวดเดียว ตัวที่เลือกอยู่ไฮไลต์ตลอดเวลา
TESA Open Knowledge · © 2026 สมาคมสมองกลฝังตัวไทย (TESA) · ดัดแปลงจาก AIoT in Action (AIC มหาวิทยาลัยบูรพา) · CC BY-NC 4.0

ui.Roller (ต่อ) — ใช้ที่ไหน และสองเรื่องที่วัดมาแล้ว

ในงานจริงมันคือ widget ของ "ทางเลือกที่มีจำกัดและรู้ล่วงหน้า" — โหมดเครื่อง ช่วงพิสัยของหัววัด กะการทำงาน ปลายทางที่จะส่งข้อมูล ทั้งหมดนี้เป็นชุดที่ปิดแล้ว ไม่ใช่ของที่ผู้ใช้พิมพ์เอง

สองเรื่องที่วัดมาแล้วและต้องรู้ก่อนใช้

  • value= ตอนสร้างทำสองหน้าที่พร้อมกัน คือ บรรทัดที่เลือก และ ขนาดฟอนต์ไทย (ui_widget_mgr.c ส่ง cfg->init_val เข้า ui_apply_content_font() ตัวเดียวกัน) ตั้ง value=24 เพราะอยากได้ตัวหนังสือขนาด 24 จะได้บรรทัดที่ 24 แถมมาด้วย — ไฟล์ตัวอย่างจึงไม่ตั้ง value= เลย แล้วสั่งเลือกด้วย .value(n) ทีหลัง
  • .prop(ui.PROP_VISIBLE_ROWS, n) เขียนทับความสูงที่ตั้งไว้ด้วย h= และคำนวณจากฟอนต์ธีม ไม่ใช่ฟอนต์ไทยที่วงล้อใช้จริง วัดบนตัวจำลอง: ขอ 5 บรรทัด ได้กล่องเตี้ยลงจนเห็นจริง 3 บรรทัด — เลือกอย่างใดอย่างหนึ่ง อย่าสั่งทั้งสองทาง

จุดที่ต้องมองตอนตรวจภาษาไทย — ดูที่แถบสีเน้น ไม่ใช่ที่บรรทัดอื่น เพราะ LVGL วาดบรรทัดที่เลือกจากส่วน LV_PART_SELECTED คนละส่วนกับบรรทัดที่เหลือ ตั้งฟอนต์ไทยที่ MAIN อย่างเดียวจะได้ตัวอักษรทุกบรรทัดยกเว้นบรรทัดที่เลือก

TESA Open Knowledge · © 2026 สมาคมสมองกลฝังตัวไทย (TESA) · ดัดแปลงจาก AIoT in Action (AIC มหาวิทยาลัยบูรพา) · CC BY-NC 4.0

หน้าจอของทุกไฟล์ในชุดบทเรียนนี้ (1/3)

หน้าจอของ 01_capsense_dimmer.py: สไลเดอร์สัมผัสเป็นสวิตช์หรี่ไฟ หน้าจอของ 02_capsense_menu_wet_hand.py: เมนูสัมผัสสองปุ่ม และเรื่องมือเปียก หน้าจอของ 03_pot_setpoint_deadband.py: ลูกบิดตั้งค่า พร้อมแถบตาย

01 สไลเดอร์สัมผัสเป็นสวิตช์หรี่ไฟ · 02 เมนูสัมผัสสองปุ่ม และเรื่องมือเปียก · 03 ลูกบิดตั้งค่า พร้อมแถบตาย
ภาพจากตัวจำลอง bento_sim ซึ่งเรนเดอร์ด้วยโค้ด CM55 ชุดเดียวกับที่รันบนบอร์ด ที่ 800x480 เท่าจอของ Eva Kit และ Dev Kit — แสดงหน้าจอที่ตัวอย่างสร้าง ค่าจากเซนเซอร์ WiFi และไมค์เป็นค่าแทนบนเครื่องโฮสต์ ไม่ใช่ผลการวัดของบอร์ด
TESA Open Knowledge · © 2026 สมาคมสมองกลฝังตัวไทย (TESA) · ดัดแปลงจาก AIoT in Action (AIC มหาวิทยาลัยบูรพา) · CC BY-NC 4.0

หน้าจอของทุกไฟล์ในชุดบทเรียนนี้ (2/3)

หน้าจอของ 04_pot_taper_volume.py: ทำไมลูกบิดเสียงต้องเป็นเส้นโค้ง หน้าจอของ 05_adc_counts_to_volts.py: เลขดิบจาก ADC ไม่ใช่แรงดัน มันคือจำนวนขั้น หน้าจอของ 06_ema_time_constant.py: alpha ของ EMA แปลว่าอะไรในหน่วยเวลาจริง

04 ทำไมลูกบิดเสียงต้องเป็นเส้นโค้ง · 05 เลขดิบจาก ADC ไม่ใช่แรงดัน มันคือจำนวนขั้น · 06 alpha ของ EMA แปลว่าอะไรในหน่วยเวลาจริง
ภาพจากตัวจำลอง bento_sim ซึ่งเรนเดอร์ด้วยโค้ด CM55 ชุดเดียวกับที่รันบนบอร์ด ที่ 800x480 เท่าจอของ Eva Kit และ Dev Kit — แสดงหน้าจอที่ตัวอย่างสร้าง ค่าจากเซนเซอร์ WiFi และไมค์เป็นค่าแทนบนเครื่องโฮสต์ ไม่ใช่ผลการวัดของบอร์ด
TESA Open Knowledge · © 2026 สมาคมสมองกลฝังตัวไทย (TESA) · ดัดแปลงจาก AIoT in Action (AIC มหาวิทยาลัยบูรพา) · CC BY-NC 4.0

หน้าจอของทุกไฟล์ในชุดบทเรียนนี้ (3/3)

หน้าจอของ 07_median_beats_mean.py: ค่าหลุดหนึ่งค่า ทำลายค่าเฉลี่ย แต่ทำอะไร median ไม่ได้ หน้าจอของ 08_six_filters_one_signal.py: ตัวกรองทั้งหกตัวของ dsp บนสัญญาณเส้นเดียวกัน หน้าจอของ 09_sensors_api_tour.py: เรียกทุกชื่อในโมดูล sensors แล้วดูว่าใครตอบ ใครปฏิเสธ

07 ค่าหลุดหนึ่งค่า ทำลายค่าเฉลี่ย แต่ทำอะไร median ไม่ได้ · 08 ตัวกรองทั้งหกตัวของ dsp บนสัญญาณเส้นเดียวกัน · 09 เรียกทุกชื่อในโมดูล sensors แล้วดูว่าใครตอบ ใครปฏิเสธ
ภาพจากตัวจำลอง bento_sim ซึ่งเรนเดอร์ด้วยโค้ด CM55 ชุดเดียวกับที่รันบนบอร์ด ที่ 800x480 เท่าจอของ Eva Kit และ Dev Kit — แสดงหน้าจอที่ตัวอย่างสร้าง ค่าจากเซนเซอร์ WiFi และไมค์เป็นค่าแทนบนเครื่องโฮสต์ ไม่ใช่ผลการวัดของบอร์ด
TESA Open Knowledge · © 2026 สมาคมสมองกลฝังตัวไทย (TESA) · ดัดแปลงจาก AIoT in Action (AIC มหาวิทยาลัยบูรพา) · CC BY-NC 4.0

อ้างอิงและเครดิต

เอกสารและตำรา

ภาพ

  • วงจร Pot/Thermistor + ผัง CAPSENSE™ — คู่มือคิต รูปที่ 77 และรูปที่ 52 ใช้เพื่อการเรียนการสอน
  • สาธารณสมบัติ: “Potentiometer linear” · “Series RC capacitor voltage”
  • CC0 1.0: “23mm knob…01(DXO)” (Retired electrician) · “EMA weights N=15” (Д.Ильин)
  • CC BY 3.0: “Quantization error” (G. Maxwell) · “ADC animation 20” (R. Puskarcik) · “Capacitive touchscreen” (Medvedev)
  • CC BY-SA 4.0: “4-bit Successive Approximation DAC” (Uwezi)
  • ที่เหลือวาดใหม่เป็น inline SVG ทั้งหมด

ทุกตัวเลขบนสไลด์ชุดนี้สืบกลับไปที่เอกสารต้นทางหรือซอร์สโค้ดได้ ยกเว้นข้อที่เขียนกำกับไว้ว่ายังต้องวัด

TESA Open Knowledge · © 2026 สมาคมสมองกลฝังตัวไทย (TESA) · ดัดแปลงจาก AIoT in Action (AIC มหาวิทยาลัยบูรพา) · CC BY-NC 4.0

หมายเหตุผู้สอน — ตัวอย่างโค้ด และข้อที่ยังไม่ลงตัว

ตัวอย่างโค้ด — โฟลเดอร์ examples/ ของบทเรียน 2.7–2.9 สิบไฟล์ (ใบล่าสุดคือ 10_roller_picks_the_filter.py) · 07_median_beats_mean.py ย้ายมาจาก โฟลเดอร์ examples/ ของบทเรียน 3.1–3.3 เมื่อ 14 ส.ค. เพราะเรื่องที่มันสอนคือการกรองสัญญาณ ซึ่งเป็นเนื้อหาของชุดบทเรียนนี้ · เพิ่มใหม่ 15 ส.ค. 08_six_filters_one_signal.py ครอบ SMA LPF HPF Kalman1D ที่เดิมไม่มีตัวอย่างเลยสักไฟล์ และ 09_sensors_api_tour.py เรียกเก้าฟังก์ชันของโมดูล sensors จริง ๆ เพื่อพิสูจน์ว่าใครตอบใครปฏิเสธ · ตัวอย่าง SMA ที่เคยอยู่ใน examples/dsp/ ถูกถอดออกจากคลังเมื่อ 14 ส.ค. โฟลเดอร์นั้นจึงว่างอยู่

ข้อที่ยังไม่ลงตัว — ต้องวัดก่อนสอน

แรงดันอ้างอิงของ pot: ผังวงจร Eva แสดง VDD_1V8 แต่ sensors.pot.voltage() ของเฟิร์มแวร์คูณด้วย 3.3 V ที่สมมติไว้ ทั้งสองบอร์ด — ห้ามใช้ตัวเลขใดตัวเลขหนึ่งเป็นข้อเท็จจริงจนกว่าจะวัดที่ขาลูกบิดของบอร์ดจริง (Eva: P15[1] · Dev Kit: VR1) · ทิศหมุนและ taper ของ VR1 บน Dev Kit ก็ยังไม่ได้วัด

เปลี่ยนจากเฟิร์มแวร์รุ่นก่อน — บน Eva Kit ถูกปฏิเสธด้วย OSError ทั้งหมด ห้าตัว ไม่ใช่สองตัว: init() scan() push() live_push() auto() · read_all() ไม่ถูกปฏิเสธ แต่บน Eva มันคืนผลของ snapshot() ตัวเดียวกัน · auto_rate() และ auto_status() เรียกได้ตามปกติ · sensors.pot.* / sensors.capsense.* / sensors.bmi270.* อ่านผ่าน snapshot ของคอร์จอ ส่วน sensors.bmm350.* อ่านตรงจากชิปได้ เพราะมันอยู่คนละบัส (I3C ขา P3[0]/P3[1]) ที่คอร์จอไม่ได้ถือไว้ · บน TESAIoT Dev Kit ห้าตัวนั้นทำงานจริง (live_push() วนจนกด Ctrl+C) · read_all() คืน pot เป็น float · snapshot() มีแล้วและคืน dict หน้าตาเดียวกับ Eva — IMU ในนั้นอ่านสดจาก CM33 ส่วนช่อง pot กับ capsense มาจากคอร์จอที่อ่านทุก 200 ms · sensors.pot.read() ต่างหากที่อ่าน SAR ตรง · มี dps368 sht40 radar เพิ่ม · ตรวจจากซอร์ส BENTO-TESAIoT-libraries/claw/common/mpy/modsensors.c และ moddsp*.c โดยตรง

ข้อที่เขียนกำกับว่ายังต้องวัด ให้วัดก่อนขึ้นสอน แล้วค่อยเขียนตัวเลขลงสไลด์

TESA Open Knowledge · © 2026 สมาคมสมองกลฝังตัวไทย (TESA) · ดัดแปลงจาก AIoT in Action (AIC มหาวิทยาลัยบูรพา) · CC BY-NC 4.0

หลักเดิมจากชุดบทเรียนก่อน ๆ ยังใช้ได้: เพิ่มตัวแปรใหม่ทีละหนึ่ง แล้วทุกความผิดพลาดจะชี้กลับไปที่สิ่งที่เราเพิ่งเพิ่มเสมอ