Raspberry PiでLCDディスプレイに文字を表示する:クラスモジュール化

 前回まででLCDに文字を表示する所まで一通り作り終えました:

前回まで作ってきたコードはサンプル的な関数ベースだったので、これをクラスとしてまとめ、モジュール化して再利用できるようにしてみます。それを次の計画で使いたいのです(^-^;

クラス化の指針

複数のLCDを想定

 そもそもですが、クラス化するというのはその裏に「インスタンス(オブジェクト)が複数ある」という意図が含まれています(例外もありますが)。今回の場合、1台のLCDディスプレイを一つのオブジェクトとして捉えます。よってラズパイに複数のLCDが搭載されていてもそれぞれをオブジェクトとして独立に使えるようにクラスを設計しなければなりません。

個々のLCDを規定する物

 個々のLCDに命令を与えるには、そのLCDを識別する何かが必ず必要ですよね。前回までの情報からそれが「I2Cバス番号」と「LCD固有ID」の2つである事が分かります。よってこの2つはメンバ変数として保持する事になります。これらは途中で変更する事はおそらく無いので、コンストラクタで与えて固定してしまいましょう。

文字表示メソッド

 LCD(キャラクタLCD)なので文字を表示するのがメインです。コンソールへの文字表示と同じ感覚と考えるならprintメソッドとかになるでしょうか。必要な物は何でしょう?まず表示行数lineはいりますね。またそのlineでの文字の表示の開始位置(offset)は指定したい所です。ざっくりとした定義はこんな感じでしょうか:

#文字表示
def print( line, offset, str ):

クリア系

 文字を表示する時に既に表示されている文字をクリアしないと部分的に上書きされて変な表示になってしまいます。クリアは「全クリア」「行クリア」くらいあればとりあえず十分かなと思います。

#全クリア
def clearAll():
 
#行クリア
def clearLine( line ):

表示行数指定

 LCDは表示行数を切り替えられます。これは指定出来るようにします:

#表示行数の設定
def setLineNum( num ):

バックライト切り替え

 バックライトはON/OFF出来た方が良いですね:

def setBacklignt( isOn ):

4bitモード限定で

 LCDは4bitモードと8bitモードがありますが、I2Cを通す場合は4bitモードのみなので、今回のクラスでは4bitモード前提とします。

カーソルとブリンカーは見送り

 カーソルとブリンカーは今回は実装を見送ります。これらを入れると設計が複雑になってしまうので。

LCDクラスの実装

 では上の指針に沿ってLCDクラスを実装してみます。まずlcdmng.pyというファイルを新規に追加します。このファイル名lcdmngがモジュール名としてインポートされます。

 全体的なコードは以下の通りです:

# LCDクラス
#  I2Cインターフェイス付属が前提

import smbus2
import time

class LCD:
    __busId = 1      # I2CバスId
    __i2cId = 0      # I2Cバス内のI2C固有ID
    __backlight = 0  # バックライトフラグ
    __bus = None     # smbus2オブジェクト

    # コンストラクタ
    #  busId  : I2CバスID(通常1)
    #  i2cId  : I2Cバス上のI2C機器固有ID(i2cdetectで表示されるID)
    #  lineNum: 表示行数(def = 1)
    def __init__( self, busId, i2cId, lineNum = 2, backlight = True ):
        self.__busId = busId
        self.__i2cId = i2cId
        self.__bus = smbus2.SMBus( busId )

        # 初期化
        self.__sendLCDCommand( 0x33 )
        self.__sendLCDCommand( 0x32 )
        self.setLineNum( lineNum )
        self.setBacklight( backlight, True )
        self.__entryMode()
        self.clearAll()

    # 表示行数を設定
    #  num: 表示行数
    def setLineNum( self, num ):
        interfaceMode = 0          # 常に4bitモード
        lineMode      = 0 if num == 1 else 0b00001000  # 2行モード
        fontMode      = 0          # スモールフォントモード

        functionSetParam = 0b00100000 | interfaceMode | lineMode | fontMode 
        self.__sendLCDCommand( functionSetParam )
        time.sleep( 0.0002 )   # 39us以上

    # 文字表示
    def print( self, line, offset, str ):
        # 表示ラインのベースアドレス
        addrBase = line * 0x40
        addrBase += offset

        # アドレス位置を指定
        #  Set DDRAM Address -> 0b00|1000|0000
        cmd = 0b0010000000 | addrBase
        self.__sendLCDCommand( cmd )

        # 1文字ずつ送信
        for s in str:
            # 文字を送信
            #  Write Data to RAM -> 0b10|char_code
            code = ord( s )
            cmd = 0b1000000000 | code
            self.__sendLCDCommand( cmd )
            time.sleep( 0.0001 )  # 43us以上
    
    # 全クリア
    def clearAll( self ):
        # ディスプレイクリア命令
        clearDisplay = 0b0000000001
        self.__sendLCDCommand( clearDisplay )
        time.sleep( 0.002 )   # 1.53ms以上待つ(重要)
    
    # 1行クリア
    # line: 行数(0基底)
    def clearLine( self, line ):
        # 1行分(64文字)をスペースで埋める
        self.print( line, 0, " " * 64 )
    
    # バックライト切り替え
    # isOn     : ONならTrue
    # immediate: 即反映するならTrue、別命令時に反映ならFalse
    def setBacklight( self, isOn, immediate = True ):
        self.__backlight = 0x08 if isOn else 0
        if immediate:
            self.__sendLCDCommand( 0 )

    # エントリーモード
    def __entryMode( self, incShift = True, dispShift = False ):
        incShiftFlag  = 0b00000010 if incShift else 0   # インクリメントシフト
        dispShiftFlag = 0b00000001 if dispShift else 0  # ディスプレイシフト
        entryModeCmd = 0b00000100 | incShiftFlag | dispShiftFlag
        self.__sendLCDCommand( entryModeCmd )
        time.sleep( 0.0001 )

    # LCDのコマンドを送信
    #  cmd: 10bitのLCD用ビット列( RS R/W D7 D6 D5 D4 D3 D2 D1 D0 )
    def __sendLCDCommand( self, cmd ):
        # RS, R/Wフラグを生成
        #  RSのmask: 0b10|0000|0000 -> 0x200
        rs = 0b0001 if ( cmd & 0x200 ) != 0 else 0
        # R/WフラグはI2Cの1bit目
        #  R/Wのmask: 0b01|0000|0000 -? 0x100
        rw = 0b0010 if ( cmd & 0x100 ) != 0 else 0
        # EnableシグナルはI2Cの2bit目
        enableSignal = 0b00000100

        # 1回目D4〜D7のbit列を作成
        #  D4〜D7のmask: 0b00|1111|0000 -> 0x0F0
        high = ( cmd & 0x0F0 ) | self.__backlight | enableSignal | rw | rs
        self.__bus.write_byte( self.__i2cId, high )
        time.sleep( 0.0005 )      # 500nsEnableのまま
        self.__bus.write_byte( self.__i2cId, self.__backlight )  # Enable OFF

        # 2回目D0〜D3のbit列を作成して送信
        low = ( ( cmd << 4 ) & 0x0F0 ) | self.__backlight | enableSignal | rw | rs
        self.__bus.write_byte( self.__i2cId, low )
        time.sleep( 0.0005 )      # 500nsEnableのまま
        self.__bus.write_byte( self.__i2cId, self.__backlight )  # Enable OFF

 個々の細かい所はこれまで説明してきた事の反復になってしまうため、詳しくは過去記事をご覧下さい:

 コンストラクタ(__init__)内で初期化処理をしています。これについては初期化の記事でじっくり説明しております:

クラスの使い方

 使い方は極めてシンプル:

import lcdmng

lcd1 = lcdmng.LCD( 1, 0x27 )
lcd1.clearAll()
lcd1.print( 0, 0, "HELLO" )
lcd1.print( 1, 2, "WORLD!" )

 lcdmngモジュールをインポートしてLCDクラスを使えるようにします。次にLCDオブジェクトを作成するためコンストラクタにI2CバスID(1)とLCDの固有番号(0x27)を与えます。固有番号は使うLCDで異なる可能性がある為、必ずi2cdetectコマンドをターミナルで叩いて確認しましょう。一応画面を全クリアして(clearAllメソッド)、後はprintメソッドを呼ぶだけです。

終わりに

 今回はLCDに文字を表示する機能をクラスにまとめてみました。これでLCDを扱いたい時にlcdmngモジュールを用意するだけで済むようになりました。折角なので色々活用してみたい所です。

ではまた(^-^)/

この記事が気に入ったらサポートをしてみませんか?