ローリングコンバットピッチなう!

AIとか仮想化とかペーパークラフトとか

気圧センサーモジュールLPS25Hとキャラクター液晶モジュールACM1602NI(HD44780互換)をraspberry pi+python3で制御する

概要

Raspberry Pi 2 MODEL B+に気圧センサーLPS25Hとキャラクター液晶モジュールACM1602NIを接続し、気圧と温度(LPS25Hに温度センサーが内蔵されている)を読み出して液晶に表示します。
どちらもI2C接続のデバイスなので、python3+smbusモジュールを使って制御します。

出来上がりのイメージは以下の様な感じです。

f:id:RC30-popo:20200807174944j:plain
LPS25H+ACM1602NI

昔買ったラズパイマガジン2015年秋号を参考にブレッドボード上に2つのHWモジュールを搭載しました。
制御用のpythonプログラムも日経BPのホームページからDL出来るのですが、python2向けなのと、ライセンス条件が明示されておらず自作プログラムに組み込んだ形で公開できないのでpython3で制御用のクラスライブラリを書き直しました。(やっている事は同じなのですが、なんだかんだビミョーにコードは異なります)

ACM1602NIの制御

ラズパイマガジンのサンプルコードと購入したモジュールに付属のデータシート(秋月電子のホームページからDL出来ます)を参照して制御方法の解析していたのですが、データシートの情報量不足でpython3のREPLモード使って手動でコマンド送って内容を読み解きました。あとACM1602NIはHD44780互換で、下記のサイトが参考になります。

219.117.208.26

I2Cデバイスアドレス

ACM1602NIのI2Cデバイスアドレスは0x50固定。

コマンドとデータ

ACM1602NIへの書き込みはスレーブアドレス(0x50),コントロールバイト,コマンドorデータ(1バイト)の順にデータを書き込みます。
コントロールバイトが0x00なら後続1バイトがコマンド、コントロールバイトが0x80なら後続1バイトが表示文字の文字コードを示すデータとして扱われます。

初期化

ACM1602NIにはいくつか制御コマンドがあり、それを組み合わせて初期化します。

  1. Function Set

    ACM1602NIを8bitモード、2行、5x8dotに設定します。(基本的にこれしか出来ない)

    b7 b6 b5 b4 b3 b2 b1 b0
    0 0 0 0 1 DL N F - -

    DL=1: 2行
    F=1:5x8dot

  2. Entry Mode Set

    文字データを書き込んだ際のカーソル移動と表示シフトモードを設定します。
    カーソル移動はデータを書き込んだ際に自動的に次の桁に書き込み先が移動しますが、カーソルの移動方向を左(デクリメント),右(インクリメント)から選択可能です。
    通常はインクリメントを設定しておきます。
    表示シフトは、これを有効にしておくと、カーソルがLCDの右端に来た際に更にそのまま文字を書き込むと表示全体をシフトして最新の書き込み文字が表示される様にLCD側で表示を自動スクロールしてくれます。

    b7 b6 b5 b4 b3 b2 b1 b0
    0 0 0 0 0 1 I/D S

    I/D=1: インクリメント
    S=0: 自動シフトしない

  3. Display ON/OFF Control

    ディスプレイのON・OFFとカーソル表示・非表示及びカーソルブリンクのON/OFFを設定します。
    通常はディスプレイはON,カーソル及びカーソルブリンクは使いたい表示方法に合わせて選択します。

    b7 b6 b5 b4 b3 b2 b1 b0
    0 0 0 0 1 D C B

    D=1: ディスプレイON
    C=0: カーソルOFF
    B=0: カーソルブリンクOFF

    なお、D=0に設定すると表示は消えます。ただしバックライトはACM1602NI上では単純にバックライト制御ピンから抵抗を介して白色LEDに接続されているだけなので、常時点灯になります。バックライトを制御したい場合は外部回路でバックライト電源のスイッチを行う必要があります。(もしくはバックライト制御ピンに外部から可変抵抗繋げばアナログ的にバックライト輝度調整ができます)

  4. Clear Display

    画面上の文字を消してブランク表示にします。(ディスプレイ自体の消灯ではありません)

    0x01をコマンドとして書き込みます。

  5. Return Home

    カーソル位置を1行目の左端に設定します。

    0x02をコマンドとして書き込みます。


文字の書き込み

コントロールバイトを0x80にしてデータを1バイト書き込むと、カーソル位置に書き込んだデータに応じた文字が表示されます。
カーソルは初期化時に設定したモードに応じて、自動的に右(インクリメント)もしくは左(デクリメント)に移動します。
ただし自分が試した設定の範囲では、カーソルが例えば1行目の右端まで移動しても、2行目の左端に自動的に移動はしませんでした。行をまたがって文字列を書き込む場合、現在の表示位置(行と桁)をソフトで制御して、カーソルが右端に移動したら、カーソル位置を次の行の先頭に移動させる制御が必要です。

カーソル位置設定

コントロールバイトを0x00にして下記のデータフォーマットで文字の表示RAM(DDRAM)のアドレスを書き込むとその位置にカーソルが移動します。

b7 b6 b5 b4 b3 b2 b1 b0
1 AC6 AC5 AC4 AC3 AC2 AC1 AC0

AC6〜AC0: DDRAMアドレスのbit6〜bit0

DDRAMアドレスは
1行目: 0x00〜0x0F
2行目: 0x40〜0x4F
で、例えば2行目の先頭にカーソルを移動させるにはコントロールバイト0x00に続いてデータ(0x80 + 0x40)(すなわち0xC0)を書き込みます。

LPS25Hの制御


ポイントは気圧の分解能は1/4096hPA,温度の分解能は1/480℃で、42.5℃オフセットという点です。

なお秋月電子では2020/8現在、LPS25Hの後継品とLPS25HBのモジュールを販売しています。
秋月電子のホームページに貼ってあるLPS25HBのデータシートでは温度を読み取るための式(42.5℃オフセットの話)が書かれていません。
http://akizukidenshi.com/download/ds/st/lps25hb.pdf


こちらのLPS25Hのデータシートにはきっちり書いてあります。
http://www.ne.jp/asahi/o-family/extdisk/LPS331AP/LPS25H_DJP.pdf

LPS25HBの仕様が変わったのか、単にデータシートに載っていないだけか不明です。
LPS25Hの仕様に合わせて説明します。

LPS25HのI2Cアドレス

0x5Cまたは0x5Dを使います。アドレスの選択はLPS25HのSDOピンをGNDに接続した場合0x5C,VDDに接続した場合は0x5Dになります。
(HWの結線で決まります)

LPS25Hのレジスタ

詳細はデータシートを参照願います。
制御に必要な代表的なレジスタのみ説明します。

レジスタアドレス レジスタ TYPE 説明
0x0F WHO_AM_I R 0xBD固定 LPS25Hを識別するためのレジスタ。正常なら常に0xBDが返ります。
0x20 CTRL_REG1 R/W default 0x00 Power Downの解除とデータ読み出し間隔の設定に使います。
0x28 PRESS_OUT_XL R 気圧読み出し値の最下位8bit(bit 7-0)
0x29 PRESS_OUT_L R 気圧読み出し値の中位8bit(bit 15-8)
0x2A PRESS_OUT_H R 気圧値読み出し値の最上位8bit(bit 23-16)
0x2B TEMP_OUT_L R 気温読み出し値の下位8bit(bit 7-0)
0x2C TEMP_OUT_H R 気温読み出し値の上位8bit(bit 15-8)

初期設定

CTRL_REG1の最上位bit(bit 7)に1を書き込むとPower Downが解除され、気圧と気温の測定が開始されます。
またCTRL_REG1のbit 6-4の3bitは測定間隔(サンプリング間隔)の指定になっています。000b〜100bまでの5種類のパターンが指定可能で、それぞれone shot,1HZ,7HZ,12.5HZ,25HZです。
下記のgithub公開しているコードでは1HZで測定しています。

気圧読み出し

PRESS_OUT_H,PRESS_OUT_L,PRESS_OUT_XLを連結(PRESS_OUT_Hが最上位)した24bitが気圧測定値で、これは符号無し整数です。
ただし、1/4096hPaの分解能のため、読み出し値を4096で割った値がhPa(ヘクトパスカル)の値になります。

自作ライブラリ内ではこんな感じで実装しています。

    def get_pressure(self):
        # レジスタ 28h,29h,2Ahを読みだす
        press_out_h = self.i2c.read_byte_data(self.lps25h_addr,0x2a)
        press_out_l = self.i2c.read_byte_data(self.lps25h_addr,0x29)
        press_out_xl = self.i2c.read_byte_data(self.lps25h_addr,0x28)
        press_out = (press_out_h << 16) + (press_out_l << 8) + press_out_xl
        press_ret = press_out / 4096
        return press_ret

気温読み出し

TEMP_OUT_HとTEMP_OUT_Lを連結した16bitが気温測定値です。これは気圧と異なり、符号付き整数となっています。
すなわち0x0000〜0x7FFFまでは正、0xFFFF〜0x8000は負の値です。(0xFFFFが-1,0xFFFEが-2...)
かつ、分解能は1/480℃で、48._5℃にオフセット(=48.5℃時の読み出し値が±0)です。

TEMP_OUT_HとTEMP_OUTLを連結した16bit符号付き整数をTEMP_OUTとすると、摂氏での気温は
T(°C) = 42.5 + (TEMP_OUT / 480)
で計算されます。

ソースコードにするとこんな感じです。

    def get_temp(self):
        temp_out_h = self.i2c.read_byte_data(self.lps25h_addr, 0x2c )
        temp_out_l = self.i2c.read_byte_data(self.lps25h_addr, 0x2b )
        temp_out = (temp_out_h << 8) + temp_out_l
        if temp_out > 0x7fff:
            temp_out = temp_out - 0x10000
        temp_ret = 42.5 + (temp_out / 480)
        return temp_ret

ソースコード

githubで公開中です。
github.com