MicroPython「machine」モジュールを調べてみる

# はじめに
本当はRaspberry Pi 4 Model Bあたりが欲しかったのですが、昨今の半導体不足等のせいで手に入らず、仕方なくPicoを購入しました。
しかし、できることは限られるので、この際だからMicroPythonを使ってみることにしました。

# 目的
とりあえずは、Raspberry Pi PicoをMicroPythonで動かすこと。
便利そうなら、他のマイコンもMiroPythonで動かすことを考えたいです。

# 今回やること
どうやら「machine」モジュールが肝となっていそうなので、その理解を進めます。

ぶっちゃけ、公式ドキュメントのまとめになります。
[https://micropython-docs-ja.readthedocs.io/ja/latest/library/machine.html](https://micropython-docs-ja.readthedocs.io/ja/latest/library/machine.html)


# 「machine」モジュール
- 特定のボード上のハードウェアに関連する固有の関数を含んでいる
- いくつかの「関数」と「定数」と「クラス」を持っている

以降、良く使うそうな機能だけをざっくりと説明していきます。

## 関数
- リセット関連
    - リセット：reset()
    - ソフトリセット：soft_reset()
    - リセットの原因の取得：reset_cause()
    - ブートローダーの起動：bootloader()
- 割り込み関連
    - 無効化／有効化：disable_irq()／enable_irq()
- 電力関連
    - CPU周波数の取得：freq()
    - アイドリング：idle()
    - スリープ（長期／短期）：lightsleep()／deepsleep()
    - 起床の原因の取得：wake_reason()
- その他
    - ボード／SoCのIDの取得：unique_id()
    - パルスの持続時間の取得：time_pulse_us()
    - ビット送信：bitstream()
    - 乱数の取得：rng()

## 定数
※省略

## クラス
用意されているのは、以下の14クラス。
- <font color="red">Pin：I/Oピンの制御</font>
- Signal：外部I/Oデバイスの制御
- <font color="red">ADC：アナログ－デジタル変換</font>
- ADCBlock：ADCペリフェラルの制御
- <font color="red">PWM：パルス幅変調</font>
- <font color="red">UART：二重シリアル通信バス</font>
- <font color="red">SPI：シリアルペリフェラルインターフェースバスプロトコル（コントローラ側）</font>
- <font color="red">I2C：2線式シリアルプロトコル</font>
- I2S：IC間（Inter-IC Sound）サウンドバスプロトコル
- RTC：リアルタイムクロック
- Timer：ハードウェアタイマーの制御
- WDT：ウォッチドッグタイマー
- SD：セキュアデジタルメモリカード
- SDCard：SDメモリカード

このうち、重要そうなもの（赤文字）だけ説明します。

### Pinクラス
GPIOピンへのデジタル制御（ON/OFF）で使用。
（例）LEDの点灯

- ピン番号（＋入出力）を指定して、インスタンス（オブジェクト）を生成
- ON/OFFを入出力

```py
from machine import Pin

# ピン #0 を出力ピンとして作成
p0 = Pin(0, Pin.OUT)

# 値をロー、ハイと順に設定
p0.value(0)
p0.value(1)

# ピン #2 をプルアップ抵抗付きの入力ピンとして作成
p2 = Pin(2, Pin.IN, Pin.PULL_UP)

# ピンの値を読み込んで表示
print(p2.value())

# ピン #0 を入力モード＆プルダウン抵抗指定で再設定
p0.init(p0.IN, p0.PULL_DOWN)

# irq コールバックを設定
p0.irq(lambda p:print(p))
```

### ADCクラス
アナログ値（電圧の変動）をデジタル値に変換して取得。
（例）ボリューム値の取得

- チャンネル（ピン）を指定してインスタンス（オブジェクト）を生成
    - 各チャンネルがどのGPIOピンに割り当てられているのかは、ハードウェアによる
- 現在値の読み込み
    - uint（0～65,535でスケーリング）、または、マイクロボルト値

```py
from machine import ADC

adc = ADC(pin)        # 指定のピンで動作する ADC オブジェクトを作成
val = adc.read_u16()  # 生のアナログ値を 0-65535 の範囲で読込み
val = adc.read_uv()   # アナログ値をマイクロボルトで読込み
```

### PWMクラス
パルス幅を変調した信号を出力。
（例）LEDの明るさの制御

- ピンを指定して、インスタンス（オブジェクト）を生成
- 周波数、パルス幅を指定

```py
from machine import PWM

pwm = PWM(pin)          # 指定のピンの PWM オブジェクトを作成
pwm.duty_u16(32768)     # パルス幅を 50% に設定

# 200us の周期、デューティ比を 5us で再初期化
pwm.init(freq=5000, duty_ns=5000)

pwm.duty_ns(3000)       # 3us のパルス幅を設定

pwm.deinit()
```

### UARTクラス
二重（送受信）シリアル通信を実装。
バイトデータ（文字など）をやり取りする感じ。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/130771/3bfb92e0-a006-3d5c-138a-720bc7d3f297.png)
（例）CO2センサー（MH-Z19Cなど）との通信

- ピンを指定して、インスタンス（オブジェクト）を生成
- 通信速度、ビット数などを設定
- 複数バイトを送受信

```py
from machine import UART

uart = UART(1, 9600)                         # 与えたボーレートで初期化
uart.init(9600, bits=8, parity=None, stop=1) # 与えたパラメータで初期化

uart.read(10)       # 10文字を読み込んで、bytes 型オブジェクトを返す
uart.read()         # 可能な限り文字を読み込む
uart.readline()     # 1行を読み込む
uart.readinto(buf)  # 読み込んで、与えたバッファに格納
uart.write('abc')   # 3文字を書き込む
```

### SPIクラス（SoftSPIクラス）
同期通信を実装。
バイナリデータをやり取りする感じ。
ソフトウェアSPIも可。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/130771/d5ad9f03-ee6e-367f-7d43-9c52963c97f5.png)
（例）OLEDディスプレイ（SSD1306など）との通信

- IDを指定して、インスタンス（オブジェクト）を生成
    - 各IDがどのGPIOピンに割り当てられているのかは、ハードウェアによる
- 通信速度、ビット数などを設定
- 複数バイトを送受信

```py
from machine import SPI, Pin

spi = SPI(0, baudrate=400000)           # 周波数 400kHz で SPI ペリフェラル 0 を作成
                                        # ユースケースによっては、追加のパラメータが必要な場合があります。使用するバスの
                                        # 特性やピンを選択するための追加のパラメータが必要になる場合があります。
cs = Pin(4, mode=Pin.OUT, value=1)      # ピン 4 でチップセレクトを作成。

try:
    cs(0)                               # ペリフェラルを選択。
    spi.write(b"12345678")            # 8 バイトを書き出し、受信データについては無視。
finally:
    cs(1)                               # ペリフェラルを選択解除。

try:
    cs(0)                               # ペリフェラルを選択。
    rxdata = spi.read(8, 0x42)          # 0x42 をバイトごとに書き出しながら、合計 8 バイトを読み込む。
finally:
    cs(1)                               # ペリフェラルを選択解除。

rxdata = bytearray(8)
try:
    cs(0)                               # ペリフェラルを選択。
    spi.readinto(rxdata, 0x42)          # 0x42 をバイトごとに書き出しながら、合計 8 バイトを指定場所に読み込む。
finally:
    cs(1)                               # ペリフェラルを選択解除。

txdata = b"12345678"
rxdata = bytearray(len(txdata))
try:
    cs(0)                               # ペリフェラルを選択。
    spi.write_readinto(txdata, rxdata)  # バイト列の書出しと読込みを同時に行う。
finally:
    cs(1)                               # ペリフェラルを選択解除。
```

### I2Cクラス
2線式プロトコルでの通信を実装。
使い方的にはSPIと似た感じ。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/130771/9e19a3d0-2ae9-784e-76e8-ae92cb3acec2.png)
（例）加速度センサー（ADXL345など）との通信

- インスタンス（オブジェクト）を生成
    - 生成時には特にID等は指定しない
- 通信速度、ビット数などを設定
- 複数バイトを送受信
    - 送受信時に、相手（ID）を指定

```py
from machine import I2C

i2c = I2C(freq=400000)          # ポートに依存して 400kHz の周波数でI2Cペリフェラルを
                                # 作成します。使用するペリフェラルやピンを選択するために
                                # 追加のパラメータが必要になる場合があります

i2c.scan()                      # ペリフェラルをスキャンし、7ビットアドレスのリストを返します

i2c.writeto(42, b'123')         # 7ビットアドレス 42 のペリフェラルに３バイトを書き込みます
i2c.readfrom(42, 4)             # 7ビットアドレス 42 のペリフェラルから４バイトを読み込みます

i2c.readfrom_mem(42, 8, 3)      # スレーブ 42 のメモリから、ペリフェラルのメモリアドレス 8 で
                                #   始まる３バイトを読み込みます
i2c.writeto_mem(42, 2, b'\x10') # スレーブ 42 のメモリの、ペリフェラルのメモリアドレス 2 で
                                #   始まるところに２バイトを書き込みます
```

# 最後に
machineモジュール以外にも、ファイルアクセスやネットワーク関連のモジュールのことも知っておいた方がいいけど、ここではここまでにしておきます。

# おまけ
C/C++党だけど、何かと（環境構築とか）楽なので、Python派に流れそう．．．
しかも、ESP32もMicroPythonが動くのね。知らなかった。