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

บทเรียน 3.4 — สุ่มสัญญาณให้ถูก: Nyquist aliasing และ ring buffer

ui.Chart หลาย Series: เห็นสัญญาณเป็นเส้นเวลา ไม่ใช่ตัวเลขกระพริบ

โมดูล 3 — แสดงผลเซนเซอร์บน HMI

คาถาประจำบทเรียน: ตัวเลขบอกว่าตอนนี้เท่าไร กราฟบอกว่ามันกำลังจะไปทางไหน

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

ดูของจริงก่อน — กราฟที่วิ่งอยู่บนบอร์ดตั้งแต่บทเรียนแรก

Sensor Dashboard — accel 3 แกน Z Y X ลองสามอย่างตามลำดับ วางนิ่ง — เส้น Z ลอยสูงกว่าเพื่อน เขย่าเบา ๆ — รอยกระเพื่อมเลื่อนซ้าย เอียงค้าง — เส้นยกตัวแล้วค้างระดับใหม่

วันนี้เราจะสร้างหน้านี้ขึ้นมาเอง ด้วยโค้ดไม่ถึงเจ็ดสิบบรรทัด

บทเรียน 3.1–3.3 เราวัด "ตอนนี้เอียงกี่องศา" — ชุดบทเรียนนี้เราจะเก็บ "สิบวินาทีที่ผ่านมาเป็นยังไง"

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

ทำไม · คืออะไร · ทำยังไง — แผนที่ของชุดบทเรียนนี้

คำถาม คำตอบของชุดบทเรียนนี้ อยู่ช่วงไหน
Why ตัวเลขบนจอก็อ่านค่าได้อยู่แล้ว ทำไมต้องมีกราฟ เพราะตัวเลขตอบได้คำถามเดียวคือ "ตอนนี้เท่าไร" ส่วนคำถามที่งานจริงถามคือ "เมื่อกี้มันเป็นยังไง" และ "มันกำลังจะไปทางไหน" · และการสุ่มไม่ทันไม่ส่งเสียงเตือน มันให้คำตอบที่ผิดและดูน่าเชื่อถือ ต้องรู้ล่วงหน้าว่าสัญญาณของเราเร็วแค่ไหน ครึ่งแรก · Nyquist และ aliasing
What มีอะไรให้ใช้บ้าง ui.Chart กับเมธอดของ Widget 14 ตัวที่ต้องรู้ — สองตัวเป็นของกราฟล้วน ๆ · บวกตัวสร้าง widget 16 ตัว ที่กางในตารางอ้างอิงของชุดบทเรียนนี้ (บัญชีเต็มของโมดูล ui อยู่ในบทเรียน 2.4) สไลด์บัญชี 14 เมธอด + ตารางอ้างอิง widget
How ประกอบยังไงให้ใช้งานได้จริง สร้างกราฟหนึ่งใบสามเส้น ป้อนด้วย int() ทุกรอบ · ปุ่มเริ่ม/หยุดบันทึกที่เป็น flag ในลูป ไม่ใช่การหยุดลูป · ตารางเก็บค่าสุดขีดที่กราฟจำไม่ได้ · และนาฬิกาที่จับคาบลูปจริงมาโชว์ สามไฟล์ตัวอย่าง + ไฟล์ฝึก

ปลายทางที่จับต้องได้ — กราฟสามสีของแกน X Y Z วิ่งตามการเขย่ามือ มีปุ่มหยุด-เดินต่อที่ทำงานจริง และบรรทัดที่บอกว่าลูปของเราหมุนอยู่ที่กี่มิลลิวินาที

บทเรียน 3.1–3.3 เราวัด "ตอนนี้เอียงกี่องศา" · ชุดบทเรียนนี้เราเก็บ "สิบวินาทีที่ผ่านมาเป็นยังไง"

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

เป้าหมายของชุดบทเรียนนี้

  1. อธิบายได้ว่า time series คืออะไร และ อัตราสุ่ม (sampling rate) มีผลกับสิ่งที่เราเห็นอย่างไร
  2. ใช้ ui.Chart กับ .add_series() และ .set_next() ได้ถูกต้อง รวมถึงรู้ว่าทำไมต้อง int()
  3. เลือกช่วงแกน Y เองเป็น และรู้ว่าค่าที่เกินช่วงจะหายไปไหน
  4. ทำปุ่มเริ่ม/หยุดบันทึกที่แยกกันด้วย flag ในลูป ที่ทำงานได้จริง ไม่ใช่ปุ่มที่กดแล้วเงียบ — ให้ไฟกับป้ายเป็นคนบอกสถานะ
  5. วัด คาบลูปจริง ด้วย time.ticks_ms() / time.ticks_diff() แล้วเอาขึ้นจอ
  6. รู้ว่าทำไม ลูปที่มีแต่ set_next() ถึงวาดช้ากว่าลูปที่มี .text() อยู่ด้วยถึง 40 เท่า

ปลายทางของวันนี้: จอบอร์ดมีกราฟสามสีวิ่งตามการเขย่า ตารางค่าสุดขีดสามแกน ปุ่มเริ่ม/หยุดบันทึก และตัวเลขบอกว่าลูปของเราหมุนจริงที่กี่มิลลิวินาที

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

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

ปลายทางของชุดบทเรียนนี้ — กราฟที่วิ่งตามมือเรา

หน้าจอจาก BENTO Emulator ของเฉลยกราฟความเร่งสามแกนแบบ real-time

หน้าจอจริงจากการรันโค้ดเฉลยบน BENTO Emulator ที่ 800x480 เท่าจอของ Eva Kit และ Dev Kit — ไม่ใช่ภาพวาด ไม่ใช่ mock-up และไม่ใช่ภาพถ่ายจากบอร์ด

หน้าจอของเฉลยปัจจุบันแบ่งเป็นสามส่วน และสองส่วนแรกตอบคนละคำถามกัน

  • ซ้าย ui.Chart เส้นสามสีของแกน X Y Z ตอบคำถามว่า "เมื่อกี้มันเป็นยังไง" (ตอนบอร์ดวางนิ่ง เส้นจึงราบ)
  • ขวา ui.Table สี่คอลัมน์ แกน · ต่ำสุด · สูงสุด · ล่าสุด ตอบคำถามที่กราฟตอบไม่ได้ คือ ค่าสุดขีดที่ผ่านไปแล้ว เพราะกราฟเก็บได้เท่าจำนวนช่องของมัน (ปริยาย 50) จุดที่เก่ากว่านั้นถูกเขี่ยทิ้งไปแล้ว
  • ซ้ายล่าง ใต้กราฟ ui.Led บอกว่ากำลังบันทึกอยู่หรือหยุดแล้ว บรรทัดคาบลูปจริง และ ปุ่มเริ่มกับปุ่มหยุดที่แยกกันคนละปุ่ม (สูง 88 px ตามขนาดเป้าสัมผัส)

กราฟตอบ "เมื่อกี้มันเป็นยังไง" ส่วนตารางตอบ "ที่ผ่านมาแรงสุดเท่าไร" — คนละคำถาม จึงต้องมีทั้งคู่

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

ทบทวนบทเรียน 3.1–3.3 — แถบกับตัวเลขตอบคำถามหนึ่งแบบ กราฟตอบอีกแบบ

บทเรียน 3.1–3.3 เราอ่าน sensors.bmi270.motion() → dsp.tilt(ax, ay, az) → (roll, pitch) → ui.Bar บน ui.Scale สองแกน + ui.Seg7

สิ่งที่แถบกับ Seg7 ทำได้ดี: บอก ค่าปัจจุบัน ชัดเจน · สิ่งที่ทำไม่ได้เลย: บอกว่า เมื่อกี้เป็นยังไง

คำถามที่ทีมอยากรู้ Bar / Seg7 Chart
ตอนนี้เอียงกี่องศา ตอบได้ทันที ต้องเพ่งที่ปลายเส้น
เมื่อ 5 วินาทีที่แล้วสั่นไหม ตอบไม่ได้ ตอบได้
การสั่นถี่ขึ้นหรือเบาลง ตอบไม่ได้ เห็นเป็นรูปร่างทันที
มีค่ากระโดดผิดปกติแวบเดียวไหม พลาดแน่นอน เห็นเป็นหนามบนเส้น

ภาพขวาคือสัญญาณ accel จริงตอนคนเดิน 10 ก้าว — ยอดที่ทำเครื่องหมายสีแดงคือก้าว ข้อมูลชุดนี้อ่านจากแถบหรือตัวเลขไม่ได้เลย ต้องเห็นเป็นเส้นเวลาเท่านั้น

กราฟขนาดความเร่งขณะเดิน ยอดที่ซ้ำกันเป็นจังหวะคือก้าวเดิน

ภาพ: Zieliński M. et al., Sensors 25(20):6358 (2025), CC BY 4.0

เลือก widget ตาม คำถามที่ต้องการคำตอบ ไม่ใช่ตามความสวย

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

Time series คืออะไร — และ sample-and-hold ที่อยู่เบื้องหลัง

Time series คือข้อมูลชุดหนึ่งที่ แต่ละค่ามีเวลากำกับ และเรียงตามเวลา

เซนเซอร์ให้สัญญาณต่อเนื่องซึ่งมีค่าอยู่ทุกเสี้ยววินาที แต่คอมพิวเตอร์เก็บค่าต่อเนื่องไม่ได้ มันทำได้อย่างเดียวคือ แอบมองเป็นระยะ ๆ แล้วจดค่าไว้ — เรียกว่า การสุ่มตัวอย่าง (sampling)

fs=1Tsf_s=\frac{1}{T_s}

ไทย: อัตราสุ่มคือส่วนกลับของระยะห่างระหว่างการมองสองครั้ง
ตัวเลขของลูปเรา: Ts=200T_s = 200 ms ⇒fs=5\Rightarrow f_s = 5 Hz

วงจร sample-and-hold (ภาพขวาบน) คือฮาร์ดแวร์ที่ทำเรื่องนี้จริง ๆ ในชิป: ปิดสวิตช์ชั่วขณะเพื่อชาร์จตัวเก็บประจุ แล้วเปิดสวิตช์ค้างค่านั้นไว้ให้ ADC อ่านทัน — เอาต์พุตจึงเป็นขั้นบันได ไม่ใช่เส้นโค้ง

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

กราฟ sample-and-hold: สัญญาณถูกสุ่มแล้วคงค่าไว้จนถึงจุดสุ่มถัดไป

ภาพ: Giacomo Alessandroni, Wikimedia Commons, CC BY-SA 4.0

กราฟสัญญาณต่อเนื่องที่ถูกสุ่มและคงค่าเป็นขั้นบันได กลายเป็นสัญญาณดิจิทัล

ภาพ: Rbj / wdwd, Wikimedia Commons, สาธารณสมบัติ — ต่อเนื่อง → ไม่ต่อเนื่อง

กราฟทุกกราฟในโลก embedded คือ "ค่าที่เราเลือกจะมอง" ไม่ใช่ "ทุกอย่างที่เกิดขึ้น"

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

สุ่มเร็วพอไหม — เงื่อนไข Nyquist

ภาพเคลื่อนไหวการสุ่มสัญญาณถี่พอจนสร้างคลื่นเดิมกลับมาได้

ภาพ: Jacopo Bertolotti, Wikimedia Commons, CC0 1.0 — สุ่มถี่พอ สร้างสัญญาณเดิมกลับมาได้

กราฟคลื่นที่สุ่มสองจุดต่อรอบคลื่น พอดีขีดจำกัด Nyquist กราฟคลื่นที่สุ่มเพียงหนึ่งจุดต่อรอบคลื่น ต่ำกว่าเงื่อนไข Nyquist

ภาพ: Pluke, Wikimedia Commons, CC0 1.0 — ซ้าย 2 จุดต่อคาบเวลา (พอดีขีดจำกัด) · ขวา 1 จุดต่อคาบเวลา (ต่ำกว่า Nyquist)
Analog Snippets · 12:08 · EN — "สุ่มเร็วกว่าที่จำเป็น" ซื้ออะไรกลับมา

ปี 1928 แฮร์รี ไนควิสต์ พิสูจน์ไว้ว่า ถ้าจะเก็บสัญญาณที่มีความถี่สูงสุด fmax⁡f_{\max} ให้ครบ ต้องสุ่มเร็วกว่าสองเท่าของมัน

fs>2fmax⁡⟺fmax⁡<fs2f_s > 2 f_{\max}\qquad\Longleftrightarrow\qquad f_{\max} < \frac{f_s}{2}

ไทย: จะเห็นคลื่นความถี่หนึ่งได้ ต้องสุ่มเร็วกว่าสองเท่าของคลื่นนั้น
ตัวเลขของลูปเรา: fs=5f_s = 5 Hz ⇒\Rightarrow เห็นการสั่นได้ไม่เกิน 2.5 Hz

การเขย่ามือของคนอยู่ราว 2-5 Hz เราจึงเห็นเฉพาะส่วนที่ช้ากว่า 2.5 Hz ส่วนที่เร็วกว่านั้นจะปลอมตัวเป็นคลื่นช้า (สไลด์ถัดไป) · อัตราสุ่มของเราไม่ได้ตั้งด้วยฮาร์ดแวร์ แต่เกิดจากความเร็วลูป Python

fs=1Tloop,Tloop=Tsleep+Tworkf_s = \frac{1}{T_{\text{loop}}},\qquad T_{\text{loop}} = T_{\text{sleep}} + T_{\text{work}}

ไทย: งานในลูปหนักขึ้น อัตราสุ่มตกลงเงียบ ๆ — จึงต้องวัดคาบลูปจริง (ท่าที่ 5)

สุ่มไม่ทันไม่ได้แปลว่า "มองไม่เห็น" มันแปลว่า "เห็นผิด" — สไลด์ถัดไปคือเหตุผล

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

Aliasing — สัญญาณที่เร็วเกินไปไม่ได้หายไป มันปลอมตัว

กราฟคลื่นที่สุ่มช้าเกินไปจนจุดสุ่มประกอบเป็นคลื่นความถี่ต่ำปลอม

ภาพ: Vierge Marie, Wikimedia Commons, สาธารณสมบัติ

กราฟคลื่นไซน์สองความถี่ที่ผ่านจุดสุ่มชุดเดียวกันทุกจุด

ภาพ: Moxfyre, Wikimedia Commons, CC BY-SA 3.0 — สองความถี่ให้จุดสุ่มชุดเดียวกันเป๊ะ

falias=∣ f−kfs ∣,k=จำนวนเต็มที่ทำให้ falias≤fs2f_{\text{alias}} = \bigl|\,f - k f_s\,\bigr|,\qquad k = \text{จำนวนเต็มที่ทำให้ } f_{\text{alias}} \le \tfrac{f_s}{2}

ไทย: ถ้าสัญญาณเร็วเกินไป มันจะไม่หายไปเฉย ๆ แต่จะ ปลอมตัว มาเป็นคลื่นช้ากว่าความจริง ซึ่งอันตรายกว่าการมองไม่เห็น

ตัวเลขจากบอร์ดนี้ — เดโมที่ทำได้ในบทเรียน: เขย่าบอร์ดที่ราว 6 Hz ขณะลูปสุ่มที่ fs=5f_s = 5 Hz

falias=∣6−1×5∣=1 Hzf_{\text{alias}} = |6 - 1\times 5| = 1\ \text{Hz}

กราฟจะโชว์คลื่นช้า ๆ 1 Hz ที่ ไม่มีอยู่จริง ทั้งที่มือเราเขย่าอยู่ 6 ครั้งต่อวินาที

นี่คือเหตุผลเดียวกับที่ล้อรถในหนังบางทีดูเหมือนหมุนถอยหลัง — กล้องคือ ADC ที่สุ่มที่ 24 เฟรมต่อวินาที

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

กราฟสัญญาณที่สุ่มพอดีความถี่วิกฤต จุดสุ่มตกตำแหน่งเดิมทุกรอบจนดูเหมือนเส้นตรง

ภาพ: Qef, Wikimedia Commons, สาธารณสมบัติ — ที่ความถี่วิกฤต: จุดสุ่มตกที่เดิมทุกคาบเวลา กราฟจึงกลายเป็นเส้นตรง

ภาพเคลื่อนไหวที่ลดอัตราสุ่มลงเรื่อย ๆ จนคลื่นที่สร้างกลับมาผิดไปจากเดิม

ภาพ: “Nyquist Shannon theorem sine visualisation animation” โดย Enormator — CC0 1.0 · Wikimedia Commons — สามภาพนิ่งเห็นแค่ผลลัพธ์ · ภาพนี้ค่อย ๆ ลดอัตราสุ่มลงให้ดู จังหวะที่คลื่นเริ่ม "ปลอมตัว" คือสิ่งที่ต้องเห็นตอนมันเคลื่อน
TESA Open Knowledge · © 2026 สมาคมสมองกลฝังตัวไทย (TESA) · ดัดแปลงจาก AIoT in Action (AIC มหาวิทยาลัยบูรพา) · CC BY-NC 4.0

Aliasing บนบอร์ดจริง — สูตรจากสไลด์ก่อน เดินให้ดูทีละขั้น

ภาพถ่ายจอบอร์ด Eva Kit ขณะรัน 03_aliasing_nyquist.py ที่แสดงคลื่นปลอมจากการสุ่มช้า

หน้าจอจริงตอนรัน 03_aliasing_nyquist.py — สูตรในสไลด์ก่อนเดินให้ดูบนบอร์ดจริงทีละขั้น อัตราสุ่มถูกตรึงไว้ที่ 40 Hz ตลอด เปลี่ยนเฉพาะความถี่ของคลื่นจริง · ภาพนี้คือขั้นแรกซึ่งเป็นตัวคุม f จริง = 5 Hz ยังต่ำกว่า Nyquist ที่ 20 Hz อยู่มาก ความถี่ที่เครื่อง "เห็น" จึงเป็น 5 เท่ากัน และเส้นทับกันสนิท — ผีจะโผล่ก็ต่อเมื่อกดเดินหน้าจนความถี่จริงเกิน 20 Hz · ภาพหน้าจอจริงจากบอร์ด Eva Kit บันทึกโดยผู้สอน · ส่วน ex16_spectrum_analyzer.py (ไม่มีภาพในสไลด์นี้) สร้างบน Emulator (โปรไฟล์ Eva): สเปกตรัมจาก dsp.fft_mag ของจริงใน C — sine 1 kHz ยอดเดี่ยวที่ bin 5 อ่าน Dominant 937 Hz เพราะความละเอียดต่อ bin คือ 48000/256 = 187.5 Hz (firmware 2026-08-20 ขึ้นไป)

การสุ่มไม่ทันคือความผิดพลาดที่ ไม่ส่งเสียง — ต้องรู้ล่วงหน้าว่าสัญญาณของเราเร็วแค่ไหน

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

int() ก่อน set_next — และราคาที่ต้องจ่าย

Chart เก็บค่าเป็น จำนวนเต็ม เท่านั้น แต่ motion() คืนทศนิยม เช่น 9.78

chart.set_next(0, int(ax))       # ถูก
chart.set_next(0, ax)            # TypeError: can't convert float to int

ราคาที่ต้องจ่ายคือ int() ตัดทศนิยมทิ้ง ไม่ใช่ปัดเศษ — 0.9 เป็น 0 และ −0.9 ก็เป็น 0 กราฟจึงหยาบเป็นขั้นละ 1 m/s² นี่คือ quantization ตัวเดียวกับบทเรียน 2.7–2.9 แค่คราวนี้เราเป็นคนสร้างเอง

สูตร map ช่วงจริงลงช่วงแกน — วิธีแก้ที่ใช้ในงานจริง

vplot=round⁡ ⁣(a−amin⁡amax⁡−amin⁡×(ymax⁡−ymin⁡)+ymin⁡)v_{\text{plot}} = \operatorname{round}\!\left(\frac{a - a_{\min}}{a_{\max} - a_{\min}}\times\bigl(y_{\max} - y_{\min}\bigr) + y_{\min}\right)

ไทย: แกน Y ของ Chart เป็นจำนวนเต็ม ต้อง map ช่วงค่าจริงลงช่วงแกนก่อน ไม่งั้นกราฟแบนหรือทะลุขอบ

chart = ui.Chart(..., min=-2000, max=2000)   # หน่วยกลายเป็น 0.01 m/s²
chart.set_next(0, int(ax * 100))             # 9.78 -> 978

ตัวเลขจากบอร์ดนี้: ช่วง ±20 กับ int() ตรง ๆ ได้ความละเอียด 1 m/s² · คูณ 100 แล้วขยายช่วงเป็น ±2000 ได้ 0.01 m/s² — ละเอียดขึ้น 100 เท่าโดยไม่แตะเซนเซอร์เลย

กราฟความคลาดเคลื่อนจากการปัดค่า (quantization error) ระหว่างสัญญาณจริงกับขั้นดิจิทัล

ภาพ: Gregory Maxwell, Wikimedia Commons, CC BY 3.0 — ความคลาดเคลื่อนจากการปัดเข้าขั้น

กราฟการสุ่มตามเวลาและการปัดค่าตามระดับของสัญญาณ PCM

ภาพ: Wikimedia Commons, CC BY-SA 3.0 — สุ่มตามเวลา + ปัดตามระดับ เป็นคนละเรื่องกัน
Microchip Developer Help · ยาว: ยังไม่ยืนยัน · EN — quantization กับ resolution (คลิปเดียวกับบทเรียน 2.7–2.9)

ตัวเลขบนแกน Y ไม่จำเป็นต้องเป็นหน่วยจริง ขอแค่ เรากับคนอ่านกราฟรู้ตรงกัน ว่ามันคูณอะไรไว้

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

Chart คือ ring buffer 50 ช่อง — set_next() ไม่ได้ "วาดกราฟ"

ค่าใหม่ดันเข้าทางขวา ค่าเก่าที่สุดหล่นออกทางซ้าย … ช่องที่ 1 ช่องที่ 50 set_next() ดันเข้าท้ายแถว ค่าเก่าสุดหล่นหาย — ไม่มีใครเก็บให้ ช่อง ↔ จุดบนกราฟ คือของสิ่งเดียวกัน หน้าต่างเวลา 50 × 200 ms = 10 วินาที สูงสุด 4 series ต่อกราฟหนึ่งใบ · แต่ละ series มีบัฟเฟอร์ 50 ช่องของตัวเอง

Twindow=Npoints×Tloop=50×0.2 s=10 sT_{\text{window}} = N_{\text{points}} \times T_{\text{loop}} = 50 \times 0.2\ \text{s} = 10\ \text{s}

ไทย: หน้าจอเรากว้าง 10 วินาที ของเก่ากว่านั้นถูกดันตกขอบไปแล้ว · อยากเห็นย้อนหลังนานขึ้นมีสองปุ่มให้หมุน — ยืดคาบให้ห่างขึ้น หรือ เพิ่มจำนวนจุด ด้วย ch.prop(ui.PROP_CHART_POINTS, n) ตั้งได้ 10-400 (firmware 2026-08-20 ขึ้นไป · รุ่นก่อนหน้า 50 ตายตัว) แต่ทุกจุดที่เพิ่มคือข้อความ IPC ที่ต้องส่งเพิ่มเวลาวาดทั้งเส้น คาบที่ห่างขึ้นจึงยังเป็นปุ่มที่ถูกกว่าเสมอ

จอเรามี 50 ช่องตามค่าปริยาย — คาบการสุ่มคือตัวกำหนดว่าเราเห็นอดีตย้อนหลังได้กี่วินาที

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

เข้าใจฮาร์ดแวร์ · ทำไมกราฟช้า ทั้งที่ลูปเราเร็ว

โค้ด Python อยู่บน CM33 กราฟถูกวาดโดย CM55 chart.set_next() ไม่ได้วาดทันที มันแค่ ฝากคำสั่งลงคิว IPC แล้วกลับมาทำงานต่อ

ฝั่ง CM55 มีตัวจับเวลาคอยหยิบคำสั่งจากคิวมาทำ และมันมี สองความเร็ว

โหมด คาบของตัวจับเวลา หยิบได้ต่อครั้ง อัตราการระบายจริง
ปกติ (idle) 200 ms 16 คำสั่ง 80 คำสั่ง/วินาที
เร่ง (fast) 5 ms 16 คำสั่ง 3,200 คำสั่ง/วินาที

อัตราระบาย=Nต่อครั้งTtimer=160.2=80เทียบกับ160.005=3,200\text{อัตราระบาย} = \frac{N_{\text{ต่อครั้ง}}}{T_{\text{timer}}} = \frac{16}{0.2} = 80 \quad\text{เทียบกับ}\quad \frac{16}{0.005} = 3{,}200

และนี่คือกับดัก — คำสั่งที่ ปลุกโหมดเร่ง มีอยู่ชุดหนึ่ง คำสั่งที่ ไม่ปลุก ก็มีอีกชุด

ปลุกโหมดเร่ง ไม่ปลุกโหมดเร่ง
สร้าง widget · .text() · .pos() · .size() · .color() .value() · .set_next() · .show() / .hide()

โหมดเร่งจะกลับเป็นปกติเองหลังเงียบไป 500 ms

ลูปที่มีแต่ set_next() ตัวจับเวลาเดินที่ 200 ms 80 จุด/วินาที เต็มจอ 50 จุด ใช้ 0.63 วินาที ≈ 1.6 เฟรม/วินาที — กระตุก ลูปที่มี .text() อยู่ด้วย ตัวจับเวลาเร่งเป็น 5 ms 3,200 จุด/วินาที
ที่มาของตัวเลขทั้งหมดบนสไลด์นี้: ซอร์สเฟิร์มแวร์ BENTO-TESAIoT-libraries/claw/common/modules/ipc_ui/ipc_ui.c — IPC_UI_TIMER_MS=200, IPC_UI_FAST_TIMER_MS=5, IPC_UI_MAX_PER_TICK=16, IPC_UI_FAST_TIMEOUT_MS=500 และรายการ opcode ที่เรียก ui_arm_fast_mode()

set_next() เป็นคำสั่งที่ ไม่ปลุก โหมดเร่ง — กราฟที่วิ่งอยู่ตัวเดียวบนจอ จึงเป็นกรณีที่ช้าที่สุดพอดี

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

กฎปฏิบัติ: มี Label สถานะอยู่ในลูปเสมอ

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

rec_msg = "กำลังบันทึก"     # เปลี่ยนเฉพาะตอนกดปุ่ม
while True:
    ax, ay, az, gx, gy, gz = sensors.bmi270.motion()
    chart.set_next(0, int(ax))          # ไม่ปลุกโหมดเร่ง
    chart.set_next(s_ay, int(ay))       # ไม่ปลุก
    chart.set_next(s_az, int(az))       # ไม่ปลุก
    led_rec.value(1)                    # ไม่ปลุก - .value() ทุกตัวไม่ปลุก
    lbl_rec.text(rec_msg)               # ← ปลุก และค้างไว้อีก 500 ms
    ui.poll()
    time.sleep_ms(PERIOD_MS)

ทำไมมันได้ผลกับลูป 200 ms ของเรา — โหมดเร่งค้างอยู่ 500 ms หลังคำสั่งสุดท้าย ลูปเราหมุนทุก 200 ms ซึ่งสั้นกว่า 500 ms ตัวจับเวลาจึงไม่มีโอกาสกลับเป็นโหมดปกติเลยตลอดการรัน

กฎนี้ชนกับกฎ "ตัวเลขเปลี่ยนไม่เกินวินาทีละครั้ง" พอดี และทางออกอยู่ตรงกลาง: ส่ง .text() ทุกรอบ แต่ส่ง ข้อความเดิม เฉลยของชุดบทเรียนนี้จึงเก็บข้อความสถานะไว้ในตัวแปร rec_msg ซึ่งเปลี่ยนเฉพาะตอนกดปุ่ม แล้วส่งซ้ำทุกรอบ · เฟิร์มแวร์เทียบข้อความเก่ากับใหม่ก่อนวาด ถ้าเท่าเดิมมันไม่วาดซ้ำ เราจึงจ่ายแค่ค่าส่งข้ามคอร์ ไม่ได้จ่ายค่าวาด และไม่มีตัวเลขไหนบนจอวิ่งเร็วกว่าที่คนอ่านทัน

คำสั่งที่ปลุกโหมดเร่งได้มีสี่กลุ่ม คือสร้าง widget · .text() · .pos() / .size() / .color() · และคำสั่งของ collection อย่าง .cell() .add_row() .prop() — ส่วน .value() ทั้งหมด ไม่ว่าจะเป็นแถบ ไฟ หรือ set_next() ของกราฟ ไม่ปลุก (ตรวจจาก ipc_ui.c โดยตรง)

อย่าตีความเกินกว่านี้ — โหมดเร่งไม่ได้ทำให้เซนเซอร์อ่านเร็วขึ้น และไม่ได้ทำให้ลูป Python เร็วขึ้น มันแค่ทำให้ คำสั่งที่เราส่งไปแล้ว ถูกวาดออกจอเร็วขึ้น

โหมดเร่งค้าง 500 ms 0 600 ms .text() .text() .text() ทุก 200 ms < 500 ms → ไม่มีวันหลุดโหมดเร่ง กราฟลื่น ปุ่มตอบไว ตัดบรรทัด .text() ออกเมื่อไร กราฟกลับไปกระตุกทันที

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

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