お前らはまだmicropythonのuctypesを知らない

はいすみません、イキりました。
知ってたら帰って貰ってよいですむしろお願いします。

ところで、pythonみたいなク〇言語を今の若い子が習うのは可哀そうだ！！
というのをtwitterで見ました。
python全然使えないのに思わずいいねしました。投げられた石はいくらでも受けます。

でもmicropythonは最高です！
全人類はmicropythonを使うべきだと思いました！！
いや、でも、おっさんハード系はC言語系好きですし必須なのは間違いないのですが、新人ハード系さんは普通のpython(とWSL)を覚える方が得だと思いますよ。

すみません、前振りはここまでにします。

# 環境

Raspberry pi picoです。
pico2も一緒だと思いますが、IO1.8VにしたRP2040基板作っちゃって移行できない…

# micropythonのuctypes

公式ドキュメントの[uctypes](https://micropython-docs-ja.readthedocs.io/ja/latest/library/uctypes.html)を見ても本家に飛ばされるだけでマジで意味わからんですよね。
C言語の構造体や共用体を無理やりbyte(array)に当てはめたものだと言えばいいですかね。
通常のstructと似てますが、[micropythonライブラリのuctypes](https://micropython-docs-ja.readthedocs.io/ja/latest/library/uctypes.html) に書いてあるように、structは本来複雑なのは向かないです。
ちなみに被ってややこしいのですが、uctypesのstructとstructは別モノです。
うっかりstructだけで定義できるようにしてstruct.packとかやると？となってハマるので注意しましょう。

## uctypesの良いところ

いろいろあるんですが、micropython的には**アドレスに直接書ける**という点ですね。
ビットフィールドを判りやすくアクセスできるようになるのはネ申です。
個人的にはmem32で直接レジスタアクセスするならutypesを使う方が良いと思ってます。(mem32遅いですし…)

## uctypesの例

とりあえずID以外のSPIのレジスタ定義したものをお見せします。
なお、皆さんにも苦しんでほしいですが、このまま書くと"import struct"と競合してエラーが出て？になるケースがあります。
解決方法は後述、はしますが、大したものではなく被らないようにするだけです。

```python
from uctypes import BF_POS, BF_LEN, UINT32, BFUINT32, struct

# SPI0とSPI1のベースアドレス
_SPI_BASE_ADDR = (
    0x4003c000,
    0x40040000
)

# Control register 0
SPI_SSPCR0_FIELDS = {
    "SCR":      8<<BF_POS | 8<<BF_LEN | BFUINT32,
    "SPH":      7<<BF_POS | 1<<BF_LEN | BFUINT32,
    "SPO":      6<<BF_POS | 1<<BF_LEN | BFUINT32,
    "FRF":      4<<BF_POS | 2<<BF_LEN | BFUINT32,
    "DSS":      0<<BF_POS | 3<<BF_LEN | BFUINT32,
}

# Control register 1
SPI_SSPCR1_FIELDS = {
    "SOD":      3<<BF_POS | 1<<BF_LEN | BFUINT32,
    "MS":       2<<BF_POS | 1<<BF_LEN | BFUINT32,
    "SSE":      1<<BF_POS | 1<<BF_LEN | BFUINT32,
    "LBM":      0<<BF_POS | 1<<BF_LEN | BFUINT32,
}

# Data register
SPI_SSPDR_FIELDS = {
    "DATA":     0<<BF_POS | 15<<BF_LEN | BFUINT32,
}

# Status register
SPI_SSPSR_FIELDS = {
    "BSY":      4<<BF_POS | 1<<BF_LEN | BFUINT32,
    "RFF":      3<<BF_POS | 1<<BF_LEN | BFUINT32,
    "RNE":      2<<BF_POS | 1<<BF_LEN | BFUINT32,
    "TNF":      1<<BF_POS | 1<<BF_LEN | BFUINT32,
    "TFE":      0<<BF_POS | 1<<BF_LEN | BFUINT32,
}

# Clock prescale register
SPI_SSPCPSR_FIELDS = {
    "CPSDVSR":  0<<BF_POS | 8<<BF_LEN | BFUINT32,
}

# Interrupt mask set or clear register
SPI_SSPIMSC_FIELDS = {
    "TXIM":     3<<BF_POS | 1<<BF_LEN | BFUINT32,
    "RXIM":     2<<BF_POS | 1<<BF_LEN | BFUINT32,
    "RTIM":     1<<BF_POS | 1<<BF_LEN | BFUINT32,
    "RORIM":    0<<BF_POS | 1<<BF_LEN | BFUINT32,
}

# Raw interrupt status register
SPI_SSPRIS_FIELDS = {
    "TXRIS":    3<<BF_POS | 1<<BF_LEN | BFUINT32,
    "RXRIS":    2<<BF_POS | 1<<BF_LEN | BFUINT32,
    "RTRIS":    1<<BF_POS | 1<<BF_LEN | BFUINT32,
    "RORIS":    0<<BF_POS | 1<<BF_LEN | BFUINT32,
}

# Masked interrupt status register
SPI_SSPMIS_FIELDS = {
    "TXMIS":    3<<BF_POS | 1<<BF_LEN | BFUINT32,
    "RXMIS":    2<<BF_POS | 1<<BF_LEN | BFUINT32,
    "RTMIS":    1<<BF_POS | 1<<BF_LEN | BFUINT32,
    "RORMS":    0<<BF_POS | 1<<BF_LEN | BFUINT32,
}

# Interrupt clear register
SPI_SSPICR_FIELDS = {
    "RTIC":     1<<BF_POS | 1<<BF_LEN | BFUINT32,
    "RORIC":    0<<BF_POS | 1<<BF_LEN | BFUINT32,
}

# DMA control register
SPI_SSPDMACR_FIELDS = {
    "TXDMAE":   1<<BF_POS | 1<<BF_LEN | BFUINT32,
    "RXDMAE":   0<<BF_POS | 1<<BF_LEN | BFUINT32,
}


SPI_REGS = {
    "SSPCR0_REG":       0x000|UINT32,
    "SSPCR0":           (0x000, SPI_SSPCR0_FIELDS),
    "SSPCR1_REG":       0x004|UINT32,
    "SSPCR1":           (0x004, SPI_SSPCR1_FIELDS),
    "SSPDR_REG":        0x008|UINT32,
    "SSPDR":            (0x008, SPI_SSPDR_FIELDS),
    "SSPSR_REG":        0x00c|UINT32,
    "SSPSR":            (0x00c, SPI_SSPSR_FIELDS),
    "SSPCPSR_REG":      0x010|UINT32,
    "SSPCPSR":          (0x010, SPI_SSPCPSR_FIELDS),
    "SSPIMSC_REG":      0x014|UINT32,
    "SSPIMSC":          (0x014, SPI_SSPIMSC_FIELDS),
    "SSPRIS_REG":       0x018|UINT32,
    "SSPRIS":           (0x018, SPI_SSPRIS_FIELDS),
    "SSPMIS_REG":       0x01c|UINT32,
    "SSPMIS":           (0x01c, SPI_SSPMIS_FIELDS),
    "SSPICR_REG":       0x020|UINT32,
    "SSPICR":           (0x020, SPI_SSPICR_FIELDS),
    "SSPDMACR_REG":     0x024|UINT32,
    "SSPDMACR":         (0x024, SPI_SSPDMACR_FIELDS),
}

# SPIレジスタアクセス用。
SPI_DEVICE = [
    struct(_SPI_BASE_ADDR[0], SPI_REGS),
    struct(_SPI_BASE_ADDR[1], SPI_REGS)
]

```

例えばSPI0のSSPCR1レジスタのSSEビットとかは下記で設定できます。

```python
SPI_DEVICE[0].SSPCR1.SSE = 0
```

32ビットレジスタとして読み書きしたい場合は*_REGを使います。

```python
SPI_DEVICE[0].SSPDR_REG = 0x01
```



## 解説
### 共用体っぽいもの

例は下記。
辞書型でkeyにアクセス名を書くのはわかると思います。
valueは"|"で繋いでビットフィールドを記載します。

```python
SPI_SSPCR0_FIELDS = {
    "SCR":      8<<BF_POS | 8<<BF_LEN | BFUINT32,
    "SPH":      7<<BF_POS | 1<<BF_LEN | BFUINT32,
    "SPO":      6<<BF_POS | 1<<BF_LEN | BFUINT32,
    "FRF":      4<<BF_POS | 2<<BF_LEN | BFUINT32,
    "DSS":      0<<BF_POS | 3<<BF_LEN | BFUINT32,
}
```

valueについては下記のとおり。
uctypesからimportするのを忘れないように。

#### BF_POS
ビットフィールドの開始位置を指定します。"<<"演算子で定義します。

#### BF_LEN
ビットフィールドのビット数を指定します。これも"<<"演算子で定義します。

#### BFUINT(8|16|32|64)
共用体にするビット数を定義します。
RP2040は32ビットレジスタなので、BFUINT32しか使わないかと思いますが。

### 構造体っぽいもの

例は下記。
辞書型でkeyにアクセス名を書くのは一緒です。
構造体は先頭からのオフセットをバイト数で記載しつつ、メンバがINT32とかUINT32を指定して"|"で繋ぎます。
オフセットがちょっと面倒ですが…
また、オフセットをメンバと同じにすれば、共用体アクセスも設定可能です。

```python
SPI_REGS = {
    "SSPCR0_REG":       0x000|UINT32,
    "SSPCR0":           (0x000, SPI_SSPCR0_FIELDS),
    "SSPCR1_REG":       0x004|UINT32,
    "SSPCR1":           (0x004, SPI_SSPCR1_FIELDS),
    "SSPDR_REG":        0x008|UINT32,
    "SSPDR":            (0x008, SPI_SSPDR_FIELDS),
    "SSPSR_REG":        0x00c|UINT32,
    "SSPSR":            (0x00c, SPI_SSPSR_FIELDS),
    "SSPCPSR_REG":      0x010|UINT32,
    "SSPCPSR":          (0x010, SPI_SSPCPSR_FIELDS),
    "SSPIMSC_REG":      0x014|UINT32,
    "SSPIMSC":          (0x014, SPI_SSPIMSC_FIELDS),
    "SSPRIS_REG":       0x018|UINT32,
    "SSPRIS":           (0x018, SPI_SSPRIS_FIELDS),
    "SSPMIS_REG":       0x01c|UINT32,
    "SSPMIS":           (0x01c, SPI_SSPMIS_FIELDS),
    "SSPICR_REG":       0x020|UINT32,
    "SSPICR":           (0x020, SPI_SSPICR_FIELDS),
    "SSPDMACR_REG":     0x024|UINT32,
    "SSPDMACR":         (0x024, SPI_SSPDMACR_FIELDS),
}
```

ちなみにオフセットは連続にする必要はないです。
不定値が入ってもよければ、飛ばして定義も可能です。
公式の[uctypes](https://micropython-docs-ja.readthedocs.io/ja/latest/library/uctypes.html)の例を見てみてください。

### structでアドレス割り当て

**uctypes**のstructに割り当てます。
レジスタアドレスを指定して直接アクセスできるようにします。

```python
SPI_DEVICE = [
    struct(_SPI_BASE_ADDR[0], SPI_REGS),
    struct(_SPI_BASE_ADDR[1], SPI_REGS)
]
```

ところで、レジスタ設定にはAtomicアクセスがあります。
Atomicで設定したい場合は、素直にAtomicアクセス用のuctypes設定を作るのが良いと思います。
が、CLEARで1とかいう記述になるので混乱するよ！
例を記載します。
辞書型を使えば["XOR"]とか、それっぽく指定できますのでやりたい人は頑張って。

```python
# Atomic register access offset
_MEM32_XOR      = 0x1000
_MEM32_SET      = 0x2000
_MEM32_CLEAR    = 0x3000

SPI_DEVICE_XOR = [
    struct(_SPI_BASE_ADDR[0] + _MEM32_XOR, SPI_REGS),
    struct(_SPI_BASE_ADDR[1] + _MEM32_XOR, SPI_REGS)
]

SPI_DEVICE_SET = [
    struct(_SPI_BASE_ADDR[0] + _MEM32_SET, SPI_REGS),
    struct(_SPI_BASE_ADDR[1] + _MEM32_SET, SPI_REGS)
]

SPI_DEVICE_CLEAR = [
    struct(_SPI_BASE_ADDR[0] + _MEM32_CLEAR, SPI_REGS),
    struct(_SPI_BASE_ADDR[1] + _MEM32_CLEAR, SPI_REGS)
]
```

## structとuctypesのstruct

いうほど書くまでもないです。
両方使う場合はだいたいハマるので書いてます。
解決方法は単純に名前が同じとなって被らないようにするだけです。
どっちもイマイチなので公式で変えてほしい…

### uctypesのまま使う

"import uctypes"にして、"uctypes.struct"にします。
"BFUNIT32"とかも"uctypes.BFUINT32"になるのは面倒ですが。

### structを別名にする

"import struct as st"とかですね。
"st.pack"とかにすれば被らないというだけです。