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

SensorHub: แดชบอร์ดรวมเซนเซอร์ทุกตัว (งานปิดชุด)

  1. รวม DPS368, SHT4x, BMI270, BMM350 และไมโครโฟน PDM ไว้บนแดชบอร์ดเดียว
  2. จัดจังหวะการอ่านเซนเซอร์แต่ละตัวให้จอไม่กระตุก
  3. นำเสนอแดชบอร์ดพร้อมอธิบายว่าเลือกแสดงข้อมูลแต่ละตัวอย่างไร

ประสานสี่เซนเซอร์ด้วย lv_timer เดียว ไม่ใช่สี่ FreeRTOS task ตามที่ README ต้นทางอธิบาย

หัวข้อที่มีชื่อว่า “ประสานสี่เซนเซอร์ด้วย lv_timer เดียว ไม่ใช่สี่ FreeRTOS task ตามที่ README ต้นทางอธิบาย”

README ต้นทางอธิบายว่า episode นี้สร้าง “4 reader task (ละ 2 KB stack) priority เท่ากัน” ที่ push ข้อมูลผ่าน single message queue แบบ tagged union แล้ว consumer dispatch ผ่าน lv_async_call() — แต่โค้ดจริงที่ commit 9a8e3ed ไม่มี xTaskCreate, xQueueCreate, หรือ lv_async_call แม้แต่ตัวเดียวใน sensorhub_presenter.c ทั้งไฟล์ สถาปัตยกรรมจริงคือ lv_timer หนึ่งตัว ที่ HUB_UI_POLL_MS = 100 ทำหน้าที่ เป็น cooperative scheduler เอง — ทุก 100 ms มันเช็คว่าถึงเวลาของเซนเซอร์ตัวไหนบ้าง แล้วอ่านเฉพาะตัวที่ถึงรอบ เท่านั้น ทั้งหมดยังรันบน LVGL thread เดียว ไม่มี thread อื่นเข้ามายุ่งกับ sensor reading เลย

แต่ละเซนเซอร์มีตัวแปร next_..._ms ของตัวเอง (next_dps_ms, next_sht_ms, next_bmi_ms, next_bmm_ms) ทุกครั้ง ที่ timer ทำงาน (ทุก 100 ms) จะเช็คแบบ (int32_t)(now_ms - next_xxx_ms) < 0 ถ้ายังไม่ถึงจะข้าม ถ้าถึงแล้วจะอ่าน เซนเซอร์ตัวนั้นแล้วตั้งเวลาถัดไปเป็น now_ms + <คาบของเซนเซอร์นั้น> — คาบของแต่ละเซนเซอร์คือค่าเดิมจากบทเรียนของ มันเองไม่มีการเปลี่ยน (DPS368_SAMPLE_PERIOD_MS = 1000, SHT4X_SAMPLE_PERIOD_MS = 1000, BMI270_SAMPLE_PERIOD_MS = 200, BMM350_SAMPLE_PERIOD_MS = 120) — ไม่ใช่ตาราง 200/500/20/50 ms ตามที่ README ต้นทางอ้างไว้เลยสักตัว

ผลข้างเคียงของ tick 100 ms: คาบที่ไม่ใช่ผลคูณของ 100 จะถูกปัดขึ้น

หัวข้อที่มีชื่อว่า “ผลข้างเคียงของ tick 100 ms: คาบที่ไม่ใช่ผลคูณของ 100 จะถูกปัดขึ้น”

เพราะ timer หลักเดินทุก 100 ms พอดี เซนเซอร์ที่มีคาบไม่ใช่ผลคูณของ 100 (เช่น BMM350 ที่ 120 ms) จะไม่มีวันถูกอ่าน ตรงตามคาบของมันเอง — ที่ t=100 ms ยังไม่ถึง 120 จึงข้าม ที่ t=200 ms ถึงแล้วจึงอ่านแล้วตั้ง next เป็น 320 ซึ่งจะถูก ข้ามที่ t=300 อีก จนอ่านจริงที่ t=400 — คาบจริงที่ใช้งานจึงกลายเป็น 200 ms ไม่ใช่ 120 ms ผลต่อเนื่องที่วัดได้ชัดคือ auto-calibration ของ BMM350 ที่ต้องเก็บ 140 ตัวอย่าง (บทเรียน 3.4) ใช้เวลาประมาณ 28 วินาทีใน episode นี้ ไม่ใช่ 16.8 วินาทีเหมือนตอนรันเดี่ยว ๆ ใน EP04 เพราะแต่ละตัวอย่างมาช้าลงเป็นสองเท่าจากการปัดเวลา

calibration ของ BMM350 เริ่มอัตโนมัติตั้งแต่ boot ไม่ต้องกดปุ่มเหมือนบทเรียน 3.4

หัวข้อที่มีชื่อว่า “calibration ของ BMM350 เริ่มอัตโนมัติตั้งแต่ boot ไม่ต้องกดปุ่มเหมือนบทเรียน 3.4”

sensorhub_presenter_start() เรียก bmm350_reader_start_calibration() เองทันทีตอนเริ่ม ต่างจากบทเรียน 3.4 ที่ ผู้ใช้ต้องกดปุ่ม Calibrate เอง — เพราะ dashboard นี้ต้องการให้เข็มทิศพร้อมใช้โดยเร็วที่สุดโดยผู้ใช้ไม่ต้องรู้ขั้นตอน เพิ่ม (ผู้ใช้แค่ต้องหมุนบอร์ดตามคำแนะนำระหว่างที่หน้าจออื่นกำลังทำงานอยู่)

หน้าจอจริงเป็นแท็บ 5 หน้า ไม่ใช่ grid 2×2 + บาร์ล่างตามที่ README ต้นทางวาดไว้

หัวข้อที่มีชื่อว่า “หน้าจอจริงเป็นแท็บ 5 หน้า ไม่ใช่ grid 2×2 + บาร์ล่างตามที่ README ต้นทางวาดไว้”

README ต้นทางวาดผังหน้าจอเป็น grid 2×2 (DPS368/SHT4x แถวบน, BMI270/BMM350 แถวล่าง) บวกแถบ mic ด้านล่างที่แสดง พร้อมกันทั้งหมด — แต่โค้ดจริงมี sensorhub_page_t ห้าค่า (SENSORHUB_PAGE_HOME, _ENV, _MOTION, _COMPASS, _AUDIO) และ sensorhub_view_set_active_page() ซ่อนหน้าที่ไม่ได้เลือกไว้ — เป็นแท็บที่แสดงทีละหน้าเหมือนโครง navigation shell จากโมดูล 2 (บทเรียน 2.4 เป็นต้นไป) ไม่ใช่ tile ทั้งหมดโชว์พร้อมกันในจอเดียว หน้า Audio เองก็แสดง ระดับเสียงที่คำนวณแล้ว (peak/avg ตามบทเรียน 3.6) ไม่ใช่รูปคลื่นดิบ

บั๊ก I3C soft-reset ของ BMM350_SensorAPI (อธิบายละเอียดในบทเรียน 3.4) ยังอยู่ในไลบรารีตัวเดียวกันที่ episode นี้ใช้ — ต้อง patch ก่อน build เหมือนเดิม การแก้เป็น idempotent ถ้าทำใน EP04 แล้วไม่ต้องทำซ้ำสำหรับ workspace เดียวกัน

โค้ดของ episode นี้อยู่ใน Developer Hub (อ้างอิงที่ commit 9a8e3ed) — อ่าน Why ของ README ต้นทาง เพื่อเข้าใจโจทย์ของบทปิดคอร์ส แต่ โค้ดตัวอย่างด้านล่างคัดลอกจากไฟล์จริง (Apache-2.0, tesaiot/developer-hub, commit เดียวกัน) เพราะสถาปัตยกรรมจริงต่างจากที่ README ต้นทางอธิบายมาก

app_ui/sensorhub/sensorhub_presenter.c — lv_timer เดียวเรียก poll ของทุกเซนเซอร์ทุกรอบ:

static void sensorhub_poll_timer_cb(lv_timer_t *timer)
{
(void)timer;
uint32_t now_ms = lv_tick_get();
sensorhub_poll_dps(now_ms);
sensorhub_poll_sht(now_ms);
sensorhub_poll_bmi(now_ms);
sensorhub_poll_bmm(now_ms);
sensorhub_poll_bmm_calibration();
sensorhub_poll_mic();
/* ... */
}
/* ... */
s_ctx.poll_timer = lv_timer_create(sensorhub_poll_timer_cb, HUB_UI_POLL_MS, NULL);

รูปแบบ “next due time” ต่อเซนเซอร์หนึ่งตัว (DPS368 เป็นตัวอย่าง โครงเดียวกันซ้ำสำหรับ SHT4x/BMI270/BMM350):

/* Poll DPS368 on its own sampling period and push value to Env page. */
static void sensorhub_poll_dps(uint32_t now_ms)
{
if ((!s_ctx.dps_ready) || ((int32_t)(now_ms - s_ctx.next_dps_ms) < 0))
{
return;
}
s_ctx.next_dps_ms = now_ms + DPS368_SAMPLE_PERIOD_MS;
dps368_sample_t sample;
if (dps368_reader_poll(&sample))
{
s_ctx.dps_sample = sample;
sensorhub_view_update_env(&s_ctx.dps_sample, s_ctx.has_sht ? &s_ctx.sht_sample : NULL);
/* ... */
}
}
  • คิดว่ามี 4 FreeRTOS task + queue ตามที่ README ต้นทางอธิบาย — โค้ดจริงใช้ lv_timer เดียวเป็น cooperative scheduler ให้ยึดโค้ดจริงเมื่ออธิบายสถาปัตยกรรม
  • จำคาบการอ่านผิดเป็น 200/500/20/50 ms — ค่าจริงคือ 1000/1000/200/120 ms ตรงจาก config.h ของแต่ละเซนเซอร์ที่ ไม่ถูกแก้เลย
  • ลืมว่า tick หลัก 100 ms ปัดคาบที่ไม่ใช่ผลคูณของ 100 ขึ้น — BMM350 (120 ms) ถูกอ่านจริงทุก 200 ms ทำให้ auto-calibration ใช้เวลาเกือบสองเท่าของตอนรันเดี่ยวใน EP04
  • คิดว่าจอแสดงทุก tile พร้อมกันแบบ grid 2×2 — จริงเป็นแท็บ 5 หน้า (Home/Env/Motion/Compass/Audio) แสดงทีละ หน้าเท่านั้น
  • ลืม patch บั๊ก BMM350 — บั๊กเดียวกับบทเรียน 3.4 ยังอยู่ ต้อง patch ก่อน build เช่นเดิม
Terminal window
# ในโฟลเดอร์ master template (ดูบทเรียน 1.1)
# 1) ลบไฟล์ของ episode เก่าใน proj_cm55/apps/
# 2) คัดลอกไฟล์ทั้งหมดของ episode นี้ลงใน proj_cm55/apps/
make build
make program # flash ผ่าน KitProg3

หรือเปิด ตัวอย่างนี้บน Developer Hub แล้ว flash เฟิร์มแวร์สำเร็จรูป

หน้าจอของ EP07 — SensorHub Final บน TESAIoT Dev Kit

ก่อนอ่านโค้ด ให้ทายว่าหน้าจอนี้มี object อะไรบ้าง และอะไรเปลี่ยนเมื่อผู้ใช้แตะหรือเมื่อค่าเซนเซอร์เปลี่ยน

  1. ทาย ก่อนแก้: เลือกค่าหนึ่งค่าที่ README ของตัวอย่างอธิบายไว้ในส่วน How แล้วเขียนว่าจะเห็นอะไรเปลี่ยนบนจอหรือใน log
  2. แก้และรัน build + flash แล้วเทียบกับที่ทายไว้ ถ้าไม่ตรง ให้หาว่าเข้าใจส่วนไหนผิด
  3. ทำเพิ่ม ต่อยอดหนึ่งอย่างที่ตัวอย่างยังไม่มี แล้วเก็บภาพหรือวิดีโอไว้ใน portfolio
  • เซนเซอร์ตัวใดควรอ่านถี่ที่สุด และตัวใดอ่านช้าได้
  • อะไรทำให้จอกระตุกเมื่อรวมเซนเซอร์หลายตัว
  • ถ้าจะส่งข้อมูลชุดนี้ขึ้น TESAIoT Platform ควรเลือกค่าใดบ้าง

คำตอบอยู่ใน README ของตัวอย่างและในโค้ด ถ้าตอบข้อใดไม่ได้ ให้กลับไปอ่านส่วน Why / What / How อีกครั้ง

คำถามทบทวน

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

  1. ในโค้ดที่ commit นี้ แดชบอร์ดจัดจังหวะการอ่านเซนเซอร์สี่ตัวอย่างไร (เป้าหมายข้อ 2)

    1. มี FreeRTOS task อ่านเซนเซอร์ตัวละหนึ่ง task แล้วส่งค่าเข้าคิวเดียว
    2. อ่านครบทั้งสี่ตัวทุกครั้งที่ timer ทำงาน
    3. lv_timer ตัวเดียวทุก 100 ms ใน LVGL context ตรวจเวลาถึงกำหนดของแต่ละตัว (DPS368 1000 ms, SHT4x 1000 ms, BMI270 200 ms, BMM350 120 ms) แล้วอ่านเฉพาะตัวที่ถึงรอบ ส่วนไมโครโฟนมี task ของตัวเอง
    4. แต่ละเซนเซอร์มี lv_timer ของตัวเอง
    ดูเฉลย

    คำตอบ: C. lv_timer ตัวเดียวทุก 100 ms ใน LVGL context ตรวจเวลาถึงกำหนดของแต่ละตัว (DPS368 1000 ms, SHT4x 1000 ms, BMI270 200 ms, BMM350 120 ms) แล้วอ่านเฉพาะตัวที่ถึงรอบ ส่วนไมโครโฟนมี task ของตัวเอง

    sensorhub_poll_timer_cb() ถูกสร้างด้วย HUB_UI_POLL_MS = 100 และเรียก sensorhub_poll_dps/sht/bmi/bmm ที่เทียบ now_ms กับ next_*_ms ส่วนไมค์ใช้ pdm_probe_logger task เดิม แล้ว hub หยิบค่าล่าสุดผ่าน mic_presenter_get_latest_sample() README ของ episode บรรยายแบบ reader task และคิว ซึ่งไม่ตรงกับโค้ดที่ commit นี้

  2. BMM350_SAMPLE_PERIOD_MS = 120 แต่ timer ของ hub ทำงานทุก 100 ms ในทางปฏิบัติ BMM350 ถูกอ่านทุกกี่ ms (เป้าหมายข้อ 2)

    1. 200 ms
    2. 100 ms
    3. 120 ms
    4. 240 ms
    ดูเฉลย

    คำตอบ: A. 200 ms

    รอบ t = 100 ms ยังไม่ถึง next_bmm_ms (120) จึงข้าม มาถึงรอบ t = 200 ms จึงอ่านแล้วตั้ง next เป็น 320 ซึ่งจะได้อ่านอีกทีที่ t = 400 ms ช่วงเวลาจริงจึงถูกปัดขึ้นเป็นผลคูณของ 100 ms ผลต่อเนื่องคือ auto-calibration 140 ตัวอย่างใช้ราว 28 วินาที ไม่ใช่ 16.8 วินาทีอย่างใน EP04

  3. อะไรเป็นสาเหตุหลักที่ทำให้จอกระตุกเมื่อรวมการอ่านเซนเซอร์หลายตัวไว้ใน LVGL timer และโค้ดลดปัญหาอย่างไร (เป้าหมายข้อ 2)

    1. ฟอนต์ภาษาไทยทำให้วาดช้า
    2. จอมีความละเอียดสูงเกินไป
    3. เพราะใช้ FreeRTOS
    4. การอ่าน I2C/I3C แต่ละครั้งบล็อก LVGL thread ระหว่างรอ bus โค้ดจึงกระจายการอ่านตามคาบเวลาของแต่ละตัว อ่านตัวที่เปลี่ยนช้าแค่วินาทีละครั้ง และย้ายไมโครโฟนที่ต้องรับ 16 kHz ไปไว้ใน task แยก
    ดูเฉลย

    คำตอบ: D. การอ่าน I2C/I3C แต่ละครั้งบล็อก LVGL thread ระหว่างรอ bus โค้ดจึงกระจายการอ่านตามคาบเวลาของแต่ละตัว อ่านตัวที่เปลี่ยนช้าแค่วินาทีละครั้ง และย้ายไมโครโฟนที่ต้องรับ 16 kHz ไปไว้ใน task แยก

    ทุกอย่างใน sensorhub_poll_timer_cb() รันใน LVGL context ระหว่างที่ driver รอ bus การวาดจอต้องรอด้วย การอ่านทุกตัวทุก 100 ms จะกินเวลามากโดยไม่จำเป็น ความดันและความชื้นเปลี่ยนช้าจึงอ่านวินาทีละครั้งก็พอ ส่วนงานเสียงหนักและต่อเนื่องจึงอยู่ใน task ที่ขับด้วย interrupt

  4. ถ้า SHT4x เริ่มต้นไม่สำเร็จ แดชบอร์ดจะเป็นอย่างไร (เป้าหมายข้อ 1)

    1. แดชบอร์ดไม่เปิดเลย
    2. sht_ready เป็น false การอ่าน SHT4x ถูกข้าม footer ขึ้น SHT:- ส่วนเซนเซอร์อื่นและไมโครโฟนทำงานต่อตามปกติ
    3. โค้ดลองอ่าน SHT4x ทุก 100 ms จนกว่าจะสำเร็จ
    4. ค่าอุณหภูมิจาก DPS368 ถูกใช้แทนความชื้น
    ดูเฉลย

    คำตอบ: B. sht_ready เป็น false การอ่าน SHT4x ถูกข้าม footer ขึ้น SHT:- ส่วนเซนเซอร์อื่นและไมโครโฟนทำงานต่อตามปกติ

    sensorhub_presenter_start() init ทีละตัวและเก็บผลใน flag ของตัวเอง sensorhub_poll_sht() ออกทันทีถ้า sht_ready เป็น false และ footer แสดงสถานะ Y หรือ - ของทุกแหล่ง การแยกความล้มเหลวของแต่ละตัวทำให้ระบบรวมยังใช้งานได้

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

    1. แบ่งเป็นแท็บ Home (ภาพรวม) Env Motion Compass Audio และแสดงทีละหน้า
    2. footer บอกความพร้อมของทุกแหล่งข้อมูล (DPS/SHT/BMI/BMM/MIC)
    3. แสดงรูปคลื่นเสียงดิบ 16 kHz ในหน้า Audio
    4. เริ่ม calibrate เข็มทิศอัตโนมัติตั้งแต่เปิดเครื่อง พร้อมแสดงความคืบหน้า
    5. วาดทุกเซนเซอร์ใหม่ที่ 60 ครั้งต่อวินาที
    ดูเฉลย

    คำตอบ: A. แบ่งเป็นแท็บ Home (ภาพรวม) Env Motion Compass Audio และแสดงทีละหน้า · B. footer บอกความพร้อมของทุกแหล่งข้อมูล (DPS/SHT/BMI/BMM/MIC) · D. เริ่ม calibrate เข็มทิศอัตโนมัติตั้งแต่เปิดเครื่อง พร้อมแสดงความคืบหน้า

    sensorhub_view_set_active_page() ซ่อนหน้าที่ไม่ได้เลือก sensorhub_update_footer() พิมพ์สถานะทุกแหล่ง และ presenter เรียก bmm350_reader_start_calibration() ตั้งแต่ start หน้า Audio แสดงระดับเสียงที่คำนวณแล้ว ไม่ใช่คลื่นดิบ และจังหวะวาดถูกกำหนดโดยคาบเวลาของแต่ละเซนเซอร์ เมื่อจะส่งข้อมูลขึ้นแพลตฟอร์มก็ใช้หลักเดียวกัน คือเลือกค่าสรุปที่มีความหมาย ไม่ใช่ข้อมูลดิบทุกตัวอย่าง

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

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

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

ข้อความอ้างอิงภาษาอังกฤษ: "SensorHub: one dashboard for every sensor (series project)" 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/tesaiot-firmware-stack/m03-interactive-sensors/l07-sensorhub-final/

บทเรียนนี้ดัดแปลงจากต้นฉบับด้านล่าง เมื่ออ้างอิงให้คงเครดิตต้นฉบับไว้ด้วย
https://github.com/tesaiot/developer-hub/blob/9a8e3ed1d813bfd67fabf6b7ac15c6ff9750b465/int_ep07_sensorhub_final · Code stays in the Developer Hub and is linked at pinned commits, never copied: the episodes, practice codes and main-branch examples are Apache-2.0; the master template and the OPTIGA client carry Infineon/Cypress EULAs.

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

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

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