micro:bit v2 の MicroPython で OLED を使う(ssd1306モジュールをSPI版に対応させる編)

空いていたので、超小ネタでいきます。

[micro:bit Advent Calendar 2021](https://qiita.com/advent-calendar/2021/microbit)の[8日目](https://qiita.com/inachi/items/8335d762e5cb4a90c075)の記事でI2CのSSD1306 OLEDが使えるように[ssd1306](https://github.com/micropython/micropython/blob/master/drivers/display/ssd1306.py)モジュールを書き換えましたが、SSD1306 OLEDにはSPIインタフェースのものもあるので、これにも対応するよう書き換えます。

```diff
--- micropython/drivers/display/ssd1306.py	2021-11-27 19:36:01.000000000 +0900
+++ ssd1306.py	2021-12-10 19:59:30.000000000 +0900
@@ -114,50 +114,47 @@
         self.i2c = i2c
         self.addr = addr
         self.temp = bytearray(2)
-        self.write_list = [b"\x40", None]  # Co=0, D/C#=1
         super().__init__(width, height, external_vcc)
 
     def write_cmd(self, cmd):
         self.temp[0] = 0x80  # Co=1, D/C#=0
         self.temp[1] = cmd
-        self.i2c.writeto(self.addr, self.temp)
+        self.i2c.write(self.addr, self.temp)
 
     def write_data(self, buf):
-        self.write_list[1] = buf
-        self.i2c.writevto(self.addr, self.write_list)
+        data = b'\x40' + bytes(buf)
+        self.i2c.write(self.addr, data)
 
 
 class SSD1306_SPI(SSD1306):
     def __init__(self, width, height, spi, dc, res, cs, external_vcc=False):
-        self.rate = 10 * 1024 * 1024
-        dc.init(dc.OUT, value=0)
-        res.init(res.OUT, value=0)
-        cs.init(cs.OUT, value=1)
+        dc.write_digital(0)
+        res.write_digital(0)
+        cs.write_digital(1)
         self.spi = spi
         self.dc = dc
         self.res = res
         self.cs = cs
         import time
 
-        self.res(1)
+        self.res.write_digital(1)
         time.sleep_ms(1)
-        self.res(0)
+        self.res.write_digital(0)
         time.sleep_ms(10)
-        self.res(1)
+        self.res.write_digital(1)
+        self.spi.init()
         super().__init__(width, height, external_vcc)
 
     def write_cmd(self, cmd):
-        self.spi.init(baudrate=self.rate, polarity=0, phase=0)
-        self.cs(1)
-        self.dc(0)
-        self.cs(0)
+        self.cs.write_digital(1)
+        self.dc.write_digital(0)
+        self.cs.write_digital(0)
         self.spi.write(bytearray([cmd]))
-        self.cs(1)
+        self.cs.write_digital(1)
 
     def write_data(self, buf):
-        self.spi.init(baudrate=self.rate, polarity=0, phase=0)
-        self.cs(1)
-        self.dc(1)
-        self.cs(0)
+        self.cs.write_digital(1)
+        self.dc.write_digital(1)
+        self.cs.write_digital(0)
         self.spi.write(buf)
-        self.cs(1)
+        self.cs.write_digital(1)
```

3Dキューブのアニメーションは SSD1306_I2C の代わりに SSD1306_SPI を使うように変えるだけです。

```python
# 3D CUBE MicroPython version with micro:bit and ssd1306 OLED 

from microbit import spi, sleep, pin8, pin9, pin16
from math import sin, cos
from ssd1306 import SSD1306_SPI


X = const(64)
Y = const(32)

oled = SSD1306_SPI(X * 2, Y * 2, spi, dc=pin8, res=pin9, cs=pin16)

f = [[0.0 for _ in range(3)] for _ in range(8)]
cube = ((-20,-20, 20), (20,-20, 20), (20,20, 20), (-20,20, 20),
        (-20,-20,-20), (20,-20,-20), (20,20,-20), (-20,20,-20))

while True:
    for angle in range(0, 361, 3):  # 0 to 360 deg 3step
        for i in range(8):
            r  = angle * 0.0174532  # 1 degree
            x1 = cube[i][2] * sin(r) + cube[i][0] * cos(r)  # rotate Y
            ya = cube[i][1]
            z1 = cube[i][2] * cos(r) - cube[i][0] * sin(r)
            x2 = x1
            y2 = ya * cos(r) - z1 * sin(r)  # rotate X
            z2 = ya * sin(r) + z1 * cos(r)
            x3 = x2 * cos(r) - y2 * sin(r)  # rotate Z
            y3 = x2 * sin(r) + y2 * cos(r)
            z3 = z2
            x3 = x3 + X
            y3 = y3 + Y
            f[i][0] = x3  # store new values
            f[i][1] = y3
            f[i][2] = z3
        oled.fill(0)  # clear
        oled.line(int(f[0][0]), int(f[0][1]), int(f[1][0]), int(f[1][1]), 1)
        oled.line(int(f[1][0]), int(f[1][1]), int(f[2][0]), int(f[2][1]), 1)
        oled.line(int(f[2][0]), int(f[2][1]), int(f[3][0]), int(f[3][1]), 1)
        oled.line(int(f[3][0]), int(f[3][1]), int(f[0][0]), int(f[0][1]), 1)
        oled.line(int(f[4][0]), int(f[4][1]), int(f[5][0]), int(f[5][1]), 1)
        oled.line(int(f[5][0]), int(f[5][1]), int(f[6][0]), int(f[6][1]), 1)
        oled.line(int(f[6][0]), int(f[6][1]), int(f[7][0]), int(f[7][1]), 1)
        oled.line(int(f[7][0]), int(f[7][1]), int(f[4][0]), int(f[4][1]), 1)
        oled.line(int(f[0][0]), int(f[0][1]), int(f[4][0]), int(f[4][1]), 1)
        oled.line(int(f[1][0]), int(f[1][1]), int(f[5][0]), int(f[5][1]), 1)
        oled.line(int(f[2][0]), int(f[2][1]), int(f[6][0]), int(f[6][1]), 1)
        oled.line(int(f[3][0]), int(f[3][1]), int(f[7][0]), int(f[7][1]), 1)
        oled.line(int(f[1][0]), int(f[1][1]), int(f[3][0]), int(f[3][1]), 1)
        oled.line(int(f[0][0]), int(f[0][1]), int(f[2][0]), int(f[2][1]), 1)
        oled.text('3D CUBE', 0, 0)
        oled.show()  # display
        sleep(1)
```

I2C インタフェースの OLED と [micro:bit のピン](https://tech.microbit.org/hardware/edgeconnector/#edge-connector-pins)とは以下のように配線しました。

| OLED | micro:bit |
|------|-----------|
| VCC  | 3V |
| GND  | GND |
| D0 | 13 (SCK) |
| D1 | 15 (MOSI) |
| RES | 9 |
| DC | 8 |
| CS | 16 |

![IMG_5584.JPG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/106817/5959e1c6-6f06-5193-c637-f0265c6a1653.jpeg)