NanoPi NEO に照度センサーを付けてみる

目的

NanoPi NEO は室内に置いてて、常時 OLED に稼働状況がスクロール表示されている。
が、不在時や夜間にまで表示させてても意味がない。

なので、「暗くなったらOLED表示を停止」させる。

用意したもの

・Nano Pi NEO
・照度センサー(以下の物)

2026年3月現在、2個セットで999円(税込)…1個当たり500円…安っ…

VEML7700 は定番なんですかね?いろんなボードが出ててるみたいで。
一応、高精度と謳われてはいるけど、今回は絶対的な精度は求めてなくて、「その場所での明るさが相対的にわかればよい」ので、まぁ、安物でもそこそこ問題は出ないだろうと期待。

また、やってることはラズパイでも問題なくできる。

ハードウェア的には全く違うので配線等はラズパイに合わせるべきだけど、配線ったって4本だし、そのうち2本は電源だし…
コマンドはどっちもUbuntuベース(debianベース)で同じだし、ソフトウェア(Pythonスクリプト)はおそらくコピペでイケちゃう。

なので、NanoPi NEO を Raspberry Pi に置き換えても全然できちゃうと思う。

つなげる

もしかすると、SDA と SCL は3.3Vにプルアップしてあげないとダメかも。
3~4.7kΩの抵抗を介して、Nano Pi NEO の 3.3V ピンに接続してあげないと…

しかし、今回はすでに OLED が繋がっていて、そっちで SDA と SCL がプルアップされているので、そのまま配線するだけでOKだった。

プルアップした時の接続例は以下のような感じ。

尚、販売サイトでは3Voについて「Regulated 3.3V 100mA」と書かれてある謎…
いや、安定化された3.3Vなら入力していいよってことだけど、100mAって…
(このボードに搭載されているレギュレータが100mAだから、それをそのまま書いてる感じやね…)

また、SDA と SDL をプルアップしてないと、たぶん、

$ sudo i2cdetect -y 0

を実行した時に、各アドレスに「–」が表示されていくけど、めっさ遅い表示になると思う。
そして、待ってみても下記の通り、何も検出されないという結果が待っているはず。

     0  1  2  3  4  5  6  7  8  9  a  b  c  d  e  f
00:                         -- -- -- -- -- -- -- --
10: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
20: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
30: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
40: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
50: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
60: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
70: -- -- -- -- -- -- -- --

下準備

物理的な配線が終わったら、接続に間違いがないことを確認して、NanoPi NEO の電源を入れる。

起動後、以下のようにしてI2C関連ツール入れて、Pythonの仮想環境を準備して、接続に問題ないかを確認しておく。

$ sudo apt install i2c-tools
$ python3 -m venv alsReader
$ cd alsReader
$ source bin/activate
$ ./bin/pip install smbus2

$sudo i2cdetect -y 0

     0  1  2  3  4  5  6  7  8  9  a  b  c  d  e  f
00:                         -- -- -- -- -- -- -- --
10: 10 -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
20: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
30: -- -- -- -- -- -- -- -- -- -- -- -- 3c -- -- --
40: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
50: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
60: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
70: -- -- -- -- -- -- -- --

0x10 が今回の照度センサーのアドレスでちゃんと認識されている。
0x3c は既に使用している OLED モニタ。

うんうん、問題ないね。

簡易チェック

とりあえず、「センサーの値を返答せよ」とやってみる。

$ sudo i2cset -y 0 0x10 0x00 0x00 0x00 i
$ sudo i2cget -y 0 0x10 0x04 w
0x084b

一行目はセンサーを起こすコマンド。
最初にこれをやっておかないと、0x0000 という値しか返ってこない。
センサーに通電された後のデフォルト状態は、「シャットダウン」だから。

で、1行目を実行した後に2行目で値の取得。

最後に 0x084b という値が生データ。

VEML7700 に設定されている「ゲイン」と「測定時間(積分時間)」から「係数」が求められるので、得られた値 0x084b が何 Lux に相当するかを計算できる。

ちなみに、i2cset / i2cget の書式は以下な感じ。

i2cset -y [BUS番号] [デバイスアドレス] [書き込みたいレジスタアドレス] [データ1] [データ2] [オプション]
i2cget -y [BUS番号] [デバイスアドレス] [読み込みたいレジスタアドレス] [オプション]

どっちも似た感じなので、覚えやすい。

Pythonで値を取得し続ける

コピペでそのままいけるんじゃないかな。

from smbus2 import SMBus
import time

I2C_BUS = 0      # I2Cのバス番号:通常は0で良いはず
ADDR = 0x10      # VEML7700のI2Cアドレス
 
# レジスタ
ALS_CONF = 0x00  #設定内容が書かれているレジスタ
ALS_DATA = 0x04  #センサーの測定値:これが読み込みたいデータ
 
with SMBus(I2C_BUS) as bus:
    # センサー有効化
    bus.write_word_data(ADDR, ALS_CONF, 0x0000)    # デフォルト設定での起動
    # bus.write_word.data(ADDR, ALS_CONF, 0x0300)   # 明るい場所用:測定時間を25msにする場合
    # bus.write_word.data(ADDR, ALS_CONF, 0x00C0)   # 暗い場所用:測定時間を800msにする場合
 
    while True:
        time.sleep(3)    # そんなにしょっちゅう読み込まなくていいけど、変化は見たいから3秒くらいの間隔で
 
        # データ読み取り
        data = bus.read_word_data(ADDR, ALS_DATA)

        # 読み取りデータ表示 
        print("RAW:", data)
        print("Lux:", data * 0.0672)

ただ、最終行にある 0.0672 をかけているところ、「違くね?」となる方もいらっしゃるかと。
まずもって、センサー購入時のAmazonカタログ上にもこの数値はないからね。

けど、VEML7700 のデータシートじゃなくてアプリケーションノートを見てみると、今は0.0672と書かれている。

データシート:https://www.vishay.com/docs/84286/veml7700.pdf
アプリケーションノート:https://www.vishay.com/docs/84323/designingveml7700.pdf

これは、VEML7700 の仕様が変わったのではなくて、最新の統計データ等から計算すると上記を係数とするのが正しいとなったために更新されたのだとか。

とにかく、係数については販売サイトの記載をうのみにするとダメね。特に安価製品は。
ちゃんとデータシートなりアプリケーションノートなりを見るべきでしょう。

…まぁ、今回の自分の用途ではそれほど大きな誤差にはなってないし、カジュアルに照度を測りたいという用途ならどちらの表を元にして計算してもさほど影響ないんじゃないかな。

けど、正確であるに越したことはないので、やっぱり最新の情報を確認する癖は大事。

あとはスクリプトをどんどん変えていって、ファイルに書き込むなりデータベースに書き込むなり、書き込んだデータを別スクリプトから読み込んでOLEDに表示してみたり、ある閾値を下回ったらOLED消してみたり、逆に超えたら室内照明消してみたり、好きに使っちゃえばいいさ!

センサーの設定の変更&チェック

用途に依るんだろうけど、基本的にはデフォルト設定のままでも使えるんじゃないかな。
だから、設定変更することも、そのチェックをすることも、それほど多くはないのかも…(いや、そんなことはないw)

そして、こっからはちゃんとデータシートを読みながらの確認。

明るすぎない/暗すぎない、そんな環境であれば、上記のコードで設定されるデフォルト値で基本的には問題ない。
けど、環境によってはセンサーの感度設定(ゲイン)や測定時間(積分時間)設定を変えてあげた方がまともになる。

そんな設定変更について、どこをどう弄ればいいのかはデータシート見ないとわからない。

https://www.vishay.com/docs/84286/veml7700.pdf

変更したいパラメータの確認

取り急ぎ、設定項目で変えたいところは以下2か所。
よく、「ゲイン」と「測定時間(積分時間)」と言ったように表現される項目。
「ALS_SM:Sensitivity mode setting」 と 「ALS_IT:ALS integration time setting」がそれに当たる。

例えば、暗い場所での精度を上げたければ、ALS_SM を 01 にして感度を上げ、ALS_ITを 0010 にして積分時間を長くする。
他の項目はとりあえず全て 0 で良いことから、この設定を表記すると 0b0000100010000000 となる。

…わかりずれぇ…w

なんで2進数で書くのよ…

16ビット幅を持ってて、どのビットに0/1を入れればどの設定が有効になるか、をパッと見でわかるようにすると2進数で書いた方が、割とすんなりわかる。

これをじゃぁ、「16進数で表記」なんてことをすると…

0x0880

となるわけだけど、
こうか書かれても、どの設定がどうなってるのかなんてパッと見ではわからない。

逆にデータシートを見ながら「何番目のビットを1にして、この部分も1にして…」って考えるとおのずと2進数になるけど、それがいきなり16進数で思い浮かべられる人は少ない。(けど、実際にはそれができる人が存在するから怖いw)

ALS_SM を 1/4 にして、ALS_IT を 25ms にしようとしたら?やっぱり2進数で書いてから16進数にするとよいよね。

0b0001100011000000 => 0x18c0

で、これをパラメータとして書き込んであげれば良い。

パラメータ(設定)変更の仕方…の前にMSBとかLSBって何よ…

まず、MSB とか LSB とかいう表現がこうした設定変更(レジスタ変更)では必ず出てくる。
上位ビットとか下位ビットとか呼ばれるもの。

今回のデバイスは設定値をレジスタというところに持っていて、その幅が16ビットになっている。
0/1で表現する箱が16個並んでるイメージかな。

んで、左8ビットを上位ビット=MSB、右8ビットを下位ビット=LSB、として分けて考える。

なんでこんな風になってるかってのはまぁいろいろ歴史があるんだろうけど、とりあえず、分けて考えるのが基本。
ただ、ややこしいのは別にくっつけちゃっても大丈夫な時が多いことかw

とはいえ、MSB、LSB を意識できるだけでいろんなサイトまわっているときに混乱しなくて済む。
次の変更の仕方のところで触れるけど、分けて書き込むことも、くっつけて書き込むこともできるから、それがどっちなのかがわかるようになっていると楽。
(慣れるまでは混乱しまくるけど…w)

ということで、こっからは MSB と LSB とに分けて考えていかなければならない。

パラメータ(設定)変更の仕方

i2csetを使うと以下のように書ける。
ややこしいのは2つの書き込みパターンがあることで、末尾に付与するオプションとして「i」とするか「w」とするかで変わる。

$ sudo i2cset -y 0 0x10 0x00 0xc0 0x18 i
$ sudo i2cset -y 0 0x10 0x00 0x18c0 w

「i」だと I2C のブロックデータとして送ることになる。
下位ビットの LSB を先に送って、次に上位ビットの MSB を送る、という送り順にパラメータを書かなければならないので、0xC0 が先に来て 0x18 が後に書かれる。

対して「w」だとワードデータとして送ることになる。(対応しているかどうかはデバイスに依るので要確認)
なので、0x18c0と、MSB と LSB をくっつけて送ることができる。

python でも 同じような書き方が存在するね。

bus.write_i2c_block_data(ADDR, ALS_CONF, [ 0xc0, 0x18 ])
bus.write_word_data(ADDR, ALS_CONF, 0x18c0)

どっちを実行しても結果は同じになる。
正常に設定がデバイスに書き込める。

MSB と LSB の関係、ほんとややこしい…
(リトルエンディアンとかビッグエンディアンとか、そう呼ばれるちょっとややこしい優先順位的な奴)

けど、これで設定値の書き込み方法はOK

データシートを見てどこのビットを1にして、どこを0のままとするかを考えて、出来上がった2進数を16進数に変え、i2cset コマンドか Python でデバイスに書き込む。

という流れ。

本当に設定出来た!? 確認方法

python スクリプトの中で設定値を確認することはほとんどないと思う。
スクリプトの中で「この値にせよ」と自分で決めて、デバイスに書き込んでいるのだから。

それよりも、コマンドで「あれ?今の設定どうなってる?」とたまに確認したくなる時はある。

そんな時は i2cget コマンドを使う。

$ sudo i2cget -y 0 0x10 0x00 w
0x18c0

…で、この時、16進数見やすい?
俺は見にくいと思う。
どのビットが立っているのかぱっと見でわからないから、データシートと見比べてもどの設定がどうなってるのかわからない。

けど、i2cget には「2進数で表示する」という機能はない。

ないならコマンドで何とかしちゃおうよ。

$ echo "obase=2; $(printf "%d" $(sudo i2cget -y 0 0x10 0x00 w))" | bc | xargs printf "%016d\n" | sed 's/.\{8\}/& /'
00001000 11000000

いや、思ったより長くなった。

長くはなったけど、ここに書いとけばいずれ必要になったときにコピペで使えるから…w

やってることは、
  「i2cget で16進数値を取得」→
  「それを obase=2; とともに文字列として bc に渡して2進数にする」→
  「xargs で左0埋め」→
  「sed で8文字ごとにスペースで区切る」

うん、これならデータシートと見比べやすいね。

今回は変更したくなる値がまだ少ないかったからいいけど、デバイスによってはレジスタがいくつもあって、それぞれが16ビット幅で、ってのもある。
そんな時に16→2進数変換のような単純計算に脳みそのリソースは使いたくないからね。
こうしておくと、あとあと便利。

稼働状況

さかさまなのはご愛敬(裏面の配線の都合)

この記事にコメントしてみる