SENSIRION のダストセンサー SPS30 を使ってみたので紹介します.
背景
以前,シャープの PM2.5 センサを使ってみたのですが,以下の点が気になっていました.
- 測定ばらつきが大きい (ざっくり 10ug/m3 程度)
- 温度特性の補正が必要
実際の測定結果をみても下記のような感じで,ほぼノイズのような感じです.
そこで,温度センサーで有名な SENSIRION の SPS30 に手を出してみることにしました.
SENSIRION SPS30
SENSIRION の SPS30 は PM2.5 を計測できるセンサーで以下の特長があります.
- 超小型
40.6 x 40.6 x 12.2㎜ なので,実物は写真よりもコンパクトに感じます. - 補正不要
IF は UART もしくは I2C で,補正不要なデジタル値が出力されます. - 長寿命
24時間の連続稼動で8年以上の耐用期間があります.
最初手に取った時,シャープのセンサーに比べてあまりにコンパクトでびっくりしました.
こちら[キャッシュ]に分解写真がありますが,方式としてはシャープと同様にレーザ光の拡散を計測しているようです.
必要な部品
秋月電子 で入手するもの
- ZHコネクタ ハウジング 5P ZHR-5
センサーにつなぐためのコネクタです.1.5mm ピッチなので,他の物で代替するのは難しいとおもいますので,ちゃんとこちらを使うのがお勧めです.
- ZHコネクタ ハウジング用コンタクト SZH-002T-P0.5
ターミナルです.カシメる際は,エンジニアの PA-09 を使うのがお勧めです.
Amazon で入手するもの
- パナソニック配線器具
スマートデザインシリーズ防雨入線カバー WP9671S -
屋外にセンサーを設置するときに便利なカバーです.内部のリブを少しカットしてやれば,専用品のようにぴったり収まります.
設置例
物置に設置していますが,こんな感じの仕上がりになりました.設置後,風雨を何度か経験していますが,問題無く動作しています.
I2C プログラム例
SPS30 を使うのは簡単です.基本的には下記を行うだけです.
- 計測開始を書き込み
- 計測値を定期的に読み出し
ESP32 で行う場合のプログラム例は次の通りです.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 |
#include "app.h" #include "driver_sps30.h" #include "driver/i2c.h" #define SPS30_DEV_ADDR 0xD2 static uint8_t crc8(const uint8_t *data, uint8_t len) { static const uint8_t poly = 0x31; uint8_t crc = 0xFF; for (uint8_t i = 0 ; i < len; i++) { crc ^= data[i]; for (uint8_t j = 0; j < 8; j++) { if (crc & 0x80) { crc = (crc << 1 ) ^ poly; } else { crc <<= 1; } } } return crc; } static inline void be32_to_cpu(void *mem) { uint32_t val = ((uint32_t *)mem)[0]; val = ((val & 0x000000FF) << 24) | ((val & 0x0000FF00) << 8) | ((val & 0x00FF0000) >> 8) | ((val & 0xFF000000) >> 24); ((uint32_t *)mem)[0] = val; } static esp_err_t sps30_write(const uint8_t *data, uint8_t len) { i2c_cmd_handle_t cmd; cmd = i2c_cmd_link_create(); ESP_ERROR_CHECK(i2c_master_start(cmd)); ESP_ERROR_CHECK(i2c_master_write_byte(cmd, SPS30_DEV_ADDR|I2C_MASTER_WRITE, 1)); for (uint8_t i = 0; i < len; i++) { ESP_ERROR_CHECK(i2c_master_write_byte(cmd, data[i], 1)); } if (len > 2) { uint8_t crc = crc8(data+2, len-2); ESP_ERROR_CHECK(i2c_master_write_byte(cmd, crc, 1)); } ESP_ERROR_CHECK(i2c_master_stop(cmd)); ESP_ERROR_CHECK(i2c_master_cmd_begin(I2C_NUM_0, cmd, 1000/portTICK_RATE_MS)); i2c_cmd_link_delete(cmd); return ESP_OK; } static esp_err_t sps30_read(const uint8_t *data, uint8_t len, uint8_t *buf, uint8_t buf_len) { i2c_cmd_handle_t cmd; uint8_t read_len = buf_len / 2 * 3; uint8_t *read_buf = (uint8_t *)alloca(read_len); sps30_write(data, len); cmd = i2c_cmd_link_create(); ESP_ERROR_CHECK(i2c_master_start(cmd)); ESP_ERROR_CHECK(i2c_master_write_byte(cmd, SPS30_DEV_ADDR|I2C_MASTER_READ, 1)); for (uint8_t i = 0; i < read_len; i++) { ESP_ERROR_CHECK(i2c_master_read_byte(cmd, &(read_buf[i]), (i == (read_len-1)))); } ESP_ERROR_CHECK(i2c_master_stop(cmd)); ESP_ERROR_CHECK(i2c_master_cmd_begin(I2C_NUM_0, cmd, 1000/portTICK_RATE_MS)); i2c_cmd_link_delete(cmd); for (uint8_t i = 0; i < read_len / 3; i++) { uint8_t crc = crc8(read_buf+(i*3), 2); if (crc != read_buf[(i*3)+2]) { ESP_LOGW(TAG, "CRC unmatch (exp: 0x%02X, act: 0x%02X)", crc, read_buf[(i*3)+2]); return ESP_FAIL; } buf[(i*2)+0] = read_buf[(i*3)+0]; buf[(i*2)+1] = read_buf[(i*3)+1]; } return ESP_OK; } esp_err_t sps30_start() { static const uint8_t SPS30_CMD_START[] = { 0x00, 0x10, 0x03, 0x00 }; ESP_ERROR_CHECK(sps30_write(SPS30_CMD_START, sizeof(SPS30_CMD_START))); vTaskDelay(100/portTICK_RATE_MS); return ESP_OK; } esp_err_t sps30_sense(sps30_sense_data_t *sense_data) { static const uint8_t SPS30_CMD_READY[] = { 0x02, 0x02 }; static const uint8_t SPS30_CMD_MEASURE[] = { 0x03, 0x00 }; uint8_t buf[2]; ESP_ERROR_CHECK(sps30_read(SPS30_CMD_READY, sizeof(SPS30_CMD_READY), buf, 2)); if (buf[1] != 1) { ESP_LOGW(TAG, "Data is not ready"); return ESP_FAIL; } ESP_ERROR_CHECK(sps30_read(SPS30_CMD_MEASURE, sizeof(SPS30_CMD_MEASURE), (uint8_t *)sense_data, sizeof(sps30_sense_data_t))); // convert big endian to litte endian for (uint8_t i = 0; i < 10; i++) { be32_to_cpu(((uint32_t *)sense_data) + i); } return ESP_OK; } |
計測結果
計測したデータをプロットすると次のようになります.ノイズがほとんど無く,精度良く計測できていることが分かります.
また,雨量計の計測結果とならべると,雨が降ることで PM2.5 が大きく減っていることが確認できます.
花粉の季節になったら活躍が期待できそうです.
コメント