ข้ามไปยังเนื้อหา

กรองสัญญาณ: EMA กับ Median แล้วแกะโค้ดเกจ

โมดูล 2 — จากจอสู่ฮาร์ดแวร์ · สไลด์: slides.md · ภาพรวมโมดูล · หน้าหลักสูตร

ทำค่าที่สั่นให้นิ่งลงโดยไม่โกงค่าด้วย dsp.EMA กับ dsp.Median เลือกฟิลเตอร์จากหน้าตาของ noise แล้วบอกราคาที่จ่ายเป็นวินาทีได้ จากนั้นแกะโค้ดเกจห้าท่าที่วางค่าดิบกับค่ากรองแล้วไว้บนจอเดียว

เมื่อจบบทเรียนนี้ คุณจะ:

  1. สร้าง dsp.EMA(alpha=0.2) และ dsp.Median(window=5) ครั้งเดียวนอกลูป แล้วป้อนทีละค่าด้วย .update() ได้ถูกท่า และคำนวณผลหนึ่งรอบของทั้งสองตัวด้วยมือได้ถูกต้อง (EMA จาก 62.00 เจอ 63.00 ได้ 62.20 · Median ของ 41 42 95 43 42 ได้ 42)
  2. เลือกฟิลเตอร์จากหน้าตาของ noise (EMA สำหรับการสั่นเล็ก ๆ ต่อเนื่อง Median สำหรับ spike เดี่ยว หรือต่อ Median ก่อน EMA) และบอกราคาเป็นเวลาได้ ด้วย tau = dt × (1 − alpha) / alpha และความหน่วงราวครึ่งหน้าต่างของ Median ที่คาบลูป 200 ms
  3. ชี้ได้ว่าแต่ละท่าในห้าท่าของโค้ดเกจแก้ปัญหาอะไร ได้แก่ ui.screen() กับการอุ่นเครื่องใน try/except, ui.Bar วางทับ ui.Scale กับเกณฑ์ที่ผู้ใช้ตั้งเอง, การครอบ max(0, min(100, …)), บรรทัดคุณภาพของค่า และลูป 200 ms ที่เขียนตัวเลขไม่เกินวินาทีละครั้ง

ต่อจากบทเรียน 2.7 คุณเห็นแล้วว่าเปอร์เซ็นต์ของลูกบิดยังขยับแม้มือหยุดนิ่ง บทเรียนนี้คือคำตอบว่าทำไม และจะทำให้นิ่งลงอย่างไรโดยไม่โกงค่า ทวนก่อนว่า ui.poll() ต้องเรียกทุกรอบ และ value= ของ ui.Label คือขนาดฟอนต์ ส่วน ui.Bar / ui.Scale ใช้ min / max เป็นพิสัยจริง ถ้าเคยเปิด 13_raw_and_filtered.py ของบทเรียน 1.3 มาแล้ว คุณเคยเรียก dsp.EMA กับ dsp.Median ไปอย่างละครั้ง วันนี้เราจะรู้ว่าทำไมถึงเลือกสองตัวนั้น

ค่าดิบสั่นเพราะสามชั้นซ้อนกัน ชั้นไฟฟ้า จอที่รีเฟรช WiFi ที่ส่ง และวงจรข้างเคียงเหวี่ยงแรงดันระดับมิลลิโวลต์เข้ามา ชั้นตัวอุปกรณ์ หน้าสัมผัสของ wiper ถูแทร็กตัวเองจึงไม่นิ่ง 100% และ ชั้นการปัดขั้น ถ้าแรงดันจริงอยู่กึ่งกลางสองขั้น ADC ก็ปัดขึ้นบ้างลงบ้าง การสั่นระดับบิตล่างจึงเป็นปกติของทุกระบบวัด งานของวิศวกรไม่ใช่กำจัดมัน แต่คือเลือกฟิลเตอร์แล้วปกป้องตัวเลือกนั้นได้

dsp.EMA(alpha=0.2) คำนวณ y[n] = alpha · x[n] + (1 − alpha) · y[n−1] ค่าใหม่มีน้ำหนัก alpha อดีตไม่หายแต่จางลงทุกรอบ จาก 62.00 เจอ 63.00 ได้ 62.20 คือขยับแค่ 0.20 ทั้งที่อินพุตกระโดด 1.00 นั่นคือความนิ่งที่ซื้อมาด้วยความหน่วง alpha 0.05 นิ่งมากแต่ตามช้า เหมาะกับค่าที่เปลี่ยนช้าอย่างอุณหภูมิ 0.2 นิ่งพอควรและยังตามทัน ใช้กับลูกบิด 0.8 แทบเท่าค่าดิบ alpha แปลงเป็นเวลาได้ด้วย tau = dt × (1 − alpha) / alpha คือเวลาที่ค่าไล่ถึง 63.2% ของขั้น ที่คาบลูป 200 ms alpha 0.2 ได้ tau ราว 0.8 วินาที ข้อควรรู้: alpha เป็น keyword-only (dsp.EMA(0.2) ได้ TypeError) ไม่ใส่เลยได้ค่าตั้งต้น 0.1 ไม่ใช่ 0.5 และตัวอย่างแรกถูกใช้เป็นค่าตั้งต้นตรง ๆ ไม่ได้คูณ alpha ฟิลเตอร์จึงไม่ต้องไต่ขึ้นจากศูนย์ EMA จำแค่ตัวเลขเดียว จึงเป็นตัวที่ระบบฝังตัวเล็ก ๆ เลือกบ่อยที่สุด และมันคือวงจร RC low-pass ในรูปดิจิทัล โดย alpha = Δt / (RC + Δt)

dsp.Median(window=5) เก็บห้าค่าล่าสุด เรียง แล้วคืนตัวกลาง ค่าที่กระโดดโดดเดี่ยวถูกดันไปอยู่ริมแถวและไม่มีใครหยิบ จาก 41 42 95 43 42 Median คืน 42 เหมือน spike ไม่เคยเกิด ขณะที่ EMA(0.2) จาก 42 เจอ 95 ได้ 52.6 คือกระเด็นตาม ราคาของ Median คือหน่วยความจำเท่า window และความหน่วงราวครึ่ง window ที่ลูป 200 ms หน้าต่างห้าค่ากินเวลา 1.0 วินาที หน่วงราว 0.4 วินาที window ถูกหนีบเงียบ ๆ ไม่มี error (ขอ 4 ได้ 5 ขอ 99 ได้ 15 พื้นคือ 3) เพราะต้องเป็นเลขคี่ และบัฟเฟอร์เป็นอาเรย์ขนาดคงที่ในภาษา C print(med) บอกค่าที่ได้จริง สรุปคือเลือกจาก หน้าตาของ noise ไม่ใช่จากชื่อ และใช้ร่วมกันได้โดยให้ Median เก็บ spike ก่อนส่งต่อให้ EMA

โมดูล dsp มี 8 คลาสกับ 8 ฟังก์ชัน ใช้ได้ครบทั้งสองบอร์ด คลาสทุกตัวมีความจำ จึงต้องสร้างครั้งเดียวนอกลูป และทุกตัวรับทีละค่า ใครมาจาก numpy ต้องเปลี่ยนวิธีคิดตรงนี้ (ข้อยกเว้นคือ fft_mag กับ s16 ที่รับทั้งชุด ใช้ในบทเรียน 3.4–3.6) ตัวกรองอีกสี่ตัวที่ควรรู้จักคือ SMA (เฉลี่ยตรง ๆ หนีบ window 2-64), LPF (EMA ที่ตั้งด้วย cutoff= กับ fs= ถ้าบอก fs=100 ทั้งที่ลูปเดินจริง 5 Hz ตัวเลข cutoff ก็ไม่เป็นความจริง), HPF (เก็บเฉพาะส่วนที่เปลี่ยนเร็ว คืน 0.0 เสมอในรอบแรก) และ Kalman1D (r สูงคือไม่ค่อยเชื่อเซนเซอร์ จึงนิ่งแต่ตามช้า q สูงคือคิดว่าโลกเปลี่ยนเร็ว จึงไวขึ้น)

ครึ่งหลังแกะโค้ดเกจของบทเรียน 2.9 เป็นห้าท่า เฟิร์มแวร์ทำงานให้แล้วราว 70% (ตั้ง ADC สเกลค่า คุย I2C หัก baseline ฟิลเตอร์ภาษา C และการวาด) งานของเราคือ 30% ที่เหลือ คือเลือก widget ตั้ง alpha จัดจังหวะลูป และตัดสินว่าค่าเชื่อได้หรือยัง

  • ท่าที่ 1 ui.screen() มาก่อนเสมอ เพราะการใช้ ui.* ครั้งแรกหยุด sensor auto-task ของเฟิร์มแวร์ หลังจากนั้นเราต้องอ่านค่าเองทุกรอบ แล้วอุ่นเครื่องด้วย sensors.pot.read() ใน try/except เพราะรอบแรกหลังรีเซ็ตอาจต้องรอคอร์จอตอบนาน ไม่มี sensors.init() ทั้งสองบอร์ด
  • ท่าที่ 2 ตัวเลข 55.4% ลอย ๆ ตอบไม่ได้ว่าสูงไหม จึงวาง ui.Bar ทับ ui.Scale (ไม้บรรทัดที่ไม่รับ .value()) ใช้ .ticks(11, 2) ได้ 0 20 40 … 100 เกณฑ์เตือนเป็น ui.Spinbox ที่ผู้ใช้ตั้งเองผ่านปุ่มเพิ่ม/ลด เพราะ spinbox เปล่า ๆ นิ้วเปลี่ยนค่าไม่ได้ และไฟ ui.Led สองดวงที่ .value(0) แล้วหรี่ ไม่ใช่หาย
  • ท่าที่ 3 แถบสัมผัสใช้ไม้บรรทัดชุดเดิม ไฟสองดวงแทนข้อความ BTN0 ON ที่ไม่ผ่านการทดสอบขาวดำ และครอบค่าด้วย max(0, min(100, ...)) เพราะถ้าชิป 4000T ไม่พร้อม ไบต์ที่ได้อาจเป็น 255
  • ท่าที่ 4 แสดงค่าดิบกับค่ากรองแล้วคู่กันด้วยทศนิยมสองตำแหน่ง (ปัดเป็นจำนวนเต็มเมื่อไร ความสั่นก็ถูกซ่อน) และมีบรรทัดคุณภาพของค่าที่บอกว่า “ค่าค้าง” เมื่ออ่านรอบนี้ไม่ได้ โดยตั้งสีก่อนเขียนข้อความ ข้อผิดพลาดที่เจอบ่อยที่สุดคือสร้างฟิลเตอร์ไว้ในลูป ค่าที่ได้จึงเท่าค่าดิบเป๊ะ
  • ท่าที่ 5 ลูป 200 ms เพราะลูปที่เร็วเกินยิงคำสั่งวาดข้ามคอร์ถี่กว่าที่ CM55 วาดทัน เฟรมส่วนเกินถูกทิ้งเงียบ ๆ แถบกับไฟอัปเดตทุกรอบ แต่ตัวเลขเขียนใหม่ไม่เกินวินาทีละครั้งผ่านประตู if sec != last_sec: และ lbl_health.text() ถูกส่งซ้ำทุกรอบนอกประตูนั้นโดยตั้งใจ เพื่อให้จออยู่ในโหมดเร็ว
  • 06_ema_time_constant.py ไม่ต้องใช้เซนเซอร์ ป้อนขั้นบันไดล้วนให้ EMA ทีละ alpha ตั้งแต่ 1.00 ถึง 0.02 ก่อนกดเดินหน้าแต่ละขั้น ลองคำนวณ tau ด้วยสูตรในหัวไฟล์ (DT_MS = 200) แล้วเทียบกับตัวเลขบนจอ เส้นเทาจาง ๆ คือ alpha ของขั้นที่แล้ว ไว้ดูว่าชันขึ้นหรือช้าลง ขั้นแรก alpha 1.00 คือไม่กรองเลย เส้นแดงจึงทับเส้นฟ้าสนิท
  • 08_six_filters_one_signal.py ป้อนสัญญาณสร้างเองเส้นเดียว (ฐาน 40 ขั้นบันไดขึ้น 60 คลื่นสั่นเล็ก ๆ และ spike สองจุด) เข้าตัวกรองทั้งหกพร้อมกัน รันซ้ำได้ผลเดิมทุกครั้ง จึงเถียงกันด้วยตัวเลขได้ ทายก่อนว่าตัวไหนจะกลืน spike ตัวไหนตามขั้นบันไดทันที และตัวไหนคืน 0.0 ในรอบแรก แล้วกดวนดูทีละตัว ลองแก้ FS ให้ไม่ตรงกับ DT_MS แล้วดูว่า LPF กับ HPF เปลี่ยนไปอย่างไร
ไฟล์ ไฟล์นี้สอน
examples/06_ema_time_constant.py alpha ของ EMA แปลว่าอะไรในหน่วยเวลาจริง
examples/08_six_filters_one_signal.py ตัวกรองทั้งหกตัวของ dsp บนสัญญาณเส้นเดียวกัน

สไลด์ของบทเรียนนี้อ้างถึงไฟล์ที่อยู่ในบทเรียนอื่นด้วย:

ภาพจอจาก BENTO Emulator ของตัวอย่างในบทนี้ (คลิกชื่อไฟล์เพื่อเปิดโค้ด)

จอของ examples/06_ema_time_constant.py ขณะรันใน BENTO Emulator: alpha ของ EMA แปลว่าอะไรในหน่วยเวลาจริง
06_ema_time_constant.py alpha ของ EMA แปลว่าอะไรในหน่วยเวลาจริง
จอของ examples/08_six_filters_one_signal.py ขณะรันใน BENTO Emulator: ตัวกรองทั้งหกตัวของ dsp บนสัญญาณเส้นเดียวกัน
08_six_filters_one_signal.py ตัวกรองทั้งหกตัวของ dsp บนสัญญาณเส้นเดียวกัน

คำถามชุดเดียวกันอยู่ใน quiz.yaml สำหรับระบบที่ตรวจอัตโนมัติ

  1. ทีมหนึ่งเขียน ema = dsp.EMA(alpha=0.2) ไว้ในลูป while แล้วพบว่าค่ากรองเท่าค่าดิบเป๊ะทุกรอบ สาเหตุคืออะไร (เลือกหนึ่งข้อ · เป้าหมายข้อ 1)

    • ก) ทุกรอบได้ฟิลเตอร์ใหม่ที่ความจำว่าง และตัวอย่างแรกถูกใช้เป็นค่าตั้งต้นตรง ๆ ค่าที่ออกมาจึงเท่าค่าดิบ
    • ข) alpha 0.2 ต่ำเกินไปจนไม่กรองอะไรเลย
    • ค) ต้องเขียน dsp.EMA(0.2) แบบไม่มีชื่ออาร์กิวเมนต์
    • ง) EMA ใช้กับค่าเปอร์เซ็นต์ไม่ได้ ต้องป้อนค่าดิบ 0-65535
    เฉลย

    ก — ฟิลเตอร์ต้องจำค่าเดิมข้ามรอบ จึงต้องสร้างครั้งเดียวนอกลูป ส่วน alpha เป็น keyword-only เขียน dsp.EMA(0.2) จะได้ TypeError

  2. dsp.EMA(alpha=0.2) มีค่าเดิม 42 แล้วได้ค่าใหม่ 95 ที่เป็น spike ส่วน dsp.Median(window=5) ที่เห็น 41 42 95 43 42 อยู่ในหน้าต่าง ทั้งสองคืนค่าอะไร (เลือกหนึ่งข้อ · เป้าหมายข้อ 1)

    • ก) EMA ได้ 52.6 · Median ได้ 42
    • ข) EMA ได้ 42 · Median ได้ 52.6
    • ค) EMA ได้ 95 · Median ได้ 43
    • ง) EMA ได้ 52.6 · Median ได้ 52.6
    เฉลย

    ก — EMA ได้ 0.2 × 95 + 0.8 × 42 = 52.6 คือกระเด็นตาม ส่วน Median เรียงได้ 41 42 42 43 95 แล้วหยิบตัวกลาง 42 spike ถูกดันไปริมแถวและไม่มีใครหยิบ

  3. ลูปของคุณเดินที่ 200 ms (5 Hz) แต่ตั้ง dsp.LPF(cutoff=2.0, fs=100) ตามตัวอย่างที่ลอกมา ข้อใดถูก (เลือกหนึ่งข้อ · เป้าหมายข้อ 2)

    • ก) ตัวเลข cutoff ที่ตั้งไว้จะไม่เป็นความจริง เพราะ LPF คำนวณ alpha จาก fs ที่เราบอก ไม่ใช่จากคาบลูปจริง
    • ข) ไม่มีผลอะไร เพราะ LPF วัดคาบลูปเองได้
    • ค) LPF จะคืน 0.0 เสมอเหมือน HPF ในรอบแรก
    • ง) บอร์ดจะเร่งลูปขึ้นเป็น 100 Hz ให้เอง
    เฉลย

    ก — LPF กับ EMA เป็นสมการเดียวกัน ต่างแค่ LPF คำนวณ alpha จาก cutoff กับ fs ให้ ถ้าบอก fs ไม่ตรงกับคาบลูปจริง fs ก็โกหก และ cutoff ที่เห็นก็ไม่ใช่ของจริง

  4. ข้อใดถูกเกี่ยวกับการเลือกฟิลเตอร์ เลือกทุกข้อที่ถูก (เลือกได้หลายข้อ · เป้าหมายข้อ 2)

    • ก) สัญญาณที่มี spike เดี่ยวเป็นครั้งคราว Median เก็บได้ทั้งก้อน
    • ข) ใช้ Median เก็บ spike ก่อน แล้วส่งต่อให้ EMA ได้
    • ค) ขอ dsp.Median(window=4) จะได้ ValueError เพราะเป็นเลขคู่
    • ง) ที่คาบลูป 200 ms alpha 0.2 ให้ tau ราว 0.8 วินาที
    • จ) alpha ยิ่งสูง ค่ายิ่งนิ่งแต่ตามช้าลง
    เฉลย

    ก, ข, ง — Median โยน spike ทิ้ง และใช้ต่อกับ EMA ได้ tau = 200 × 0.8 / 0.2 = 800 ms ส่วน window ถูกหนีบเงียบ ๆ ขอ 4 ได้ 5 ไม่มี error และ alpha ต่ำต่างหากที่นิ่งแต่ตามช้า

  5. ในโค้ดเกจ ตัวเลขเปอร์เซ็นต์ถูกเขียนใหม่ในบล็อก if sec != last_sec เท่านั้น ขณะที่แถบกับไฟอัปเดตทุกรอบ 200 ms เหตุผลคืออะไร (เลือกหนึ่งข้อ · เป้าหมายข้อ 3)

    • ก) ตาอ่านตำแหน่งของแถบได้โดยไม่ต้องหยุดอ่าน แต่ตัวเลขที่วิ่งห้าครั้งต่อวินาทีอ่านไม่ทัน คนจะเลิกอ่าน จึงเขียนใหม่ไม่เกินวินาทีละครั้ง
    • ข) ui.Label เขียนได้แค่วินาทีละครั้ง ถ้าถี่กว่านั้นจะเกิด error
    • ค) เพื่อประหยัดโควตา widget
    • ง) เพราะเซนเซอร์ให้ค่าใหม่วินาทีละครั้ง
    เฉลย

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

ทดลองให้ตัวเลขเป็นคนตอบ จดผลลงบันทึกการเรียน

  • รัน 06_ema_time_constant.py จด tau และ “ถึง 63.2% ที่ตัวอย่างที่” ของทุก alpha แล้วเทียบกับที่คำนวณจากสูตร
  • รัน 08_six_filters_one_signal.py ทำตารางหกแถว: ตัวกรอง · กลืน spike ได้ไหม · ตามขั้นบันไดทันไหม แล้วเลือกหนึ่งตัวสำหรับลูกบิดพร้อมเหตุผลหนึ่งประโยค
  • ลองสร้าง dsp.Median(window=4) แล้ว print() ดูว่าได้ window เท่าไรจริง และลอง dsp.EMA(0.2) เพื่อเห็น TypeError ด้วยตาตัวเอง
  • อ่านโค้ดห้าท่าในสไลด์ แล้วเขียนหนึ่งบรรทัดต่อท่าว่าถ้าตัดท่านั้นทิ้ง จอจะผิดอย่างไร

บทเรียน 2.9 ลงมือเติมโค้ดเกจที่แกะวันนี้ให้ทำงานจริงบนบอร์ดจนผ่านเกณฑ์ MVP ของชุดบทเรียน 2.7–2.9 ก่อนไป อย่าลืมลำดับการรัน: เปิด BENTO Playground ค้างไว้ และตอนเสียบสาย USB ยกนิ้วออกจากแผ่นสัมผัสให้หมด

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

  • noise ของเซนเซอร์ในงานของทีมหน้าตาแบบไหน สั่นเล็ก ๆ ต่อเนื่อง มี spike เป็นครั้งคราว หรือทั้งสองอย่าง และคุณจะเลือกฟิลเตอร์อะไร
  • งานของทีมยอมให้ค่าหน่วงได้กี่วินาที แล้ว alpha หรือ window ที่คุณเลือกจ่ายเวลาไปเท่าไร

คำถามทบทวน

ลองตอบเองก่อน แล้วค่อยเปิดดูเฉลย

  1. ทีมหนึ่งเขียน ema = dsp.EMA(alpha=0.2) ไว้ในลูป while แล้วพบว่าค่ากรองเท่าค่าดิบเป๊ะทุกรอบ สาเหตุคืออะไร (เป้าหมายข้อ 1)

    1. ทุกรอบได้ฟิลเตอร์ใหม่ที่ความจำว่าง และตัวอย่างแรกถูกใช้เป็นค่าตั้งต้นตรง ๆ ค่าที่ออกมาจึงเท่าค่าดิบ
    2. alpha 0.2 ต่ำเกินไปจนไม่กรองอะไรเลย
    3. ต้องเขียน dsp.EMA(0.2) แบบไม่มีชื่ออาร์กิวเมนต์
    4. EMA ใช้กับค่าเปอร์เซ็นต์ไม่ได้ ต้องป้อนค่าดิบ 0-65535
    ดูเฉลย

    คำตอบ: A. ทุกรอบได้ฟิลเตอร์ใหม่ที่ความจำว่าง และตัวอย่างแรกถูกใช้เป็นค่าตั้งต้นตรง ๆ ค่าที่ออกมาจึงเท่าค่าดิบ

    ฟิลเตอร์ต้องจำค่าเดิมข้ามรอบ จึงต้องสร้างครั้งเดียวนอกลูป ส่วน alpha เป็น keyword-only เขียน dsp.EMA(0.2) จะได้ TypeError

  2. dsp.EMA(alpha=0.2) มีค่าเดิม 42 แล้วได้ค่าใหม่ 95 ที่เป็น spike ส่วน dsp.Median(window=5) ที่เห็น 41 42 95 43 42 อยู่ในหน้าต่าง ทั้งสองคืนค่าอะไร (เป้าหมายข้อ 1)

    1. EMA ได้ 52.6 · Median ได้ 42
    2. EMA ได้ 42 · Median ได้ 52.6
    3. EMA ได้ 95 · Median ได้ 43
    4. EMA ได้ 52.6 · Median ได้ 52.6
    ดูเฉลย

    คำตอบ: A. EMA ได้ 52.6 · Median ได้ 42

    EMA ได้ 0.2 × 95 + 0.8 × 42 = 52.6 คือกระเด็นตาม ส่วน Median เรียงได้ 41 42 42 43 95 แล้วหยิบตัวกลาง 42 spike ถูกดันไปริมแถวและไม่มีใครหยิบ

  3. ลูปของคุณเดินที่ 200 ms (5 Hz) แต่ตั้ง dsp.LPF(cutoff=2.0, fs=100) ตามตัวอย่างที่ลอกมา ข้อใดถูก (เป้าหมายข้อ 2)

    1. ตัวเลข cutoff ที่ตั้งไว้จะไม่เป็นความจริง เพราะ LPF คำนวณ alpha จาก fs ที่เราบอก ไม่ใช่จากคาบลูปจริง
    2. ไม่มีผลอะไร เพราะ LPF วัดคาบลูปเองได้
    3. LPF จะคืน 0.0 เสมอเหมือน HPF ในรอบแรก
    4. บอร์ดจะเร่งลูปขึ้นเป็น 100 Hz ให้เอง
    ดูเฉลย

    คำตอบ: A. ตัวเลข cutoff ที่ตั้งไว้จะไม่เป็นความจริง เพราะ LPF คำนวณ alpha จาก fs ที่เราบอก ไม่ใช่จากคาบลูปจริง

    LPF กับ EMA เป็นสมการเดียวกัน ต่างแค่ LPF คำนวณ alpha จาก cutoff กับ fs ให้ ถ้าบอก fs ไม่ตรงกับคาบลูปจริง fs ก็โกหก และ cutoff ที่เห็นก็ไม่ใช่ของจริง

  4. ข้อใดถูกเกี่ยวกับการเลือกฟิลเตอร์ เลือกทุกข้อที่ถูก (เป้าหมายข้อ 2)

    1. สัญญาณที่มี spike เดี่ยวเป็นครั้งคราว Median เก็บได้ทั้งก้อน
    2. ใช้ Median เก็บ spike ก่อน แล้วส่งต่อให้ EMA ได้
    3. ขอ dsp.Median(window=4) จะได้ ValueError เพราะเป็นเลขคู่
    4. ที่คาบลูป 200 ms alpha 0.2 ให้ tau ราว 0.8 วินาที
    5. alpha ยิ่งสูง ค่ายิ่งนิ่งแต่ตามช้าลง
    ดูเฉลย

    คำตอบ: A. สัญญาณที่มี spike เดี่ยวเป็นครั้งคราว Median เก็บได้ทั้งก้อน · B. ใช้ Median เก็บ spike ก่อน แล้วส่งต่อให้ EMA ได้ · D. ที่คาบลูป 200 ms alpha 0.2 ให้ tau ราว 0.8 วินาที

    Median โยน spike ทิ้ง และใช้ต่อกับ EMA ได้ tau = 200 × 0.8 / 0.2 = 800 ms ส่วน window ถูกหนีบเงียบ ๆ ขอ 4 ได้ 5 ไม่มี error และ alpha ต่ำต่างหากที่นิ่งแต่ตามช้า

  5. ในโค้ดเกจ ตัวเลขเปอร์เซ็นต์ถูกเขียนใหม่ในบล็อก if sec != last_sec เท่านั้น ขณะที่แถบกับไฟอัปเดตทุกรอบ 200 ms เหตุผลคืออะไร (เป้าหมายข้อ 3)

    1. ตาอ่านตำแหน่งของแถบได้โดยไม่ต้องหยุดอ่าน แต่ตัวเลขที่วิ่งห้าครั้งต่อวินาทีอ่านไม่ทัน คนจะเลิกอ่าน จึงเขียนใหม่ไม่เกินวินาทีละครั้ง
    2. ui.Label เขียนได้แค่วินาทีละครั้ง ถ้าถี่กว่านั้นจะเกิด error
    3. เพื่อประหยัดโควตา widget
    4. เพราะเซนเซอร์ให้ค่าใหม่วินาทีละครั้ง
    ดูเฉลย

    คำตอบ: A. ตาอ่านตำแหน่งของแถบได้โดยไม่ต้องหยุดอ่าน แต่ตัวเลขที่วิ่งห้าครั้งต่อวินาทีอ่านไม่ทัน คนจะเลิกอ่าน จึงเขียนใหม่ไม่เกินวินาทีละครั้ง

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

อ้างอิงบทเรียนนี้

ถ้านำบทเรียนนี้ไปสอน ทำสไลด์ หรือทำเอกสารต่อ ให้อ้างอิงด้วยข้อความนี้ ถ้าดัดแปลงเนื้อหา ให้เติม (ดัดแปลง)ต่อท้ายชื่อบทเรียน

"กรองสัญญาณ: EMA กับ Median แล้วแกะโค้ดเกจ" จาก TESA Open Knowledge โดยสมาคมสมองกลฝังตัวไทย (Thai Embedded Systems Association: TESA) https://github.com/tesaiot/tesa-qualification-program สัญญาอนุญาต CC BY-NC 4.0

ข้อความอ้างอิงภาษาอังกฤษ: "Filtering: EMA vs Median, then the gauge code" from TESA Open Knowledge by the Thai Embedded Systems Association (TESA), https://github.com/tesaiot/tesa-qualification-program, licensed under CC BY-NC 4.0

ลิงก์บทเรียน: https://tesaiot.github.io/tesa-qualification-program/courses/aiot-micropython/m02-ui-to-hardware/l08-filters/

บทเรียนนี้ดัดแปลงจากต้นฉบับด้านล่าง เมื่ออ้างอิงให้คงเครดิตต้นฉบับไว้ด้วย
https://github.com/Advance-Innovation-Centre-AIC/embedded-systems-for-aiot-developer/blob/a80bbe88a34bcb9bb8d991f42f9252b77cdab079/session-05.html (slides 21–41)

วิธีอ้างอิง TESA ฉบับเต็ม

TESA Open Knowledge · © 2026 สมาคมสมองกลฝังตัวไทย (TESA) · CC BY-NC 4.0

เนื้อหาเผยแพร่ภายใต้ CC BY-NC 4.0 นำไปใช้ต่อในงานที่ไม่ใช่เพื่อการค้าได้ โปรดอ้างอิงสมาคมสมองกลฝังตัวไทย (TESA) ทุกครั้ง · วิธีอ้างอิง TESA