編集履歴一覧に戻る
nihsokのアイコン画像

nihsok が 2025年10月20日01時02分32秒 に編集

初版

タイトルの変更

+

雷センサーモジュールAS3935をESP32で動かす (micropython)

タグの変更

+

雷センサー

+

AS3935

+

ESP32

+

I2C

+

micropython

+

IoT

+

Ambient

+

ESP32-WROOM-32

+

周波数カウンタ

+

IRQ

メイン画像の変更

メイン画像が設定されました

記事種類の変更

+

セットアップや使用方法

ライセンスの変更

+

(MIT) The MIT License

本文の変更

+

秋月電子で販売されているAS3935搭載の雷センサーモジュールをESP32からmicropythonで操作する。 Raspberry Pi & Python(ライブラリ利用)、ESP32 & Arduinoの組み合わせはいくつか見かけたが、もっと簡素な構成を目指す。今回やることは、 - micropythonからのI2C通信 - アンテナのチューニング - 本番 - 取得データのアップロード - 今回はAmbientへ送信するため、ライブラリ (https://github.com/AmbientDataInc/ambient-python-lib) を使用 # 1. 使用機材 - ESP-WROOM-32D ESP32-DevKitC V4 - たとえば https://www.aliexpress.us/item/2251832678407407.html - 500円くらい - 秋月電子 AS3935使用 雷センサーモジュール - https://akizukidenshi.com/catalog/g/g108685/ - 2280円(税込、2025年7月購入時) - ピンヘッダは同梱だが実装されていないため、自分ではんだづけ - 同梱の説明書に日本語の解説が少しあるが、本家の説明書 (https://akizukidenshi.com/goodsaffix/AS3935.pdf) から省略されている点がある(後述) - ジャンパワイヤ 5本 - つながればよい。自分はブレッドボード等は使わず直結 ピン接続は以下。 | AS3935 | ESP32 | |:---:|:---:| | SDA | GPIO26 (P26) | | SCL | GPIO25 (P25) | | IRQ | GPIO12 (P12) | | GND | GND(P12の隣) | | VDD | 5V | ++配線は他にも考えられるが、最もすっきりするものを選んだ。++ # 2. アンテナのチューニング 取扱説明書の記述より 1. REG0x08[7]のDISP_LCOを1にするとIRQピンにLCO周波数が出力される 2. このとき、REG0x03[7:6]のLCO_FDIVに基づき分割した結果が出力される。 デフォルトはLCO_FDIV=1/16 3. REG0x08[3:0]のTUN_CAPで追加キャパシタ容量を指定し、LCO周波数が500 kHzに最も近くなるものを選ぶ。指定範囲は16段階で、0-120 pFの8 pF刻み この作業のためのスクリプトを作成した。 ```python:anntena_tune.py import utime from machine import Pin,I2C,Counter,Timer paddr = 0x03 i2c = I2C(1) #check frequency division byte1 = i2c.readfrom_mem(paddr,0x03,1)[0] div = (16,32,64,124)[byte1 >> 6] if div != 16: print(f'Frequency division ratio {div} is not 16') #set display LCO on IRQ byte1 = i2c.readfrom_mem(paddr,0x08,1)[0] byte1 = (byte1 & 0b00011111) | 0b10000000 i2c.writeto_mem(paddr,0x08,bytes([byte1])) #initialize counter counter = Counter(0,Pin(12,Pin.IN)) counts = [] def callback(timer): counts.append(counter.value(0)) timer = Timer(0) #capacitance loop for i in range(16): byte1 = (byte1 & 0b11110000) | i i2c.writeto_mem(paddr,0x08,bytes([byte1])) utime.sleep(0.002) #wait 2ms counter.init() timer.init(period=1000,mode=Timer.PERIODIC,callback=callback) utime.sleep(5.5) print(i,sum(counts[1:5])*div/4*.001,counts) counts = [] #reset display LCO on IRQ i2c.writeto_mem(paddr,0x08,bytes([0b00000000])) ``` ESP32に備わっている周波数カウンタを使うため追加の機器は不要。結果をわかりやすくするため、周波数分割比LCO_FDIVをかけた値を表示している。 タイマーを使い1秒ごとに周波数を取得し、平均を出す。今回は5回のうち1回目は(タイマーが稼働するまでの時間を考えて)捨て、残り4回の平均を計算した。出力は以下のようになった。 > 0 507.88004 [31874, 31739, 31743, 31744, 31744] > 1 506.24802 [47673, 31638, 31641, 31642, 31641] > 2 504.59604 [47504, 31536, 31538, 31537, 31538] > 3 502.984 [47348, 31436, 31436, 31437, 31437] > 4 501.37604 [47192, 31335, 31336, 31336, 31337] > 5 499.78804 [47047, 31235, 31237, 31237, 31238] > 6 498.21604 [46898, 31137, 31139, 31139, 31139] > 7 496.66804 [46752, 31041, 31042, 31042, 31042] > 8 495.04404 [46602, 30939, 30940, 30941, 30941] > 9 493.520032 [46455, 30844, 30845, 30846, 30845] > 10 492.00404 [46315, 30749, 30751, 30750, 30751] > 11 490.516032 [46172, 30656, 30657, 30658, 30658] > 12 489.028 [46035, 30563, 30564, 30565, 30565] > 13 487.56404 [45889, 30471, 30474, 30473, 30473] > 14 486.11204 [45757, 30380, 30383, 30382, 30383] > 15 484.680032 [45622, 30292, 30292, 30293, 30293] この結果から、最も500kHzに近い**TUN_CAP=5**を次のステップで使う。 同じ設定での周波数のばらつき(1の位)に対し、静電容量ごとの周波数の違い(100の位)は2桁大きいので、そこまで平均をとらなくても十分ということもわかる。 ++この操作自体をメインプログラムに組み込み自動化することもできそうだが、機差であり一度設定すれば同じ値でよいと考えスクリプトを分けた。実際何度か試しても同じ値が選ばれる。++ # 3. 雷センサーの運用 ```python:main.py import utime from machine import Pin,I2C import wifi import ambient paddr = 0x03 i2c = I2C(1) #reset to default i2c.writeto_mem(paddr,0x3D,bytes([0x96])) #Initial setting print('Setting:') #Read AFE Setting, Outdoor vs. Indoor byte1 = i2c.readfrom_mem(paddr,0x00,1)[0] AFE_GB = (byte1 & 0b00111110) >> 1 if AFE_GB == 0b10010: print(' Indoor') noise_level = (28,45,62,78,95,112,130,146) elif AFE_GB == 0b01110: print(' Outdoor') noise_level = (390,630,860,1100,1140,1570,1800,2000) else: print(' Unknown AFE setting') exit() #Read Noise Floor/Watchdog Threshold byte1 = i2c.readfrom_mem(paddr,0x01,1)[0] threshold = noise_level[(byte1 & 0b01110000) >> 4] print(f' Noise floor threshold: {threshold} uVrms') WDTH = (byte1 & 0b0001111) print(f' Watchdog threshold: {WDTH}') #Read Minimum Number of Lightning Detection/SREJ byte1 = i2c.readfrom_mem(paddr,0x02,1)[0] MIN_NUM_LIGH = (1,5,9,16)[(byte1 & 0b00110000) >> 4] print(f' Minimum number of lightning: {MIN_NUM_LIGH}') SREJ = (byte1 & 0b00001111) print(f' Spike rejection: {SREJ}') #Antenna caliblation TUN_CAP = 5 byte1 = i2c.readfrom_mem(paddr,0x08,1)[0] byte1 = (byte1 & 0b11110000) | TUN_CAP i2c.writeto_mem(paddr,0x08,bytes([byte1])) #Ambient setting print(wifi.connect()) am = ambient.Ambient(CHANNEL_ID,WRITE_KEY) #On interruption def interrupt(irq): byte1 = i2c.readfrom_mem(paddr,0x03,1)[0] INT = (byte1 & 0b00001111) if INT == 0b0001: #Modify noise floor print('Noise level too high') byte1 = i2c.readfrom_mem(paddr,0x01,1)[0] NF_LEV = (byte1 & 0b01110000) >> 4 byte1 = (byte1 & 0b10001111) | (min(NF_LEV+1,7) << 4) i2c.writeto_mem(paddr,0x01,bytes([byte1])) elif INT == 0b0100: #Modify MASK_DIST print('Disturber detected') byte1 = i2c.readfrom_mem(paddr,0x03,1)[0] byte1 = byte1 | 0b00100000 i2c.writeto_mem(paddr,0x03,bytes([byte1])) elif INT == 0b1000: print('Lightning interrupt') byte1 = i2c.readfrom_mem(paddr,0x07,1)[0] DISTANCE = (byte1 & 0b00111111) byte1 = i2c.readfrom_mem(paddr,0x04,1)[0] S_LIG_L = byte1 byte1 = i2c.readfrom_mem(paddr,0x05,1)[0] S_LIG_M = byte1 byte1 = i2c.readfrom_mem(paddr,0x06,1)[0] S_LIG_MM = (byte1 & 0b00011111) print(am.send({'d1':DISTANCE,'d2':S_LIG_L,'d3':S_LIG_M,'d4':S_LIG_MM})) #IRQ setting event = Pin(12,Pin.IN) event.irq(interrupt,trigger=Pin.IRQ_RISING) #main while True: utime.sleep(1800) print(wifi.connect()) ``` 雷が検出されるとIRQピンに割り込みが発生するので、これをトリガーに以下の処理を行う。 - REG0x03[3:0]がINT_NH (0001): 高ノイズレベル⇒ノイズフロア閾値を上げる - REG0x03[3:0]がINT_L (1000): 妨害信号⇒REG0x03[5]のMASK_DISTを1にしてマスク。この分岐は最初のみ - REG0x03[3:0]がINT_L (1000): 雷を検出⇒REG0x07[5:0]のDISTANCE(雷雲までの**最短**距離)、強度 (REG0x04[7:0]=S_LIG_L, REG0x05[7:0]=S_LIG_M, REG0x06[4:0]=S_LIG_MM) を取得 Wi-Fi接続に関しては[過去の記事](https://elchika.com/article/f6bc3521-e7db-4ef9-9256-2396b3a75f21/)を参照 IRQの割り込みはHレベルを使いたかったのだが、micropythonではPin.IRQ_HIGH_LEVELがサポートされていなかったので代わりにPin.IRQ_RISINGを使った。 ==結果はAmbientにアップロードしており、https://ambidata.io/bd/board.html?id=100013 で確認できる。== ## 3.1 秋月電子提供の説明書で省略されていた点 1. 本家説明書p23 Direct Command:レジスタの設定をすべてデフォルトの値へとリセットするにはREG0x03C、RCOを校正するにはREG0x3Dに直接コマンドを送る。このためには、それぞれのレジスタへ0x96 (0b10010110) を書き込む。 (秋月の説明書にはコマンドの送信先は書いてあるが、コマンドのフォーマットが書かれていない) 2. 本家説明書p29 Watchdog threshold:REG0x01[3:0]のWDTHで閾値を設定できる。このパラメータもSREJと同様に感度へ影響する。 3. 本家説明書p35 距離推定のリセット:雷雲までの距離の統計をリセットするには、REG0x02[6]にhigh-low-highを与える。 4. 本家説明書p36 RC Oscillatorの校正:system RCO (SRCO; 1.1 MHz) とtimer RCO (TRCO; 32.768kHz) が搭載されており、温度変化による周波数変動は自動で補償される。 TRCOはREG0x08[5]、SRCOはREG0x08[6]を1にするとその周波数がIRQピンに出力される。校正のためのコマンドがあるが、その精度はアンテナの共振周波数に依存するため、アンテナ調整後に発信回路の校正を行う。校正結果は揮発メモリに記録されるため、起動のたびに校正するが、温度や電圧の変動に対しては内部的に補償される。power-downモードの際にTRCOを校正するには以下の手続きを行う。 (power-downモードはREG0x00[0]を1に変えない限りは入らないので、基本的には不要?) 1. CALIB_RCOのコマンド送信:REG0x3Dに0x96 (0b10010110) を書き込む 2. REG0x08[6]を1にする 3. 2ミリ秒待つ 4. REG0x08[6]を0にする 校正結果はREG0x3A[7:6]とREG0x3B[7:6]に出力される。それぞれTRCOとSRCOのステータスに対応し、校正が成功していれば上位ビットが1、失敗なら下位ビットが1となる(ここは秋月の説明書のレジスタマップにも書いてある) # 4. 所感 - 雷が発生していないとちゃんと動いているかわからない。 - IRQ割込みをトリガーとしてスリープ復帰するようにすれば待機時の消費電力を抑えられる気もするが、そのたびに毎回Wi-Fi接続していると雷の発生間隔よりも時間がかかってしまいそうなので今回は不採用。 - 消費電力調査も今後の課題 - 連続稼働に難があるようで、誤検出が頻出するときがある。起動時にすべての設定をリセットするようにしているので、データがおかしいときはいったん電源を差しなして対応。 - 配線やはんだなどハードの問題の場合、通信そのものができないはずなので別の理由と考えている。