อ่านความดันและอุณหภูมิจาก DPS368 ผ่าน I2C
เป้าหมาย
หัวข้อที่มีชื่อว่า “เป้าหมาย”- อ่านค่าความดันบรรยากาศและอุณหภูมิจาก DPS368 ผ่าน I2C แล้วแสดงบนจอ
- อธิบายการแบ่งชั้น driver → reader → presenter → view ของ episode
- ตรวจค่าที่อ่านได้กับค่าความดันอ้างอิงของพื้นที่ และอธิบายส่วนต่าง
DPS368 คืออะไร และตัวเลขในสเปกหมายถึงอะไร
หัวข้อที่มีชื่อว่า “DPS368 คืออะไร และตัวเลขในสเปกหมายถึงอะไร”DPS368 เป็นเซนเซอร์ความดันบรรยากาศ (barometer) แบบ capacitive MEMS ของ Infineon วัดความดันได้ในช่วง 300–1200 hPa ด้วยความละเอียดระดับ ±0.002 hPa (ประมาณการเปลี่ยนระดับความสูง 2 ซม.) และวัดอุณหภูมิร่วมด้วยในตัวเดียวกัน เพราะค่า ความดันต้องชดเชยด้วยอุณหภูมิภายในเซนเซอร์เอง (การคำนวณนี้ทำอยู่ในไลบรารีของ Infineon ให้แล้ว ไม่ต้องเขียนสูตรเอง)
สี่ชั้นของโค้ด: driver → reader → presenter → view
หัวข้อที่มีชื่อว่า “สี่ชั้นของโค้ด: driver → reader → presenter → view”โค้ดแบ่งงานเป็นสี่ชั้นตามชื่อโฟลเดอร์ app_sensor/dps368/ มี dps368_driver (คุยกับเซนเซอร์ผ่านไลบรารี
xensiv_dps3xx ของ Infineon โดยตรง) และ dps368_reader (ห่อ driver ให้เป็น dps368_reader_poll() ฟังก์ชันเดียว
คืน bool บอกว่ามี sample ใหม่หรือไม่) ส่วน app_ui/dps368/ มี dps368_presenter (ผูก timer + ตัดสินใจว่าจะ
อัปเดตจอเมื่อไร) และ dps368_view (สร้างและอัปเดต label ล้วน ๆ ไม่มี gauge หรือ arc ตามที่บางครั้งอาจถูกเข้าใจผิด)
แต่ละชั้นรู้จักแค่ชั้นที่อยู่ติดกัน — dps368_view ไม่รู้จัก I2C เลย และ dps368_driver ไม่รู้จัก LVGL เลย
สัญญาณเข้าเซนเซอร์ผ่าน bus ที่ master เตรียมไว้แล้ว พร้อม fallback ที่อยู่ I2C
หัวข้อที่มีชื่อว่า “สัญญาณเข้าเซนเซอร์ผ่าน bus ที่ master เตรียมไว้แล้ว พร้อม fallback ที่อยู่ I2C”main_example.c ส่ง &sensor_i2c_controller_hal_obj (HAL handle ที่ master template เปิดไว้แล้วตั้งแต่ก่อน
example_main() ถูกเรียก — ดูบทเรียน 1.1) เข้าไปให้ dps368_presenter_start() โดยตรง ไม่ต้อง cyhal_i2c_init()
ซ้ำ ภายใน app_dps368_service_init() ลองต่อที่ address เริ่มต้น (XENSIV_DPS3XX_I2C_ADDR_DEFAULT = 0x77) ก่อน
ถ้าไม่ตอบจะลองที่อยู่สำรอง (XENSIV_DPS3XX_I2C_ADDR_ALT = 0x76 ซึ่งกำหนดโดยขา SDO ของเซนเซอร์) — โค้ดจึงใช้งานได้
กับบอร์ดที่ strap ขา SDO ไว้คนละแบบโดยไม่ต้องแก้โค้ด
วิธี poll จริง: lv_timer เดียวบน LVGL thread ไม่ใช่ FreeRTOS task แยก
หัวข้อที่มีชื่อว่า “วิธี poll จริง: lv_timer เดียวบน LVGL thread ไม่ใช่ FreeRTOS task แยก”dps368_presenter_start() เรียก lv_timer_create(dps368_poll_sensor_cb, DPS368_SAMPLE_PERIOD_MS, NULL) โดย
DPS368_SAMPLE_PERIOD_MS = 1000 (วินาทีละครั้ง) — dps368_poll_sensor_cb() รันอยู่บน LVGL thread เดียวกัน
กับส่วนที่วาดจอ ไม่ใช่ FreeRTOS task แยกต่างหากที่ส่งข้อมูลผ่านคิวแล้วกลับเข้า LVGL ด้วย lv_async_call()
ตามที่บางครั้งอาจเข้าใจผิด การอ่าน I2C (dps368_driver_read_hpa_c()) จึงเป็นการเรียกแบบ blocking ตรง ๆ ภายใน
callback ของ timer เพราะ transaction I2C หนึ่งครั้งเร็วพอที่จะไม่ทำให้จอกระตุก ที่ 1 ครั้ง/วินาที episode นี้จึง
เลือกความเรียบง่ายเหนือการแยก thread เพราะเซนเซอร์ตัวเดียวไม่คุ้มความซับซ้อนของ task+queue
แยก “ยังไม่มีข้อมูลใหม่” ออกจาก “error จริง”
หัวข้อที่มีชื่อว่า “แยก “ยังไม่มีข้อมูลใหม่” ออกจาก “error จริง””dps368_reader_poll() เช็ค return code ของ driver พิเศษกรณีเดียวคือ XENSIV_DPS3XX_RSLT_ERR_DATA_NOT_READY — ถ้า
เจอค่านี้จะคืน false แต่ ไม่ถือเป็น error (แค่ยังไม่ถึงรอบ conversion ถัดไปของเซนเซอร์) ส่วน error code อื่น
ถือเป็นปัญหาจริงที่ presenter จะเอาไปแสดงบน status label เป็นข้อความ "Sensor: read error (0x........)" การแยก
สองกรณีนี้ออกจากกันสำคัญ เพราะถ้าถือว่า “ยังไม่พร้อม” เป็น error ทุกครั้ง จอจะกระพริบข้อความ error ทุกวินาทีที่ยัง
ไม่ถึงรอบอ่านจริง
ตัวอย่างสมบูรณ์
หัวข้อที่มีชื่อว่า “ตัวอย่างสมบูรณ์”โค้ดของ episode นี้อยู่ใน Developer Hub (อ้างอิงที่ commit 9a8e3ed) — อ่าน Why ของ README ต้นทาง เพื่อเข้าใจจุดประสงค์ แต่ โค้ดตัวอย่างด้านล่างคัดลอกจากไฟล์จริง (Apache-2.0, tesaiot/developer-hub, commit เดียวกัน) เพราะกลไก poll จริงต่างจากที่ README ต้นทางอธิบาย (ไม่มี FreeRTOS task/queue แยก)
app_ui/dps368/dps368_presenter.c — lv_timer เดียวที่ poll แล้วอัปเดตจอ:
static void dps368_poll_sensor_cb(lv_timer_t *timer){ (void)timer;
dps368_sample_t sample; bool has_new_sample = dps368_reader_poll(&sample);
if (has_new_sample) { dps368_view_update_sample(&sample); /* ... */ return; }
cy_rslt_t rslt = dps368_reader_get_last_error(); if (CY_RSLT_SUCCESS == rslt) { return; /* not-ready is not an error */ } /* ... real error -> update status label ... */}app_sensor/dps368/dps368_reader.c — แยก “ยังไม่พร้อม” ออกจาก error จริง:
if (CY_RSLT_SUCCESS == rslt){ s_last_sample.pressure_hpa = pressure_hpa; s_last_sample.temperature_c = temperature_c; s_last_sample.sample_count++; if (NULL != out_sample) { *out_sample = s_last_sample; } return true;}
/* At low sample rate, polling can happen before conversion is ready. */if (rslt == XENSIV_DPS3XX_RSLT_ERR_DATA_NOT_READY){ s_last_error = CY_RSLT_SUCCESS; return false;}
s_last_error = rslt;return false;app_sensor/app_dps368_service.c — ลองที่อยู่ I2C หลักก่อน แล้วค่อย fallback ไปที่อยู่สำรอง:
rslt = mtb_xensiv_dps3xx_init_i2c(&s_dps368, i2c_bus, XENSIV_DPS3XX_I2C_ADDR_DEFAULT);if (CY_RSLT_SUCCESS != rslt){ rslt = mtb_xensiv_dps3xx_init_i2c(&s_dps368, i2c_bus, XENSIV_DPS3XX_I2C_ADDR_ALT); /* ... */}main_example.cส่ง handle I2C ที่ master เตรียมไว้เข้าdps368_presenter_start()ตรงตามที่ README ต้นทางอธิบาย- ไฟล์
basic_label_legacy.*และdps368_monitor_legacy.*เป็นโค้ดเวอร์ชันก่อน refactor ไว้เทียบ ไม่ใช่ส่วนที่ทำงานจริงของ episode - ดูโฟลเดอร์เต็มที่
int_ep01_dps368_monitor/
จุดที่มักพลาด
หัวข้อที่มีชื่อว่า “จุดที่มักพลาด”- คิดว่ามี FreeRTOS task แยกต่างหาก poll เซนเซอร์ทุก 100 ms — โค้ดจริงใช้
lv_timerเดียวบน LVGL thread ที่ 1000 ms และอ่าน I2C แบบ blocking ตรง ๆ ในนั้นเลย ไม่มี queue หรือlv_async_call()แนวคิดนี้ใช้ได้เพราะมีเซนเซอร์เดียวและ I2C transaction สั้น — ถ้าเพิ่มเซนเซอร์ที่อ่านช้าลงในอนาคต ค่อยพิจารณาแยก task จริง (ดู EP07 — SensorHub Final) - ถือว่า
XENSIV_DPS3XX_RSLT_ERR_DATA_NOT_READYเป็น error — ต้องแยกจาก error code อื่นเสมอ เพราะเป็นแค่ สัญญาณว่ายังไม่ถึงรอบ conversion ถัดไป ไม่ใช่ความล้มเหลว ถ้าปฏิบัติเหมือน error ทุกครั้งจะได้ log/สถานะ error ที่ไม่ตรงความจริง - ลืมว่าเซนเซอร์มีสองที่อยู่ I2C ที่เป็นไปได้ — ถ้า hard-code แค่
0x77โค้ดจะใช้ไม่ได้กับบอร์ดที่ strap ขา SDO ต่างไป ต้องลอง fallback เสมอแบบที่app_dps368_service_init()ทำ
build และ flash
หัวข้อที่มีชื่อว่า “build และ flash”# ในโฟลเดอร์ master template (ดูบทเรียน 1.1)# 1) ลบไฟล์ของ episode เก่าใน proj_cm55/apps/# 2) คัดลอกไฟล์ทั้งหมดของ episode นี้ลงใน proj_cm55/apps/make buildmake program # flash ผ่าน KitProg3หรือเปิด ตัวอย่างนี้บน Developer Hub แล้ว flash เฟิร์มแวร์สำเร็จรูป
ดูของจริงก่อน
หัวข้อที่มีชื่อว่า “ดูของจริงก่อน”
ก่อนอ่านโค้ด ให้ทายว่าหน้าจอนี้มี object อะไรบ้าง และอะไรเปลี่ยนเมื่อผู้ใช้แตะหรือเมื่อค่าเซนเซอร์เปลี่ยน
- ทาย ก่อนแก้: เลือกค่าหนึ่งค่าที่ README ของตัวอย่างอธิบายไว้ในส่วน How แล้วเขียนว่าจะเห็นอะไรเปลี่ยนบนจอหรือใน log
- แก้และรัน build + flash แล้วเทียบกับที่ทายไว้ ถ้าไม่ตรง ให้หาว่าเข้าใจส่วนไหนผิด
- ทำเพิ่ม ต่อยอดหนึ่งอย่างที่ตัวอย่างยังไม่มี แล้วเก็บภาพหรือวิดีโอไว้ใน portfolio
เช็กความเข้าใจ
หัวข้อที่มีชื่อว่า “เช็กความเข้าใจ”- ชั้น presenter ทำอะไรที่ view ไม่ทำ
- ความดันควรเปลี่ยนอย่างไรเมื่อยกบอร์ดขึ้นสูงหนึ่งชั้นตึก
- ถ้าเซนเซอร์ตอบ I2C ไม่ได้ ข้อความผิดพลาดควรขึ้นที่ชั้นใด
คำตอบอยู่ใน README ของตัวอย่างและในโค้ด ถ้าตอบข้อใดไม่ได้ ให้กลับไปอ่านส่วน Why / What / How อีกครั้ง
แหล่งอ้างอิง
หัวข้อที่มีชื่อว่า “แหล่งอ้างอิง”- README ของ episode · โฟลเดอร์โค้ด · commit
9a8e3ed - เปิดตัวอย่างนี้บน Developer Hub
- โค้ดเป็นของ Developer Hub และอ้างอิงด้วยลิงก์ ไม่ได้คัดลอกเข้าคลังนี้
คำถามทบทวน
ลองตอบเองก่อน แล้วค่อยเปิดดูเฉลย
-
ถ้า DPS368 ไม่ตอบบน I2C ตอนเริ่มต้น จะเห็นอะไร (เป้าหมายข้อ 1)
- บอร์ดรีเซ็ตวนไม่หยุด
- ค่าความดันเป็น 0.00 hPa แต่สถานะขึ้น ready
- driver ลอง address หลักแล้วลอง address สำรอง ถ้าไม่ตอบทั้งคู่จะ log INIT_FAIL จอขึ้น “Sensor: init failed” และไม่สร้าง timer อ่านค่า
- presenter ลองใหม่ทุก 1 วินาทีจนกว่าเซนเซอร์จะตอบ
ดูเฉลย
คำตอบ: C. driver ลอง address หลักแล้วลอง address สำรอง ถ้าไม่ตอบทั้งคู่จะ log INIT_FAIL จอขึ้น “Sensor: init failed” และไม่สร้าง timer อ่านค่า
dps368_driver_init() เรียก mtb_xensiv_dps3xx_init_i2c() ด้วย DPS368_I2C_ADDR_PRIMARY ก่อนแล้วจึง ALTERNATE เมื่อยังล้ม dps368_presenter_start() เรียก dps368_view_set_init_failed() แล้ว return ก่อน lv_timer_create() จึงไม่มีการอ่านซ้ำ ต้องแก้สายหรือบอร์ดแล้วรีเซ็ต ส่วน I2C bus เองถูก master เตรียมไว้ให้แล้ว episode แค่รับ handle sensor_i2c_controller_hal_obj
-
ระหว่างทำงาน การอ่าน I2C ล้มเหลว ข้อความ error บนจอเกิดจากการทำงานร่วมกันของแต่ละชั้นอย่างไร (เป้าหมายข้อ 2)
- driver คืนรหัส error, reader เก็บเป็น last error, presenter ตัดสินใจแสดงเมื่อรหัสเปลี่ยน แล้วเรียก view ให้เขียนข้อความ
- driver เรียก lv_label_set_text() เองทันทีที่อ่านล้ม
- view อ่าน I2C เองเพื่อตรวจว่าเซนเซอร์ยังอยู่
- master template ตรวจแล้วแสดงข้อความให้
ดูเฉลย
คำตอบ: A. driver คืนรหัส error, reader เก็บเป็น last error, presenter ตัดสินใจแสดงเมื่อรหัสเปลี่ยน แล้วเรียก view ให้เขียนข้อความ
dps368_reader_poll() เก็บ s_last_error presenter อ่านด้วย dps368_reader_get_last_error() และเรียก dps368_view_set_read_error() เฉพาะเมื่อรหัสต่างจากครั้งก่อน เพื่อไม่ให้จอกระพริบและ log ท่วม view ไม่รู้จัก I2C และ driver ไม่รู้จัก LVGL แต่ละชั้นจึงเปลี่ยนแยกกันได้
-
บางรอบ xensiv_dps3xx_read() คืน XENSIV_DPS3XX_RSLT_ERR_DATA_NOT_READY โค้ดจัดการอย่างไร และทำไม (เป้าหมายข้อ 2)
- ถือเป็น error ร้ายแรงและแสดงบนจอทันที
- init เซนเซอร์ใหม่ทุกครั้ง
- หน่วงเวลา 100 ms แล้วอ่านซ้ำในรอบเดียวกัน
- reader คืน false แต่ไม่ถือเป็น error presenter จึงข้ามรอบนั้นเงียบ ๆ และจอคงค่าเดิม เพราะที่อัตรา 1 ครั้งต่อวินาทีกับ oversampling 8 เท่า การ poll อาจมาถึงก่อนการแปลงค่าเสร็จ ซึ่งเป็นเรื่องปกติ
ดูเฉลย
คำตอบ: D. reader คืน false แต่ไม่ถือเป็น error presenter จึงข้ามรอบนั้นเงียบ ๆ และจอคงค่าเดิม เพราะที่อัตรา 1 ครั้งต่อวินาทีกับ oversampling 8 เท่า การ poll อาจมาถึงก่อนการแปลงค่าเสร็จ ซึ่งเป็นเรื่องปกติ
comment ใน dps368_reader.c เขียนว่า At low sample rate, polling can happen before conversion is ready. จึงตั้ง s_last_error = CY_RSLT_SUCCESS เพื่อไม่ให้ presenter แสดง error ปลอม ตัวอย่างนี้ poll ผ่าน lv_timer ทุก DPS368_SAMPLE_PERIOD_MS (1000 ms) ในขณะที่เซนเซอร์วัดอยู่เบื้องหลัง (background mode)
-
ยกบอร์ดขึ้นสูงหนึ่งชั้นตึก (ราว 3 เมตร) ค่าความดันที่อ่านได้ควรเปลี่ยนอย่างไร (เป้าหมายข้อ 3)
- เพิ่มขึ้นราว 0.35 hPa
- ลดลงราว 0.35 hPa
- ลดลงราว 3.5 hPa
- ไม่เปลี่ยน เพราะเซนเซอร์ละเอียดไม่พอ
ดูเฉลย
คำตอบ: B. ลดลงราว 0.35 hPa
README ของ episode ให้ตัวเลขว่ายกขึ้น 1 เมตร ความดันลดราว 0.12 hPa (ใกล้ระดับน้ำทะเล) 3 เมตรจึงลดราว 0.36 hPa ความละเอียดของ DPS368 ราว ±0.002 hPa จึงเห็นได้ชัด แต่ควรดูค่าเฉลี่ยหลายวินาที เพราะลมและแอร์ในห้องทำให้ค่าแกว่งได้
-
บอร์ดอ่านได้ 1005 hPa ขณะที่สถานีอุตุนิยมวิทยาใกล้เคียงรายงาน 1012 hPa ข้อใดอธิบายส่วนต่างได้ดีที่สุด (เป้าหมายข้อ 3)
- เซนเซอร์เสีย ต้องเปลี่ยนตัวใหม่
- ค่าจากบอร์ดเป็นหน่วย kPa
- ค่าที่สถานีรายงานมักปรับเป็นระดับน้ำทะเลแล้ว ส่วนบอร์ดวัดความดันจริงตรงความสูงที่วางอยู่ ส่วนต่างราว 7 hPa ตรงกับความสูงราว 60 เมตร จึงต้องเทียบในเกณฑ์เดียวกันก่อน
- ความดันในอาคารสูงกว่าภายนอกเสมอ
ดูเฉลย
คำตอบ: C. ค่าที่สถานีรายงานมักปรับเป็นระดับน้ำทะเลแล้ว ส่วนบอร์ดวัดความดันจริงตรงความสูงที่วางอยู่ ส่วนต่างราว 7 hPa ตรงกับความสูงราว 60 เมตร จึงต้องเทียบในเกณฑ์เดียวกันก่อน
สูตร barometric ใน README คือ h = 44330 × (1 − (P/P0)^0.1903) แทนค่า 1005 กับ 1012 hPa ได้ราว 58 เมตร ให้ตรวจก่อนว่าค่าอ้างอิงเป็นความดันที่ปรับเป็นระดับน้ำทะเลหรือความดันที่สถานี และจุดที่วัดสูงเท่าไร ก่อนสรุปว่าเซนเซอร์คลาด
อ้างอิงบทเรียนนี้
ถ้านำบทเรียนนี้ไปสอน ทำสไลด์ หรือทำเอกสารต่อ ให้อ้างอิงด้วยข้อความนี้ ถ้าดัดแปลงเนื้อหา ให้เติม (ดัดแปลง)ต่อท้ายชื่อบทเรียน
"อ่านความดันและอุณหภูมิจาก DPS368 ผ่าน I2C" จาก TESA Open Knowledge โดยสมาคมสมองกลฝังตัวไทย (Thai Embedded Systems Association: TESA) https://github.com/tesaiot/tesa-qualification-program สัญญาอนุญาต CC BY-NC 4.0
ข้อความอ้างอิงภาษาอังกฤษ: "Pressure and temperature from the DPS368 over I2C" 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://github.com/tesaiot/developer-hub/blob/9a8e3ed1d813bfd67fabf6b7ac15c6ff9750b465/int_ep01_dps368_monitor · 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 Open Knowledge · © 2026 สมาคมสมองกลฝังตัวไทย (TESA) · CC BY-NC 4.0
เนื้อหาเผยแพร่ภายใต้ CC BY-NC 4.0 นำไปใช้ต่อในงานที่ไม่ใช่เพื่อการค้าได้ โปรดอ้างอิงสมาคมสมองกลฝังตัวไทย (TESA) ทุกครั้ง · วิธีอ้างอิง TESA