RaspberryPi
PM2.5計測システのつくりかた

 PM2_5は健康被害があり、最近隣国で深刻な状況で、我が国にも飛来してきます。  粒子状物質の測定法は主に、大気を吸引してフィルタ上に粒子を集め電子天秤でその重量を測定する「フィルタ法」と、同様に集めた粒子にベータ線を照射してその透過率から重量を測定する「ベータ線吸収法」、フィルタ経由でカートリッジに集めた粒子を振動により重量測定する「フィルタ振動法」 があるようです。いずれの測定器もアマチュアには手が出せませんが、空気清浄機や煙式火災報知器につかわれている、レーザ光がホコリで散乱する原理のホコリセンサーであれば、入手可能です。
 いままで神栄テクノロジー株式会社のPPD 42NJを用いていましたが、たびたびセンサーの不良で、そのたびに新品と交換してきましたが、正確な計測ができているのか、自信が持てませんでした。ケースにいれていたにもかかわらず、屋外の高温多湿の空気を取り入れるため、金属部分にさびが発生し、連続計測は困難でした。最近、PM2_5専用のシャープ製のセンサー DN7C3CA007を入手しましたので、あらたに計測システムを構築しました。

 (2017/09/20公開) 


DN7C3CA007センサーモジュール
 シャープ製のホコリセンサーユニットには、いくつかの種類があり、いずれも赤外発光ダイオードIRED(Infrared Emitting Diode)とフォトトランジスタを斜めに配置し、空気中の汚れからの反射光量を検出する方式です。GP2Y1010AU0Fをもちいた、ホコリセンサーの事例がいくつか発表されています。
 2013年12月にシャープは新たにPM2.5以下の浮遊している粒子だけを取り込むことを可能とした「バーチャルインパクタ方式」を応用した分粒器を開発し、これとホコリセンサーユニットを組み合わせた方式を発表しました。記者発表時の資料が公開されていますが、自信作のようです。現在入手可能なPM2.5センサモジュールは「DN7C3CA007」で、米国、あるいは中国のサイトから入手可能です。

              PM2_5専用のシャープ製のセンサー

型番 :DN7C3CA007
大きさ:37mmX53mmX51mm
重さ:53g

メーカ: シャープ株式会社
データシート:こちら
販売サイト:Digi-Key eBay など
価格:約5000円

 バーチャルインパクタ方式とは、次のような説明がデータシートにあります。すなわち図のようにファンによる風圧を利用し微小粒子状物質(PM2.5)とそれ以外の大きな粒子を分流させる分粒器があり、それをPM2.5のみをホコリセンサーユニットで検出するものです。


 購入した製品を分解すると、次のようでした(タッピングビス等を外してはいけない)。すなわち、中央に空気が通過する穴のあるホコリセンサーユニットと、ファンの付いた本体、さらに分流するためのいくつかの黒のプラスチック部品で構成されます。このあたりが開発上のノウハウなのでしょう。つかわれているホコリセンサーユニットの型番はGP2Y1012で、一般的なGP2Y1010より高感度とのことです。ファンのON/OFFにより、ホコリセンサーユニットを通過する空気を制御しています。
 データシートには取り付けに際し次のような注意書きがあります。
「測定物質は吸気口(ファンと反対側)から入りファンから出ていきます。モジュールは気流を邪魔しない風通りの良い場所に設置してください。吸気口が上向きになる様筐体設計下さい。設置時に吸気口を下向きにすると特性が得られませんので、吸気口は下向きにしないようお願いします。」



 以下データシートを抜き書きしていきます。ホコリセンサーユニットの計測方法はGP2Y1010と同じで、発光素子を決められたパルスLEDで駆動し、ホコリ粒子により反射された光を受光素子で受けます。増幅回路から出力される電圧Voを所定のタイミングで読み取ります。


発光素子に与える電圧V-LEDには注3で示されるC,Rを接続します。また発光と読み取りのタイミングは注4の通り規定されていおります。


これにより得られたサンプリング電圧を次のようなステップで、PM2.5の濃度(μg/m3)に換算します。基準電圧Vs、アルファ、ベータの値の求め方はプログラム作成の過程で、述べていきます。


 ファンモータのON/OFFがキイです。次のような注意書きがあります。
「ファンの平均寿命(MTTF)を 15,000時間 としております。そのため、当モジュールは長寿命化のため間欠動作を推奨しています。PM2.5物質を測定するときだけファンを動作することにより、ファン寿命が延び、ほこりもたまりにくくなりますので、その使用方法を強く推奨します。ファンの間欠動作の ON時間は Min : 10秒 となります。ただし、ON時間を長くする方がより安定した値を得ることができます。」

 こまかなタイミングをとり、またアナログ出力を読み取る必要があること、またファンモータのON/OFFをプログラムから制御する必要があります。@の基準電圧の決定時にはファンモータをOFFにして計測、本番時AではファンモータをONしなければなりません。
 このようなセンサーを直接RaspberryPiで制御するのは難しいので、Arduinoマイコンを用いることとします。また、従来からRaspberryPiをWindowsPCからファイルとしてアクセスしデータを取り込んできたので、それは継承したいとおもいます。したがって系としては
DN7C3CA007 → Arduino → RaspberryPi → WindowsPC
となります。


ArduinoとRaspberryPiの接続
 ArduinoとRaspberryPiの接続には、I2C通信による方法、シリアル通信による方法があります。さらにシリアル通信はUSBを使う場合とGPIOのUART(Universal Asynchronous Receiver/Transmitter)端子をもちいる方法があり、ここでは後者をトライします。

 RaspberryPiは3.3V 系、Arduinoは5V系ですので、直接接続しない方がよさそうなので、抵抗で分圧し、下の図のように接続します。Arduinoではデジタル端子D2、D3を用いています。RaspberryPiでは、GPIO8,10がUART端子です。


 Arduino側には次のプログラム(スケッチ)を用意します。
 #include <SoftwareSerial.h>ソフトウェアシリアルライブラリをincludeします。ソフトウェアシリアルライブラリはArduinoの標準ポート以外のピンを使ってシリアル通信を行うために開発されました。06行でRX,TXをどのピンを用いるかを指定しています。
 18行のRasPiSerial.available() はソフトウェアシリアルポートのバッファに何バイトのデータが到着しているかを返しますので、RaspberryPiからデータが届いたことを監視することができます。1文字ずつバッファにとりこんで、くみたて、文字列が所定のコマンドか確認し、メッセージ "Hello Pi"をRaspberryPiに返送します。

 RaspberryPiとの通信テスト  RaspberryPi_serialtest.ino
/* 2017/09/17 RaspberryPiとの通信テスト
 * RaspberryPiからコマンド"get"が来たら、"Hello Pi"を送り返す
 */

#include <SoftwareSerial.h>
SoftwareSerial RasPiSerial( 2, 3 );// RX, TX

void setup()
{
  Serial.begin(9600);
  RasPiSerial.begin(9600);
}

void loop() {
  String RasPi = "";
  char inChar;
  int length_buf = 0;
  if (RasPiSerial.available() > 0) {//RaspberryPiからバッファに文字が入ったら
    delay(100);
    length_buf = RasPiSerial.available();
    for (int iii = 0; iii < length_buf; iii++) {//1文字づつ取り込んで
      inChar = RasPiSerial.read();
      RasPi.concat(inChar);//つなげる
    }
    Serial.println(RasPi);
    if (RasPi == "get") {//"get"だったら
      RasPiSerial.println("Hello Pi");//"Hello Pi"を送り返す
    }
  }
}





 つぎにRaspberryPi側の準備です。UARTを使えるようにします。


RaspberryPi モデルB (OSはRASPBIAN WHEEZY)の場合

ステップ1  /boot/cmdline.txtを次のように編集
$ sudo nano /boot/cmdline.txt
dwc_otg.lpm_enable=0 console=ttyAMA0,115200 kgdboc=ttyAMA0,115200 console=tty1 root=/dev/mmcblk0p2 rootfstype=ext4 elevator=deadline rootwait

dwc_otg.lpm_enable=0 console=tty1 root=/dev/mmcblk0p2 rootfstype=ext4 elevator=deadline rootwait
のように編集。すなわちconsole=ttyAMA0,115200 という部分を外し、ttyAMA0をコンソールとして使用しないようにします。

ステップ2 /etc/inittabの最後の行をコメントアウト
$ sudo nano /etc/inittab
#Spawn a getty on Raspberry Pi serial line
#T0:23:respawn:/sbin/getty -L ttyAMA0 115200 vt100

ステップ3 リブートして/devディレクトリのなかに、ttyAMA0 があるのを確認します。このデバイス名は、後ほどプログラムに必要です。

ステップ4 つぎにPythonからUARTを使えるようにするために、シリアルポート操作ライブラリpython-serialをインストール
$ sudo apt-get install python-serial


RaspberryPi モデルB3(OSはRaspbian Jessie)の場合(こちらを参考)

ステップ1  /boot/cmdline.txtを次のように編集
$ sudo nano /boot/cmdline.txt
dwc_otg.lpm_enable=0 console=serial0,115200 console=tty1 root=/dev/mmcblk0p7 rootfstype=ext4 elevator=deadline fsck.repair=yes rootwait

dwc_otg.lpm_enable=0 console=tty1 root=/dev/mmcblk0p7 rootfstype=ext4 elevator=deadline fsck.repair=yes rootwait
のように編集。すなわちconsole=serial0,115200という部分を外します。

ステップ2 /dev/ttyS0 をGPIOから使えるようにつぎの2つを実行します。
$ sudo systemctl stop serial-getty@ttyS0.service
$ sudo systemctl disable serial-getty@ttyS0.service

ステップ3 config.txtを次のように編集
$ sudo nano /boot/config.txt
config.txtの最後に
enable_uart=1
を追加

ステップ4 リブートして/devディレクトリのなかに、ttyS0 があるのを確認します。このデバイス名は、後ほどプログラムに必要です。

ステップ5 つぎにPythonからUARTを使えるようにするために、シリアルポート操作ライブラリpython-serialをインストール
$ sudo apt-get install python-serial


 つぎにRaspberryPi側に次のArduinoとの通信テストプログラム  arduino_serialtest.pyを準備します。ttyのデバイス名は上記により変更します。

Arduino側でRaspberryPi_serialtest.inoが動いていれば、このプログラムが定期的に"get"コマンドをおり、Arduinoから返事"Hello Pi"が返されます。
 Arduinoとの通信テスト  arduino_serialtest.py
#!/usr/bin/env python
# -*- coding: utf-8 -*-
# 2017/09/17 Arduinoとの通信テスト
# Arduinoにコマンド"get"を送り、相手側からのデータを受信する

import serial #UARTを読めるようにserialをinclude
import time


def setup():
	global s
	s = serial.Serial('/dev/ttyAMA0',baudrate=9600, timeout=5)#ttyAMA0 にUARTの設定 

def readdata():#受信
	return s.readline()

if __name__ == '__main__':
	setup()
	while True:
		s.write("get")#コマンドを送る
		print (readdata())#受信データをプリント
		time.sleep(5)
		

 これにより、ArduinoとRaspberryPiとの間で通信が可能となり、RaspberryPiの指示に従ってArduinoからデータを送ることができるようになりました。

PM2.5センサの作成
 つぎにハードウエアの工作です。回路図は下記の通りです。RaspberryPiとArduinoとの接続は上と同じです。DN7C3CA007とArduinoとは6ピンのヘッダで接続します。データシートの通りLED駆動のために150Ωと220μFのケミコンが必要です。さらにFANのON/OFFをソフトから行うためにArduinoのデジタル端子D8を使います。ファンモータ駆動に90mA必要ですので、トランジスタ2SC1815で電流増幅しています。


 Arduinoとしてはさまざまモデルがありますが、安価な秋月電子のAE−ATMEGA−328 MINIをつかいました。この回路を組み立てる基板としては同じく秋月電子のRaspberry Pi用ユニバーサル基板を使用してみました。RaspberryPiのGPIOの配線やVcc、グランド線のとりまわしがとてもやりやすく、お勧めです。
写真はくみ上げたもので、下から旧型のRaspberryPiモデルB(RCAビデオ端子は撤去)、その上にRaspberry Pi用ユニバーサル基板、さらにその上に部品とArduinoがのっています。この基板はRaspberryPi B3でも使えます。
 


 DN7C3CA007とArduino、RaspberryPiを一つのケースに納め、屋外に設置します。タカチのTW13-5-13がセンサーの高さにぴったりでした。空気の取り入れ口の工作が必要です。



雨のかからない場所で、上から風を取り込み下に吹き出すように設置します。




Arduinoの読み取りソフトの作成

 通常ArduinoとPCとはUSB接続して、スケッチ(プログラム)をPCから送り込んだり、Arduinoからの出力をPCに送信したりします。開発時はこの通りですが、いったん運用に入ってからは、PCから離れた場所に設置されるため、USB接続はできません。したがってArduinoにあるプログラムは遠隔更新はできず、ROMに焼き付けたプログラムと考えたほうがよろしい。これを前提にRaspberryPiとの機能分担を考えます。すなわちArduino側ではなるべく汎用的な基本機能に限定し、変更があり得るものはRaspberryPiに持たせることとします。細かな機能に分割しすぎるとArduinoとRaspberryPiの通信量がふえますので、悩ましいところです。

 そこで、次のようなArduinoプログラムを作りました。

(1)  センサーのFanをONして計測する場合と、OFFにして計測する場合があるので、RaspberryPiから、"FanOFF"あるいは "FanON"というコマンドをおくり、それによりファンモータを制御し計測をする。
(2) 複数回計測し、平均値をRaspberryPiに返す。
(3) 測定の間隔、リファレンスとの差の計算、湿度の補正、ppm値への換算、ファイルへの書き込み、LANとの接続などはRaspberryPiで実行する。

  DN7C3CA007センサー読み取りArduinoプログラム  PM2_5.ico
/*
  PM2.5 Sensor Reader
  RaspberryPiからのコマンド(FanOFF FanON)があると1回計測 
  VsRead(0-1023の値)を返す
*/

//   Arduinoピン番号
#define SensorPin 0  // DN7C3CA007のA Voを A0につなぐ
#define PowerLEDPin 7  // DN7C3CA007のC LEDを D7につなぐ
#define PowerFanPin  8 // Fanを D8につなぐ

// データシートにより各種タイミングを設定:   パルス幅 = 0.32ms サンプリング = 0.28ms
int Tsample = 280; // サンプリングのタイミング 0.28ms 
int deltaTime = 40; // サンプリングのあとLEDをoffにするまでの時間 0.32ms - 0.28ms

// global variables
unsigned long time1;
int timer0 = 10000;//Fanを回転してから計測開始まで
int timer1 = 1000;//読み取り周期
int timer2 = 100;//0.1秒 文字読み取り
int countReading = 10; //reading counter
float OutputVoltage = 0.0;
String data = "";
String FAN = "FanOFF";

//RaspberryPi接続用SoftwareSerial
#include <SoftwareSerial.h>
SoftwareSerial RasPiSerial( 2, 3 );// RX, TXのピン番号

// セットアップ
void setup() {
  RasPiSerial.begin(9600);
  Serial.begin(9600);
  pinMode(PowerLEDPin, OUTPUT); //declare LED output
  pinMode(PowerFanPin, OUTPUT); //declare Fan output
  SensorVoltage();//1回読み飛ばし
  Serial.println("Measurement start");
}

// メイン
void loop() {
  String RasPi = "";
  char inChar;
  int length_buf = 0;
 //RaspberryPiとの通信
  if (RasPiSerial.available() > 0) {//RaspberryPiからバッファに文字が入ったら
    delay(timer2);
    length_buf = RasPiSerial.available();
    for (int iii = 0; iii < length_buf; iii++) {//1文字づつ取り込んで
      inChar = RasPiSerial.read();
      RasPi.concat(inChar);//つなげる
    }
  //コマンドに従って計測
    if (RasPi.indexOf("FanOFF") >= 0) {//RaspberryPiからの指示が"FanOFF"の場合
      digitalWrite(PowerFanPin, LOW); // Fan停止
      FAN = "FanOFF";
      Serial.println(FAN);
      measure();//Fan停止して計測
    } else if (RasPi.indexOf("FanON") >= 0) {//RaspberryPiからの指示がFanONの場合
      digitalWrite(PowerFanPin, HIGH); // Fanを回す
      FAN = "FanON";
      Serial.println(FAN);
      delay(timer0); //FanONしてから10秒後に計測開始
      measure();
      digitalWrite(PowerFanPin, LOW); // Fan停止
    }
  }
}

// LEDをパルスで駆動し出力される電圧Voを所定のタイミングで読み取る
int SensorVoltage() {
  int VsRead = 0;
  digitalWrite(PowerLEDPin, LOW); // LED点灯 逆相に注意
  delayMicroseconds(Tsample); //Tsampleを待つ
  VsRead = analogRead(SensorPin); // 出力電圧を読む
  delayMicroseconds(deltaTime); //残り時間を待つ
  digitalWrite(PowerLEDPin, HIGH); // LED消灯 
  return  VsRead ;//出力は0-1023の値
}

// 複数回読んで平均する
void measure() {
  long VsTotal = 0;
  for (int i = 0; i < countReading; i++) {
    VsTotal = VsTotal + SensorVoltage();
    delay(timer1);
  }
  OutputVoltage = VsTotal / (float)countReading;
  time1 = millis() ;
  data = (String)time1 + "," + (String)OutputVoltage + "," +  (String)FAN;
  Serial.println(data);  
  RasPiSerial.println(data);//RaspberryPiに報告
}



キャリブレーションソフトの作成

 キャリブレーションは基準電圧(Vs)を決定しなければなりません。データシートによれば、「ほこりの少ない環境下(例えばクリーンボックス)で基準電圧(Vs)を記憶する。 又はファンを止め数分経過後(ほこりが重力で落ち着いた状態)に基準電圧(Vs)を記憶する。」とあります。
 RaspberryPi側で次のPM2.5キャリブレーションプログラムを用意します。すなわちファンを停止した状態で、OutputVoltageを求めています。 この値は後で使います。

 PM2.5キャリブレーションプログラム  PM2_5calibrate.py
#!/usr/bin/env python
# -*- coding: utf-8 -*-
# 2017/08/27 ArduinoでPM2_5.icoを動かしておく

import serial  #UARTを読めるようにserialをinclude
import time
Vcc = 5.0

def setup():
	global s
	s = serial.Serial('/dev/ttyAMA0',baudrate=9600 ,timeout = 0.1)

def readdata():
 	result = s.readline()
	print result
	ary = result.split(",")
	OutputVoltage = Vcc * float(ary[1]) / 1024 * 1000.0
	Fan = ary[2]
	if 'FanOFF' in Fan :
		return OutputVoltage 

if __name__ == '__main__':
	setup()

	VsTotal = 0.0
	cnt = 0
	Vs = 0.0
	while cnt < 10:
		s.write('FanOFF')	
		VsTotal = VsTotal + readdata()
		cnt = cnt + 1
		Vs = VsTotal / float(cnt)
		data =  ","  + str(Vs)  + "," + str(cnt)
		print (data)
	print "RefVoltage =" + str(Vs)

		





本番用プログラムの作成

 いよいよRaspberryPiで動く、本番の計測ソフトの作成です。63行以下のメインプログラムの通り、RaspberryPiからs.write('FanON') によりArduinoにFanONのコマンドを送った後、readdata()します。返されたデータはCSV形式ですから、22行で","で分割、aryに入れます。27行以下で取り出した0-1023の電圧を、0-5000mVに変換しさらに、データシートの換算式に基づき、PM2.5の濃度を計算します。
 キャリブレーションのプログラムで得られた基準電圧は10行目で指定します。
 重要なのは、アルファとベータの値です。アルファはとりあえず0.6固定とします。(このページの最後で述べます)
 次にベータですが、データシートに
「大気中のほこりなどが湿気により吸湿した場合、検出可能粒径 (大気含有0.5um)以下のほこりの粒径が大きくなります。その結果、検出可能粒径(大気含有0.5um)以下のほこりの粒径が検出可能粒径範囲内に入り、 濃度係数を補正する必要があります。そのため、湿度による影響を下記の換算式中の”β”で補正することを推奨します。」
とあります。 たしかに、湿度が10%変化した場合、測定値は15%変化しますから、無視できません。私の場合、別途湿度をリアルタイムに計測しWindowsPCに保持していますので、その値で補正することとしました。(専用の湿度計を実装する方法もあります。)
 Windows上のファイルにRaspberryPiからアクセスするにはmount.cifsというコマンドを用います。mount.cifsは、「samba-client」パッケージに含まれてており、インストールしておきます。 そして、RaspberryPi上でWindowsPCのIPアドレスに対し
$ sudo mount.cifs //192.168.X.XX/MyDocuments /mnt/win
を実行します。これにより、WindowsPCのMyDocumentsファイルがRaspberryPiのプログラムから/mnt/winとして見えるようになります。
 下のプログラムではhumidity()により湿度をWindowsPCから読み取り、beta()でβの値計算しています。

 PM2.5計測プログラム  PM2_5.py
#!/usr/bin/env python
# -*- coding: utf-8 -*-
# 2017/08/27 PM2_5.icoを動かしておく

import serial  #UARTを読めるようにserialをinclude
import time
import datetime
RefVoltage = 2200.0 # [mV] 別途PM2_5calibrate.pyで求めてこれを書き換える
alpha = 0.6 #alpha factor データシート参照
Vcc = 5.0

#WindowsPCにある、気象データ
winfilename = '/mnt/win/weatherdata/currentweather.csv'

def setup():
	global s
	s = serial.Serial('/dev/ttyAMA0',baudrate=9600 ,timeout = 0.1)	

def readdata():
	OutputVoltage = 0.0
	result = s.readline()
	ary = result.split(",") #CSVファイルデータを","で分割、aryに入れる
	if len(ary) > 2:
		Fan = ary[2]
		if 'FanON' in Fan :
			#PM2.5の濃度に換算
			OutputVoltage = Vcc * float(ary[1]) / 1024 * 1000.0
			if (OutputVoltage > RefVoltage) :
				concentration = alpha * beta() * (OutputVoltage - RefVoltage) 
			else :
				concentration = 0.0
			now = datetime.datetime.now().strftime("%Y/%m/%d %H:%M:%S")
			data = now + "," + str(OutputVoltage) + "," + str(concentration) + "," 
			filename = 'dustdata/'  + 'pm2_5.txt'
			file_data = open(filename , "a" )
			file_data.write(data)
			print (data)
			file_data.close()

def humidity():
	#WindowsPCにある、気象データを読み取る
	try:
		global currentweather
		win_data = open(winfilename , "r" )
		win_data.readline()
		currentweather = win_data.readline()  #2行目
		win_data.close()
		ary0 = currentweather.split(",") #CSVファイルデータを","で分割、ary0に入れる
		return float(ary0[2]) #湿度は2番目にある
	except:
		print "Win reed error"
		return 51.0 #読み取れなかったときは51%固定

def beta():
	#湿度によりβを変化させる 
	hum = humidity()
	if hum > 50.0:
		b = 1.0 - 0.01467 * (hum - 50.0) #データシートによる
	else:
		b = 1.0
	return b

if __name__ == '__main__':
	setup()
	while True:
		s.write('FanON') #ArduinoにFanONのコマンドを送る
		readdata()
		time.sleep(60) #計測周期は1分



観測の実行
 Arduinoのプログラムは、パソコンから書込まれると電源の入っている間はいつも動いています。 RaspberryPi側ではホームディレクトリにプログラムを置き、dustdataというディレクトリを作っておきます。
 Windows上のファイルにRaspberryPiからアクセスするため、上述のmount.cifsコマンドを実行します。そして
$ sudo python ./PM2_5.py で起動します。
 次のようなデータを1分ごとに書込んで、CSVファイルPM2_5.txtができています。
年月日 時刻,OutputVoltage,PM2.5濃度(ppm)
2017/09/05 16:14:42,2206.05,3.15317
 ファイル PM2_5.txtを、親機のWindowsパソコンからよんで、適宜グラフ化します。PM2.5濃度(ppm)はかなり上下に変化が激しいので、60分の移動平均値を表示しています。

 最後にアルファの値ですが、データシートには0.6を推奨となっていますが、このままですと環境省の公式サイトに比べ感度が良すぎ変化が大きく、0.4くらいが良いようです。
 10分に1回グラフを描画し、生データをグレイ、移動平均値をオレンジで示します。こちら