$\huge{元氣ですかーーーーッ!!!}$
$\huge{元氣があればなんでもできる!}$
$\huge{闘魂とは己に打ち克つこと。}$
$\huge{そして闘いを通じて己の魂を磨いていく}$
$\huge{ことだと思います}$
はじめに
この記事は、2023/03/19(日)に行われた「NervesJP #36 Build a Weather Station を読む会 4回目」で行った照度センサーでの明るさ測定をまとめました。
この記事のメインは「照度センサーで明るさを測る」です。
以下、周辺知識をまとめています。
Nerves?
Nervesは、Elixir製のナウでヤングでクールなすごいIoTフレームワークです。
Elixir専用OSと言っても過言ではありません。
伝統的な組み込みエンジニアのあなたにNervesをぜひ使って欲しいです。
Jon Thacker 氏の言葉を引用します。
I’m traditionally an embedded engineer, and I only learned Elixir as part of this project. However, transferring my mental model was so easy that I do believe that we would be capable of training other embedded engineers as well.
- Jon Thacker, Senior VP of Engineering
私は伝統的に組み込みエンジニアであり、このプロジェクトの一環として Elixir を学んだだけです。しかし、自分のメンタル モデルを移植するのはとても簡単だったので、他の組み込みエンジニアも同様にトレーニングできると確信しています。
- Jon Thacker 氏、エンジニアリング担当上級副社長
Elixir?
Elixirは、プログラミング言語です。
パイプ演算子|>でつなげて処理を書くのが気持ちいいプログラミング言語です。
Mix.install [{:req, "~> 0.3.6"}]
"https://qiita.com/api/v2/items?query=tag:Elixir&per_page=100"
|> Req.get!()
|> Map.get(:body)
|> Enum.filter(fn %{"likes_count" => likes_count} -> likes_count >= 1 end)
|> Enum.map(fn %{"title" => title, "user" => %{"id" => id}, "likes_count" => likes_count} -> {title, likes_count, id} end)
上記は、Qiita API v2を利用させていただいて、Elixirに関する記事を取得しています。
最新の100件を取得し、いいねが1以上付いた記事でフィルタリングし、各記事の情報を丸めています。
ここでは、Elixirを未インストールの方のために、Dockerで動かす例を示します。
$ docker run --rm -it hexpm/elixir:1.14.3-erlang-25.2.3-ubuntu-jammy-20221130 iex
IExと呼ばれるREPL(Read-Eval-Print Loop)が立ち上がりますので、上記のプログラムを迷わず実行してみてください。
途中、Shall I install Hex? (if running non-interactively, use "mix local.hex --force") [Yn]
やShall I install rebar3? (if running non-interactively, use "mix local.rebar --force") [Yn]
を訊かれたら、迷わず元氣よくY
を答えてください。
これであなたもアルケミスト(Alchemist)です。
Elixirを日本語訳すると「不老不死の霊薬」です。
プログラミング言語Elixirの使い手は、アルケミスト(Alchemist) ーー 錬金術師と尊称されます。
前置きが長くなりました。
そろそろ本題です。
対象読者
次の方におすすめです。
つまりは全人類です
Build a Weather Station を読む会について
「そろそろ本題です」と言いつつ、「Build a Weather Station を読む会」の説明をします。
書籍があります。
『Build a Weather Station with Elixir and Nerves』という名の書籍です。
この本を読みましょう、ウェザーステーションを作りましょう、値をPhoenixで作ったAPIサーバーに放り込んで、Grafanaを使ってグラフを書いてみましょうという内容です。
Elixir、Nervesを使って話が進行します。
英語の本です。
ご安心ください。
Google翻訳という文明の利器があります。
書籍の通りに進めるのが詰まるところがなくてよいとおもわれますが、日本ではなかなか手に入りにくいデバイスがあります。
ご安心ください。
日本のNerves界のお歴々が、日本で入手しやすいデバイスを選定してくれています。
ただ書籍から外れることをするので、デバイスの制御部分は自分たちで作らないといけません。
ご安心ください。
NervesJPがいっしょに開発を進めてくれます。
いい加減、本題に移ります。
Nervesの始め方
「いい加減、本題に移ります」と言いつつ、前置きの最後です。
Nervesの始め方を紹介しておきます。
ElixirでIoT!?
Nerves超入門 ーー Nervesを使った開発の日常風景(景色)、ElixirでIoTを楽しむ
Elixir、NervesのセットアップからmicroSDカードへのファームウェア焼き込みを解説しています。
もう本当に今度こそ本題に入ります。
照度センサーで明るさを測る
照度センサーでのルクスを測ります。
照度センサーは、GROVE - I2C デジタル光センサを使います。
組み立て
安心してください。組み立ては難しくありません。
はめ込み式ですので、手先が不器用ですからの私でも組み立てられます。
I2Cのところに、GROVE - I2C デジタル光センサを差し込んでいるのがポイントです。
使用デバイスの一覧はコチラです。
データシート
日本語訳をしてくださっている方がいらっしゃいました。ありがとうございます。
伝統的な組み込みエンジニアのあなたはすらすら読めるのだとおもいます。
私は門外漢でさっぱり読めません。こういったドキュメントのことを総じて「データシート」と呼ばれることもあまり馴染みがありません。
@pojiro さんのスペシャルサンクスでなんとかルクスを計算できました。
実装
この記事では、Nervesにsshで入ってIEx上で動かしてみることにします。
まず、バンとプログラムを全掲載します。
Circuits.I2C.detect_devices
{:ok, bus} = Circuits.I2C.open("i2c-1")
{:ok, <<_::4, 3::4>>} = Circuits.I2C.write_read(bus, 0x29, <<0xA0, 0x03>>, 1) # Power on
{:ok, <<partno::4, revno::4>>} = Circuits.I2C.write_read(bus, 0x29, <<0xAA>>, 1) # 部品番号とシリコン改訂番号の読み出し
f = fn ->
{:ok, <<channel0::little-16, channel1::little-16>>} = Circuits.I2C.write_read(bus, 0x29, <<0xAC>>, 4)
# 以下、部品番号が0 or 1のCSパッケージの場合の計算式
channel0 = channel0 * 16
channel1 = channel1 * 16
ratio = channel1 / channel0
cond do
0 < ratio and ratio <= 0.52 -> 0.0315 * channel0 - 0.0593 * channel0 * (ratio ** 1.4)
0.52 < ratio and ratio <= 0.65 -> 0.0229 * channel0 - 0.0291 * channel1
0.65 < ratio and ratio <= 0.80 -> 0.0157 * channel0 - 0.0180 * channel1
0.80 < ratio and ratio <= 1.30 -> 0.00338 * channel0 - 0.00260 * channel1
1.30 < ratio -> 0
true -> 0
end
end
私一人では一生かかってもできなかったかもしれません。正直に言うと、データシートの日本語訳をにらめっこしても電源ONで手が止まりました。
@pojiro さんのスペシャルサンクスのおかげです。
解説
以下、解説します。
Circuits.I2C.detect_devices
iex> Circuits.I2C.detect_devices
Devices on I2C bus "i2c-1":
* 4 (0x4)
* 41 (0x29)
Devices on I2C bus "i2c-20":
Devices on I2C bus "i2c-21":
2 devices detected on 3 I2C buses
この結果から、デバイスのアドレスは、0x29
です。(0x4
はなに?)
Circuits.I2C.open
まずデバイスをオープンします。
{:ok, bus} = Circuits.I2C.open("i2c-1")
電源ON
電源を入れます。
{:ok, <<_::4, 3::4>>} = Circuits.I2C.write_read(bus, 0x29, <<0xA0, 0x03>>, 1) # Power on
2バイトのデータ<<0xA0, 0x03>>
を書き込んで1バイト読み込んでいます。
0xA0
は、「COMMANDレジスター」の節をご参照ください。
- 上位4bit(つまり
A
)は、COMMANDレジスターの選択とSMBのワード書き込み/ワード読み出しプロトコルの選択を意味します。 - 下位4bit(つまり
0
)は、CONTROL レジスター(0h)を指定しています。
0x03
は、CONTROL レジスター(0h)に書き込む値です。
「CONTROL レジスター」の節をご参照ください。
下位2bitが1つまり、0x03を書き込むと電源ONになります。
0x00を書き込むと電源OFFとなり、照度センサーから値は取れなくなります。
左辺の{:ok, <<_::4, 3::4>>}
を説明します。
電源ONすると、下位4bitからは3が返ってくるとのことです。上位4bitも3のような気もしますが、「何らかの情報」とのことなので無視することにします。
Elixirの場合、::bit数
でビット指定で値を取り出すことができます。
詳しくは<<>>をご参照ください。
こちらの記事(「組込みに欠かせない Elixir でのビットの扱い方」、「ビット操作関数 Bitwise を使ってみる」 両方とも@kikuyuta先生の記事)もあわせてお読みください。
部品番号とシリコン改訂番号の読み出し
部品番号とシリコン改訂番号を読み出します。
さきほどと同じような要領で4bitのデータを取り出します。
「IDレジスター (Ah)」の節をご参照ください。
部品番号(partno)は4種類の値があります。
0, 1: CSパッケージ
4, 5: T/FN/CLパッケージ
GROVE - I2C デジタル光センサは、2種類のパッケージがあります。
CSパッケージとT/FN/CLパッケージの2つです。
取得した値をルクスに変換する際の計算式がパッケージによって異なります。
{:ok, <<partno::4, revno::4>>} = Circuits.I2C.write_read(bus, 0x29, <<0xAA>>, 1)
上位4bitに部品番号、下位4bitにシリコン改訂番号が格納されています。
シリコン改訂番号(revno)は特に今回は使いません。
ルクス計算
f = fn ->
{:ok, <<channel0::little-16, channel1::little-16>>} = Circuits.I2C.write_read(bus, 0x29, <<0xAC>>, 4)
# 以下、部品番号が0 or 1のCSパッケージの場合の計算式
channel0 = channel0 * 16
channel1 = channel1 * 16
ratio = channel1 / channel0
cond do
0 < ratio and ratio <= 0.52 -> 0.0315 * channel0 - 0.0593 * channel0 * (ratio ** 1.4)
0.52 < ratio and ratio <= 0.65 -> 0.0229 * channel0 - 0.0291 * channel1
0.65 < ratio and ratio <= 0.80 -> 0.0157 * channel0 - 0.0180 * channel1
0.80 < ratio and ratio <= 1.30 -> 0.00338 * channel0 - 0.00260 * channel1
1.30 < ratio -> 0
true -> 0
end
end
何回も実行できるように無名関数をf
に束縛しています。
実行は、f.()
です。
まずCircuits.I2C.write_read(bus, 0x29, <<0xAC>>, 4)
は、Cレジスターを選択して4バイトのデータを読み出しています。
2バイトずつ意味があり、リトルエンディアンです。channel0
とchannel1
という変数に束縛しています。なぜchannel
なのかはピンときていませんが、データシートにそう書いてあります。
値が取れればあとは計算式に当てはめるだけです。
データシートにルクスの計算式は書いてあります。
ルクスの計算は「Lux(ルクス)の計算」の節をご参照ください。
さきほど触れた通り、CSパッケージとT/FN/CLパッケージとでは計算式が異なります。
私が所持しているものはCSパッケージでした。
ここでのプログラム例は、CSパッケージの計算式を書いています。
16倍しているところは、Cのサンプルに書いてある「もし、利得が16Xでない場合はスケールする。」に対応します。
「TIMING レジスター (1h)」の設定次第です。
今回は設定を変更していないので、初期値の0x02
となり上記の形になります。
疑り深い方は以下のプログラムで値を取得して、「TIMING レジスター (1h)」の節と見比べてみてください。
{:ok, <<0::3, gain::1, manual::1, 0::1, integ::2>>} = Circuits.I2C.write_read(bus, 0x29, <<0xA1>>, 1)
実行
f.()
でルクスを測り放題です。
f.()
238.3050122220337
が返ってきました。夜ではありますが、パソコン作業するのにはちょっと暗いのかも。
このコラムによると、「●勉強・読書:500~1000lx」とのことです。
お手本
@pojiro さんのお手本は以下にあります。
ありがとうございます!!!
さいごに
まとめます。
照度センサーで明るさを測ることを楽しみました。
この記事で言いたいことをもう一度書いておきます。
Nervesは、Elixir製のナウでヤングでクールなすごいIoTフレームワークです。
伝統的な組み込みエンジニアのあなたにぜひNervesを触ってもらいたいです。
ビットの演算が超楽チンです。
闘魂とは、 「己に打ち克つこと。そして闘いを通じて己の魂を磨いていくことである」 との猪木さんの言葉をそのまま胸に刻み込んでいます。
知っているだけで終わらせることなく、実行する、断行する、一歩を踏み出すことを自らの行動で示していきたいとおもいます。
アントニオ猪木さんのメッセージから元氣をもらったものとして、それを次代に語り継ぎ、自分自身が「闘魂」を体現するものでありたいとおもいます。
$\huge{元氣ですかーーーーッ!!!}$
$\huge{元氣があればなんでもできる!}$
$\huge{1、2、3 ぁっダァー!}$