秋月電子で販売されている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. アンテナのチューニング
取扱説明書の記述より
- REG0x08[7]のDISP_LCOを1にするとIRQピンにLCO周波数が出力される
- このとき、REG0x03[7:6]のLCO_FDIVに基づき分割した結果が出力される。
デフォルトはLCO_FDIV=1/16 - REG0x08[3:0]のTUN_CAPで追加キャパシタ容量を指定し、LCO周波数が500 kHzに最も近くなるものを選ぶ。指定範囲は16段階で、0-120 pFの8 pF刻み
この作業のためのスクリプトを作成した。
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. 雷センサーの運用
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接続に関しては過去の記事を参照
IRQの割り込みはHレベルを使いたかったのだが、micropythonではPin.IRQ_HIGH_LEVELがサポートされていなかったので代わりにPin.IRQ_RISINGを使った。
結果はAmbientにアップロードしており、https://ambidata.io/bd/board.html?id=100013 で確認できる。
3.1 秋月電子提供の説明書で省略されていた点
- 本家説明書p23 Direct Command:レジスタの設定をすべてデフォルトの値へとリセットするにはREG0x03C、RCOを校正するにはREG0x3Dに直接コマンドを送る。このためには、それぞれのレジスタへ0x96 (0b10010110) を書き込む。
(秋月の説明書にはコマンドの送信先は書いてあるが、コマンドのフォーマットが書かれていない) - 本家説明書p29 Watchdog threshold:REG0x01[3:0]のWDTHで閾値を設定できる。このパラメータもSREJと同様に感度へ影響する。
- 本家説明書p35 距離推定のリセット:雷雲までの距離の統計をリセットするには、REG0x02[6]にhigh-low-highを与える。
- 本家説明書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に変えない限りは入らないので、基本的には不要?)- CALIB_RCOのコマンド送信:REG0x3Dに0x96 (0b10010110) を書き込む
- REG0x08[6]を1にする
- 2ミリ秒待つ
- REG0x08[6]を0にする
校正結果はREG0x3A[7:6]とREG0x3B[7:6]に出力される。それぞれTRCOとSRCOのステータスに対応し、校正が成功していれば上位ビットが1、失敗なら下位ビットが1となる(ここは秋月の説明書のレジスタマップにも書いてある)
4. 所感
- 雷が発生していないとちゃんと動いているかわからない。
- IRQ割込みをトリガーとしてスリープ復帰するようにすれば待機時の消費電力を抑えられる気もするが、そのたびに毎回Wi-Fi接続していると雷の発生間隔よりも時間がかかってしまいそうなので今回は不採用。
- 消費電力調査も今後の課題
- 連続稼働に難があるようで、誤検出が頻出するときがある。起動時にすべての設定をリセットするようにしているので、データがおかしいときはいったん電源を差しなして対応。
- 配線やはんだなどハードの問題の場合、通信そのものができないはずなので別の理由と考えている。
投稿者の人気記事





-
nihsok
さんが
2025/10/20
に
編集
をしました。
(メッセージ: 初版)
ログインしてコメントを投稿する