ลงมือทำ: DAQ logger เก็บ dataset ลง CSV
โมดูล 2 — เก็บข้อมูลจากเซนเซอร์ (DAQ) · สไลด์: slides.md · ภาพรวมโมดูล · หน้าหลักสูตร
เขียน DAQ logger ตามสี่จังหวะ schema → sample → record → rate จนเก็บ dataset ที่ติด label ลง /gestures.csv บนบอร์ด แล้วตรวจไฟล์ วัดอัตราจริง และเข้าใจกับดักเงียบที่ทำให้ dataset เสีย
เป้าหมาย
หัวข้อที่มีชื่อว่า “เป้าหมาย”เมื่อจบบทเรียนนี้ คุณจะ:
- เติมสี่ช่องใน practice/s04_daq_logger.py จนกดปุ่ม label หนึ่งครั้งแล้วได้ 200 บรรทัดใหม่ในไฟล์ ที่ขึ้นต้นด้วยหัวตาราง label,ax,ay,az,gx,gy,gz และค่าไม่เป็นศูนย์หมด
- เก็บครบสาม label อย่างน้อยคนละสอง burst แล้วเปิดไฟล์ใน REPL ยืนยันว่าจำนวนบรรทัดเท่ากับ 1 + (จำนวน burst × 200) และแต่ละ label สมดุล
- อธิบายได้ว่าทำไมอัตราจริงต่ำกว่า 50 Hz ที่ตั้งไว้ และระบุกับดักเงียบสี่ข้อ (โหมด “w”, label ผิด, คอลัมน์สลับ, ลืม \n) ได้
ก่อนเริ่ม
หัวข้อที่มีชื่อว่า “ก่อนเริ่ม”ผ่านบทเรียน 2.1 มาแล้ว จำ schema และเหตุผลของ 50 Hz ได้ เตรียมบันทึกการเรียนไว้จดจำนวน burst ต่อ label และจำนวนบรรทัดที่นับได้จริง
- อุปกรณ์: บอร์ด TESAIoT Dev Kit ที่ลงเฟิร์มแวร์ MicroPython ของ BENTO แล้ว หรือ BENTO Emulator ใน BENTO IDE — Emulator จำลอง IMU ให้ครบ ใช้ซ้อมเก็บ dataset ได้ แต่ข้อมูลที่จะเอาไปฝึกโมเดลจริงควรเก็บจากบอร์ด
- เรียนมาก่อน: บทเรียน 2.1 — สุ่มสัญญาณให้ตรงกับโมเดล: อัตราสุ่ม Nyquist หน้าต่าง และ schema ของ CSV
logger ก็เดินโครงสี่จังหวะเดิม แต่ในลูปมี สี่จังหวะของ DAQ: schema (เขียนหัวตารางครั้งเดียวถ้าไฟล์ยังไม่มี โดยใช้ try/except
เปิดอ่านดูก่อน) → sample (sensors.bmi270.motion() หกแกนในครั้งเดียว) → record (f.write หนึ่งบรรทัดเรียงคอลัมน์ตรง schema
ปิดด้วย \n) → rate (time.sleep_ms(RATE_MS)) วน BURST รอบต่อการกดหนึ่งครั้ง ทุก DAQ ในโลกเดินสี่จังหวะนี้ ต่างแค่รายละเอียด
การเปิดด้วย with open(PATH, "a") ต่อหนึ่ง burst ทำให้ไฟล์ถูกปิดและ flush ทุกครั้ง ถอดสายกลางคันก็ยังไม่เสีย burst ที่เขียนจบแล้ว
time.sleep_ms(20) ไม่ได้ให้ 50 Hz เป๊ะ เพราะแต่ละรอบยังมีเวลาอ่านเซนเซอร์และเขียนไฟล์บวกเข้าไป อัตราจริงจึงต่ำกว่าเล็กน้อย
(ราว 40–45 Hz) ที่ยอมรับได้คือต่ำกว่าอย่างสม่ำเสมอ ที่แย่คืออัตราแกว่ง บทเรียนคือ อย่าเชื่อค่าที่ตั้ง จงวัดค่าที่ได้จริง
ซึ่งฉบับเต็มทำให้ดูด้วย time.ticks_diff
เก็บ label ให้ตรงกับคลาสของโมเดล Motion (idle, circle, shaking) และเก็บให้สมดุล แล้ว ตรวจก่อนเชื่อ ทุกครั้ง: นับบรรทัด ดูหัว
ดูค่า กับดักที่ไม่ขึ้น error มีสี่ข้อ คือเปิดด้วย "w" จนทับของเก่า กดปุ่มผิด label เขียนคอลัมน์สลับ และลืม \n
โครงนี้เป็นแม่แบบ เปลี่ยนแค่บรรทัด sample กับ schema ก็เก็บเซนเซอร์อื่นได้ เช่น sensors.dps368.pressure_temperature()
หรือ sensors.sht40.temperature_humidity() ที่อัตราช้ากว่านี้มาก ไฟล์ /gestures.csv นี้คือวัตถุดิบของโมดูล 5 (Training)
ตัวอย่างสมบูรณ์
หัวข้อที่มีชื่อว่า “ตัวอย่างสมบูรณ์”s04_daq_logger.py ในโฟลเดอร์ examples คือฉบับอ้างอิงที่บทเรียนนี้สร้างขึ้นรอบ ๆ ส่วน s04_daq_logger_full.py เพิ่มตัวนับแยก label
(ดู class balance สด ๆ) วัดอัตราจริงเป็น Hz และมีปุ่ม Clear ล้างไฟล์เริ่มใหม่ เปิดเทียบหลังเติมไฟล์ฝึกเสร็จ
| ไฟล์ | ไฟล์นี้สอน |
|---|---|
| examples/s04_daq_logger.py | เก็บข้อมูล sensor ลงไฟล์ CSV (Data Acquisition) |
| examples/s04_daq_logger_full.py | เก็บ dataset ลง CSV (ฉบับเต็ม) |
ฝึกเติม
หัวข้อที่มีชื่อว่า “ฝึกเติม”ไฟล์มี pass สี่จุด (บรรทัด 55, 69, 73 และ 76) ตรงสี่จังหวะ DAQ พอดี:
- schema
f.write("label,ax,ay,az,gx,gy,gz\n")2) sampleax, ay, az, gx, gy, gz = sensors.bmi270.motion() - record
f.write("%s,%.4f,%.4f,%.4f,%.4f,%.4f,%.4f\n" % (label, ax, ay, az, gx, gy, gz))4) ratetime.sleep_ms(RATE_MS)ถ้าทุกบรรทัดเป็น 0.0 แปลว่าช่อง 2 ยังว่าง ถ้าไฟล์ไม่เพิ่มเลยแปลว่าช่อง 3 ยังว่าง
| ไฟล์ฝึก | เรื่อง |
|---|---|
| practice/s04_daq_logger.py | เก็บข้อมูล sensor ลงไฟล์ CSV (Data Acquisition) (ฉบับฝึกเติมโค้ด) |
เปิดเฉลยหลังจากลองเองแล้วอย่างน้อยหนึ่งรอบ และอ่าน วิธีใช้เฉลย ก่อน
| เฉลย | คู่กับ |
|---|---|
| solution/s04_daq_logger.py | practice/s04_daq_logger.py |
เช็กความเข้าใจ
หัวข้อที่มีชื่อว่า “เช็กความเข้าใจ”คำถามชุดเดียวกันอยู่ใน quiz.yaml สำหรับระบบที่ตรวจอัตโนมัติ
-
เรียงสี่จังหวะของ DAQ ในไฟล์ logger (เรียงลำดับ · เป้าหมายข้อ 1)
- ก) record: เขียนหนึ่งบรรทัดลงไฟล์
- ข) schema: เขียนหัวตารางครั้งเดียว
- ค) rate: time.sleep_ms(RATE_MS)
- ง) sample: sensors.bmi270.motion()
เฉลย
ข → ง → ก → ค — schema ทำครั้งเดียวตอนเริ่ม แล้ววน sample → record → rate ครบ BURST รอบต่อการกดหนึ่งครั้ง
-
เปิดไฟล์ดูแล้วทุกบรรทัดเป็น 0.0000 หมดยกเว้นคอลัมน์ label ช่องใดยังว่าง (เลือกหนึ่งข้อ · เป้าหมายข้อ 1)
- ก) ช่อง 1 schema
- ข) ช่อง 2 sample ที่อ่าน motion()
- ค) ช่อง 3 record
- ง) ช่อง 4 rate
เฉลย
ข — บรรทัดสำรอง ax = ay = … = 0.0 ทำให้โปรแกรมไม่ error แต่ถ้าไม่ได้อ่านเซนเซอร์จริง ทุกค่าจะเป็นศูนย์ dataset จึงไร้ประโยชน์
-
เก็บ idle 2 burst, circle 2 burst และ shaking 3 burst ไฟล์ที่ถูกต้องควรมีกี่บรรทัด (รวมหัวตาราง) (เลือกหนึ่งข้อ · เป้าหมายข้อ 2)
- ก) 1400
- ข) 1401
- ค) 1407
- ง) 601
เฉลย
ข — 7 burst × 200 = 1400 บรรทัดข้อมูล บวกหัวตาราง 1 บรรทัด เป็น 1401 และ shaking มีมากกว่าคลาสอื่นอยู่หนึ่ง burst ควรเก็บเพิ่มให้สมดุล
-
ตั้ง RATE_MS = 20 แต่วัดได้จริงราว 43 Hz เพราะอะไร (เลือกหนึ่งข้อ · เป้าหมายข้อ 3)
- ก) เซนเซอร์เสีย
- ข) แต่ละรอบมีเวลาอ่านเซนเซอร์และเขียนไฟล์บวกกับ sleep 20 ms หนึ่งรอบจริงจึงยาวกว่า 20 ms
- ค) time.sleep_ms ปัดเป็นวินาที
- ง) flash เขียนได้แค่ 43 ครั้งต่อวินาที
เฉลย
ข — นี่คือความจริงของงานฝังตัว ตราบใดที่ต่ำกว่าอย่างสม่ำเสมอก็ยังใช้ได้ สิ่งสำคัญคือวัดค่าจริงแทนการเชื่อค่าที่ตั้ง
-
ข้อใดเป็นกับดักเงียบที่โปรแกรมรันผ่านแต่ dataset ผิด (เลือกทุกข้อที่ถูก) (เลือกได้หลายข้อ · เป้าหมายข้อ 3)
- ก) เขียน gx ก่อน ax ไม่ตรง schema
- ข) ลืม \n ท้ายบรรทัด ทุก sample ต่อกันเป็นบรรทัดเดียว
- ค) สะกด sensors ผิดจนเกิด NameError
- ง) เปิดไฟล์ด้วย “w” ในลูปบันทึก
เฉลย
ก, ข, ง — NameError ขึ้น error ให้เห็นทันทีจึงไม่ใช่กับดักเงียบ ส่วนอีกสามข้อรันผ่านแต่ข้อมูลผิด ต้องเปิดไฟล์ตรวจเสมอ
MVP ของชุดบทเรียน 2.1–2.2: logger ที่เก็บ N samples ที่ติด label ลง CSV ได้จริง เลือก label ได้ สุ่มที่อัตราคงที่ เขียนลงไฟล์บนบอร์ด และตรวจได้ว่าจำนวนบรรทัดถูกต้อง
- เติมไฟล์ฝึกครบสี่ช่อง เก็บ dataset ได้บน Emulator หรือบอร์ด
- เก็บครบสาม label อย่างน้อยคนละสอง burst แล้วนับบรรทัดใน REPL ให้ได้ 1 + (burst × 200)
- เปลี่ยน
RATE_MSเป็น 40 (25 Hz) เก็บอีกชุด แล้วจดลงบันทึกการเรียนว่าเวลาต่อ burst และรูปคลื่นต่างไปอย่างไร - อธิบายได้ว่าโค้ดทำสี่จังหวะ schema, sample, record, rate ตรงไหน และทำไม 50 Hz
ชุดบทเรียนถัดไป (บทเรียน 2.3–2.4) เราจะเก็บเสียงกับ IMU พร้อมกันบนเส้นเวลาเดียว ให้ dataset รวยขึ้น
บทเรียนถัดไป: บทเรียน 2.3 — เสียงและหลายเซนเซอร์บนเส้นเวลาเดียว: PDM 16 kHz ประทับเวลา และ jitter
สะท้อนคิด
หัวข้อที่มีชื่อว่า “สะท้อนคิด”- dataset ของคุณสมดุลหรือยัง ถ้าไม่ คุณจะเก็บเพิ่มอย่างไรโดยไม่ทำให้ label ปนกัน
- ถ้าอยากเก็บความดันอากาศแทน IMU คุณต้องแก้กี่บรรทัด และควรตั้ง RATE_MS เท่าไร
คำถามทบทวน
ลองตอบเองก่อน แล้วค่อยเปิดดูเฉลย
-
เรียงสี่จังหวะของ DAQ ในไฟล์ logger (เป้าหมายข้อ 1)
- record: เขียนหนึ่งบรรทัดลงไฟล์
- schema: เขียนหัวตารางครั้งเดียว
- rate: time.sleep_ms(RATE_MS)
- sample: sensors.bmi270.motion()
ดูเฉลย
ลำดับที่ถูก: B. schema: เขียนหัวตารางครั้งเดียว → D. sample: sensors.bmi270.motion() → A. record: เขียนหนึ่งบรรทัดลงไฟล์ → C. rate: time.sleep_ms(RATE_MS)
schema ทำครั้งเดียวตอนเริ่ม แล้ววน sample → record → rate ครบ BURST รอบต่อการกดหนึ่งครั้ง
-
เปิดไฟล์ดูแล้วทุกบรรทัดเป็น 0.0000 หมดยกเว้นคอลัมน์ label ช่องใดยังว่าง (เป้าหมายข้อ 1)
- ช่อง 1 schema
- ช่อง 2 sample ที่อ่าน motion()
- ช่อง 3 record
- ช่อง 4 rate
ดูเฉลย
คำตอบ: B. ช่อง 2 sample ที่อ่าน motion()
บรรทัดสำรอง ax = ay = ... = 0.0 ทำให้โปรแกรมไม่ error แต่ถ้าไม่ได้อ่านเซนเซอร์จริง ทุกค่าจะเป็นศูนย์ dataset จึงไร้ประโยชน์
-
เก็บ idle 2 burst, circle 2 burst และ shaking 3 burst ไฟล์ที่ถูกต้องควรมีกี่บรรทัด (รวมหัวตาราง) (เป้าหมายข้อ 2)
- 1400
- 1401
- 1407
- 601
ดูเฉลย
คำตอบ: B. 1401
7 burst × 200 = 1400 บรรทัดข้อมูล บวกหัวตาราง 1 บรรทัด เป็น 1401 และ shaking มีมากกว่าคลาสอื่นอยู่หนึ่ง burst ควรเก็บเพิ่มให้สมดุล
-
ตั้ง RATE_MS = 20 แต่วัดได้จริงราว 43 Hz เพราะอะไร (เป้าหมายข้อ 3)
- เซนเซอร์เสีย
- แต่ละรอบมีเวลาอ่านเซนเซอร์และเขียนไฟล์บวกกับ sleep 20 ms หนึ่งรอบจริงจึงยาวกว่า 20 ms
- time.sleep_ms ปัดเป็นวินาที
- flash เขียนได้แค่ 43 ครั้งต่อวินาที
ดูเฉลย
คำตอบ: B. แต่ละรอบมีเวลาอ่านเซนเซอร์และเขียนไฟล์บวกกับ sleep 20 ms หนึ่งรอบจริงจึงยาวกว่า 20 ms
นี่คือความจริงของงานฝังตัว ตราบใดที่ต่ำกว่าอย่างสม่ำเสมอก็ยังใช้ได้ สิ่งสำคัญคือวัดค่าจริงแทนการเชื่อค่าที่ตั้ง
-
ข้อใดเป็นกับดักเงียบที่โปรแกรมรันผ่านแต่ dataset ผิด (เลือกทุกข้อที่ถูก) (เป้าหมายข้อ 3)
- เขียน gx ก่อน ax ไม่ตรง schema
- ลืม \n ท้ายบรรทัด ทุก sample ต่อกันเป็นบรรทัดเดียว
- สะกด sensors ผิดจนเกิด NameError
- เปิดไฟล์ด้วย "w" ในลูปบันทึก
ดูเฉลย
คำตอบ: A. เขียน gx ก่อน ax ไม่ตรง schema · B. ลืม \n ท้ายบรรทัด ทุก sample ต่อกันเป็นบรรทัดเดียว · D. เปิดไฟล์ด้วย "w" ในลูปบันทึก
NameError ขึ้น error ให้เห็นทันทีจึงไม่ใช่กับดักเงียบ ส่วนอีกสามข้อรันผ่านแต่ข้อมูลผิด ต้องเปิดไฟล์ตรวจเสมอ
อ้างอิงบทเรียนนี้
ถ้านำบทเรียนนี้ไปสอน ทำสไลด์ หรือทำเอกสารต่อ ให้อ้างอิงด้วยข้อความนี้ ถ้าดัดแปลงเนื้อหา ให้เติม (ดัดแปลง)ต่อท้ายชื่อบทเรียน
"ลงมือทำ: DAQ logger เก็บ dataset ลง CSV" จาก TESA Open Knowledge โดยสมาคมสมองกลฝังตัวไทย (Thai Embedded Systems Association: TESA) https://github.com/tesaiot/tesa-qualification-program สัญญาอนุญาต CC BY-NC 4.0
ข้อความอ้างอิงภาษาอังกฤษ: "Hands-on: a DAQ logger that writes a CSV dataset" 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/edge-ai-developer/m02-daq/l02-daq-logger-lab/
TESA Open Knowledge · © 2026 สมาคมสมองกลฝังตัวไทย (TESA) · CC BY-NC 4.0
เนื้อหาเผยแพร่ภายใต้ CC BY-NC 4.0 นำไปใช้ต่อในงานที่ไม่ใช่เพื่อการค้าได้ โปรดอ้างอิงสมาคมสมองกลฝังตัวไทย (TESA) ทุกครั้ง · วิธีอ้างอิง TESA