micro:bitで温度を液晶表示(16x2LCD)させる

#### マイクロビットでのｉ２ｃの取り扱いが理解できてきました。

燻製などの調理の際に肉の温度や周辺温度を食の安全と品質向上のため調べたいことがある。肉の調理条件は63℃30分などの例がある。

プログラム作成に当たり、アナログ入力の使い方、i2c出力の扱い方がポイントになる。

利用機器
 - micro:bit ver1.5
 - マイクロビット用ブレイクアウトボード（秋月で500円くらい　https://akizukidenshi.com/catalog/g/gK-13090/ ）
 - アナログ出力温度計 MCP9700 (秋月で50円くらい　https://akizukidenshi.com/catalog/g/gI-14300/ ）
 - LCDモニタ　GROVE - 16 x 2 LCD（スイッチサイエンスで1000円くらい 色違いでもOK　https://www.switch-science.com/catalog/5342/ ）
 
## なぜmicrobitを選んだか
今回利用したい温度計はアナログ出力。分解能は高い方がいい。電源電圧2.3-5.5V。0℃で500mV。温度が1度上昇するごとに10mV出力電圧が上昇する。
マイクロビットなら３．３Vを1024分割
Arduino uno なら５Vを1024分割
Sony Spresenseも５Vを1024分割(spresense SDKを使いこなせれば16bit)
ラズパイはアナログ入力無し
以上より、マイクロビットを選択した。


202208追記
[SpresenseとArduinoIDEでも簡単に１６ビット拡張が使えるようでした。](#spresenseとarduinoideで16bitデータを受信する拡張)
## 温度センサ
今回はアナログ出力 ３本線。（熱電対なら２本線で良いが、熱電対用のアンプなど必要。サーミスタはケーブルが長くなるとケーブル抵抗を考慮する必要がある）
mcp9700は３端子。VDDはマイクロビットの３V(3.3V)端子。GNDはGND、Voutはマイクロビットの０，１，２のいずれかにつなぐ。電気的特性は500mVで０℃、１℃上昇するごとに10mV増加する。
https://akizukidenshi.com/download/ds/microchip/mcp9700a.pdf
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1520250/f0979017-6b3b-a1a1-ed5a-ab32764c964a.png)
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1520250/71d7bf11-d899-5f78-0804-7918fc9032c4.png)



## LCDモニタ
スイッチサイエンスのリンクを辿っていくとデータシートがある。製品名がJDH-1804、使われているチップがAIP31068Lらしい。
https://github.com/SeeedDocument/Grove-16x2_LCD_Series/raw/master/res/JDH_1804_Datasheet.pdf
i2cアドレスは0x3E = 62 サイトに載ってたはずだが確認したら見当たらない。接続してi2cのスキャナーで探すことが出来る。
https://www.sato-susumu.com/entry/i2c-address

16x2キャラクタディスプレイなんて、どれも同じと思っていたら違っていました。
キャラクタディスプレイにつながるパラレルの信号線をi2cに変換してくれるICが有るのだけれど、PCF8574AとかPCF8574がメジャーらしい。このため、マイクロビットの拡張機能からLCDで検索して取り込んでも使えない。改造等が必要。以下その例。カスタムブロックと言うらしい。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1520250/42a7da24-406f-57e5-6b81-ba9c55b78823.png)

### 次のサイトのコードを使ったらうまくいきました。
秋月製AQM1602 LCDモジュールとのことです。i2cアドレスが0x3Eで同じだったので試しました。 
https://jhalfmoon.com/dbc/2021/07/20/%E3%83%96%E3%83%AD%E3%83%83%E3%82%AF%E3%82%92%E7%A9%8D%E3%81%BF%E3%81%AA%E3%81%8C%E3%82%8928-bbc-microbit%E3%80%81%E5%A4%96%E4%BB%98%E3%81%91lcd%E3%82%92i2c%E6%8E%A5%E7%B6%9A/

液晶の2行目に表示する方法が分からなかったので、調べた。
秋月のサイトの簡易説明書https://akizukidenshi.com/download/ds/xiamen/AQM1602_rev2.pdf から
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1520250/1dbb63a2-d24b-d0d5-cdc0-a9fab6ab3eca.png)
どうやら、書き込み開始アドレスの指定が必要らしい。どうやって指定するのか？
１行目の先頭が１６進で００ということは、２進で００００００００、
２行目の先頭が１６進で４０ということは２進で０１００００００
１６進変換ツール(https://hogehoge.tk/tool/number.html )

本家のデータシート（https://github.com/SeeedDocument/Grove-16x2_LCD_Series/raw/master/res/JDH_1804_Datasheet.pdf ）からDDRAM AD SETという命令コマンドを発見。DB7からDB0まで8ビット16進2桁。先頭(DB7)ビットが１（１？？？？？？？）なので、先の先頭のアドレス指定から後半7ビットを合成すると
１行目の先頭の命令コマンドは、２進で１０００００００（10進で128)
２行目の先頭の命令コマンドは、２進で１１００００００（10進で192)
結局writeCommand(192)とすることで、2行目の書き込みをすることが出来ました。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1520250/1e966fad-57a0-324b-61b7-6ec808c9e185.png)

### 初期化コマンド
上から順に
Function setは１０１０００＝４０（１０進）　Xの部分は機能ナシなので０を挿入
Display setは１１００＝１２
Display clearは１＝１
Entry mode setは上に図を見るとinput setの一部誤植かと思うが、ナシで動作したので無視した。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1520250/ac26b9ba-84e2-c58f-3e03-5d6faa9f5706.png)


## 最終的なコード

以上の知見を元に、３チャンネルのアナログ温度計をログしてLCDモニタに表示するプログラムにしました。スリープを１秒入れる関係で１分あたり４５回程度更新されます。食肉加熱の目安になるように、６３度に達してからの累計時間も表示するようにしました。温度表示を小数１位にするために、１０倍して四捨五入してから０．１倍にしています（もっとスマートな記述はないでしょうか）
シリアル出力もしているので、一般的なシリアルモニタで監視できます。ArduinoIDEのシリアルプロッタを使うと簡易的ながらグラフっぽくなります。
参考にしたinitAQM1602用のコードも残っていますが、悪さはしていないようです。液晶のコントラストコマンドなんかは使えなさそうです。（元コマンドで56→57→20→115→86→108→56→1→12だったのを後日40→12→1と修正しました）
i2cではUInt16BEなどいろいろな記述法があります。もうすこし勉強する必要がありそうです。
とりあえず、マイクロビットの編集画面でJavaScriptを選んで書きコードを貼り付けると動かせます。

```mcp9700_x3_lcd16x2.js
function initAQM1602 () {
    basic.pause(100)
    writeCommand(40)
    writeCommand(12)
    writeCommand(1)
}
// 62=0x3E
// 
// 16384=0x4000
function writeData (dat: string) {
    pins.i2cWriteNumber(
    62,
    16384 + dat.charCodeAt(0),
    NumberFormat.UInt16BE,
    false
    )
    basic.pause(1)
}
function writeCommand (command: number) {
    pins.i2cWriteNumber(
    62,
    0 + command,
    NumberFormat.UInt16BE,
    false
    )
    basic.pause(20)
}
let msg2 = ""
let msg = ""
let P2電圧値 = 0
let P1電圧値 = 0
let P0電圧値 = 0
let P2入力値 = 0
let P1入力値 = 0
let P0入力値 = 0
let P2温度 = 0
let P1温度 = 0
let P0温度 = 0
let P1温度累計 = 0
let P0温度累計 = 0
initAQM1602()
let 電源電圧 = 3.3
serial.redirectToUSB()
basic.forever(function () {
    P0入力値 = pins.analogReadPin(AnalogPin.P0)
    P1入力値 = pins.analogReadPin(AnalogPin.P1)
    P2入力値 = pins.analogReadPin(AnalogPin.P2)
    P0電圧値 = P0入力値 * (電源電圧 / 1023)
    P1電圧値 = P1入力値 * (電源電圧 / 1023)
    P2電圧値 = P2入力値 * (電源電圧 / 1023)
    P0温度 = (P0電圧値 - 0.5) / 0.01
    P1温度 = (P1電圧値 - 0.5) / 0.01
    P2温度 = (P2電圧値 - 0.5) / 0.01
    // 中心温度６３℃３０分が目安
    if (P0温度 >= 63) {
        P0温度累計 += 1
    }
    if (P1温度 >= 63) {
        P1温度累計 += 1
    }
    //serial.writeValue("temp0", Math.round(P0温度 * 10) * 0.1)
    //serial.writeValue("temp1", Math.round(P1温度 * 10) * 0.1)
    //serial.writeValue("temp2", Math.round(P2温度 * 10) * 0.1)
    serial.writeLine("temp0:" + convertToText(Math.round(P0温度 * 10) * 0.1) + ",temp1:" + convertToText(Math.round(P1温度 * 10) * 0.1) + ",temp1:" + convertToText(Math.round(P2温度 * 10) * 0.1))

    msg = "" + (Math.round(P0温度 * 10) * 0.1).toString() + " " + (Math.round(P1温度 * 10) * 0.1).toString() + " " + (Math.round(P2温度 * 10) * 0.1).toString()
    // 45ループぐらいで1分
    msg2 = ">63C " + P0温度累計.toString() + " " + (Math.round(P0温度累計 / 45 * 10) * 0.1).toString() + " " + (Math.round(P1温度累計 / 45 * 10) * 0.1).toString()
    led.toggle(0, 0)
    writeCommand(1)
    writeCommand(0)
    writeCommand(128)
    for (let index = 0; index <= msg.length; index++) {
        writeData(msg.charAt(index))
    }
    writeCommand(192)
    for (let index2 = 0; index2 <= msg2.length; index2++) {
        writeData(msg2.charAt(index2))
    }
    basic.pause(1000)
})

```

ブロックでも表示できます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1520250/f7bfa904-206f-3bc3-bde5-ed645450ab85.png)

## シリアル通信でPCから値の確認もできる（おまけ）
serial.writeValue("temp0", P0温度)などと記載すると、PCのシリアルモニタで値を確認できます。115200bpsに設定して適切なcomポートを選びましょう。テラタームもいいですがArduinoIDEのツールに入っているシリアルモニタは手軽です。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1520250/b8df7924-34ae-a305-0c4f-180891166c9b.png)
入力された数値をグラフ化するシリアルプロッタもあります。(3チャンネルの数字が同じ直線で結ばれてしまっていますが、何とか見えます）
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1520250/b8617319-9fdc-402b-4914-27b8ef759a82.png)
https://nn-hokuson.hatenablog.com/entry/2021/10/04/145415 を参考にシリアル出力を１行で３つの数値が出るように修正しました。すっきりと表示できています。
以下のようなデータ形式で、シリアル出力しています。
temp0:23.5,temp1:24.2,temp1:24.2                              
temp0:23.5,temp1:24.2,temp1:24.2
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1520250/98d61e3c-17fb-1cb8-398f-b95cafae95d1.png)

## 考察：RSって何？なぜ0x4000を足しているの？
秋月のデータシートを見てみる。データのビットが並んでいて、左から順に送信する。
先ず、相手先のi2cアドレスと書込モード１か読み取りモード０かを記した信号を送る。あどれすは、７ビット０１１１１１０＝0x3Eだが読み取りモードの意味の０が右側に来るので、それまで含めると０１１１１１００＝0x7cとのこと。マイクロビットでは0x3E=62。
送信後、液晶側から「聞いてるよ！」の意味のAcknowlegementが返される。そして、８ビット２連で構成されるデータを送っていく。
先頭のCoはまだまだ続くよ、の意味。今回のプログラムでは１文字１文字別々に送っているので、C0=0。それに続く、RSは液晶のセッティングのデータか、液晶の表示のデータを送っているのかを示すビット：RS=0なら液晶コマンド、RS=1は液晶に表示させる文字を送ってる意味。
RS=1は１６ビットの２番目の位置にあるので、0x4000=0100 0000 0000 0000と８ビットで構成される文字コードを足してやると文字を表示する信号になる。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1520250/fa00f5bf-c929-ddf1-aac3-a0668f39003f.png)

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1520250/84dde65a-6994-3864-004d-2d3db5a9e6da.png)


## 考察：UInt16BEとかビックエンディアンってなに？
https://makecode.microbit.org/types/buffer/number-format

マイクロビットのサイトを見てみると、
UInt16BE: two bytes, unsigned, big endian　と記載されている。
２バイトなので、８ビットが２こ（１６ビット）、unsignedなので、正の整数、big endian ビックエンディアンとある。

（英語でつらいが、自動翻訳するともっと分からないので頑張って読む。）

１０進の2552を１６ビットの２進で表すと、00001001 11111000
このとき、
Big end: 00001001
Little end: 11111000
という。

２バイトのデータの左側（ビックエンド）と右側（リトルエンド）のどちらを先にメモリに格納するかがエンディアン順序である。左側（ビックエンド）を先にメモリーに格納するタイプがビックエンディアン（ナンバー）である。
１バイトの時は右も左もないので、エンディアン順序は無いが、互換性のために変数形はあるとのこと。
（１バイト中の８ビットの右から左からといったものではない。）

i2cのコマンド送信が、コントロールバイト→データバイトだからビックエンディアンを用いていたが、逆だったらリトルエンディアンを使うって事ね！

## Javascriptのi2c Write Number命令
https://makecode.microbit.org/reference/pins/i2c-write-number
function pins.i2cWriteNumber(address: number, value: number, format: NumberFormat, repeated: boolean): void;
となっている。アドレスは７ビットアドレスを使う（送信時は一時的に書込の０を追加して８ビットになる）
アドレスが８ビットの場合は数字を半分にする（右端のビットを落とす）
書き込む数値は変数型UInt16BEなどを指定。



## 文字コード
液晶モニタの説明書に文字コード表があります。
これは、機種固有ではなく一般的な文字コードなので、文字からキャラクターコードを取得する一般的なコマンドで得ることができます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1520250/61e9828c-4052-4477-bdd9-c2dbc6ca373a.png)

## micro:bitのシリアル通信のおまけ
マイクロビットでWindowsPCとUSB通信を行う。
PC側で受け取ったデータを、pythonなどで処理判定。
### micro:bitのサンプルプログラム
とりあえず、これだけでデータが送れる。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1520250/27dfa925-33e4-b89e-9779-601324e422d9.png)

### 受け取り側の設定
テラタームを以下の設定で受信可能。
　　　![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1520250/5963fc72-534b-fd92-55eb-f31154301517.png)


## spresenseとArduinoIDEで16bitデータを受信する拡張

ソニー公式のanalogRead()関数とanalogReadMap() [独自拡張]を読むと以下の記述がある。
「analogReadMap() 関数の min と max に同じ値を設定した場合、analogRead() 関数は map() による変換を行わずに、ADC の生値を返します。」


https://developer.sony.com/develop/spresense/docs/arduino_developer_guide_ja.html#_analogread

これに従い、setupでanalogReadMap(pin,0,0);を実行したところ１６ビットのsigned shortの値が得られるようだ。
値をグラフで確認したところ、飛び飛びの値になっていないので細かい値まで得られそうだ。
ちなみに、アナログピンはA0-A5まであるが、A4,A5が高速サンプリング対応らしい。（分解能は変わらない）

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1520250/c0c69bc0-a1d5-68a4-6ba0-bba02e6070af.png)