Posted on Leave a comment

วัดปริมาณ carbon dioxide ด้วย SCD-40 (Low power)

บทความนี้เป็นบทความต่อขยายจากบทความหลักในเรื่องของ “วัดปริมาณ carbon dioxide ด้วย SCD-40” โดยในบทความนี้จะกล่าวถึงการทำงานในโหมด Low power ซึ่งจะช่วยให้เราประหยัดพลังงานได้ โดยในบทความนี้สาธิตว่าทำอย่างไร และที่สำคัญ นอกจากช่วยลดการใช้พลังงงานแล้ว มีผลกระทบอื่น ๆ หรือไม่ แก้อย่างไร มาติดตามกันเลย

Low Power Periodic Measurement

ก่อนอื่นต้องทราบก่อนว่าส่วนที่ใช้พลังงานมากที่สุดของ SCD40 นี้คือการทำงานในส่วนของการวัดปริมาณ carbon dioxide ดังนั้นการลดความถึ่ในการวัดปริมาณ cabon dioxide จึงเป็นวิธีที่ตรงไปตรงมา และได้ผลมากโดยทางผู้ผลิตอ้างว่า สามารถลดพลังงานได้มากกว่า 80% เลยทีเดียว ซึ่งวิธีการก็ง่าย ๆ ไม่ยุ่งยากอะไร คือ ทางผู้ผลิตได้เตรียม function การใช้งานสำหรับเรื่องนี้มาโดยเฉพาะอยู่แล้ว นั่นคือ การใช้คำสั่ง startLowPowerPeriodicMeasurement แทนคำสั่ง startPeriodicMeasurement โดยคำสั่ง startLowPowerPeriodicMeasurement นี้เป็นการสั่งให้ SCD40 ทำการวัดค่า carbon dioxide ประมาณทุก ๆ 30 วินาที (จากเดิมทุก ๆ 5 วินาที) ซึ่งก็จะได้ code ดังนี้ (code ทั้งหมดจะให้ไว้ที่ด้านล่างของบทความนะครับ)

  scd40.begin(Wire);
  scd40.stopPeriodicMeasurement();
  // scd40.startPeriodicMeasurement();          // Performance mode (every 5s)
  scd40.startLowPowerPeriodicMeasurement();     // Low Power mode (every 30s)

แต่เรื่องมันไม่ได้จบแค่นี้ (ถ้าจบแค่นี้ก็ไม่สนุกใช่ไหมล่ะ)

Temperature Offset

เมื่อใช้ Low power mode นั้น ค่าของอุณหภูมิที่อ่านได้ จะคลาดเคลื่อนจากตอนที่อยู่ในโหมดปกตินิดหน่อย คาดว่าเกิดจากการใช้พลังงานลดลง ทำให้อุณหภูมิของตัวเอง (self-heating) ลดลง ทำให้ค่าอุณหภูมิที่วัดได้ต่างจากเดิมนิดหน่อย ดังนั้นเพื่อให้ sensor รายงานค่าอุณหภูมิ และความชื้นสัมพัทธ์ได้ถูกต้อง เราต้องปรับ offset ของการวัดอุณหภูมิของ sensor ด้วย ซึ่งตัว sensor เองก็มี function การทำงานในส่วนนี้รองรับไว้อยู่แล้ว (เจ๋ง ใช่ไหมล่ะ) นั่นคือใช้คำสั่ง setTemperatureOffset โดยที่ค่า offset ที่จะใส่เข้าไป สามารถหาได้ตามสูตร (จาก datasheet) ดังนี้

Toffset_actual = TSCD40TReference + Toffset_previous

Toffset_actual : ค่า offset ที่จะใส่เข้าไป

TSCD40 : ค่าอุณหภูมิที่ SCD40 อ่านได้

TReference : ค่าอุณหภูมิอ้างอิง (ที่เราคิดว่าถูกต้อง)

Toffset_previous : ค่า offset ปัจจุบัน (ก่อนที่จะใส่ offset ใหม่)

นอกจากปัจจัยในเรื่องของ self-heating ของตัว sensor เองแล้ว ยังมีปัจจัยจากสิ่งแวดล้อม เช่นความร้อนของอุปกรณ์ข้างเคียงอีก ดังนั้นเราควรหาค่า offset หลังจากที่ประกอบอุปกรณ์ในลักษณะพร้อมใช้ และติดตั้งในสถานที่จริงเรียบร้อยแล้ว ซึ่งจะเป็นลักษณะของใครของมัน ส่วนของผมนั้น ค่าอุณหภูมิที่อ่านได้ (TSCD40) จะน้อยกว่าค่าที่คิดว่าถูกต้อง (เมื่อเทียบกับ thermometer ที่เราเชื่อใจ ) อยู่ประมาณ 3.5 องศา (TSCD40TReference = -3.5) ส่วนค่า Toffset_previous คือค่า offset ปัจจุบัน เราสามารถหาได้จากคำสั่ง getTemperatureOffset

เมื่อได้ offset ที่ต้องการแล้ว เราก็ใส่ offset เข้าไปด้วยคำสั่ง setTemperatureOffset ซึ่งจะได้ code ดังนี้

  scd40.begin(Wire);
  scd40.stopPeriodicMeasurement();
  // scd40.startPeriodicMeasurement();          // Performance mode (every 5s)
  float tempOffsetPre = 0.0;
  scd40.getTemperatureOffset(tempOffsetPre);
  float tempOffset = -3.5 + tempOffsetPre;
  scd40.setTemperatureOffset(tempOffset);
  scd40.startLowPowerPeriodicMeasurement();     // Low Power mode (every 30s)

เมื่อเราปรับ code เป็นอย่างนี้แล้ว เราก็จะได้ค่าอุณหภูมิ และความชื้นสัมพัทธ์ถูกต้อง(มากขึ้น) ซึ่งการปรับค่าอุณหภูมินี้มีผลแค่การคำนวนค่าความชื้นสัมพัทธ์เท่านั้นนะครับ ไม่มีผลต่อการวัดค่า carbon dioxide ส่วนค่าที่จะมีผลต่อการวัดค่า carbon dioxide คือค่าความดันอากาศ ซึ่งยังไม่ได้กล่าวถึงในบทความนี้ แต่ใครสนใจสามารถศึกษาเพิ่มเติมได้จาก datasheet

Reload user setting from EEPROM

เอาหละ ถ้าดูผิวเผินก็เหมือนจะจบแค่นี้ แต่… ใช่แล้วครับ ความสนุกยังไม่จบแค่นี้ คนที่มีประสบการณ์ด้านการเขียนโปรแกรมมาเยอะ (ผิดพลาดมาเยอะ) ก็น่าจะรู้อยู่แล้วว่ามันยังไม่จบง่าย ๆ แบบนี้แน่ นั่นคือ ยังจำเหตุที่เราต้องใส่คำสั่ง stopPeriodicMeasurement ก่อนที่จะสั่ง startPeriodicMeasurement ได้ไหมครับ นั่นคือ เมื่อเรากด reset ที่ microcontroller ตัว SCD40 จะไม่ได้ reset ไปด้วย ดังนั้นถ้าเราปล่อยไว้อย่างนี้ จะทำให้ค่า offset จะถูกลดลงไป 3.5 องศาในทุก ๆ ครั้งที่เรากด reset ซึ่งไม่ดีแน่ แล้วเราจะแก้อย่างไรล่ะ

เราต้องเข้าใจการทำงานของ SCD40 เพิ่มอีกหน่อย นั่นคือ เมื่อเราจ่ายไฟให้กับ SCD40 นั้น ตัว sensor จะทำการ load ค่า setting ต่าง ๆ จาก EEPROM เข้าสู่ RAM เพื่อใช้ในการวัดค่าต่าง ๆ และคำสั่ง setTemperatureOffset ที่เราใช้เปลี่ยนค่า offset นี้ ก็ไปทำการเปลี่ยนค่า offset ที่ RAM เท่านั้น (ที่จริง เราสามารถบันทึกค่า offset ใหม่นี้ใส่ EEPROM ได้ แต่จะยังไม่พูดถึงในที่นี้) ดังนั้นเมื่อมีการกด reset ที่ microcontroller เราต้องการให้ SCD40 ทำการ reload ค่า setting ต่าง ๆ ขึ้นมาใหม่ด้วย ซึ่งแน่นอนว่าเค้าเตรียมให้เราอยู่แล้ว นั่นคือคำสั่ง reinit ซึ่งคำสั่งนี้ก็ต้องสั่งหลังจากคำสั่ง stopPeriodicMeasurement เช่นกัน ดังนั้นเราจะได้ code ออกมาอย่างนี้

  scd40.begin(Wire);
  scd40.stopPeriodicMeasurement();
  scd40.reinit();
  // scd40.startPeriodicMeasurement();          // Performance mode (every 5s)
  float tempOffsetPre = 0.0;
  scd40.getTemperatureOffset(tempOffsetPre);
  float tempOffset = -3.5 + tempOffsetPre;
  scd40.setTemperatureOffset(tempOffset);
  scd40.startLowPowerPeriodicMeasurement();     // Low Power mode (every 30s)

เพียงเท่านี้เราก็จะได้ โหมดประหยัดพลังงานที่อ่านค่า อุณหภูมิ และความชื้นสัมพัทธ์ ได้อย่างถูกต้องมากขึ้นแล้ว ที่จริง การใส่ offset นี้ ไม่จำเป็นต้องรอเปลี่ยนมาเป็น Low power mode ก็ได้ ใน mode ปกติ ถ้าเราเห็นว่าค่าอุณหภูมิที่อ่านได้ไม่ถูกต้อง เราก็สามารถมาปรับ offset ได้เหมือนกัน

สลับโหมด

เอาหละ เหมือนจะจบแล้ว แต่ไหน ๆ เราก็เปลี่ยนโหมดได้ และมีโอกาสที่เราจะต้องการเปลี่ยนโหมดไปมาชั่วคราว เพื่อไม่ต้องเสียเวลามา upload program ใหม่ เราจะใช้สายไฟเพิ่มอีกสายหนึ่ง เพื่อให้เราสามารถกำหนดได้ว่า เราอยากใช้โหมดไหน โดยใช้ขาที่ยังว่าง ซึ่งก็มีอยู่มากมาย ในที่นี้จะใช้ขา D2 โดยถ้า D2 เป็น Low (ขา D2 ต่อลง GND)ให้ใช้ Low power mode ถ้าเป็น High (ขา D2 ต่อ 3.3 V) ให้ใช้ โหมดปกติ เราจะได้ code ดังนี้

  scd40.begin(Wire);
  scd40.stopPeriodicMeasurement();
  scd40.reinit();
  pinMode(2, INPUT);
  if (digitalRead(2)) {
    scd40.startPeriodicMeasurement();  // Performance mode (every 5s)
  } else {
    float tempOffsetPre = 0.0;
    scd40.getTemperatureOffset(tempOffsetPre);
    float tempOffset = -3.5 + tempOffsetPre;
    scd40.setTemperatureOffset(tempOffset);
    scd40.startLowPowerPeriodicMeasurement();  // Low Power mode (every 30s)
  }

เพียงเท่านี้ เราก็สามารถเลือกใช้ได้ทั้งสองโหมดเพียงแค่เปลี่ยนการเชื่อมต่อของขา D2 ด้วยสายไฟเพียงเส้นเดียว และอย่าลืมว่า เมื่อเปลี่ยนการเชื่อมต่อแล้วต้องกด reset ที่ microcontroller ด้วยนะครับ

Code ทั้งหมด

#include <SensirionI2CScd4x.h>
#include <U8g2lib.h>

#define BUZZER_PIN 7             // D7
#define CO2_THRESHOLD_BASE 1500  // ppm
#define ALARM_INTV 5000          // ms
#define ON 1
#define OFF 0

unsigned int alarmThreshold = CO2_THRESHOLD_BASE;
bool alarmStatus = OFF;
unsigned long alarmStart = 0;

SensirionI2CScd4x scd40;
uint16_t co2 = 0;
float temperature = 0.0;
float humidity = 0.0;

U8G2_SSD1306_128X64_NONAME_F_HW_I2C oled(U8G2_R0, /* reset=*/U8X8_PIN_NONE);

void setup() {

  Serial.begin(115200);
  // while (!Serial) {      // Waiting for connection wiht computer completed
  //     delay(100);
  // }

  oled.begin();
  oled.clearDisplay();
  oled.setContrast(1);

  pinMode(BUZZER_PIN, OUTPUT);
  digitalWrite(BUZZER_PIN, OFF);
  pinMode(LED_BUILTIN, OUTPUT);
  digitalWrite(LED_BUILTIN, OFF);

  scd40.begin(Wire);
  scd40.stopPeriodicMeasurement();
  scd40.reinit();
  pinMode(2, INPUT);
  if (digitalRead(2)) {
    scd40.startPeriodicMeasurement();  // Performance mode (every 5s)
  } else {
    float tempOffsetPre = 0.0;
    scd40.getTemperatureOffset(tempOffsetPre);
    float tempOffset = -3.5 + tempOffsetPre;
    scd40.setTemperatureOffset(tempOffset);
    scd40.startLowPowerPeriodicMeasurement();  // Low Power mode (every 30s)
  }
  
  Serial.println("Waiting for measurement...");

  oled.clearBuffer();
  oled.setFont(u8g2_font_siji_t_6x10);
  oled.drawStr(0, 30, "Waiting for");
  oled.drawStr(0, 40, "measurement....");
  oled.sendBuffer();
}

void loop() {

  bool dataReady = false;
  scd40.getDataReadyFlag(dataReady);
  if (dataReady) {

    scd40.readMeasurement(co2, temperature, humidity);

    Serial.print("CO2:");
    Serial.print(co2);
    Serial.print("  ");
    Serial.print("Temperature:");
    Serial.print(temperature);
    Serial.print("  ");
    Serial.print("Humidity:");
    Serial.println(humidity);

    oled.clearBuffer();

    oled.setFont(u8g2_font_8x13_tf);
    oled.drawStr(0, 10, "CO");
    oled.setFont(u8g2_font_siji_t_6x10);
    oled.drawStr(16, 14, "2");
    oled.setFont(u8g2_font_siji_t_6x10);
    oled.drawStr(0, 22, "(ppm)");

    String co2_string = String(co2);
    oled.setFont(u8g2_font_courB24_tf);
    int x_pos = (oled.getDisplayWidth() - oled.getStrWidth(co2_string.c_str()));
    oled.setCursor(x_pos, 22);
    oled.print(co2_string);

    oled.setFontMode(1);
    oled.setDrawColor(2);
    oled.drawBox(0, 31, 60, 11);
    oled.drawBox(70, 31, 60, 11);
    oled.setFont(u8g2_font_6x12_t_symbols);
    oled.drawGlyph(20, 41, 176);
    oled.drawGlyph(26, 40, 67);
    oled.drawStr(90, 40, "%");
    oled.drawStr(100, 40, "RH");

    oled.setFont(u8g2_font_courB18_tf);
    oled.setCursor(0, 62);
    oled.print(temperature, 1);
    oled.setCursor(70, 62);
    oled.print(humidity, 1);

    oled.sendBuffer();
  }  // if (dataReady)

  // Alarm Control
  alarmControl();

}  // Loop

void alarmControl(void) {
  if (co2 >= alarmThreshold) {
    if ((alarmStatus == OFF)) {
      setAlarm(ON);
      alarmStart = millis();

    } else if ((millis() - alarmStart) > ALARM_INTV) {
      setAlarm(OFF);
      alarmStart = 0;
      alarmThreshold = co2 + 500;
    }
  } else {
    setAlarm(OFF);
    alarmStart = 0;

    if (co2 < CO2_THRESHOLD_BASE) {
      alarmThreshold = CO2_THRESHOLD_BASE;
    }
  }
}

void setAlarm(bool state) {
  digitalWrite(BUZZER_PIN, state);
  digitalWrite(LED_BUILTIN, state);
  alarmStatus = state;
}

สรุป

บทความนี้เราได้เรียนรู้การใช้ SCD40 ในโหมดประหยัดพลังงาน และได้เรียนรู้วิธีการใส่ offset ให้กับการวัดอุณหภูมิ ทำให้ SCD40 รายงานค่าอุณหภูมิและความชื้นสัมพัทธ์ได้ถูกต้องมากยิ่งขึ้น ซึ่งการที่เราใส่ offset ได้ถือว่าดีมาก ๆ ซึ่งต่างจาก sensor หลาย ๆ ตัวที่ไม่สามารถทำได้

ถาม: เราสามารถชดเชย (offset) ค่าอุณหภูมิเองใน code ได้หรือไม่ โดยไม่ต้องใช้ offset ใน sensor

ตอบ: สำหรับอุณหภูมิเราอาจจะ ชดเชยเองใน code ได้ แต่เราก็จะได้ค่าของความชื้นสัมพัทธ์ไม่ถูกต้องอยู่ดี เพราะค่าความชื้นสัมพัทธ์ที่ sensor รายงานก็จะขึ้นอยู่กับอุณหภูมิที่ sensor อ่านได้ และการชดเชยเองก็ทำได้ยากกว่า

ขอจบความสนุกของบทความนี้ไว้เพียงเท่านี้ ส่วนใครที่ยังไม่อยากหยุดสนุก ผมก็ขอฝากโจทย์ว่า จาก code ที่ให้ไปเป็นการใส่ offset ในส่วนของโหมด low power เท่านั้น ถ้าต้องการใส่ offset ที่โหมดปกติด้วย จะต้องปรับ code อย่างไร

ที่มา

  1. SCD4X datasheet
  2. SCD4X Low power operation