การใช้งานเซ็นเซอร์ของ Cucumber RS

Supachai Vorapojpisut
8 min readMay 10, 2020

--

การเขียนโค้ดสำหรับ ESP-IDF

การเขียนโค้ดสำหรับโปรแกรมหลักที่จะ build ด้วยชุดเครื่องมือ ESP-IDF จะแตกต่างจากภาษา C ซึ่งเริ่มต้นด้วยฟังก์ชัน int main() และ Arduino ซึ่งใช้ฟังก์ชัน void setup() และ void loop() ส่วนโค้ดหลักของ ESP-IDF จะเริ่มจากฟังก์ชัน void app_main() ที่จะถูกเรียกใช้จากระบบปฏิบัติการทันเวลา FreeRTOS ที่ถูกรวมไว้เป็นโครงของซอฟต์แวร์ ทั้งนี้ เราสามารถเรียกใช้ API ต่างๆของ FreeRTOS รวมไปถึง API ของ middleware ต่างๆ เช่น โพรโทคอลสแตก HTTP, MQTT, MODBUS ได้เลย เนื่องจากตัว ESP-IDF ได้รวมซอร์สโค้ดของ FreeRTOS และ middleware ต่างๆไว้แล้ว รายละเอียดของ FreeRTOS และ middleware สามารถปรับแต่งได้จากกดปุ่ม F1 แล้วเลือกคำสั่ง ESP-IDF: Launch gui configurion tool

บอร์ด Cucumber รุ่น RS มาพร้อมเซ็นเซอร์ 3 ตัวคือ HTS221 สำหรับวัดอุณหภูมิ/ความชื้น BMP280 สำหรับวัดความดันอากาศ และ MPU-6050 สำหรับตรวจจับการเคลื่อนไหว ซึ่งทั้งสามเซ็นเซอร์จะเชื่อมต่อกับหน่วยประมวลผลทางพอร์ต I2C0 ทางขา IO40/IO41 การเขียนโค้ดเพื่ออ่านค่าจากเซ็นเซอร์แต่ละตัวจึงมีรูปแบบที่เหมือนกันคือ กำหนดให้ ESP32-S2 ทำงานเป็น I2C master แล้วสั่งอ่าน/เขียนค่ารีจิสเตอร์ของแต่ละเซ็นเซอร์ด้วยการระบุค่าแอดเดรส โดยค่าแอดเดรสของ HTS221 คือ 0x5F, BMP280 คือ 0x76 และ MPU-6050 คือ 0x68 นอกจากเซ็นเซอร์บนบอร์ด ตัวบอร์ดเองยังสามารถเชื่อมต่อกับเซ็นเซอร์แอนะล็อกผ่านทาง ADC 12 บิต ซึ่งเลือกต่อได้กับขาสัญญาณทางขวาของตัวบอร์ดได้เกือบทุกขาดังรูป

รายละเอียดขาสัญญาณของบอร์ด Cucumber รุ่น R/RS

ESP-IDF ได้เตรียม API สำหรับ I2C ในโหมด master โดยแบ่งรูปแบบการอ่าน/เขียนค่ากับอุปกรณ์ออกเป็น 2 รูปแบบคือ polling และ ISR ลำดับของการเขียนโค้ดมีขั้นตอนดังนี้

  1. กำหนดการทำงานของ I2C โดยกำหนดค่าโหมด (master) ขาที่ใช้ และความเร็ว clock ให้กับ struct i2c_config_t แล้วเรียกใช้ฟังก์ชัน i2c_param_config() เพื่อตั้งค่าให้กับพอร์ต I2C0 หรือ I2C1
  2. เรียกใช้ฟังก์ชัน i2c_driver_install() เพื่อเปิดใช้งานพอร์ต I2C0 หรือ I2C1
  3. การเขียนค่า (master write) เริ่มด้วยฟังก์ชัน i2c_cmd_link_create() จากนั้นเรียกใช้ i2c_master_start() กำหนดบิตเริ่ม i2c_master_write_byte() กำหนดแอดเดรสของ slave i2c_master_write() กำหนดข้อมูลที่จะเขียน และ i2c_master_stop() กำหนดบิตจบ แล้วจึงเรียกใช้ฟังก์ชัน i2c_master_cmd_begin() เพื่อเริ่มต้นสื่อสาร สุดท้ายคือเรียกฟังก์ชัน i2c_cmd_link_delete() เพื่อล้างทรัพยากรที่ร้องขอ
  4. การอ่านค่า (master read) มีขั้นตอนคล้ายกับ master write โดยเพิ่มการเรียกใช้ฟังก์ชัน i2c_master_read_byte() หลังฟังก์ชัน i2c_master_write_byte()
ลำดับของโค้ดสำหรับ master write จากเอกสารออนไลน์ของ ESP-IDF

การเขียนโค้ดใช้งาน I2C ด้วย API ของ ESP-IDF จะต้องเรียกใช้หลายฟังก์ชันเพื่อกำหนดรายละเอียดของการสื่อสารแต่ละเฟรม ผมจึงเลือกใช้ไลบรารี esp-idf-lib ของคุณ Ruslan ที่มี i2cdev เป็นไดรเวอร์สำหรับการสื่อสารแบบ I2C ซึ่งมีจุดเด่นของการเป็น thread-safe ด้วยการหุ้มทั้ง API ของ I2C และ MUTEX ไว้ด้วยกัน การเขียนโค้ดสำหรับทดสอบเซ็นเซอร์บนบอร์ดด้วย เริ่มต้นด้วยการสร้าง project ใหม่แล้วติดตั้ง component จากไลบรารี esp-idf-lib อย่างไรก็ตามการติดตั้งไลบรารี esp-idf-lib ไม่สามารถใช้คำสั่ง git clone มารวมกับส่วนโค้ดของ project ได้โดยตรง เนื่องจากตัวไลบรารีถูกพัฒนาสำหรับ ESP-IDF รุ่น 3.2 ซึ่งมีจุดที่แตกต่างจาก ESP-IDF รุ่น 4.2 จึงต้องดำเนินตามขั้นตอนดังนี้

  1. กดปุ่ม F1 แล้วเลือกคำสั่ง Git: Clone เพื่อดาวน์โหลดไลบรารี
  2. สร้างโฟลเดอร์ย่อย components ในโฟลเดอร์ของ project
  3. สำเนาโฟลเดอร์ย่อย i2cdev และ esp_idf_lib_helpers จากในโฟลเดอร์ย่อย components ของไลบรารี
  4. แก้ไขไฟล์ CMakeLists.txt เพื่อระบุตำแหน่งของซอร์สโค้ดที่จะเพิ่มเข้าสู่ project
set(EXTRA_COMPONENT_DIRS path/to/project/components)

ผมเตรียมโค้ดตัวอย่างที่สาธิตการแยกส่วนโค้ดที่เข้าถึงเซ็นเซอร์ออกเป็น 3 แทสค์ย่อย โดยแต่ละแทสค์จะทำหน้าที่แค่ยืนยันการเชื่อมต่อตัวเซ็นเซอร์ผ่าน I2C เท่านั้น ส่วนโปรแกรมหลักจึงมีเพียงการสร้าง i2cdev และการสร้างแทสค์ทั้งสามเท่านั้น ทั้งนี้ การใช้ไดรเวอร์ของ I2C ที่รวม MUTEX ไว้ด้วยทำให้ลดปัญหาเรื่อง race condition ในการแย่งเข้าถึงเซ็นเซอร์ผ่านทางบัส I2C0 เพียงชุดเดียว

#include <stdio.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "i2cdev.h"
i2c_dev_t dev;void app_main(void) {
printf(“Starting I2C0 with pins IO40/IO41\n”);
i2cdev_init();
dev.port = 0;
dev.cfg.sda_io_num = 41;
dev.cfg.scl_io_num = 40;
dev.cfg.master.clk_speed = 400000;
i2c_dev_create_mutex(&dev);
xTaskCreate(bmp280_task, “BMP280 task”, configMINIMAL_STACK_SIZE*8, NULL, 5, NULL);
vTaskDelay(10 / portTICK_PERIOD_MS);
xTaskCreate(hts221_task, “HTS221 task”, configMINIMAL_STACK_SIZE*8, NULL, 5, NULL);
vTaskDelay(10 / portTICK_PERIOD_MS);
xTaskCreate(mpu6050_task, “MPU6050 task”, configMINIMAL_STACK_SIZE*8, NULL, 5, NULL);
}

แทสค์สำหรับเช็คเซ็นเซอร์ HTS221 จะกำหนดค่า address (0x5F) จากนั้นอ่านค่าจากรีจิสเตอร์ 0x0F (WHO_AM_I) แล้วตรวจสอบว่าเท่ากับ 0xBC หรือไม่

void hts221_task(void *pvParamters) {
const uint8_t HTS221_ADDR = 0x5F;
const uint8_t HTS221_WHOAMI_ADDR = 0x0F;
const uint8_t HTS221_WHOAMI_VAL = 0xBC;
uint8_t buf;
while(1) {
dev.addr = HTS221_ADDR;
I2C_DEV_TAKE_MUTEX(&dev);
i2c_dev_read_reg(&dev, HTS221_WHOAMI_ADDR, &buf, 1);
if (buf == HTS221_WHOAMI_VAL) {
printf(“Found HTS221\n”);
} else {
printf(“HTS221 not found\n”);
}
I2C_DEV_GIVE_MUTEX(&dev);
vTaskDelay(1000 / portTICK_PERIOD_MS);
}
}

แทสค์สำหรับเช็คเซ็นเซอร์ MPU-6050 จะทำงานเช่นเดียวกัน โดยกำหนดค่า address (0x68) จากนั้นอ่านค่าจากรีจิสเตอร์ 0x75 (WHO_AM_I) แล้วตรวจสอบว่าเท่ากับ 0x68 หรือไม่

void mpu6050_task(void *pvParamters) {
const uint8_t MPU6050_ADDR = 0x68;
const uint8_t MPU6050_WHOAMI_ADDR = 0x75;
const uint8_t MPU6050_WHOAMI_VAL = 0x68;
uint8_t buf;
while(1) {
dev.addr = MPU6050_ADDR;
I2C_DEV_TAKE_MUTEX(&dev);
i2c_dev_read_reg(&dev, MPU6050_WHOAMI_ADDR, &buf, 1);
if (buf == MPU6050_WHOAMI_VAL) {
printf(“Found MPU6050\n”);
} else {
printf(“MPU6050 not found\n”);
}
I2C_DEV_GIVE_MUTEX(&dev);
vTaskDelay(1000 / portTICK_PERIOD_MS);
}
}

แทสค์สำหรับเช็คเซ็นเซอร์ BMP280 กำหนดค่า address (0x76) จากนั้นอ่านค่าจากรีจิสเตอร์ 0xD0 (ID) แล้วตรวจสอบว่าเท่ากับ 0x58 หรือไม่

void bmp280_task(void *pvParamters) {
const uint8_t BMP280_ADDR = 0x76;
const uint8_t BMP280_ID_ADDR = 0xD0;
const uint8_t BMP280_ID_VAL = 0x58;
uint8_t buf;
while(1) {
dev.addr = BMP280_ADDR;
I2C_DEV_TAKE_MUTEX(&dev);
i2c_dev_read_reg(&dev, BMP280_ID_ADDR, &buf, 1);
if (buf == BMP280_ID_VAL) {
printf(“Found BMP280\n”);
} else {
printf(“BMP280 not found\n”);
}
I2C_DEV_GIVE_MUTEX(&dev);
vTaskDelay(1000 / portTICK_PERIOD_MS);
}
}

เมื่อใช้คำสั่ง ESP-IDF: Build your project แล้วตามด้วย Flash your project เพื่อทดลองโค้ดตัวอย่างพบว่ายืนยันสถานะของทั้งสามเซ็นเซอร์ได้

หน้าจอยืนยันการตรวจพบเซ็นเซอร์จากการอ่านค่าผ่าน I2C

การใช้งานเซ็นเซอร์ BMP280

ไลบรารี esp-idf-lib มีไดรเวอร์ของเซ็นเซอร์ BMP280 ที่เขียนหุ้มการทำงานของ i2cdev ไว้ จึงเริ่มได้ง่ายเพียงแค่สำเนาโฟลเดอร์ย่อย bmp280 มาไว้ในโฟลเดอร์ /components ของ project ข้อควรระวังคือ โค้ดตัวอย่างของเรามีการเข้าถึงพอร์ต I2C0 จาก 3 แทสค์ที่จะไปอ่านค่าจากเซ็นเซอร์ต่างกัน จึงไม่สามารถเรียกใช้ฟังก์ชัน bmp280_init_desc() ซึ่งจะไปจองใช้งานพอร์ต I2C0 และจอง MUTEX ได้ การเขียนโค้ดจึงต้องหลีกเลี่ยงไปใช้การ copy ค่า struct i2cdev ไปไว้ใน struct bmp280_t แทน รวมทั้งต้องกำหนดแอดเดรสของ BMP280 (0x76) ก่อนการเรียกใช้ฟังก์ชัน

โค้ดตัวอย่างของแทสค์ bmp280_task ถูกปรับให้มาเรียกใช้ฟังก์ชันจาก component ใหม่ โดยตัวไลบรารีจะรองรับทั้ง BMP280 (มีเซ็นเซอร์วัดอุณหภูมิไว้ชดเชยการอ่านค่าความดันอากาศ) และ BME280 (มีทั้งเซ็นเซอร์วัดอุณหภูมิ/ความชื้น) จึงมีการอ่านค่าตัวแปร dummy ออกมาที่ไม่ได้ใช้ในงานนี้

#include "bmp280.h"void bmp280_task(void *pvParamters) {
bmp280_params_t params;
bmp280_t bmp280_dev;
bmp280_init_default_params(&params);
memset(&bmp280_dev, 0, sizeof(bmp280_t));
memcpy(&(bmp280_dev.i2c_dev), &dev, sizeof(i2c_dev_t));
bmp280_dev.i2c_dev.addr = BMP280_I2C_ADDRESS_0;
bmp280_init(&bmp280_dev, &params);
while(1) {
float temperature, pressure, humidity;
bmp280_dev.i2c_dev.addr = BMP280_I2C_ADDRESS_0;
if (bmp280_read_float(&bmp280_dev, &temperature, &pressure, &humidity) != ESP_OK) {
printf(“Temperature/pressure reading failed\n”);
continue;
}
printf(“Pressure: %.2f Pa, Temperature: %.2f C\n”, pressure, temperature);
vTaskDelay(1000 / portTICK_PERIOD_MS);
}
}

การทดลองโค้ดตัวอย่างพบว่าตัวเซ็นเซอร์ BMP280 มีความละเอียดได้ตามสเปคคือ 1 เมตร คือ จับเซ็นเซอร์ไปวางใต้โต๊ะและวางลอยเหนือโต๊ะ พบว่าค่าความดันอากาศที่อ่านได้แตกต่างประมาณหลักสิบจากหมื่น Pascal

ผลการอ่านค่าความดันอากาศและอุณหภูมิจาก BMP280

การใช้งานเซ็นเซอร์ HTS221

เนื้อหาถัดไปคือ การเขียนโค้ดเพื่ออ่านค่าอุณหภูมิและความชื้นจากเซ็นเซอร์ HTS221 ซึ่งไม่มีโค้ดอยู่ในไลบรารี esp-idf-lib ดังนั้นเราจึงต้องสร้าง component เพิ่มสำหรับเซ็นเซอร์ HTS221 โดยเริ่มจากการสร้างโฟลเดอร์ย่อย hts221 ในโฟลเดอร์ /components ของ project จากนั้นจึงสำเนาไฟล์ CMakeLists.txt และ component.mk จากโฟลเดอร์ย่อย bmp280 มาเป็นต้นแบบ สุดท้ายคือ การสร้างไฟล์ hts221.c และ hts221.h เพื่อเป็นส่วนโค้ดสำหรับอ่านค่าเซ็นเซอร์ HTS221

การเข้าถึงเซ็นเซอร์ HTS221 ผ่านพอร์ต I2C จะเรียกใช้ฟังก์ชันจากไลบรารี esp-idf-lib เช่นเดียวกับโมดูล BMP280 จึงสามารถศึกษาโครงสร้างของส่วนโค้ดได้จากไฟล์ bmp280.c และ bmp280.h ฟังก์ชันที่จะเขียนขึ้นแบ่งออกเป็น 3 ฟังก์ชัน ซึ่งประกาศไว้ในไฟล์ hts221.h ร่วมกับค่าคงที่ของรีจิสเตอร์ต่างๆ รวมทั้ง struct สำหรับจับกลุ่มพารามิเตอร์สำหรับตั้งค่าการทำงาน hts221_params_t และตัวไดรเวอร์ hts221_t การตั้งชื่อของค่าคงที่ โครงสร้างข้อมูล และฟังก์ชัน พยายามอ้างอิงจาก datasheet และโค้ด bmp280 เพื่อให้ไม่สับสน

#ifndef __HTS221_H__
#define __HTS221_H__
#include <stdint.h>
#include <stdbool.h>
#include <esp_err.h>
#include <i2cdev.h>
#ifdef __cplusplus
extern "C" {
#endif
#define HTS221_I2C_ADDRESS 0x5F
#define HTS221_WHO_AM_I 0x0F
#define HTS221_CHIP_ID 0xBC
#define HTS221_AV_CONF 0x10
#define HTS221_AV_CONF_MASK (0x38 | 0x07)
#define HTS221_AVGH_4 0x00
#define HTS221_AVGH_8 0x01
#define HTS221_AVGH_16 0x02
#define HTS221_AVGH_32 0x03
#define HTS221_AVGH_64 0x04
#define HTS221_AVGH_128 0x05
#define HTS221_AVGH_256 0x06
#define HTS221_AVGH_512 0x07
#define HTS221_AVGT_2 0x00
#define HTS221_AVGT_4 0x08
#define HTS221_AVGT_8 0x10
#define HTS221_AVGT_16 0x18
#define HTS221_AVGT_32 0x20
#define HTS221_AVGT_64 0x28
#define HTS221_AVGT_128 0x30
#define HTS221_AVGT_256 0x38
#define HTS221_CTRL_REG1 0x20
#define HTS221_CTRL_REG1_MASK (0x04 | 0x03)
#define HTS221_ODR_ONESHOT 0x00
#define HTS221_ODR_1HZ 0x01
#define HTS221_ODR_7HZ 0x02
#define HTS221_ODR_12_5HZ 0x03
#define HTS221_BDU_OFF 0x00
#define HTS221_BDU_ON 0x04
#define HTS221_POWER_ACTIVE 0x80
#define HTS221_CTRL_REG2 0x21
#define HTS221_CTRL_REG2_MASK (0x80 | 0x02 | 0x01)
#define HTS221_ONESHOT_START 0x01
#define HTS221_HEATER_OFF 0x00
#define HTS221_HEATER_ON 0x02
#define HTS221_MEMORY_REBOOT 0x80
#define HTS221_CTRL_REG3 0x22
#define HTS221_CTRL_REG3_MASK (0x80 | 0x40 | 0x04)
#define HTS221_DRDY_DISABLE 0x00
#define HTS221_DRDY_ENABLE 0x04
#define HTS221_DRDY_PUSHPULL 0x00
#define HTS221_DRDY_OPENDRAIN 0x40
#define HTS221_DRDY_ACTIVEH 0x00
#define HTS221_DRDY_ACTIVEL 0x80
#define HTS221_STATUS_REG 0x27
#define HTS221_STATUS_REG_MASK (0x02 | 0x01)
#define HTS221_TEMP_RDY_BIT 0x01
#define HTS221_HUMID_RDY_BIT 0x02
#define HTS221_HUMIDITY_OUT_L 0x28
#define HTS221_HUMIDITY_OUT_H 0x29
#define HTS221_TEMP_OUT_L 0x2A
#define HTS221_TEMP_OUT_H 0x2B
#define HTS221_H0_RH_X2 0x30
#define HTS221_H1_RH_X2 0x31
#define HTS221_T0_DEGC_X8 0x32
#define HTS221_T1_DEGC_X8 0x33
#define HTS221_T0_T1_DEGC_H2 0x35
#define HTS221_H0_T0_OUT 0x36
#define HTS221_H1_T0_OUT 0x3A
#define HTS221_T0_OUT 0x3C
#define HTS221_T1_OUT 0x3E
typedef struct {
uint8_t internal_avg_temp;
uint8_t internal_avg_humid;
uint8_t data_rate;
uint8_t BDU_flag;
uint8_t internal_heater;
uint8_t DRDY_pin;
uint8_t DRDY_mode;
uint8_t DRDY_active;
} hts221_params_t;
typedef struct {
float T_degC_slope;
float T_degC_offset;
float T_offset;
float H_rH_slope;
float H_rH_offset;
float H_offset;
i2c_dev_t i2c_dev; //!< I2C device descriptor
uint8_t id; //!< Chip ID
} hts221_t;
esp_err_t hts221_init_default_params(hts221_params_t *params);
esp_err_t hts221_init(hts221_t *dev, hts221_params_t *params);
esp_err_t hts221_read_float(hts221_t *dev, float *temperature, float *humidity);
#ifdef __cplusplus
}
#endif
#endif // __HTS221_H__

การเขียนโค้ดในไฟล์ hts221.c อิงตามรูปแบบภาษา C โดยเรียกใช้ฟังก์ชัน และ จากไลบรารี esp-idf-lib เท่านั้น เพื่อให้สามารถปรับเปลี่ยนไปใช้ไลบรารีอื่นได้ง่าย การตั้งค่าของ HTS221 จะอยู่ที่ฟังก์ชัน hts221_init_default_params() เป็นหลัก โค้ดด้านล่างนี้แสดงการตั้งค่าเริ่มต้นของ HTS221 ให้อ่านค่าอัตโนมัติด้วยอัตรา 7 Hz เฉลี่ยทั้งอุณหภูมิและความชื้น 4 ค่า โดยไม่เปิดใช้งานขา Data Ready

#include <esp_log.h>
#include <esp_idf_lib_helpers.h>
#include "hts221.h"
esp_err_t hts221_init_default_params(hts221_params_t *params) {
params->internal_avg_temp = HTS221_AVGT_4;
params->internal_avg_humid = HTS221_AVGH_4;
params->data_rate = HTS221_ODR_7HZ;
params->BDU_flag = HTS221_BDU_ON;
params->internal_heater = HTS221_HEATER_OFF;
params->DRDY_pin = HTS221_DRDY_DISABLE;
params->DRDY_mode = HTS221_DRDY_PUSHPULL;
params->DRDY_active = HTS221_DRDY_ACTIVEH;
return ESP_OK;
}

ส่วนโค้ดที่ยาวที่สุดคือ ฟังก์ชัน hts221_init() ซึ่งทำหน้าที่อ่าน/เขียนค่ากับรีจิสเตอร์เพื่อตั้งการทำงานของตัวชิพ รวมทั้งยังต้องอ่านค่าสัมประสิทธิ์สอบเทียบทั้งอุณหภูมิและความชื้น จุดสำคัญของการตั้งค่าคือ การตั้งให้อ่านค่าอัตโนมัติ ไม่ใช้แบบ one-shot ซึ่งต้องมาสั่งเริ่มอ่านค่าและรอเช็คสถานะ READY ปัญหาใหญ่ที่เจอในช่วงเขียนโค้ดนี้คือ ฟังก์ชัน i2c_dev_read_reg() และ i2c_dev_write_reg() ของไลบรารี esp-idf-lib ต่างไม่สามารถอ่าน/เขียนข้อมูลได้มากกว่า 1 ไบต์ โดยจะมีค่าซ้ำกัน จึงต้องอ่าน/เขียนทีละไบต์แม้ว่ารีจิสเตอร์บางตัวจะเป็นแบบ 16 บิต

esp_err_t hts221_init(hts221_t *dev, hts221_params_t *params) {
uint8_t buf[2];
uint16_t T0_out, T1_out;
uint16_t T0_degC_x8, T1_degC_x8, T0_degC, T1_degC;
uint16_t H0_out, H1_out;
uint8_t H0_rH, H1_rH;

I2C_DEV_TAKE_MUTEX(&dev->i2c_dev);
// Check chip ID
i2c_dev_read_reg(&dev->i2c_dev, HTS221_WHO_AM_I, &dev->id, 1);
if (dev->id != HTS221_CHIP_ID) {
I2C_DEV_GIVE_MUTEX(&dev->i2c_dev);
return ESP_FAIL;
}
// Enable chip, configure data rate and BDU
i2c_dev_read_reg(&dev->i2c_dev, HTS221_CTRL_REG1, &buf[0], 1);
buf[0] &= ~(HTS221_CTRL_REG1_MASK);
buf[0] |= (HTS221_POWER_ACTIVE | params->data_rate | params->BDU_flag);
i2c_dev_write_reg(&dev->i2c_dev, HTS221_CTRL_REG1, &buf[0], 1);
// Configure internal heater
i2c_dev_read_reg(&dev->i2c_dev, HTS221_CTRL_REG2, &buf[0], 1);
buf[0] &= ~(HTS221_CTRL_REG2_MASK);
buf[0] |= (params->internal_heater);
i2c_dev_write_reg(&dev->i2c_dev, HTS221_CTRL_REG2, &buf[0], 1);
// Configure DRDY pin
i2c_dev_read_reg(&dev->i2c_dev, HTS221_CTRL_REG3, &buf[0], 1);
buf[0] &= ~(HTS221_CTRL_REG3_MASK);
buf[0] |= (params->DRDY_pin | params->DRDY_mode | params->DRDY_active);
i2c_dev_write_reg(&dev->i2c_dev, HTS221_CTRL_REG2, &buf[0], 1);
// Set internal averaging parameters
i2c_dev_read_reg(&dev->i2c_dev, HTS221_AV_CONF, &buf[0], 1);
buf[0] &= ~(HTS221_AV_CONF_MASK);
buf[0] |= (params->internal_avg_temp | params->internal_avg_humid);
i2c_dev_write_reg(&dev->i2c_dev, HTS221_AV_CONF, &buf[0], 1);
// Read temperature calibration coefficients
i2c_dev_read_reg(&dev->i2c_dev, HTS221_T0_OUT, &buf[0], 1);
i2c_dev_read_reg(&dev->i2c_dev, HTS221_T0_OUT+1, &buf[1], 1);
T0_out = ((uint16_t)buf[1] << 8) | (uint16_t)buf[0];
i2c_dev_read_reg(&dev->i2c_dev, HTS221_T1_OUT, &buf[0], 1);
i2c_dev_read_reg(&dev->i2c_dev, HTS221_T1_OUT+1, &buf[1], 1);
T1_out = ((uint16_t)buf[1] << 8) | (uint16_t)buf[0];
i2c_dev_read_reg(&dev->i2c_dev, HTS221_T0_DEGC_X8, &buf[0], 1);
T0_degC_x8 = buf[0];
i2c_dev_read_reg(&dev->i2c_dev, HTS221_T1_DEGC_X8, &buf[0], 1);
T1_degC_x8 = buf[0];
i2c_dev_read_reg(&dev->i2c_dev, HTS221_T0_T1_DEGC_H2, &buf[0], 1);
T0_degC_x8 |= (((uint16_t)buf[0] & 0x03) << 8);
T1_degC_x8 |= (((uint16_t)buf[0] & 0x0C) << 6);
T0_degC = T0_degC_x8 >> 3;
T1_degC = T1_degC_x8 >> 3;
dev->T_degC_slope = (float)(T1_degC - T0_degC) / (float)(T1_out - T0_out);
dev->T_degC_offset = T0_degC;
dev->T_offset = (float)T0_out;
// Read humidity calibration coefficients
i2c_dev_read_reg(&dev->i2c_dev, HTS221_H0_T0_OUT, &buf[0], 1);
i2c_dev_read_reg(&dev->i2c_dev, HTS221_H0_T0_OUT+1, &buf[1], 1);
H0_out = ((uint16_t)buf[1] << 8) | (uint16_t)buf[0];
i2c_dev_read_reg(&dev->i2c_dev, HTS221_H1_T0_OUT, &buf[0], 1);
i2c_dev_read_reg(&dev->i2c_dev, HTS221_H1_T0_OUT+1, &buf[1], 1);
H1_out = ((uint16_t)buf[1] << 8) | (uint16_t)buf[0];
i2c_dev_read_reg(&dev->i2c_dev, HTS221_H0_RH_X2, buf, 1);
H0_rH = buf[0] >> 1;
i2c_dev_read_reg(&dev->i2c_dev, HTS221_H1_RH_X2, buf, 1);
H1_rH = buf[0] >> 1;
dev->H_rH_slope = (float)(H1_rH - H0_rH) / (float)(H1_out - H0_out);
dev->H_rH_offset = H0_rH;
dev->H_offset = (float)H0_out;
I2C_DEV_GIVE_MUTEX(&dev->i2c_dev);
return ESP_OK;
}

ส่วนโค้ดของฟังก์ชัน hts221_init() จะเข้าใจได้ยากหน่อย เพราะผู้ผลิตชิพ HTS221 เลือกที่จะกระจายค่าสัมประสิทธิ์ไปอยู่ในหลายรีจิสเตอร์ ทำให้ต้องเอามารวมกันด้วยตัวดำเนินการระดับบิตที่จัดมาครบทั้งเลื่อนบิต กรองบิต ผสานบิต แต่ส่วนโค้ดสำหรับอ่านค่าเซ็นเซอร์จะง่ายขึ้น เพราะเราได้คำนวณและบันทึกค่าสัมประสิทธิ์ในรูปของ slope และ offset (สมการเชิงเส้น) ไว้ในตัวแปรที่เป็นไดรเวอร์เลย ส่วนโค้ดสำหรับอ่านค่าอุณหภูมิและความชื้นจึงกระชับมาก

esp_err_t hts221_read_float(hts221_t *dev, float *temperature, float *humidity) {
uint8_t buf[2];
uint16_t T_out, H_out;
I2C_DEV_TAKE_MUTEX(&dev->i2c_dev); // Check data status
i2c_dev_read_reg(&dev->i2c_dev, HTS221_STATUS_REG, buf, 1);
while ((buf[0] & HTS221_STATUS_REG_MASK) != (HTS221_TEMP_RDY_BIT | HTS221_HUMID_RDY_BIT)) {
vTaskDelay(10 / portTICK_PERIOD_MS);
}
// Read raw temperature data
i2c_dev_read_reg(&dev->i2c_dev, HTS221_TEMP_OUT_L, &buf[0], 1);
i2c_dev_read_reg(&dev->i2c_dev, HTS221_TEMP_OUT_H, &buf[1], 1);
T_out = ((uint16_t)buf[1] << 8) | (uint16_t)buf[0];
*temperature = (T_out - dev->T_offset) * dev->T_degC_slope + dev->T_degC_offset;
// Read raw humidity data
i2c_dev_read_reg(&dev->i2c_dev, HTS221_HUMIDITY_OUT_L, &buf[0], 1);
i2c_dev_read_reg(&dev->i2c_dev, HTS221_HUMIDITY_OUT_H, &buf[1], 1);
H_out = ((uint16_t)buf[1] << 8) | (uint16_t)buf[0];
*humidity = (H_out - dev->H_offset) * dev->H_rH_slope + dev->H_rH_offset;
I2C_DEV_GIVE_MUTEX(&dev->i2c_dev);
return ESP_OK;
}

เนื่องจากไฟล์ hts221.c และ hts221.h ได้ใช้ MUTEX ในการคุมจังหวะการเข้าถึงฮาร์ดแวร์ I2C ทำให้การเขียนโค้ดของโปรแกรมหลักยังคงไม่มีปัญหา race condition นอกจากนี้ ส่วนโค้ดของแทสค์ HTS221 เองก็แทบไม่ต่างจากส่วนโค้ดของแทสค์ BMP280 ที่สามารถย้อนกลับไปเปรียบเทียบได้

void hts221_task(void *pvParamters) {
hts221_params_t params;
hts221_t hts221_dev;
hts221_init_default_params(&params);
memset(&hts221_dev, 0, sizeof(hts221_t));
memcpy(&(hts221_dev.i2c_dev), &dev, sizeof(i2c_dev_t));
hts221_dev.i2c_dev.addr = HTS221_I2C_ADDRESS;
if (hts221_init(&hts221_dev, &params) != ESP_OK) {
printf("Error at initialization\n");
}
while(1) {
float temperature, humidity;
hts221_dev.i2c_dev.addr = HTS221_I2C_ADDRESS;
if (hts221_read_float(&hts221_dev, &temperature, &humidity) != ESP_OK) {
printf("Temperature/pressure reading failed\n");
continue;
}
printf("Humidity: %.2f %%RH, Temperature: %.2f C\n", humidity, temperature);
vTaskDelay(2000 / portTICK_PERIOD_MS);
}
}

การอ่านค่าอุณหภูมิพบว่า HTS221 จะให้ค่าต่างจาก BMP280 ไม่มาก (< 1 degC) ซึ่งยังไม่ทราบว่าเซ็นเซอร์ตัวไหนที่ให้ค่าแม่นยำกว่า เนื่องจากไม่มีเซ็นเซอร์ที่สามารถใช้อ้างอิงได้

ผลการอ่านค่าเซ็นเซอร์ BMP280 และ HTS221

การใช้งานเซ็นเซอร์ MPU-6050

เนื้อหาถัดไป

--

--

Supachai Vorapojpisut
Supachai Vorapojpisut

Written by Supachai Vorapojpisut

Assistant Professor at Thammasat University

Responses (1)