秋月のI2C接続有機ELディスプレイをラズパイ４に接続する （続き）


[この前の記事](https://qiita.com/amax/items/6e90395614a5e3b24ed4)の続き。

# 日本語＋記号対応
![コード表](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/214012/65aa4d8b-9429-e6cc-fae4-f6a4302701a0.jpeg)

前回の記事では青領域に加えて緑領域も対応させた。
今回は橙領域も対応させる。
赤領域（アクセント符号）については割愛。
イ●ンとかポ●モンとかをロゴ風表記しない限りたぶん使わない。

コードを調べると

|記号|OLEDの番地|UTF16の番地|
|:--|:--|:--|
|™️|0x10|0x2122|
|†|0x11|0x2020|
|§|0x12|0xa7|
（以下省略）

このように番地がバラバラなので、Pythonの辞書型配列　（キー：ordで得られる十進数値、値：OLEDの番地十六進数値）　で登録してifで場合分けする。
ついでに、アルファベットと形がほぼ同じギリシャ文字（ΒとBなど）はOLEDに登録されていないが使い勝手の都合上、Β（大文字ベータ）はB（大文字ビー）として表示する様に登録しておく。

``` python3:
input_def={8482:"0x10",8224:"0x11",167:"0x12",182:"0x13",\
8594:"0x7E",8592:"0x7F",163:"0xE5",165:"0xE6",402:"0xE8",\
8709:"0xEE",966:"0xEF",188:"0xF6",189:"0xF5",215:"0xF7",\
247:"0xF8",8807:"0xF9",8806:"0xFA",12298:"0xFB",\
12299:"0xFC",8800:"0xFD",8730:"0xFE",\
913:"0x41",914:"0x42",915:"0x14",916:"0x15",917:"0x45",\
918:"0x5A",919:"0x48",920:"0x16",921:"0x49",922:"0x4B",\
923:"0x17",924:"0x4D",925:"0x4E",926:"0x18",927:"0x4F",\
928:"0x19",929:"0x50",931:"0x1A",932:"0x54",933:"0x1B",\
934:"0x1C",935:"0x57",936:"0x1D",937:"0x1E",945:"0x1F"}
```

この辞書をもとに関数を作る。
前回はshift-jisにエンコードかけてたが、そもそも半角カナは0xFF60領域にあるのでordでとって得られた値を減算すれば問題ないのでそういう処理に変更。
ついでに漢字や非対応記号が入力されたときに化けないよう、指定外文字は置き換えて表示するように変更。

``` python3:
#文字列変換処理
def convi(i):
    j=ord(i) #特殊記号判定のため一度ordで処理
    if j>65376 :              #半角カナの領域（0xA0以降）はordから一定差分をとる
        j=j-65216
    elif  j<=125 and j>=32 :  #英数字の領域(0x20〜0x7D)はそのまま
        j=j
    elif j in input_def:      #指定した記号（ギリシャ文字と一部の記号）は辞書から
        j=int(input_def[j],16)
    else:                     #変換できない文字は上バー
        j=255
    return j

```

これにwrite_byte_dateで書き込むバイト処理を変更すればいい。

``` python3:
    def writeLine(self, str = '', line = 0, align = 'left', oled_size=20):
 #中略
        #１バイトずつ書き込む
        for i in range(len(str)):
            self.bus.write_byte_data(self.addr, 0x40, convi(str[i]))
```


#輝度調整機能追加
秋月電子作成の取扱説明書を見ると輝度も変更できるようなので取扱説明書２ページに記載のプログラムを参照し実装する。
プログラムによれば、0x2a（RE=1）、0x79（SD=1）、0x81（コントラストセット）、0x7F（輝度を0x00〜0xFFで指定）、0x78（SD=0）、0x28（２行文字）を入力するようです。
なお、最後の0x28を省略すると表示がおかしくなります。

既に作成しているクラスの中に関数として作成します。

``` python3:
class SO1602A():
   #中略

    #輝度設定。引数指定なしは初期値0x7f
    def contrastset(self,contrast=127 ):
        self.bus.write_byte_data(self.addr, 0x00, 0x2a)
        self.bus.write_byte_data(self.addr, 0x00, 0x79)
        self.bus.write_byte_data(self.addr, 0x00, 0x81)
        self.bus.write_byte_data(self.addr, 0x00, contrast)
        self.bus.write_byte_data(self.addr, 0x00, 0x78)
        self.bus.write_byte_data(self.addr, 0x00, 0x28)
```
これで、main関数内で `oled.contrastset(contrast=0 )` のように使ってあげることで輝度を指定できます。
0と255を繰り返すように使えば警告表示がわりに、光センサーの照度と連動させれば自動で輝度調整できそう。

#別ファイル化
以上のプログラムを他の処理も書いたりすると分かりにくいのでわけてしまいます。
main関数に関する部分を削除したものをoledmod.pyとして保存し、実際に実行するプログラムをmain.pyとして作成し、oledmodをインポートして使います

```python3:main.py
#!/usr/bin/python3
# -*- coding: utf-8 -*-

import oledmod

#定数
oled_size = 20 #SO1602シリーズは16、SO2002シリーズは20

def main():
    oled = oledmod.SO1602A(sa0 = 1)
    text0 = 'Σ(;w;)そんなばかな'
    text1 = 'α=¼×Π×Π'
    oled.contrastset(contrast=0 )
    oled.writeLine(str = text0 , line = 0, oled_size=oled_size)
    oled.writeLine(str = text1, line = 1, oled_size=oled_size)

if __name__ == '__main__':
    main()
```

#まとめ
以下のプログラムをoledmod.pyとして保存し、実行したいプログラムと同じディレクトリに入れる。

``` python3:oledmod.py
#!/usr/bin/python3
# -*- coding: utf-8 -*-

import smbus
import jaconv

#辞書（一部の記号とギリシャ大文字のみ対応。ギリシャ文字の一部は英字で代替）
input_def={8482:"0x10",8224:"0x11",167:"0x12",182:"0x13",\
8594:"0x7E",8592:"0x7F",163:"0xE5",165:"0xE6",402:"0xE8",\
8709:"0xEE",966:"0xEF",188:"0xF6",189:"0xF5",215:"0xF7",\
247:"0xF8",8807:"0xF9",8806:"0xFA",12298:"0xFB",\
12299:"0xFC",8800:"0xFD",8730:"0xFE",\
913:"0x41",914:"0x42",915:"0x14",916:"0x15",917:"0x45",\
918:"0x5A",919:"0x48",920:"0x16",921:"0x49",922:"0x4B",\
923:"0x17",924:"0x4D",925:"0x4E",926:"0x18",927:"0x4F",\
928:"0x19",929:"0x50",931:"0x1A",932:"0x54",933:"0x1B",\
934:"0x1C",935:"0x57",936:"0x1D",937:"0x1E",945:"0x1F"}

#文字列変換処理
def convi(i):
    j=ord(i) #特殊記号判定のため一度ordで処理
    if j>65376 :              #半角カナの領域（0xA0以降）はordから一定差分をとる
        j=j-65216
    elif  j<=125 and j>=32 :  #英数字の領域(0x20〜0x7D)はそのまま
        j=j
    elif j in input_def:      #指定した記号（ギリシャ文字と一部の記号）は辞書から
        j=int(input_def[j],16)
    else:                     #変換できない文字は上バー
        j=255
    return j

class SO1602A():
    #初期処理
    def __init__(self, sa0 = 0, cursor = False, blink = False):
        self.bus = smbus.SMBus(1)

        if (sa0 == 0):
            self.addr = 0x3c
        else:
            self.addr = 0x3d

        self.clearDisplay()
        self.returnHome()
        self.displayOn(cursor, blink)
        self.clearDisplay()

#文字を書き込む処理
    def writeLine(self, str = '', line = 0, align = 'left', oled_size=20):
        #ひらがな・カタカナを半角カナに変換し、必要な文字数だけトリミングする。
        str=jaconv.z2h(jaconv.hira2hkata(str),digit=True,ascii=True)[0:oled_size]

        #スペースを入れて位置調整他
        while (len(str) < oled_size):
            if (align == 'right'):
                str = ' ' + str
            else:
                str = str + ' '

        #初期カーソル位置の調整
        if (line == 1):
            self.bus.write_byte_data(self.addr, 0x00, (0x80 + 0x20))
        else:
            self.bus.write_byte_data(self.addr, 0x00, 0x80)

        #１バイトずつ書き込む
        for i in range(len(str)):
            self.bus.write_byte_data(self.addr, 0x40, convi(str[i]))

    def clearDisplay(self):
        self.bus.write_byte_data(self.addr, 0x00, 0x01)
    
    def returnHome(self):
        self.bus.write_byte_data(self.addr, 0x00, 0x02)

    def displayOn(self, cursor = False, blink = False):
        cmd = 0x0c
        if (cursor):
            cmd += 0x02
        if (blink):
            cmd += 0x01
        self.bus.write_byte_data(self.addr, 0x00, cmd)

    def displayOff(self):
        self.bus.write_byte_data(self.addr, 0x00, 0x08)

    #輝度設定。引数指定なしは初期値0x7f
    def contrastset(self,contrast=127 ):
        self.bus.write_byte_data(self.addr, 0x00, 0x2a)
        self.bus.write_byte_data(self.addr, 0x00, 0x79)
        self.bus.write_byte_data(self.addr, 0x00, 0x81)
        self.bus.write_byte_data(self.addr, 0x00, contrast)
        self.bus.write_byte_data(self.addr, 0x00, 0x78)
        self.bus.write_byte_data(self.addr, 0x00, 0x28)
```

実際のプログラムはこっち
これを `sudo python3 main.py`で実行すれば表示が変わる。

``` python3:main.py
#!/usr/bin/python3
# -*- coding: utf-8 -*-

import time
import datetime
import oledmod

#定数
oled_size = 20 #SO1602シリーズは16、SO2002シリーズは20
sa0       = 1  #SA0の値
contrast  = 0  #輝度の値0-127の範囲

#表示メッセージ
text0 = 'Σ(;w;)そんなばかな'
text1 = 'α=¼×Π×Π'

def main():
    oled = oledmod.SO1602A(sa0 = sa0)
    oled.contrastset(contrast= contrast )
    oled.writeLine(str = text0 , line = 0, oled_size=oled_size)
    oled.writeLine(str = text1, line = 1, oled_size=oled_size)

if __name__ == '__main__':
    main()
```

#おまけ
スクロールと時刻表示を実装してみた。
定数でいろいろいじれる様にはしてるけどもうちょっとシンプルにできそう。


``` python3:scroll.py
#!/usr/bin/python3
# -*- coding: utf-8 -*-

import time
import datetime
import oledmod

#定数
oled_size     =  20                 #表示器の１行当たりの字数
sa0           =  True               #表示器のSA0の値が1かどうか（H=True、L=False）
refresh_speed = 0.1                 #再描写の速度（秒単位）
scroll_ratio0 =  0                  #１行目のスクロール速度（再描写との倍率）
scroll_ratio1 =  3                  #２行目のスクロール速度（再描写との倍率）
scroll_text0  = False               #１行目スクロールするか
scroll_text1  = True                #２行目スクロールするか
time_format   = '%Y/%m/%d %H:%M:%S' #表示する時刻の書式
time_view_0   = True                #１行目に時刻を出すか
time_view_1   = False               #２行目に時刻を出すか

#表示メッセージ
text0 = ''  #１行目のメッセージ
text1 = 'クリスマスはひとりでごぜーますか。' #２行目のメッセージ

#スクロール用のスペース作成
def scr_space(text):
    i=0
    while (i < oled_size):
        text = ' ' + text
        i=i+1
    return text

def main():
    #初期処理
    oled = oledmod.SO1602A(sa0 = int(sa0) )
    i=0
    j=0
    k=0

    #スクロールする場合スペースを左に入れる。
    if scroll_text0 : n_text0=scr_space(text0)
    if scroll_text1 : n_text1=scr_space(text1)

    while True:
        nowtime=(datetime.datetime.now()).strftime(time_format)
        #１行目の処理
        if time_view_0 :
            oled.writeLine(str = nowtime , line = 0, oled_size=oled_size)
        elif scroll_text0 :
            oled.writeLine(str = n_text0[j:] , line = 0, oled_size=oled_size)
        else : 
            oled.writeLine(str = n_text0 , line = 0, oled_size=oled_size)
        #２行目の処理
        if time_view_1 :
            oled.writeLine(str = nowtime , line = 1, oled_size=oled_size)
        elif scroll_text1 :
            oled.writeLine(str = n_text1[k:] , line = 1, oled_size=oled_size)
        else : 
            oled.writeLine(str = n_text1 , line = 1, oled_size=oled_size)

        time.sleep(refresh_speed)
        try:
            if i%scroll_ratio0 ==0 and scroll_ratio0!=0 and scroll_text0 : j=j+1
        except ZeroDivisionError:
            pass
        try:
            if i%scroll_ratio1 ==0 and scroll_ratio1!=0 and scroll_text1 : k=k+1
        except ZeroDivisionError:
            pass
        if j==len(text0)+oled_size : j=0
        if k==len(text1)+oled_size : k=0
        i=i+1


if __name__ == '__main__':
    main()


```