MicroPython(ESP32)でモノクロ液晶(LS027B4DH01)の全ライン書き換えを高速化してみる(SPI通信)

2020/08/13 categories:ESP32| tags:ESP32|MicroPython|LS027B4DH01|SPI|

MicroPython(ESP32)でモノクロ液晶(LS027B4DH01)の全ライン書き換えを行ったときに意外と処理時間が掛かったので、SPIでデータを送信するためのプログラム回りで高速化できないか検討してみました。

全ライン送信の処理

送信の処理を2パターン作成して、処理時間を比較してみました。

処理1

最初に試した処理は、書き換えるラインの数×50バイトのbytearray( =bytearray(50)[240] )を渡して、ダミー+ラインアドレス+bytearray(50)のデータ送信を240回(全ライン分)繰り返すという内容です。

def update_multi_line(self, start_line, line_count, byte_array):
    
    self.scs.on() # chip select on
    utime.sleep_us(4)
    self.spi.write(b'\x01') # send mode

    # first line
    self.spi.write(b'\x00') # gate line address
    self.spi.write(byte_array[0]) # one line data
    
    # second line ~ last line
    for y, fifty_byte in enumerate(byte_array):
        self.spi.write(b'\x00') # dummy data
        self.spi.write(bytearray([y + start_line])) # gate line address
        self.spi.write(fifty_byte) # one line data

    self.spi.write(b'\x00\x00') # dummy data
    utime.sleep_us(4)
    self.scs.off()
    utime.sleep_us(4)

処理2

次に試した処理は、あらかじめ送信する全データ分の配列、bytearray(12482)を作成して、それをspi.write()に渡して一気に書き込むという内容です。

self.data = bytearray(12482) # (1+1+50) * 240 + 2

def update_all_line(self):
    self.scs.on()
    utime.sleep_us(4)
    self.spi.write(self.data)
    utime.sleep_us(4)
    self.scs.off()
    utime.sleep_us(4)

描画用データの配列

bytearray(12482)の中身は液晶のデータシートに書いている送信内容を参考に、初期値を下記のようにしています。下記表ではArray indexの0~12481がself.dataのインデックスに当たり、そのインデックスの初期値がValue、内容がContentsとなっています。表示を書き換えるためにはData1~Data50の値を書き換えるということになります。

処理1の実行時間

処理1を実行するためのコードは下記の通りです。SPIで送信する部分だけ時間を測定したかったので、timed_function()にはlcd.update_all_line()という処理1の内容だけを渡すようにしています。

def update2(a, b):
    lcd.update_multi_line(  0, 120, a)
    lcd.update_multi_line(120, 120, b)

def timed_function(f, *args, **kwargs):
    def new_func(*args, **kwargs):
        t = utime.ticks_us()
        result = f(*args, **kwargs)
        delta = utime.ticks_diff(utime.ticks_us(), t)
        print( 'Time = {:6.3f}ms'.format(delta/1000) )
        return result
    return new_func

timed_update2 = timed_function(update2)

count = 0
while count < 3:
    a = [ bytearray([0x00 for x in range(50)]) for y in range(120) ]
    b = [ bytearray([0xFF for x in range(50)]) for y in range(120) ]

    timed_update2(a, b)

SPIの送信処理だけで88msecかかるという結果でした。

Time = 87.880ms
Time = 87.699ms
Time = 87.670ms

処理2の実行時間

def update():
    lcd.update_all_line()

def timed_function(f, *args, **kwargs):
    def new_func(*args, **kwargs):
        t = utime.ticks_us()
        result = f(*args, **kwargs)
        delta = utime.ticks_diff(utime.ticks_us(), t)
        print( 'Time = {:6.3f}ms'.format(delta/1000) )
        return result
    return new_func

timed_update  = timed_function(update)

count = 0
while count < 3:

    for y in range(240):
        if y > 120:
            d = 0xFF
        else:
            d = 0x00
        for x in range(50):
            lcd.data[2 + y*52 + x] = d
    
    timed_update()

SPIの送信処理だけで11msecかかるという結果で、処理1よりも8倍速くなりました。

Time = 10.675ms
Time = 10.876ms
Time = 10.876ms

MicroPythonで処理時間の計測

MicroPythonで処理時間を計測する場合は、MicroPythoのリファレンスに下記のような関数で計測すると良い、と書いていたのでそれを使用しました。timed_function()に計測したい関数を渡してあげることで、utimeモジュールで関数の処理時間を計測できます。

import utime

def timed_function(f, *args, **kwargs):
    def new_func(*args, **kwargs):
        t = utime.ticks_us()
        result = f(*args, **kwargs)
        delta = utime.ticks_diff(utime.ticks_us(), t)
        print( 'Time = {:6.3f}ms'.format(delta/1000) )
        return result
    return new_func

timed_update  = timed_function(update)

所感

まだまだ改良の余地はあると思いますが、処理2にすることで処理時間を短縮できました。とりあえず処理2で一気に表示を書き換えるといった使い方をしようかなと思います。

ソースコード

main.py

import utime
from machine import Pin, SPI
from LS027B4DH01 import LS027B4DH01

def main():
    
    print('hello')

    lcd = LS027B4DH01()
    lcd.spi = SPI(
        2, #vspi = id = 2
        baudrate=10_000_000, #1MHz
        polarity=0, phase=0, bits=8, firstbit=SPI.LSB,
        sck=Pin(18), mosi=Pin(23), miso=Pin(19)
    )
    lcd.scs      = Pin(32, Pin.OUT)
    lcd.extcomin = Pin(33, Pin.OUT)
    lcd.disp     = Pin(25, Pin.OUT)
    lcd.initialize()
    lcd.disp.on()

    print('initialized')

    def update():
        lcd.update_all_line()

    def update2(a, b):
        lcd.update_multi_line(  0, 120, a)
        lcd.update_multi_line(120, 120, b)

    def timed_function(f, *args, **kwargs):
        def new_func(*args, **kwargs):
            t = utime.ticks_us()
            result = f(*args, **kwargs)
            delta = utime.ticks_diff(utime.ticks_us(), t)
            print( 'Time = {:6.3f}ms'.format(delta/1000) )
            return result
        return new_func
    
    timed_update  = timed_function(update)
    timed_update2 = timed_function(update2)

    count = 0
    while count < 3:

        for y in range(240):
            if y > 120:
                d = 0xFF
            else:
                d = 0x00
            for x in range(50):
                lcd.data[2 + y*52 + x] = d
        
        print('send 1 time')
        timed_update()
        
        a = [ bytearray([0x00 for x in range(50)]) for y in range(120) ]
        b = [ bytearray([0xFF for x in range(50)]) for y in range(120) ]

        print('send 240 times')
        timed_update2(a, b)

        count += 1

if __name__ == "__main__":
    main()

LS027B4DH01.py

import utime

class LS027B4DH01():
    def __init__(self):
        self.scs = None
        self.extcomin = None
        self.disp = None
        self.spi = None
        self.data = bytearray(12482) # (1+1+50) * 240 + 2

        self.data_reset()

    def update_one_line(self, line, data_array):
        self.scs.on()
        # send mode
        self.spi.write(b'\x01')
        # send gate line address
        self.spi.write( bytearray([line]) )
        # send data
        self.spi.write(data_array)
        # dummy data
        self.spi.write(b'\x00\x00')
        self.scs.off()

    def data_reset(self):
        self.data[0] = 0x01
        self.data[1] = 0x00
        for i in range(1, 240):
            self.data[i*52+1] = i
        
    def update_all_line(self):
        self.scs.on()
        utime.sleep_us(4)
        self.spi.write(self.data)
        utime.sleep_us(4)
        self.scs.off()
        utime.sleep_us(4)

    def update_multi_line(self, start_line, line_count, byte_array):
        
        self.scs.on() # chip select on
        utime.sleep_us(4)
        self.spi.write(b'\x01') # send mode

        # first line
        self.spi.write(b'\x00') # gate line address
        self.spi.write(byte_array[0]) # one line data
        
        # second line ~ last line
        for y, fifty_byte in enumerate(byte_array):
            self.spi.write(b'\x00') # dummy data
            self.spi.write(bytearray([y + start_line])) # gate line address
            self.spi.write(fifty_byte) # one line data

        self.spi.write(b'\x00\x00') # dummy data
        utime.sleep_us(4)
        self.scs.off()
        utime.sleep_us(4)

    def clear_all(self):
        self.disp.off()
        self.scs.on()
        utime.sleep_us(3)
        self.spi.write(b'\x04\x00')
        utime.sleep_us(3)
        self.scs.off()
        utime.sleep_us(5)
        self.disp.on()

    def initialize(self):
        self.disp.off()
        utime.sleep_us(500)
        self.scs.off()
        self.clear_all()
        utime.sleep_ms(5)
        self.disp.on()

Share post

Related Posts

Comments

comments powered by Disqus