RaspberryPi
RaspberryPiによる雷センサー
  |Page Top|
 従来からデジタル日野気象台では、1-Wireによる雷センサがありました。これは雷の放電電波を受信し、その回数をカウントする簡単なもので、雷の強さや距離はわかりませんでした。秋月電子のホームページを見ていると最近雷センサーのキットが発売されているのが見つかり、早速購入しRaspberryPiに接続し、計測器を作ることとしました。(2015.4.11初版)
 この雷センサーの主要部品はAS3935というLSIで、AustrianMicroSystem社の製品です。どのようなアルゴリズムによって距離を求めているのかは、データシートや解説論文を見ても公表されていませんが「Franklin社の長年の雷研究の成果が詰まっている」と言います。以下は私の想像です。 雷の放電によって放出される電波は,急峻なパルスのため広い範囲の周波数分布のスペクトルを持っていて、周波数に対し1/fで減少することが知られています。それが遠くに伝搬されるに従って空間のキャパシタの影響で波形はなまり、高い周波数のエネルギーがより多く減少します。すなわち、全体のエネルギーと特定の高い周波数のエネルギーの比が距離により変わってくると考えられます。LSIの内部に特定の周波数(500kHz)のエネルギーが距離に対しどのように変化するか基準をもっていて、それと比較することで,距離を推定しているのではないかと思います。
 もしうまく働けば、小さなLSIで雷までの距離が瞬時にわかるわけで、画期的なことです。
 AS3935というLSIは、既存の製品、例えばGPS、スマホ、魚群探知器など多くの電池式消費者向け電子装置に組み込まれることを想定し、低消費電力、小形となっています。
 秋月電子のホームページには、Arduinoマイコンによるプログラムが公開されています。ここでは、Arduinoは使わずにRaspberryPiにより、雷センサーを作りたいと思います。作るプログラムはPythonとします。こちらを参考にしました。マニュアルを日本語訳がこちらにあります。
 設置してから2016.7.4に本格的な雷がありました。ログファイルが出力されていましたので、グラフ化してみました。運用結果に示すとおり充分に実用になります。(2016.7.12 2017.7.1修正)
最近の落雷はこちら

雷センサーの概要
 

 AS3935は、40km以内1kmまでの落雷や雲間放電で発生した信号を小形のバーアンテナで受信し、独自のアルゴリズムによって雷雲までの距離やエネルギーを判定します。雷の発生を検知すると、内部のレジスタにデータが記録されます。同時に割り込み信号(IRQ)が出力されます。IRQをトリガとして、マイコンからレジスタデータを呼び出すことによって、雷雲の発生や動きを知ることが出来ます。
 外部部品がいくつか必要ですが、秋月のキットは小さなボードにすべて実装されています。MA5532-AEが専用バーアンテナで100μHのコイルです。2.54mmの汎用基板にマウントでき、マイコンとのインターフェースはI2Cとなっています(I2Cデバイスアドレスは 0x00)。

 
 データシートによるAS3935のブロック図を示します。外部アンテナはAnalog Front-end(AFE)に直接接続され、AFEで受信信号を増幅し、復調します。Watchdog回路は、連続的にAFEの出力をモニターして、信号があると、Lightning Algorithm回路に対して注意イベントを喚起します。
 Lightning Algorithm回路は、信号パターンをチェックすることによって、信号を確認します。雷に起因する信号か人工的ノイズ源に起因している信号かを区別し、もし信号が人工的と判断された場合、イベントは拒絶され、センサーはリスニングモードに自動的に戻ります。イベントが雷と分類されると、統計距離評価ブロックが嵐の近端までの距離の評価を実行します。
 AS3935は雷をともない接近してくる嵐の存在を見つけることができ、嵐の最近端の距離を評価し出力します。嵐の最近端とはセンサーから最も近い端までの距離と定義されます。雷を検出するたびに、AS3935に物理的に組み込まれた距離評価アルゴリズムがIRQピンに割り込みを発生しますが、その時の距離評価レジスターで示される距離値は、個別の雷までの距離ではなく、嵐の最近端までの距離となります。このことをグラフに示します。

  I2Cインターフェースはマイコンとの間で、レジスタの制御を行い通信を行います。AS3935には0x00から0x32までのレジスタがありますが、そのうち下の図のような11個のレジスタを読み書きし、通信することになります。



 まず準備としてRaspberryPiのPythonからI2Cインターフェースを使えるようにします
$ sudo nano /etc/modules
により /etc/modulesファイルを修正しつぎをつけくわえます。

i2c-bcm2708
i2c-dev

 つぎに
$ sudo nano /etc/modprobe.d/raspi-blacklist.conf
により、/etc/modprobe.d/raspi-blacklist.confにあるブラックリストを次のように #でコメントアウトします。
#blacklist i2c-bcm2708

そしてI2Cのパッケージをインストールします。
$ sudo apt-get install python-smbus

さらにI2Cのユーティリティをインストールします。
$ sudo apt-get install i2c-tools

次に割り込み信号IRQを読み取るために、GPIOも使いますので、GPIOライブラリもインストールします。

$ sudo apt-get install python-rpi.gpio

PythonによるAS3935ライブラリ
 PythonによるAS3935ライブラリは、こちらに Phil Fenstermacher氏が 開発されたものが公開されており、ダウンロードできます。バグがありましたので(修正を依頼すみ)、一部修正・追加したものを次に示します。上のレジスタを変更したり、読み出したりするための機能の集合です。
 このファイルを RaspberryPiのホームディレクトリに RPi_AS3935.py というなまえで保管しておいてください。

 AS3935ライブラリRPi_AS3935 2016/12/30改版
# -*- coding: utf-8 -*- 
 
#Developed by  Phil Fenstermacher. Revised by Hiroshi Ishikawa.
# 2016/12/30 (C) Hiroshi Ishikawa.

import time

class RPi_AS3935:
	def __init__(self, address, bus=1):
		self.address = address
		import smbus
		self.i2cbus = smbus.SMBus(bus)

	def calibrate(self, tun_cap=None):
		#tune_capの値をレジスタ0x08のビット[3:0]にセットする
		time.sleep(0.08)#マニュアルに指定のタイミングをとる
		self.read_data()
		if tun_cap is not None:
			if tun_cap < 0x10 and tun_cap > -1:
				self.set_byte(0x08, (self.registers[0x08] & 0xF0) | tun_cap)
				time.sleep(0.002)
			else:
				raise Exception("Value of TUN_CAP must be between 0 and 15")
		#レジスタ0x3Dに0x96をセットし、内部クロック発振器の較正
		self.set_byte(0x3D, 0x96)
		time.sleep(0.002)
		self.read_data() 
		self.set_byte(0x08, self.registers[0x08] | 0x20)
		time.sleep(0.002)
		self.read_data() 
		self.set_byte(0x08, self.registers[0x08] & 0xDF)
		time.sleep(0.002)

	def reset(self):
		#レジスタ0x3Cに0x96を書き込み、すべてのレジスタをデフォルト値に
		self.set_byte(0x3C, 0x96)

	def get_interrupt(self):
		#レジスタ0x03のビット[3:0]が割り込み情報
		self.read_data()
		return self.registers[0x03] & 0x0F

	def get_distance(self):
		#レジスタ0x07のビット[5:0]が距離情報
		self.read_data()
		return self.registers[0x07] & 0x3F

	def get_energy(self):
		#レジスタ0x04、0x05、0x06のビット[4:0]がエネルギー。ただしこの値は一つの「雷」のエネルギー量で、内部処理用であり物理的な意味を持たない
		self.read_data()
		return ((self.registers[0x06] & 0x1F) * 65536)+ (self.registers[0x05] * 256)+ (self.registers[0x04])

	def set_noise_floor(self, noisefloor):
		#レジスタ0x01のビット[6:4]にノイズ下限レベルを設定
		self.read_data()
		noisefloor = (noisefloor & 0x07) << 4
		write_data = (self.registers[0x01] & 0x8F) + noisefloor
		self.set_byte(0x01, write_data)

	def get_noise_floor(self):
		#レジスタ0x01のビット[6:4]のノイズ下限レベルを読む
		self.read_data()
		return (self.registers[0x01] & 0x70) >> 4

	def raise_noise_floor(self, max_noise=7):
		#ノイズ下限レベルを1あげる
		floor = self.get_noise_floor()
		if floor < max_noise:
			floor = floor + 1
			self.set_noise_floor(floor)
		return floor

	def lower_noise_floor(self, min_noise=0):
		#ノイズ下限レベルを1さげる
		floor = self.get_noise_floor()
		if floor > min_noise:
			floor = floor - 1
			self.set_noise_floor(floor)
		return floor

	def set_indoors(self, indoors):
		#レジスタ0x00のビット[5:1] AFE利得に屋内か屋外かを指定
		self.read_data()
		if indoors:
			write_value = (self.registers[0x00] & 0xC1) | (0b10010 << 1) 
		else:
			write_value = (self.registers[0x00] & 0xC1) | (0b01110 << 1) 
		self.set_byte(0x00, write_value)

	def set_mask_disturber(self, mask_dist):
		#レジスタ0x03のビット[5]を制御
		self.read_data()
		if mask_dist:
			write_value = self.registers[0x03] | 0x20
		else:
			write_value = self.registers[0x03] & 0xDF
		self.set_byte(0x03, write_value)

	def get_mask_disturber(self):
		#レジスタ0x03のビット[5]を読む
		self.read_data()
		if self.registers[0x03] & 0x20 == 0x20:
			return True
		else:
			return False

	def set_disp_lco(self, display):
		#レジスタ0x08のビット[7]により、アンテナ共振周波数をIRQピンに出力
		self.read_data()
		if display:
			self.set_byte(0x08, (self.registers[0x08] | 0x80))
		else:
			self.set_byte(0x08, (self.registers[0x08] & 0x7F))
		time.sleep(0.002)

	def set_byte(self, register, value):
		#i2cbus書き込みの基本コマンド
		try:
			self.i2cbus.write_byte_data(self.address, register, value)
		except Exception as e:#エラーでもメッセージをだして処理続行
			print '==Error set_byte()==' + str(e)

	def read_data(self):
		#i2cbus読み込みの基本コマンド
		try:
			self.registers = self.i2cbus.read_i2c_block_data(self.address, 0x00)
		except Exception as e:#エラーでもメッセージをだして処理続行
			print '==Error read_data()==' + str(e)


アンテナチューニング

 AS3935を使う前に、アンテナのチューニングを行う必要があります。やっかいですがこの雷センサーの正確な測定のために必要なことだそうです。アンテナはLとCの同調回路になっており、正確に500kHzに同調されていなければなりません(±3.5%以内におさめる)。部品のばらつきによってずれている場合があり、それを補正します。図のように補正用のキャパシタが内蔵されており、レジスタ0x08のビット[7]を1にセットすると、チューニング回路LCOが発信回路として動作し、分周された矩形波がIRQピンに出力されます。レジスタ0x08のビット[3:0]の値(以下tun_cap)を0から15まで16ステップ(1ステップは8pF)変化させることが出来ます。
 何分の一に分周するかをレジスタ0x03のビット[7:6]で指定することができ、デフォルトは16分周です。たとえばこの場合IRQピンには1/16周期の矩形波が出力されるので、500kHzの直接計測は無理でも分周されたものであれば、通常のマルチメータで計測できます。500kHzのばあい、500/16=31.25ですから、tun_capをプログラムで変化させながら、IRQの周波数を計測し、この値になるようなtun_cap値を求めます。
 そのためのPythonプログラムをこちらを参考にを作ります。calibrate.pyという名前で、ホームディレクトリに保存しておきます。



アンテナのチューニング用のプログラム calibrate.py 2016/12/30改版
#!/usr/bin/python
# -*- coding: utf-8 -*- 
# 2016/12/30 (C) Hiroshi Ishikawa.
# アンテナチューニングのプログラム

from RPi_AS3935 import RPi_AS3935

import RPi.GPIO as GPIO
import time
from datetime import datetime

GPIO.setmode(GPIO.BCM)
IRQ = 4 #RaspberryPiのGPIO4(7番ピン)
GPIO.setup(IRQ, GPIO.IN)

sensor = RPi_AS3935(address=0x00, bus=1)#秋月製の雷センサーはI2Cアドレスは0x00固定

sensor.reset() 
print "set indoors"
sensor.set_indoors(True)
print "set noise floor to 0"
sensor.set_noise_floor(0)

sensor.read_data()
print "register0x08= ",bin(sensor.registers[0x08])
#レジスタ0x08をアンテナ共振周波数をIRQピンに出力するよう指定
sensor.set_disp_lco(True)
sensor.read_data()
print "register0x08= ",bin(sensor.registers[0x08])

#IRQピンにマルチメータを接続し周波数を測定せよ
print "Please measure the frequency of the IRQ pin by a multi-meter"
#tune capを10秒ごとに変化させる
print "tune cap is changed every 10 seconds"
for x in range(0, 16):
	print "tun_cap= ",x
	sensor.calibrate(tun_cap=x)
	time.sleep(10.0)

time.sleep(5.0)
#レジスタ0x08をもとに戻しておく
sensor.set_disp_lco(False)
sensor.read_data()
print "register0x08= ",bin(sensor.registers[0x08])




 次に写真のような測定系をバラックでくみたてます。RaspberryPiとセンサーモジュールとの接続は次の通りです。

 雷センサー        RaspberryPi 
 VDD   3.3v (pin 1) 
 GND   Ground 
 IRQ   GPIO 4 (pin 7) 
 SCL   SCL (pin 5) 
 SDA   SDA (pin 3) 
2017.8.5修正

 マルチメータは交流レンジ、赤リードをIRQ、黒リードをGNDにつなぎ、マルチメータのHzボタン(黄色)を押します。RaspberryPiで
$ sudo python calibrate.py
を実行。コンソールに
Please measure the frequency of the IRQ pin by a multi-meter
のメッセージのあと、つぎつぎにtun_capの値が、0,1,2,・・・と10秒ごとに出力されるので、そのたびにマルチメータに表示される周波数値をメモしておきます。(写真では今tun_capの値が8の時で、マルチメータに30.87kHzと表示されている)

 プログラムが終了し、メモしたすべての値を16倍し、その中で最も500kHzに近い値が、チューニングのためのキャパシタtun_capの値です。これは本番のプログラムで使います。私の場合はtun_cap=4のとき、31.28kHzで、16倍すると500.48kHzとなり、もっとも500kHzに近い値でした。補正用のキャパシタ値は、4X8=32pFです。

雷センサーのプログラム
 雷センサーの本番のプログラムを作ります。こちらのデモプログラムを参考にしました。 本番のプログラムでは起動のたびに、(5)のように上のAS3935ライブラリのcalibrate()を呼んで、レジスタ0x08のビット[5:0]にtun_cap値をセットすると共に、TRCOとSRCOのクロック発生回路を正しくセットしています。

(1) 観測結果を lightning ディレクトリに YYYYlightning.txt というファイル名で、csvファイルを出力します。
(2)  アドレスは Rev. 1 Raspberry Piの場合は bus=0, rev. 2 の場合はbus=1
 また 秋月製の雷センサーはI2Cアドレスは0x00固定であることに注意
(3) 屋外設置を指定
(4) ノイズを自動設定しますが、初期値は0
(5) うえで求めたチューニング結果tun_cap値をここで指定します。
(6) IRQ割り込みの原因が3種類あり、ノイズが高い場合、閾値を上げます。
(7) 信号が人工的なものと判断された場合はマスクビットを立てます
(8) 信号が雷と判断された場合、距離とエネルギーのデータを該当のレジスタから読み取り、出力します
(9) GPIOのadd_event_detectをつかって、割り込みがあったら handle_interrupt が動くようにします
(10) 1秒ごと、無限ループ

本番のプログラム lightning.py  改2017/01/04
#!/usr/bin/python
# -*- coding: utf-8 -*- 
# (C)2016 Dr.Hiroshi Ishikawa

from RPi_AS3935 import RPi_AS3935
import RPi.GPIO as GPIO
import time
import csv #(1)
from datetime import datetime

GPIO.setmode(GPIO.BCM)

# Rev. 1 Raspberry Piの場合は bus=0,  rev. 2 の場合はbus=1
# 秋月製の雷センサーはI2Cアドレスは0x00固定

sensor = RPi_AS3935(address=0x00, bus=1) #(2)
sensor.reset() 
sensor.set_indoors(False) #(3) Outdoor
sensor.set_noise_floor(0) #(4)
sensor.calibrate(tun_cap=0x04) #(5) 32pF チューニングの結果をここで指定する

def handle_interrupt(channel):
	time.sleep(0.003)
	global sensor
	reason = sensor.get_interrupt()
	now = datetime.now().strftime("%Y/%m/%d %H:%M:%S")
	outputfile = open(filename , "a" )
	writer = csv.writer(outputfile)
	if reason == 0x01: #(6)
		sensor.raise_noise_floor()
		buffer = [now, "Noise level too high",  str(distance)  , str(energy)]
		print (buffer)
		writer.writerow(buffer)
	elif reason == 0x04: #(7)
		sensor.set_mask_disturber(True)
		buffer = [now, "Disturber detected",  str(distance)  , str(energy)]
		print (buffer)
		writer.writerow(buffer)
	elif reason == 0x08: #(8)
		distance = sensor.get_distance()
		energy = sensor.get_energy()
		buffer = [now, "lightning!" ,  str(distance)  , str(energy)]
		print (buffer)
		writer.writerow(buffer)
	outputfile.close()

#ここから始まり
IRQ = 4

GPIO.setup(IRQ, GPIO.IN)
GPIO.add_event_detect(IRQ, GPIO.RISING, callback=handle_interrupt) #(9)
now = datetime.now().strftime("%Y/%m/%d %H:%M:%S")

filename = 'lightning/' + datetime.now().strftime("%Y") + 'lightning.txt' #(1)
outputfile = open(filename , "a" )
writer = csv.writer(outputfile)
distance = sensor.get_distance()
energy = sensor.get_energy()
buffer = [now, "Waiting for lightning",  str(distance)  , str(energy) ]
print (buffer)
writer.writerow(buffer)
outputfile.close()

while True:
	time.sleep(1.0) #(10)

雷センサーの本番機

 雷センサーを本番用に設置することとなりますが、AS3935のデータシートによると、金属から遠ざけるように、指示があります。小さいですから、RaspberryPiと同じケースに入れても良いですが、電子的ノイズの点でも少し離して設置することとします。I2Cインターフェースは長延な伝送には適さず、500pF程度のケーブルしか使えません。このためCAT5のLANケーブルを用いて1mほど離して設置することとしました。LANケーブルは1mあたり50pFで優秀です。4対のより線で構成されていますので、対の片方をGRD線としなければ性能が出ません。SDA、SCL、IRQ、VDDそれぞれにGRD線と共に1対を割り当てました。(決まりはありませんが、とりあえず下の表のように)

 LANケーブル        I2Cインターフェース 
 1   Ground 
 2   IRQ
 3   Ground 
 4   SDA 
 5   Ground 
 6   SCL
 7   VDD
 8   Ground

 これらの接続のために小さい基板を用意し、写真のように構成し、雷センサーは雨のかからない軒先に設置しました。
 雷センサーのプログラムは、わずかなCPU負荷しかかかりませんので、RaspberryPiは以前作ったソフトラジオ用のものに相乗りとしました。

 時々IOError: [Errno 5] Input/Output errorというエラーメッセジが出るときがあり、つぎのような対策をしました。(2016.9.9)
このメッセージはI2C通信のエラーで、I2C通信速度が高すぎるためで、
/boot/config.txt
というファイルの最後に次を書き加えます。
dtparam=i2c1_baudrate=10000



 
 AS3935は後で交換できるように、ピンヘッダに刺す。RJ45のコネクタに配線する  小さいケースに入れて、軒先に設置
   
 ソフトラジオ用と相乗りRaspberryPi。GPIOヘッダに小さい基板をさし、雷センサー用のRJ45LANコネクタとインターフェースを作ります。USBはWiFiアダプタが使用している。あいているRaspberryPiのRJ45のコネクタに間違って接続しないように。下半分の基板は5v2Aの電源回路

 設置してからだいぶたちましたが、2016.7.4に本格的な雷がありました。ログファイルが出力されていましたので、グラフ化してみました。充分に実用になります。(2016.7.12追記)

 当方の真上を雷雲が通過し、雷センサーは、落雷を記録していました。5分ごとの落雷数と、落雷ごとの距離をグラフに示します。午後3時頃30km離れたところの落雷を観測したのが始まりで、その後どんどん激しくなりました。午後5時頃には、5分間で47回の落雷があり、その距離は1km以下で、ほぼ頭上を雷雲が通過したことが、記録されました。そして午後6時頃におさまりました
ナチュラル研究所 雷センサーによる、雷の記録 (2016/07/04)
 2016.7.4は、梅雨前線が東北地方にあり、東日本は太平洋高気圧が広く覆っていました。午前中から気温が高く、当方の最高気温は33.8 ℃となっていました。午後は広い範囲に雷雲が発生。局地的に激しい雨がりました。東京電力の午後5時時点の「雨量・雷観測情報」は下の図の通り、東京の西部から、都心にかけ、落雷が移動していったことを示しています。

2016.7.4 17時 落雷の情報 東京電力「雨量・雷観測情報」による


最近の落雷はこちら