マイクロビットでSPIカラー電子ペーパモジュールを使ってみる


マイクロビットでSPIカラー電子ペーパーモジュールを使ってみます。

今回使用するモジュールはWaveShare 3インチ 400 × 168 電子ペーパーモジュール(G) 赤/黄/黒/白(以後、e-paperと略す)で、これから記載するコードは公式サイトで配布されているラズベリーパイ用のMicroPythonのコードをマイクロビット用に書き換えたものになります。

3インチ 400 × 168 電子ペーパーモジュール(G) 赤/黄/黒/白 — スイッチサイエンス


配線はマイクロビットに拡張ボードをかまし

e-paper microbit
VCC 3.3V
GND GND
DIN(MISO) GPIO 15
SCLK GPIO 13
DC GPIO 2
CS GPIO 3
RST GPIO 0
BUSY GPIO 1

にします。


from microbit import *

# 画像データ
buf = []

# pin3 を使用できるようにする
display.off()

# Pin definition
RST_PIN  = pin0
DC_PIN   = pin2
CS_PIN   = pin3
BUSY_PIN = pin1

# Display resolution
EPD_WIDTH	   = 168
EPD_HEIGHT	  = 400

class EPD:
	def __init__(self):
		spi.init()
		self.width = EPD_WIDTH
		self.height = EPD_HEIGHT
		self.BLACK  = 0x000000   #   00  BGR
		self.WHITE  = 0xffffff   #   01
		self.YELLOW = 0x00ffff   #   10
		self.RED	= 0x0000ff   #   11
		
	# Hardware reset
	def reset(self):
		RST_PIN.write_digital(1)
		sleep(200)
		RST_PIN.write_digital(0)		 # module reset
		sleep(2)
		RST_PIN.write_digital(1)
		sleep(200)

	def send_command(self, cmd):
		DC_PIN.write_digital(0)
		CS_PIN.write_digital(0)
		spi.write(bytes([cmd]))
		CS_PIN.write_digital(1)

	def send_data(self, d):
		DC_PIN.write_digital(1)
		CS_PIN.write_digital(0)
		spi.write(bytes([d]))
		CS_PIN.write_digital(1)
		
	def ReadBusyH(self):
		print("e-Paper busy H")
		while(BUSY_PIN.read_digital() == 0):	  # 0: idle, 1: busy
			sleep(5)
		print("e-Paper busy H release")

	def ReadBusyL(self):
		print("e-Paper busy L")
		while(BUSY_PIN.read_digital() == 1):	  # 0: busy, 1: idle
			sleep(5)
		print("e-Paper busy L release")

	def TurnOnDisplay(self):
		self.send_command(0x12) # DISPLAY_REFRESH
		self.send_data(0x01)
		self.ReadBusyH()

		self.send_command(0x02) # POWER_OFF
		self.send_data(0X00)
		self.ReadBusyH()
		
	def init(self):
		self.reset()

		self.send_command(0x66)
		self.send_data(0x49)
		self.send_data(0x55)
		self.send_data(0x13)
		self.send_data(0x5D)
		self.send_data(0x05)
		self.send_data(0x10)

		self.send_command(0xB0)
		self.send_data(0x00) # 1 boost

		self.send_command(0x01)
		self.send_data(0x0F)
		self.send_data(0x00)

		self.send_command(0x00)
		self.send_data(0x4F)
		self.send_data(0x6B)

		self.send_command(0x06)
		self.send_data(0xD7)
		self.send_data(0xDE)
		self.send_data(0x12)

		self.send_command(0x61)
		self.send_data(0x00)
		self.send_data(0xA8)
		self.send_data(0x01)
		self.send_data(0x90)

		self.send_command(0x50)
		self.send_data(0x37)

		self.send_command(0x60)
		self.send_data(0x0C)
		self.send_data(0x05)

		self.send_command(0xE3)
		self.send_data(0xFF)

		self.send_command(0x84)
		self.send_data(0x00)
		return 0
	
	def display(self, l):
		if self.width % 4 == 0 :
			Width = self.width // 4
		else :
			Width = self.width // 4 + 1
		Height = self.height

		self.send_command(0x04)
		self.ReadBusyH()

		count = len(l)
		for j in range(0, Height):
			for i in range(0, Width):
				idx = i + j * Width
				if count > idx:				
					self.send_data(l[idx])
				else:
					# 足りない分はWHITEで埋める
					self.send_data(0)
		
		self.TurnOnDisplay()
		
	def Clear(self, color=0x55):
		if self.width % 4 == 0 :
			Width = self.width // 4
		else :
			Width = self.width // 4 + 1
		Height = self.height

		self.send_command(0x04)
		self.ReadBusyH()

		self.send_command(0x10)
		for j in range(0, Height):
			for i in range(0, Width):
				self.send_data(color)

		self.TurnOnDisplay()

	def sleep(self):
		self.send_command(0x02) # POWER_OFF
		self.send_data(0x00)

		self.send_command(0x07) # DEEP_SLEEP
		self.send_data(0XA5)
		
		sleep(2000)
		
print("epd3in0g Demo")

epd = EPD()

# Drawing on the image
epd.init()
print("Drawing on the image...")
epd.display(buf)
sleep(3000)

print("Goto Sleep...")

epd.sleep()

下記URL先のページでもMicroPythonのコードを確認できます。

https://github.com/inunosinsi/color-e-paper_on_microbit/blob/main/main.py




上記のコードは試しに実行してみますと、ディスプレイが真っ白になり何も表示されません。


# 画像データ
buf = []

コードの冒頭にあるリスト型のbufに出力したいデータを格納する必要があるのですが、何も指定されていません。


今回のe-PaperではPythonのPILライブラリを用いて画像データを生成してディスプレイにデータを送信することでディスプレイに出力されますが、マイクロビットではPILライブラリが使用できないため、事前に画像データを作成する必要があります。

Python Imaging Library - Wikipedia


そこで事前にコード作成用のPCで画像のデータを作成しておいて、生成したデータをフラッシング用のコードに直接貼り付けることで試しています。

PILで画像を生成するコードは下記のディレクトリのように作成します。

.
├── d.txt
└── generate.py

generate.py

from PIL import Image,ImageDraw,ImageFont

EPD_WIDTH       = 168
#EPD_HEIGHT      = 400
EPD_HEIGHT      = 40

BLACK  = 0x000000   #   00  BGR
WHITE  = 0xffffff   #   01
YELLOW = 0x00ffff   #   10
RED    = 0x0000ff   #   11

def getbuffer(image):
	# Create a pallette with the 4 colors supported by the panel
	pal_image = Image.new("P", (1,1))
	pal_image.putpalette( (0,0,0,  255,255,255,  255,255,0,   255,0,0) + (0,0,0)*252)

	# Check if we need to rotate the image
	imwidth, imheight = image.size
	if(imwidth == EPD_WIDTH  and imheight == EPD_HEIGHT):
		image_temp = image
	elif(imwidth == EPD_HEIGHT and imheight == EPD_WIDTH ):
		image_temp = image.rotate(90, expand=True)
	#else:
		#logger.warning("Invalid image dimensions: %d x %d, expected %d x %d" % (imwidth, imheight, EPD_WIDTH , EPD_HEIGHT))

	# Convert the soruce image to the 4 colors, dithering if needed
	image_4color = image_temp.convert("RGB").quantize(palette=pal_image)
	buf_4color = bytearray(image_4color.tobytes('raw'))

	# into a single byte to transfer to the panel
	buf = [0x00] * int(EPD_WIDTH  * EPD_HEIGHT / 4)
	idx = 0
	for i in range(0, len(buf_4color), 4):
		buf[idx] = str((buf_4color[i] << 6) + (buf_4color[i+1] << 4) + (buf_4color[i+2] << 2) + buf_4color[i+3]) + ","
		idx += 1

	return buf

img = Image.new('RGB', (EPD_WIDTH, EPD_HEIGHT), 0xffffff)  
draw = ImageDraw.Draw(img)
draw.text((5, 0), 'hello world', fill = RED)
buf = getbuffer(img)
print(len(buf))
with open('d.txt','w') as f:
	f.writelines((buf))

下記URL先のページでもPythonのコードを確認できます。

https://github.com/inunosinsi/color-e-paper_on_microbit/blob/main/generate.py


コードが出来ましたら、実行してみます。

作成したコードが

/path/to/dir/generate.py

にある場合、

sudo apt-get update
sudo apt-get install python3-pip
sudo apt-get install python3-pil
python3 /path/to/dir/generate.py

の順にコマンドを実行して、画像データを生成します。


生成した画像のデータはd.txtに格納されていますので、d.txtの内容をフラッシングするコードの方の

# 画像データ
buf = [85,85,85...]

のように数字の羅列を貼り付けます。


上記の処理を終えてコードをフラッシングしてみますと、フラッシング終了後に



のようにディスプレイに生成した画像データが出力されます。


generate.pyの方のパラメータで

EPD_WIDTH = 168
#EPD_HEIGHT = 400
EPD_HEIGHT = 40

の画像生成用の値があります。


マイクロビットのメモリの都合上、e-Paperに合わせた画像データを持つことが出来ません。

マイクロビット用にデータを小さくして生成する必要がありますが、この時に変更できる値はEPO_HEIGHTのみになります。

EPO_WIDTHを変更するとディスプレイ出力時に表示がずれてしまいます。




ここから今回利用したSPIというシリアル通信について触れていきます。

SPIはシリアル・ペリフェラル・インタフェース(serial peripheral interface)の略で、データの送受信を行うシリアル通信の一種です。


en:User:Cburnett - 投稿者自身による作品 このW3C-unspecified ベクター画像Inkscapeで作成されました ., CC 表示-継承 3.0, リンクによる


SPIでは、モジュールを操作する為のマイコン等のマスター(Master)と、操作される側のスレーブ(Slave)があります。

マスターとスレーブを繋ぐ時は、SCLK、MOSI、MISOとSSがあり、必要に応じて繋いでいきます。

各々のピンの役割ですが、

  • ・SCLK(Serial Clock:CLKに該当):マスターモードの時はクロック出力ピンになり、スレーブモードの時はクロック入力ピンになる
  • ・MISO(Master In Sleve Out:Doutに該当):マスターモードの時はデータ入力ピンになり、スレーブモードの時にはデータ出力ピンになる
  • ・MOSI(Master Out Sleve In:Dinに該当):マスターモードの時はデータ出力ピンになり、スレーブモードの時はデータ入力ピンになる
  • ・SS(Sleve Select:CSに該当):スレーブの制御に用いて、スレーブの数だけ必要になるピン

になります。


I, Cburnett, CC 表示-継承 3.0, リンクによる


SPIではMISOやMOSIでデータを送受信する際、SCLKがあり自身でクロックを発生させることで、マイクロビットでUARTを使ってみるで見たUARTのようにデータ送信側と受信側のボーレートを合わせるといった手続きが不要になります。



SSはどのマスターがどのスレーブとやりとりをするか?の合図を送る為に使用します。


※今回のコードではSPIデバイスに対して書き込みのみを行いましたが、SPIには読み込みの方のモジュールもあります。