2021年9月27日月曜日

タイムコーダーを作る【3】RaspberryPi4+I2CネイティブOLEDを使う

前回、I2Cインターフェースで接続したOLEDで作成したタイムコーダーを今回はI2Cにネイティブ対応したOLEDで作り直します。I2Cインターフェースモジュールを使用した場合、通信速度が上がらず、計測間隔が長くなっていたのを改善します。また、LCD1602のようなキャラクターOLEDを使うことで、ビットマップ型のSDD1332よりも表示間隔が短くできるので、今回の用途にはキャラクタータイプの方が適しています。


前回の話:

タイムコーダーを作る【2】RaspberryPi4+I2C+OLEDで表示高速化

タイムコーダーを作る【1】 RaspberryPi4+I2Cで液晶LCD1602を使う時は 3.3V版を使う

のつづき

■前提とする環境

RaspberryPi4
RaspberryPi W/WH
Python3
SO1602AWWB-UC-WB-U <Sunlike Display Tech. Corp.> (秋月電子)

 

■I2CインターフェールモジュールからネイティブOLEDに変更した

I2Cインターフェースモジュールは、LCDに送るメッセージを4ビットに分割して、コントロール信号も別に操作する必要があり、さらにI2Cのアドレスも送るのでLCDメッセージ1バイトを送信するのになんと12バイトが必要となっていました。
これを、OLEDのICがI2Cを直接解釈できるならば、このオーバーヘッドが大きく改善できるはずです。

そして、見つけたのが…

SOC1602F <SHENZHEN SURENOO TECHNOLOGY CO.,LTD.> AliExpress

SOC1602F

SOC1602F-open



SO1602AWWB-UC-WB-U <Sunlike Display Tech. Corp.> 秋月電子

の2つでした。

SOC1602F 

SOC1602Fはピンの配置が短辺側についており、配線の取り回しの良さから一番の本命だったのですが、どうやっても動作せず、I2Cデバイス一覧でも出てきたり出てこなくなったり、メッセージを送ってもウントもスントも言わず、2つ購入していたのにも関わらずどちらも同じ症状。ネットで解決策を探るも解決策はおろか動作実績さえ見つけられない。プリント基板の配線も多層基板で配線を追いきれず、お手上げ状態となり諦めました。こちらはまた日を改めて再度試してみたいところです。

SO1602AWWB-UC-WB-U

 今回、秋月電子さんのSO1602AWWB-UC-WB-Uをためしたところ、一発で動作したのでこちらを利用することにしました。回路的にはどちらのモジュールもほとんど同じで、OLEDモジュール(画面)とそのコントローラーは同じUS2066を使用しています。実は基板上にはほとんど回路が有りません。                                                    
 SOC1602Fもエポキシ基板上の回路も大したものは乗っていないので実は簡単なことで改善できそうなんですが私の実力では解明できませんでした。
RaspberyPiで動作出来た方、是非アドバイスをお願いいたします。

今回は、キャラクターOLEDでネイティブでI2Cを解釈できる、SO1602AWWB-UC-WB-Uを使用して作り直してみます。

■制作・回路

秋月さんのリファレンス回路をもとに作成します。

注意点は、RaspberryPiでOLEDを使用する時外部電電源が必要になります。RaspberryPi上の3.3V出力は絶対定格が50mAと低く、全点灯したときのOLEDの最大消費電流が80mAなので、定格を超えてしまいます。これが、LCD(SC1602BBWB-XA-GB-G)の消費電流がパネル3.0mA、バックライト20mA前後で有ることを考えると結構大きな電流を消費しているのがわかります。
今回、不足分を補うため外部電源として手元にあった3.3VなACアダプターを利用し、ブレッドボードに供給しています。


■ソースコード

I2Cネイティブで動作するUS2066では、今までのプログラムが大幅に簡略化出来ます。
また、視認性や速度が重要となるならUS2066を使用したキャラクターOLEDモジュールは良い選択肢になります。
ソースコードはライブラリとそれを呼び出すプログラムとの2つで構成されます。
今回時計プログラムを掲載していますが、他の記事のGNSS/GPSを使用した内部時計の同期を使えば電波時計になります。

時計プログラム 
[us2066clock.py]
#! /usr/bin/env python3

import smbus, time
import datetime
import us2066

#get us2006_lib into oled(Instance)
oled = us2066.Us2066(0x3c)
oled.brightness(255)

#write strings data(1st row)
now = datetime.datetime.now()

#write strings data(2nd row)
micro = 1000000
pos_micro = 0
while True:
    now = datetime.datetime.now()

    if micro > now.microsecond:
        oled.pos(0, 0)
        current = now.strftime("%y%m%d %H:%M:%S")
        oled.print(current)  # date times
        oled.pos(pos_micro, 1)

    micro = now.microsecond
    if pos_micro == 0:
        oled.pos(pos_micro, 1)

    str_milli = str(micro)[0:3]
    oled.print(str_milli)  #write strings
    pos_micro = (pos_micro + 3)
    if pos_micro >= 15:
        pos_micro = 0
    time.sleep(0.00)


#モニタの表示アドレス(2行16列の場合)
#     [1][2][3][4][5][6][7][8][9][10][11][12][13][14][15][16]
# 1行目:[0x00][0x01][0x02][0x03]・・・[0x0F]
# 2行目:[0x20][0x21][0x22][0x23]・・・[0x2F]



ライブラリ本体
同じフォルダに保存してご利用ください。
[us2066.py]
# -*- coding: utf-8 -*-
#I2C Interface library
#  For US2066 OLED/PLED Segment/Common Driver with Controller
#
#SOC series
# SUNLIKE SO1602A,SO2002A,SO2004A,
# SO1602AWWB-UC-WB-U
#  not work: Surenoo SOC1602F

import smbus
import time
name = 'us2066'

class Us2066:
    i2c = None
    address = None

    def __init__(self, address):
        print("us2066.__init__()", address)
        self.i2c = smbus.SMBus(1)
        self.set_address(address)
        self.init()
        self.off()
        self.clear()
        self.home()
        self.on()

    def init(self):
        #LCD Intialize
        self.command(0x2A)         
        self.command(0x71)        
        self.command(0x00)          #Disable Internal Regulator
        self.command(0x28)          #

    def set_address(self, address):
        print("set_address()", address)
        self.address = address

    def wait_busy(self):
        busy = 0x80
        while busy & 0x80:
            busy = self.i2c.read_byte_data(self.address, 0x00)
            time.sleep(0)

    def command(self, code):
        #print("command()", code)
        self.i2c.write_byte_data(self.address, 0x00, code)
        #time.sleep(0.002)

    def print(self, message):
       #print("write()", message)
       mojilist = []
       for char in message:
            mojilist += self.CHAR_TABLE[char]
            #time.sleep(0.00002)
       self.wait_busy()
       self.i2c.write_i2c_block_data(self.address, 0x40, mojilist)

    def off(self):
        self.wait_busy()
        self.command(0x08)          #Display Off

    def on(self):
        self.wait_busy()
        self.command(0x0c)          # Diplay ON = 1100b

    def clear(self):
        self.wait_busy()
        self.command(0x01)          #Clear =1b

    def home(self):
        self.wait_busy()
        self.command(0x02)          #Return home =2b

    def pos(self, xxx, yyy):
        self.wait_busy()
        self.command(0x80 + 0x20 * yyy + xxx)

    def brightness(self,  bright):
        self.wait_busy()
        self.command(0x80)        # set RE=1
        self.command(0x2A)

        self.command(0x80)        # set SD=1
        self.command(0x79)

        self.command(0X81)        # set Brightness
        self.command(bright)

        self.command(0x80)        # set SD=0
        self.command(0x78)

        self.command(0x80)        # set RE=0
        self.command(0x28)

    CHAR_TABLE = {
        u'†': [0x11],
        u'§': [0x12],
        u'¶': [0x13],
        u'Γ': [0x14],
        u'Δ': [0x15],
        u'θ': [0x16],
        u'Λ': [0x17],
        u'Ξ': [0x18],
        u'Π': [0x19],
        u'Σ': [0x1a],
        u'Φ': [0x1c],
        u'Ψ': [0x1d],
        u'Ω': [0x1e],
        u'α': [0x1f],

        u' ': [0x20],
        u'!': [0x21],
        u'"': [0x22],
        u'#': [0x23],
        u'$': [0x24],
        u'%': [0x25],
        u'&': [0x26],
        u"'": [0x27],
        u'(': [0x28],
        u')': [0x29],
        u'*': [0x2a],
        u'+': [0x2b],
        u',': [0x2c],
        u'-': [0x2d],
        u'.': [0x2e],
        u'/': [0x2f],

        u'0': [0x30],
        u'1': [0x31],
        u'2': [0x32],
        u'3': [0x33],
        u'4': [0x34],
        u'5': [0x35],
        u'6': [0x36],
        u'7': [0x37],
        u'8': [0x38],
        u'9': [0x39],
        u':': [0x3a],
        u';': [0x3b],
        u'<': [0x3c],
        u'=': [0x3d],
        u'>': [0x3e],
        u'?': [0x3f],

        u'@': [0x40],
        u'A': [0x41],
        u'B': [0x42],
        u'C': [0x43],
        u'D': [0x44],
        u'E': [0x45],
        u'F': [0x46],
        u'G': [0x47],
        u'H': [0x48],
        u'I': [0x49],
        u'J': [0x4a],
        u'K': [0x4b],
        u'L': [0x4c],
        u'M': [0x4d],
        u'N': [0x4e],
        u'O': [0x4f],

        u'P': [0x50],
        u'Q': [0x51],
        u'R': [0x52],
        u'S': [0x53],
        u'T': [0x54],
        u'U': [0x55],
        u'V': [0x56],
        u'W': [0x57],
        u'X': [0x58],
        u'Y': [0x59],
        u'Z': [0x5a],
        u'[': [0x5b],
        u'¥': [0x5c],
        u']': [0x5d],
        u'^': [0x5e],
        u'_': [0x5f],

        u'`': [0x60],
        u'a': [0x61],
        u'b': [0x62],
        u'c': [0x63],
        u'd': [0x64],
        u'e': [0x65],
        u'f': [0x66],
        u'g': [0x67],
        u'h': [0x68],
        u'i': [0x69],
        u'j': [0x6a],
        u'k': [0x6b],
        u'l': [0x6c],
        u'm': [0x6d],
        u'n': [0x6e],
        u'o': [0x6f],

        u'p': [0x70],
        u'q': [0x71],
        u'r': [0x72],
        u's': [0x73],
        u't': [0x74],
        u'u': [0x75],
        u'v': [0x76],
        u'w': [0x77],
        u'x': [0x78],
        u'y': [0x79],
        u'z': [0x7a],
        u'{': [0x7b],
        u'|': [0x7c],
        u'}': [0x7d],
        u'→': [0x7e],
        u'←': [0x7f],

        u'。': [0xa1],
        u'「': [0xa2],
        u'」': [0xa3],
        u'、': [0xa4],
        u'・': [0xa5],
        u'ヲ': [0xa6],
        u"ァ": [0xa7],
        u'ィ': [0xa8],
        u'ゥ': [0xa9],
        u'ェ': [0xaa],
        u'ォ': [0xab],
        u'ャ': [0xac],
        u'ュ': [0xad],
        u'ョ': [0xae],
        u'ッ': [0xaf],

        u'ー': [0xb0],
        u'ア': [0xb1],
        u'イ': [0xb2],
        u'ウ': [0xb3],
        u'エ': [0xb4],
        u'オ': [0xb5],
        u'カ': [0xb6],
        u'キ': [0xb7],
        u'ク': [0xb8],
        u'ケ': [0xb9],
        u'コ': [0xba],
        u'サ': [0xbb],
        u'シ': [0xbc],
        u'ス': [0xbd],
        u'セ': [0xbe],
        u'ソ': [0xbf],

        u'タ': [0xc0],
        u'チ': [0xc1],
        u'ツ': [0xc2],
        u'テ': [0xc3],
        u'ト': [0xc4],
        u'ナ': [0xc5],
        u'ニ': [0xc6],
        u'ヌ': [0xc7],
        u'ネ': [0xc8],
        u'ノ': [0xc9],
        u'ハ': [0xca],
        u'ヒ': [0xcb],
        u'フ': [0xcc],
        u'ヘ': [0xcd],
        u'ホ': [0xce],
        u'マ': [0xcf],

        u'ミ': [0xd0],
        u'ム': [0xd1],
        u'メ': [0xd2],
        u'モ': [0xd3],
        u'ヤ': [0xd4],
        u'ユ': [0xd5],
        u'ヨ': [0xd6],
        u'ラ': [0xd7],
        u'リ': [0xd8],
        u'ル': [0xd9],
        u'レ': [0xda],
        u'ロ': [0xdb],
        u'ワ': [0xdc],
        u'ン': [0xdd],
        u'゛': [0xde],
        u'゜': [0xdf],

        u''': [0xf0],
        u'"': [0xf1],
        u'°': [0xf2],
        u'×': [0xf7],
        u'÷': [0xf8],
        u'≧': [0xf9],
        u'≦': [0xfa],
        u'≪': [0xfb],
        u'≫': [0xfc],
        u'≠': [0xfd],
        u'√': [0xfe],
        u' ̄': [0xff],

        u'ガ': [0xb6, 0xde],
        u'ギ': [0xb7, 0xde],
        u'グ': [0xb8, 0xde],
        u'ゲ': [0xb9, 0xde],
        u'ゴ': [0xba, 0xde],
        u'ザ': [0xbb, 0xde],
        u'ジ': [0xbc, 0xde],
        u'ズ': [0xbd, 0xde],
        u'ゼ': [0xbe, 0xde],
        u'ゾ': [0xbf, 0xde],
        u'ダ': [0xc0, 0xde],
        u'ヂ': [0xc1, 0xde],
        u'ヅ': [0xc2, 0xde],
        u'デ': [0xc3, 0xde],
        u'ド': [0xc4, 0xde],
        u'バ': [0xca, 0xde],
        u'ビ': [0xcb, 0xde],
        u'ブ': [0xcc, 0xde],
        u'ベ': [0xcd, 0xde],
        u'ボ': [0xce, 0xde],
        u'パ': [0xca, 0xdf],
        u'ピ': [0xcb, 0xdf],
        u'プ': [0xcc, 0xdf],
        u'ペ': [0xcd, 0xdf],
        u'ポ': [0xce, 0xdf],
    }



アメグラ2号のブログさんを参考にさせていただきました

■結果

やはりI2Cにネイティブで対応したUS2066は高速で、ディスプレイの書き換えが1msec弱となりました。しかし、1msecでは逆にシャッター速度が遅いときに読めなくなります。表題の写真に掲載しているものは、視認性を上げるために遅くさせています。4msecあたりの間隔で書き換えを行っているのが見て取れるかと思います。
上記の写真では、下段の数字が3桁ごとのmsec時間を表示しています。
わざわざ3桁づつ横に出力しているのは、カメラのシャッターの開放時間と書き換え時間が重なると文字が重なり読めなくなるためです。
この写真では、2段め左から3桁づつ読み
324
3**
312
316
320
と読めます。
読みにくかった3**は4msec毎に書き換えされていることから
328
あたりと推測できます。
なので現在時刻は。
2021/9/24 20:04:56.328-331
と推測できます。

かなり精度よくOLEDから時刻を読むことができるようになったので、次はGNSS/GPSからの時刻を精度良くRaspberryPiに反映させることが重要となってきました。

そうなってくると、GNSS/GPSユニットから送られてくる時刻情報と、実際の時刻とのずれを計測して補正する必要があります。
現状そこまで精度が必要ないので、次期課題としておきます。