Raspberry Pi 3B でI2CなLCDを使う

RaspberryPi

前提

LCDモジュール:下記をAmazonで購入。
使用言語:C++ (ラズパイ的情報量としてはPythonを使った方が楽)

I2C 20×4 LCD モジュール

準備

ラズパイの電源を入れる前に、ピンアサインを確認しつつLCDを接続しておく。

ラズパイ起動後はお約束のアップデートと必要なツールのインストール。

$ sudo apt-get update
$ sudo apt-get upgrade
$ sudo apt install i2c-tools

i2c-tools がインストールできたら、とりあえず現状確認。

$ i2cdetect -l
i2c-1   i2c             bcm2835 (i2c@7e804000)                  I2C adapter

I2Cバスは、Raspberry Pi 3 は上記の通り1つだけど、Pi4 とかだと最大4つも追加できるのだとか。
ただ、ソフトウェアI2Cなので、GPIOを使用し、それはCPUクロックに依存するので厳密なSCLクロックが必要なデバイスはコケるとか。
とはいえ、i2c-0は拡張ボードのHATが使用することが多いようなので、実質3つ?
まぁ、今回はRPi3なので余談ですが。

次に、予めI2C-LCDを繋げていたのでその接続状況も確認する。

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

アドレス 0x27に接続されていることが確認出来、LCDの仕様書もそうなっているのでOK。

仕様書:https://wiki.52pi.com/index.php?title=Z-0235

・・・2022年8月に購入して、古いバージョンが来たのはチトがっかりだけど、まぁ、その違いなんて顕著にわかるようなものでもないのでスルーしよう。

プログラムる

え~っと・・・普通、Pythonで書くよね。RaspberryPiなら。
けど、ここではC++で行きます。
C#いじってて、C++いじったことなくて、勉強のためにと思いまして。
(Pythonも触れたことないので迷ったけど・・・)

以下、参考になったサイト

工作と小物のがらくた部屋
http://junkroom2cyberrobotics.blogspot.com/2012/08/raspberry-pi-i2c-2-i2c-lcd.html

OSOYOO
https://osoyoo.com/2016/06/01/drive-i2c-lcd-screen-with-raspberry-pi/

OSOYOOの下記Python用コードは本当に助かった。これがなかったら作れてなかったってほどに。
http://osoyoo.com/driver/i2clcdb.py

色々あって、そして色々したくて、LCDはクラスにしちゃってる。
なので、こんな感じで呼び出して色々すればいいんじゃないかな。

// main.cpp

#include <stdio.h>
#include <stdlib.h>
#include <time.h>
#include <unistd.h>
#include <linux/i2c-dev.h>
#include <fcntl.h>
#include <sys/ioctl.h>


#include "lcd.cpp"";

int main(int argc, char** argv) {
	LCD lcd;
	if (lcd.Init()) {
		lcd.DisplayInfo();
	}

}
// lcd.cpp

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#include <unistd.h>

#include <linux/i2c-dev.h>
#include <fcntl.h>
#include <sys/ioctl.h>


class LCD {

private:
    const int LCD_ADDRESS = 0x27;
    const int LCD_RS_CMD = 0;
    const int LCD_RS_DATA = 1;
    const int LCD_ENABLE = 0x04;
    const int LCD_LINE_1 = 0x80;
    const int LCD_LINE_2 = 0xC0;
    const int LCD_LINE_3 = 0x94;
    const int LCD_LINE_4 = 0xD4;
    int LCD_BACKLIGHT = 0x08; // 0x08 = 0n / 0x00 = off

public:
    int lcd;                            // ファイルディスクリプタ。
    char* i2cFileName = "/dev/i2c-1";   // I2Cドライバファイル名。
    int lcdAddress = LCD_ADDRESS;       // I2C LCD のアドレス。


    // LCDの初期化
    bool Init() {
        // I2Cポートをオープン
        if ((lcd = open(i2cFileName, O_RDWR)) < 0) {
            return false;
        }

        // LCDデバイスのアドレスを設定
        if (ioctl(lcd, I2C_SLAVE, lcdAddress) < 0) {
            return false;
        }

        usleep(40 * 1000);

        // LCD初期化
        WriteLCD(LCD_RS_CMD, 0x33);
        WriteLCD(LCD_RS_CMD, 0x32);
        WriteLCD(LCD_RS_CMD, 0x06);
        WriteLCD(LCD_RS_CMD, 0x0c);
        WriteLCD(LCD_RS_CMD, 0x28);
        WriteLCD(LCD_RS_CMD, 0x01);

        // ウェイト
        usleep(300 * 1000);

        return true;
    }

    void WriteLCD(unsigned char rs, unsigned char data) {
        unsigned char buf[2];

        if (rs == LCD_RS_CMD || rs == LCD_RS_DATA) {
            // LCD_RS_CMD ならコマンドモード。LCD_RS_DATA ならデータモード。

            int bits_high = rs | (data & 0xF0) | LCD_BACKLIGHT;
            int bits_low = rs | ((data << 4) & 0xF0) | LCD_BACKLIGHT;

            buf[0] = rs;
            buf[1] = bits_high;

            // High bits
            write(lcd, buf, 1);
            ToggleEnable(bits_high);

            buf[1] = bits_low;

            // Low bits
            write(lcd, buf, 1);
            ToggleEnable(bits_low);
        }
    }

    void ToggleEnable(int data) {
        unsigned char buf[1];

        buf[0] = (data | LCD_ENABLE);
        write(lcd, buf, 1);

        buf[0] = (data & ~LCD_ENABLE);
        write(lcd, buf, 1);
    }

    void Puts(char* msg) {
        int i;
        for (i = 0; i < 20; i++) {
            if (msg[i] == 0x00) {
                break;
            }
            else {
                WriteLCD(LCD_RS_DATA, (unsigned int)msg[i]);
            }
        }
    }

    void BackLightCtrl(bool flag) {
        if (flag) {
            LCD_BACKLIGHT = 0x08;
        }
        else {
            LCD_BACKLIGHT = 0x00;
        }
        WriteLCD(LCD_RS_CMD, 0x00);
    }

    void DisplayInfo() {
        // メッセージ表示。
        WriteLCD(LCD_RS_CMD, LCD_LINE_1);
        Puts("line 1");
        WriteLCD(LCD_RS_CMD, LCD_LINE_2);
        Puts("line 2");
        WriteLCD(LCD_RS_CMD, LCD_LINE_3);
        Puts("line 3");
        WriteLCD(LCD_RS_CMD, LCD_LINE_4);
        Puts("     dualbalance.net");
    }
};

仮組み

ラズパイ起動と同時に下記表示になるようプログラムを書き換えてみた。
使用する場所が自宅だけとは限らない(となる予定)なので、DHCPで割り振られたIPが何か、表示させておくとすぐにSSH出来たりしてよろしいと思い。

蛇足

今はUSB-UART(TTL)なアダプタを挿してるけど、これをRS485-UART(TTL)なアダプタと差し替えて、某産業用機器(?)の制御プロトコルの解析に使いたいんだよね。

とりあえず、受信したコマンドのLCDへの表示は出来ているので、次は送信なんだけど、十字キーとかボタンとか付けたくなってくるなぁ。
んで、表示では受信したコマンドなのか送信したコマンドなのかもわかるようにTXDやらRXDやらを付け足してもいいか。

・・・先はまだまだ長い・・・

それにしても、C++って文字の扱い方が凄く面倒なのね。
charの配列にするのかポインタにするのかとか意識して使わないと簡単なもプログラムできん・・・
慣れないとほんと戸惑うけど・・・慣れたら素晴らしい言語なんだろうか・・・?
(ま、自分の低スキルさを棚に上げて面倒とか言ってる時点でお察しなんですがw)

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