LoginSignup
2
0

More than 1 year has passed since last update.

Sensirionのco2センサーSCD41をSpresenseで試してみた

Last updated at Posted at 2021-07-15

SONY Spresense用の co2センサー SCD41 Addonボードを作ったので評価してみました。
SCD4xのページ

構成

名称 型名 メーカー
CPU SPRESENSE SONY
LTE-M ボード CXD5602PWBLM1J SONY
CO2センサー SCD41 Sensirion
LTE SIM 定額10MBプラン MEEQ

写真

IMG_0115.jpg

回路図

SpresensesとSCD41はI2Cで接続しますが、現状レベルシフターICがことごとく入手不可なので N-Ch MOSFET で構成しました。
CO2_TEMP_SCD40.png

基板

CO2_TEMP_SCD40_a.png
CO2_TEMP_SCD40_b.png
Spresense_SCD4X.png
3D View

グラフ

まだサンプル数は少ないですがこんな感じです。
(Google chart)
SCD40_graph.JPG

SPRESENSE プログラム

テストに使ったプログラムです、整理していませんが参考にしてください。
SCD41(40)用のライブラリが Sensirion から提供されているのでそれを使いました。
SCD40 arduinoライブラリ

データは HTTP GET で自社システムに5分毎に送信しています。

SCD40_test.ino
/*
  Sample program for the Sensirion SCD30 Sensor
  By: Kazuaki Ueno
  Next Step LLC
  Date: Jun. 20th, 2021
  License: This code is public domain.

  SCD30 Datasheet: https://www.mouser.jp/datasheet/2/682/Sensirion_CO2_Sensors_SCD30_Datasheet-1901872.pdf

  This example reads the sensors calculated CO2 and TEMP and Humidity.
*/

// libraries
#include <stdlib.h>
#include <LTE.h>                                                                // LTEライブラリ
#include <Wire.h>                                                               // I2Cライブラリ
#include <Watchdog.h>                                                           // ウォッチドッグライブラリ https://github.com/janelia-arduino/Watchdog
#include <SensirionI2CScd4x.h>                                                  // SCD4Xライブラリ        https://github.com/Sensirion/arduino-i2c-scd4x
                                                                                // Sensirion Arduino Core Library  https://github.com/Sensirion/arduino-core/
                                                                                //                                 SCD4Xライブラリで include されている

SensirionI2CScd4x scd4x;

// APN data
// MEEQ
#define LTE_APN       "meeq.io"                                                 // replace your APN
#define LTE_USER_NAME "meeq"                                                    // replace with your username
#define LTE_PASSWORD  "meeq"                                                    // replace with your password
// SORACOM
// #define LTE_APN       "soracom"                                              // replace your APN
// #define LTE_USER_NAME "sora"                                                 // replace with your username
// #define LTE_PASSWORD  "sora"                                                 // replace with your password

// アップロード間隔
#define SAMPLE_NUMS (30)                                                        // 5分間隔 設定値×10秒

// Timer割り込み間隔
#define INTERVAL 1000                                                           // 1000uS = 1mS

// センシング間隔
#define SENSOR_INTERVAL (10000)                                                 // センサーの読み込み間隔(mS)

// Watchdog
#define WATCHDOG_TIME   (15000)

// initialize the library instance
// U8G2_SSD1306_128X64_NONAME_F_HW_I2C u8g2(U8G2_R0, /* reset=*/ U8X8_PIN_NONE);
// SCD30 airSensor;                                                             //create an object of the SCD30 class
LTEModem modem;
LTE lteAccess;
LTEClient client;
LTEScanner scannerNetworks;

// URL, path & port (for example: arduino.cc)
char server[] = "server_url";                                                   // 使用するザーバーの情報に書き換えて下さい。
char path[200];
int  port = 80;                                                                 // port 80 is the default for HTTP

// Grubal variable
bool lte_connect = false ;
uint16_t int_counter  = 0;
uint8_t  sensing_flag = 0;
String IMEI;
char imei_char[50];

// Function prototype
uint8_t callback_func(void);
uint8_t compareFloat(const void*, const void*);
float   get_median(const float*, uint8_t);                                        // メディアンフィルター
void    printUint16Hex(uint16_t);
void    printSerialNumber(uint16_t, uint16_t, uint16_t);
void    sensing_job();

uint8_t callback_func()
{
  int_counter++ ;
  if(int_counter >= SENSOR_INTERVAL){
    sensing_flag = 1;
    int_counter = 0;
  }
  return INTERVAL;
}

uint8_t compareFloat(const void* a, const void* b)
{
    int aNum = *(float*)a;
    int bNum = *(float*)b;

    if( aNum < bNum ){
        return -1;
    }
    else if( aNum > bNum ){
        return 1;
    }
    return 0;
}

/* メディアンフィルタ処理 */
float get_median(const float* array, uint8_t size)
{
    float median;

    float* array_copy = malloc(sizeof(float) * size);
    memcpy(array_copy, array, sizeof(float) * size);
    qsort(array_copy, size, sizeof(float), compareFloat);
    median = array_copy[size / 2];
    free(array_copy);
    return median;
}

void printUint16Hex(uint16_t value)
{
    Serial.print(value < 4096 ? "0" : "");
    Serial.print(value < 256 ? "0" : "");
    Serial.print(value < 16 ? "0" : "");
    Serial.print(value, HEX);
}

void printSerialNumber(uint16_t serial0, uint16_t serial1, uint16_t serial2)
{
    Serial.print("Serial: 0x");
    printUint16Hex(serial0);
    printUint16Hex(serial1);
    printUint16Hex(serial2);
    Serial.println();
}


void sensing_job()
{
  static uint8_t counter = 0 ;
  static float co2_arry[5]  = {0.0};
  static float temp_arry[5] = {0.0};
  static float hum_arry[5]  = {0.0};

  static float co2_sum  = 0.0;
  static float temp_sum = 0.0;
  static float hum_sum  = 0.0;

  static uint16_t disp_counter = 0;

  float co2,temp,hum;
  uint16_t co2_i;
  uint16_t temp_i;
  uint16_t hum_i;

  uint8_t i;

  String rssi_str;
  int16_t  rssi;

  uint16_t error;
  char errorMessage[256];

  disp_counter++;
  Watchdog.start(WATCHDOG_TIME);

  // Obtain data from the sensors.
  error = scd4x.readMeasurement(co2_i, temp_i, hum_i);
  if (error) {
      Serial.print("Error trying to execute readMeasurement(): ");
      errorToString(error, errorMessage, 256);
      Serial.println(errorMessage);
  } else if (co2_i == 0) {
      Serial.println("Invalid sample detected, skipping.");
  } else {
      co2 = co2_i * 1.0;
      temp = temp_i * 175.0 / 65536.0 - 45.0;
      hum  = hum_i * 100.0 / 65536.0;
  }

  // Median Filtering.
  for(i = 1 ; i < 5; i++){
    co2_arry[i-1]  = co2_arry[i];
    temp_arry[i-1] = temp_arry[i];
    hum_arry[i-1]  = hum_arry[i];
  }
  co2_arry[4]  = co2;
  temp_arry[4] = temp;
  hum_arry[4]  = hum;

  co2   = get_median(co2_arry,5);
  temp  = get_median(temp_arry,5);
  hum   = get_median(hum_arry,5);
  // End Median Filtering.
  co2_sum  += co2;
  temp_sum += temp;
  hum_sum  += hum;

  // Data sent to serial port
  Serial.print("Counter: ");
  Serial.print(disp_counter);
  Serial.print(" ");

  Serial.print("CO2: ");
  Serial.print(co2,0);
  Serial.print("ppm");

  Serial.print("  RH: ");
  Serial.print(hum, 2);
  Serial.print("%  TEMP: ");
  Serial.print(temp, 2);
  Serial.println("℃");

  counter++;

  // Data upload to Thingspeak.
  if(counter >= SAMPLE_NUMS){
    if(lte_connect == true){
      // read signal strength
      rssi_str = scannerNetworks.getSignalStrength();
      rssi = rssi_str.toInt(); 
      Serial.print("RSSI : ");
      Serial.println(rssi);

      // データ送信先と送信データのセット
      sprintf(path,"/uec/sensor.php?id=%s&co2=%3.0f&temp=%5.2f&hum=%5.2f&rssi=%2d",imei_char,co2_sum/SAMPLE_NUMS,temp_sum/SAMPLE_NUMS,hum_sum/SAMPLE_NUMS,rssi);
      Serial.print(server);
      Serial.println(path);

      if (client.connect(server, port)) {
        Serial.println("connected");
        // Make a HTTP request:
        client.print("GET ");
        client.print(path);
        client.println(" HTTP/1.1");
        client.print("Host: ");
        client.println(server);
        client.println("Connection: close");
        client.println();
        Serial.println("Data send.");
      } else {
        // if you didn't get a connection to the server:
        Serial.println("connection failed");
      }
      delay(100);

      // if the server's disconnected, stop the client:
      if (!client.available() && !client.connected()) {
        Serial.println();
        Serial.println("disconnecting.");
        client.stop();

      // do nothing forevermore:
        for (;;)
          sleep(1);
      }
    }
    co2_sum  = 0.0;
    temp_sum = 0.0;
    hum_sum  = 0.0;
    counter = 0;
  }

  // Watch dog clear
  Watchdog.kick();
}

void setup()
{
  uint16_t error;
  char errorMessage[256];
  Wire.begin();

  // initialize serial communications and wait for port to open:
  Serial.begin(115200);
  while (!Serial) {
      ; // wait for serial port to connect. Needed for native USB port only
  }
  Serial.println("SCD41 Example");

  if (LTE_IDLE == modem.begin()) {               
    Serial.println("modem.begin() succeeded.");
  } else {
    Serial.println("ERROR, no modem answer.");
  }
  scd4x.begin(Wire);

  // stop potentially previously started measurement
  error = scd4x.stopPeriodicMeasurement();
  if (error) {
    Serial.print("Error trying to execute stopPeriodicMeasurement(): ");
    errorToString(error, errorMessage, 256);
    Serial.println(errorMessage);
  }

  uint16_t serial0;
  uint16_t serial1;
  uint16_t serial2;

  error = scd4x.getSerialNumber(serial0, serial1, serial2);
  if (error) {
    Serial.print("Error trying to execute getSerialNumber(): ");
    errorToString(error, errorMessage, 256);
    Serial.println(errorMessage);
  } else {
    printSerialNumber(serial0, serial1, serial2);
  }

  // Start Measurement
  error = scd4x.startPeriodicMeasurement();
  if (error) {
    Serial.print("Error trying to execute startPeriodicMeasurement(): ");
    errorToString(error, errorMessage, 256);
    Serial.println(errorMessage);
  }

IMEI = modem.getIMEI();
  IMEI.toCharArray(imei_char, 50);

  Serial.print("Modem IMEi = ");
  Serial.println(IMEI);

  /* LTE 通信開始 */
  Serial.println("Starting web client.(LTE connection)");

  // If your SIM has PIN, pass it as a parameter of begin() in quotes
  while (true) {
    if (lteAccess.begin() == LTE_SEARCHING) {
      if (lteAccess.attach(LTE_APN, LTE_USER_NAME, LTE_PASSWORD) == LTE_READY) {
        Serial.println("attach succeeded.");
        lte_connect = true;
        break;
      }
      Serial.println("An error occurred, shutdown and try again.");
      lteAccess.shutdown();
//      sleep(1);
      break;
    }
  }  
  // Watch dog start
  attachTimerInterrupt(callback_func, INTERVAL);
  Watchdog.begin();
}

void loop()
{
  if(sensing_flag == 1){
    sensing_job();
    sensing_flag = 0;
  }
}

昨日データを取り始めたのでまだ評価は難しいですが、データ的には実用の範囲内だと思っています。
同じ場所で Sensirion の SCD30 を使った装置でも計測していますのでもう少しデータがたまったら比較をしてみます。

2021年9月7日 追記
SCD30とSCD41の比較

2
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
0