micro:bit v2 の MicroPython で OLED を使う(ssd1306モジュール修正編)

[micro:bit Advent Calendar 2021](https://qiita.com/advent-calendar/2021/microbit)の[7日目](https://qiita.com/inachi/items/930608fa3fb98690977d)の続きです。

今回は、[ssd1306](https://github.com/micropython/micropython/blob/master/drivers/display/ssd1306.py)モジュールを[7日目](https://qiita.com/inachi/items/930608fa3fb98690977d)に作成した MicroPython で使えるようにしてみます。

# I2C API の相違

[ssd1306](https://github.com/micropython/micropython/blob/master/drivers/display/ssd1306.py)モジュールは [machine.I2C](https://micropython-docs-ja.readthedocs.io/ja/latest/library/machine.I2C.html) モジュールの利用を前提にしていますが、micro:bit にはこのモジュールが無く、I2C の利用には [microbit.i2c](https://microbit-micropython.readthedocs.io/ja/v2-docs/i2c.html)を使います。両者のAPIは多少違っているので、ssd1306モジュールを micro:bit 用に書き換える必要があります。具体的には以下のように書き換えてみました。

```diff
--- micropython/drivers/display/ssd1306.py	2021-11-27 19:36:01.000000000 +0900
+++ ssd1306.py	2021-12-10 00:29:43.000000000 +0900
@@ -114,17 +114,16 @@
         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):
```

[mpremote](https://micropython-docs-ja.readthedocs.io/ja/latest/reference/mpremote.html)を使って、micro:bit に転送しておきます。

```sh
$ mpremote cp ssd1306.py :ssd1306.py
```

これで `ssd1306` モジュールが micro:bit でも使えるようになりました。

# 動かしてみる

試しに 3D CUBE のアニメーションを動かしてみます。

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

| OLED | micro:bit |
|------|-----------|
| VCC  | 3V |
| GND  | GND |
| SCL | 19 (SCL) |
| SDA | 20 (SDA) |

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

from microbit import i2c, sleep
from math import sin, cos
from ssd1306 import SSD1306_I2C


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

oled = SSD1306_I2C(X * 2, Y * 2, i2c)

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)
```

めでたく動きました。

```sh
$ mpremote cp main.py :main.py
```

![unnamed.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/106817/b2aa8982-14a2-7bb0-5eb7-1daab4b77f76.jpeg)