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

ลงมือทำ: DAQ logger เก็บ dataset ลง CSV

โมดูล 2 — เก็บข้อมูลจากเซนเซอร์ (DAQ) · สไลด์: slides.md · ภาพรวมโมดูล · หน้าหลักสูตร

เขียน DAQ logger ตามสี่จังหวะ schema → sample → record → rate จนเก็บ dataset ที่ติด label ลง /gestures.csv บนบอร์ด แล้วตรวจไฟล์ วัดอัตราจริง และเข้าใจกับดักเงียบที่ทำให้ dataset เสีย

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

  1. เติมสี่ช่องใน practice/s04_daq_logger.py จนกดปุ่ม label หนึ่งครั้งแล้วได้ 200 บรรทัดใหม่ในไฟล์ ที่ขึ้นต้นด้วยหัวตาราง label,ax,ay,az,gx,gy,gz และค่าไม่เป็นศูนย์หมด
  2. เก็บครบสาม label อย่างน้อยคนละสอง burst แล้วเปิดไฟล์ใน REPL ยืนยันว่าจำนวนบรรทัดเท่ากับ 1 + (จำนวน burst × 200) และแต่ละ label สมดุล
  3. อธิบายได้ว่าทำไมอัตราจริงต่ำกว่า 50 Hz ที่ตั้งไว้ และระบุกับดักเงียบสี่ข้อ (โหมด “w”, label ผิด, คอลัมน์สลับ, ลืม \n) ได้

ผ่านบทเรียน 2.1 มาแล้ว จำ schema และเหตุผลของ 50 Hz ได้ เตรียมบันทึกการเรียนไว้จดจำนวน burst ต่อ label และจำนวนบรรทัดที่นับได้จริง

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 พอดี:

  1. schema f.write("label,ax,ay,az,gx,gy,gz\n") 2) sample ax, ay, az, gx, gy, gz = sensors.bmi270.motion()
  2. record f.write("%s,%.4f,%.4f,%.4f,%.4f,%.4f,%.4f\n" % (label, ax, ay, az, gx, gy, gz)) 4) rate time.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 สำหรับระบบที่ตรวจอัตโนมัติ

  1. เรียงสี่จังหวะของ DAQ ในไฟล์ logger (เรียงลำดับ · เป้าหมายข้อ 1)

    • ก) record: เขียนหนึ่งบรรทัดลงไฟล์
    • ข) schema: เขียนหัวตารางครั้งเดียว
    • ค) rate: time.sleep_ms(RATE_MS)
    • ง) sample: sensors.bmi270.motion()
    เฉลย

    ข → ง → ก → ค — schema ทำครั้งเดียวตอนเริ่ม แล้ววน sample → record → rate ครบ BURST รอบต่อการกดหนึ่งครั้ง

  2. เปิดไฟล์ดูแล้วทุกบรรทัดเป็น 0.0000 หมดยกเว้นคอลัมน์ label ช่องใดยังว่าง (เลือกหนึ่งข้อ · เป้าหมายข้อ 1)

    • ก) ช่อง 1 schema
    • ข) ช่อง 2 sample ที่อ่าน motion()
    • ค) ช่อง 3 record
    • ง) ช่อง 4 rate
    เฉลย

    ข — บรรทัดสำรอง ax = ay = … = 0.0 ทำให้โปรแกรมไม่ error แต่ถ้าไม่ได้อ่านเซนเซอร์จริง ทุกค่าจะเป็นศูนย์ dataset จึงไร้ประโยชน์

  3. เก็บ idle 2 burst, circle 2 burst และ shaking 3 burst ไฟล์ที่ถูกต้องควรมีกี่บรรทัด (รวมหัวตาราง) (เลือกหนึ่งข้อ · เป้าหมายข้อ 2)

    • ก) 1400
    • ข) 1401
    • ค) 1407
    • ง) 601
    เฉลย

    ข — 7 burst × 200 = 1400 บรรทัดข้อมูล บวกหัวตาราง 1 บรรทัด เป็น 1401 และ shaking มีมากกว่าคลาสอื่นอยู่หนึ่ง burst ควรเก็บเพิ่มให้สมดุล

  4. ตั้ง RATE_MS = 20 แต่วัดได้จริงราว 43 Hz เพราะอะไร (เลือกหนึ่งข้อ · เป้าหมายข้อ 3)

    • ก) เซนเซอร์เสีย
    • ข) แต่ละรอบมีเวลาอ่านเซนเซอร์และเขียนไฟล์บวกกับ sleep 20 ms หนึ่งรอบจริงจึงยาวกว่า 20 ms
    • ค) time.sleep_ms ปัดเป็นวินาที
    • ง) flash เขียนได้แค่ 43 ครั้งต่อวินาที
    เฉลย

    ข — นี่คือความจริงของงานฝังตัว ตราบใดที่ต่ำกว่าอย่างสม่ำเสมอก็ยังใช้ได้ สิ่งสำคัญคือวัดค่าจริงแทนการเชื่อค่าที่ตั้ง

  5. ข้อใดเป็นกับดักเงียบที่โปรแกรมรันผ่านแต่ 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 เท่าไร

คำถามทบทวน

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

  1. เรียงสี่จังหวะของ DAQ ในไฟล์ logger (เป้าหมายข้อ 1)

    1. record: เขียนหนึ่งบรรทัดลงไฟล์
    2. schema: เขียนหัวตารางครั้งเดียว
    3. rate: time.sleep_ms(RATE_MS)
    4. 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 รอบต่อการกดหนึ่งครั้ง

  2. เปิดไฟล์ดูแล้วทุกบรรทัดเป็น 0.0000 หมดยกเว้นคอลัมน์ label ช่องใดยังว่าง (เป้าหมายข้อ 1)

    1. ช่อง 1 schema
    2. ช่อง 2 sample ที่อ่าน motion()
    3. ช่อง 3 record
    4. ช่อง 4 rate
    ดูเฉลย

    คำตอบ: B. ช่อง 2 sample ที่อ่าน motion()

    บรรทัดสำรอง ax = ay = ... = 0.0 ทำให้โปรแกรมไม่ error แต่ถ้าไม่ได้อ่านเซนเซอร์จริง ทุกค่าจะเป็นศูนย์ dataset จึงไร้ประโยชน์

  3. เก็บ idle 2 burst, circle 2 burst และ shaking 3 burst ไฟล์ที่ถูกต้องควรมีกี่บรรทัด (รวมหัวตาราง) (เป้าหมายข้อ 2)

    1. 1400
    2. 1401
    3. 1407
    4. 601
    ดูเฉลย

    คำตอบ: B. 1401

    7 burst × 200 = 1400 บรรทัดข้อมูล บวกหัวตาราง 1 บรรทัด เป็น 1401 และ shaking มีมากกว่าคลาสอื่นอยู่หนึ่ง burst ควรเก็บเพิ่มให้สมดุล

  4. ตั้ง RATE_MS = 20 แต่วัดได้จริงราว 43 Hz เพราะอะไร (เป้าหมายข้อ 3)

    1. เซนเซอร์เสีย
    2. แต่ละรอบมีเวลาอ่านเซนเซอร์และเขียนไฟล์บวกกับ sleep 20 ms หนึ่งรอบจริงจึงยาวกว่า 20 ms
    3. time.sleep_ms ปัดเป็นวินาที
    4. flash เขียนได้แค่ 43 ครั้งต่อวินาที
    ดูเฉลย

    คำตอบ: B. แต่ละรอบมีเวลาอ่านเซนเซอร์และเขียนไฟล์บวกกับ sleep 20 ms หนึ่งรอบจริงจึงยาวกว่า 20 ms

    นี่คือความจริงของงานฝังตัว ตราบใดที่ต่ำกว่าอย่างสม่ำเสมอก็ยังใช้ได้ สิ่งสำคัญคือวัดค่าจริงแทนการเชื่อค่าที่ตั้ง

  5. ข้อใดเป็นกับดักเงียบที่โปรแกรมรันผ่านแต่ dataset ผิด (เลือกทุกข้อที่ถูก) (เป้าหมายข้อ 3)

    1. เขียน gx ก่อน ax ไม่ตรง schema
    2. ลืม \n ท้ายบรรทัด ทุก sample ต่อกันเป็นบรรทัดเดียว
    3. สะกด sensors ผิดจนเกิด NameError
    4. เปิดไฟล์ด้วย "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 ฉบับเต็ม

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

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