Raspberry Pi のペリフェラル制御(GPIO/I2C/SPI)

# はじめに
ラズベリーパイ(Raspberry Pi / Raspberry Pi Zero) のGPIO、I2C、SPIをPythonで制御する方法をまとめる。PythonのライブラリにはGPIO([RPi.GPIO](https://pypi.org/project/RPi.GPIO/))、I2C([smbus2](https://pypi.org/project/smbus2/))、SPI([spidev](https://pypi.org/project/spidev/))等あるが、それぞれ使い分けが必要となる。本記事では、GPIO、I2C、SPI全てが一つにまとまっている**pigpio**で制御する。

RaspberryPi Picoの場合はmicropythonなので、別記事の下記参照。

https://qiita.com/tapitapi/items/c3c5e7530d49794489fb

# 目次
+ [Raspberry Piのピン配置](#raspberry-piのピン配置)
+ [ペリフェラル通信の設定](#ペリフェラル通信の設定)
+ [pigpio](#pigpio)
+ [GPIO制御基本](#gpio制御基本)
+ [GPIOの割り込み](#gpioの割り込み)
+ [I2C制御](#i2c制御)
+ [SPI制御](#spi制御)
+ [おまけ1：UART制御](#おまけ1uart制御)
+ [おまけ2：bytearray型の扱いについて](#おまけ2bytearray型の扱いについて)


## Raspberry Piのピン配置
5Vと3.3Vの電源出力を備えている。GPIOは3.3V。GPIOはシリアル通信のI2C/SPI/UARTを兼ねている。ピン配置はRaspberry Pi/Raspberry Pi Zero共通。下図は公式サイトより。

種別|Pin.No|GPIO.No
---|---|---
I2C|Pin3:SDA<br>Pin5:SCL|GPIO2<br>GPIO3
UART(シリアルコンソールと兼用)|Pin8:TXD<br>Pin10:RXD|GPIO14<br>GPIO15
SPI|Pin19:MOSI<br>Pin21:MISO<br>Pin23:SCLK<br>Pin24:CE0(チップセレクト0)<br>Pin26:CE1(チップセレクト1)|GPIO10<br>GPIO9<br>GPIO11<br>GPIO8<br>GPIO7
GPIO|Pin.11<br>Pin.13<br>Pin.15<br>Pin.29<br>Pin.31<br>Pin.37|GPIO17<br>GPIO27<br>GPIO22<br>GPIO5<br>GPIO6<br>GPIO26
GPIO|Pin.16<br>Pin.18<br>Pin.22<br>Pin.36|GPIO23<br>GPIO24<br>GPIO25<br>GPIO16

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1299802/5da7fa63-ebb2-7d89-e509-5a7a39d75380.png)

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1299802/200c541b-4d45-734b-6daf-de267e8288ca.png)

[戻る](#目次)

## ペリフェラル通信の設定
[設定]→[Raspberry Piの設定]→[インターフェース]でSPIとI2Cとを有効にする。シリアルポートはUARTのこと。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1299802/19664d3c-c0ff-34dc-2302-29b4e04cf7b8.png)


[戻る](#目次)

## pigpio
pigpioはRaspberryPiでGPIO/I2C/SPIの制御ができるPythonライブラリ。RaspberryPiにデフォルトでインストールされている。使用する場合は、```sudo pigpiod```を打つ必要がある。起動時に自動起動させたい場合はsystemctl enable。解除したい場合はdisable。

```shell:pigpioの自動起動
systemctl enable pigpiod
```
https://abyz.me.uk/rpi/pigpio/python.html

[戻る](#目次)

## GPIO制御基本
pigpioでGPIO制御するためのAPIは下記の通り。基本のGPIO制御を列挙しているがPWMの制御もできる。

設定内容|API|パラメータ
---|---|---
初期化|pi=pigpio.pi()
IN/OUT設定|pi.set_mode(gpio,mode)|gpio:<br>　GPIO番号<br>mode:<br>　pigpio.INPUT<br>　pigpio.OUTPUT
プルアップ・ダウン|pi.set_pull_up_down(gpio,pud)|gpio:<br>　GPIO番号<br>pud:<br>　pigpio.PUD_UP<br>　pigpio.PUD_DOWN<br>　pigpio.PUD_OFF
GPIO状態確認|ret=pi.get_mode(gpio)|gpio:<br>　GPIO番号<br>ret:<br>　0:IN/1:OUT
Write|pi.write(gpio,level)|gpio:<br>　GPIO番号<br>level:<br>　0:low/1:high
Read|level=pi.read(gpio)|gpio:<br>　GPIO番号<br>level:<br>　0:low/1:high

GPIO17とGPIO27を線で直結、GPIO5をOpenにしている状態で動作確認。
```python:pigpio_gpio_test.py
import pigpio

pi = pigpio.pi()
#GPIO17をOUT設定
pi.set_mode(17, pigpio.OUTPUT)
#GPIO27をIN設定
pi.set_mode(27, pigpio.INPUT)
#GPIOのモード確認
print(f'# {pi.get_mode(17)=}/{pi.get_mode(27)=}')
# pi.get_mode(17)=1/pi.get_mode(27)=0

#GPIO17=Low
pi.write(17,0)
print(f'# {pi.read(27)=}')
# pi.read(27)=0

#GPIO17=High
pi.write(17,1)
print(f'# {pi.read(27)=}')
# pi.read(27)=1

#GPIO5 PullUp
pi.set_mode(5, pigpio.INPUT)
pi.set_pull_up_down(5,pigpio.PUD_UP)
print(f'# {pi.read(5)=}')
# pi.read(5)=1

```
[戻る](#目次)



## GPIOの割り込み
pigpioは入力信号のエッジ割り込みによって、コールバック関数を呼ぶこともできる。

設定内容|API|パラメータ
---|---|---
初期化|pi=pigpio.pi()
コールバック設定|pi.callback(gpio, edge, cb_func)|gpio:<br>　GPIO番号<br>edge:<br>　pigpio.FALLING_EDGE(↓)<br>　pigpio.RISING_EDGE(↑)<br>　pigpio.EITHER_EDGE((↓↑))<br>cb_func:<br>　コールバック関数

コールバック関数の型は
```python:cb_func
def cb_func(gpio, level, tick):
    #gpio : gpio番号
    #level: 0:low_edge/1:high_edge
    #tick : bootからの時間 usec 72minでオーバフロー
```

GPIO17とGPIO27を線で直結、GPIO17を1秒毎にHigh/Lowさせ、GPIO27に1秒毎に割り込みを発生させたときの例。
```python:pigpio_gpioint_test.py
import pigpio
import time

#GPIO27のコールバック関数
def cb_gpio27(gpio, level, tick):
    print (f'# {gpio=} : {level=} : {tick/(1000*1000)=}s')

pi = pigpio.pi()
#GPIO27をINでPullUp設定。両エッジでコールバック。
pi.set_mode(27, pigpio.INPUT)
pi.set_pull_up_down(27,pigpio.PUD_UP)
cb = pi.callback(27, pigpio.EITHER_EDGE, cb_gpio27)

#GPIO17=Low
#GPIO17をOUT設定
pi.set_mode(17, pigpio.OUTPUT)
pi.write(17,1)

while(1):
    #1秒毎にトグル
    time.sleep(1)
    pi.write(17,0)
    time.sleep(1)
    pi.write(17,1)

# gpio=27 : level=1 : tick/(1000*1000)=540.658528s
# gpio=27 : level=0 : tick/(1000*1000)=541.659448s
# gpio=27 : level=1 : tick/(1000*1000)=542.660413s
# gpio=27 : level=0 : tick/(1000*1000)=543.661243s
# gpio=27 : level=1 : tick/(1000*1000)=544.662802s
# gpio=27 : level=0 : tick/(1000*1000)=545.664352s
# gpio=27 : level=1 : tick/(1000*1000)=546.665922s
....
```
[戻る](#目次)

## I2C制御
I2Cは、組み込みデバイスを制御するときに使う通信手段。通信速度は数100kbps程度。組み込みデバイスのアドレスを指定して通信を行う。アドレスは7bitで、下位1bitでRead(1)、Write(0)を切り替える。I2Cアドレスがわからない場合は下記で確認できる。下記例では0x1Aと0x50のI2Cデバイスが接続している結果。
```shell:i2c
sudo i2cdetect -y 1

     0  1  2  3  4  5  6  7  8  9  a  b  c  d  e  f
00:                         -- -- -- -- -- -- -- -- 
10: -- -- -- -- -- -- -- -- -- -- 1a -- -- -- -- -- 
20: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- 
30: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- 
40: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- 
50: 50 -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- 
60: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- 
70: -- -- -- -- -- -- -- --       
```


設定内容|API|パラメータ
---|---|---
初期化|pi=pigpio.pi()
I2C Open|handle=pi.i2c_open(ch, addr)|ch:チャネル1に設定<br>addr:アドレス7bit<br>handle:Openハンドル
書き込み|pi.i2c_write_device(handle,data)|handle:<br>　i2c_openの戻り値<br>data:<br>　bytearray型
読み出し|(cnt,data)=<br>pi.i2c_read_device(handle,size)|handle:<br>　i2c_openの戻り値<br>size:<br>　Readバイト数<br>(cnt,data):<br>　読込データtupple型


例として、EEPROM [24FC1025](https://qiita.com/tapitapi/items/23bbe242fc3d482c1b49)のI2Cアクセス例を示す。

```python:pigpio_i2ctest.py
import pigpio
import time

pi = pigpio.pi()

#open adddre=0x50
h = pi.i2c_open(1, 0x50)

#write:offset 0x0000から3byte 0xAA,0xBB,0xCCを書き込む。
pi.i2c_write_device(h, b'\x00\x00\xAA\xBB\xCC')
time.sleep(0.01) #10ms wait EEPROMの書き込み時間用。

#read:offset 0x0000を設定してから3byte読み込む。
pi.i2c_write_device(h, b'\x00\x00')
print(f'# {pi.i2c_read_device(h, 3)=}')

# pi.i2c_read_device(h, 3)=(3, bytearray(b'\xaa\xbb\xcc'))
```
[戻る](#目次)

## SPI制御
SPIは、組み込みデバイスを制御するときに使う通信手段。通信速度は数Mbps程度。高速通信が可能。1対1で通信することが多いが、チップセレクトを使えば、デバイスを指定して通信することもできる。SPIは通信なし時の極性(POL)と通信取り込みエッジ(PHA)の組み合わせで4種類のモードがある。
Mode|POL(通信なし時の極性)|PHA(通信取り込みエッジ)
-|-|-
0|0|0
1|0|1
2|1|0
3|1|1

SPIのAPIは下記。spi_open()のspi_flagsは、22bitで詳細設定ができるが、今回はModeを設定する下位3bitを使う。詳細は https://abyz.me.uk/rpi/pigpio/python.html 参照。

設定内容|API|パラメータ
---|---|---
SPI Open|spi_open(spi_channel, baud, spi_flags)|spi_channel:<br>CE0(0)/CE1(1)選択<br>baud:<br>通信速度bps<br>spi_flags:<br>Mode設定(0/1/2/3)
書き込み|pi.spi_write(handle, data) |handle:<br>spi_openの戻り値<br>data:<br>書込データbytearray型
読み込み|(cnt, data) =<br>pi.spi_read(handle, size)|handle:<br>spi_openの戻り値<br>size:<br>Readバイト数<br>(cnt,data):<br>読込データtupple型
読み書き|(cnt, data) =<br>pi.spi_xfer(handle, data)|handle:<br>spi_openの戻り値<br>data:<br>書込データbytearray型<br>(cnt,data):<br>読込データtupple型

読み書きは、書き込んだByte数分、読み出しができる。

 ```python:pigpio_spitest.py
import pigpio
import time

pi = pigpio.pi()

#open CE0 1000000bps Mode=3
h = pi.spi_open(0, 1000000, 3)

#write 3byte 0x40.0x09.0xFF を書き込み
pi.spi_write(h, b'\x40\x09\xFF') # write 3 bytes

#read 3byte
read_data=pi.spi_xfer(h, 3)
print(f'# {read_data=}')
# read_data=(3, bytearray(b'\x00\x00\x00'))

#write/read 3byte 0x41.0x09.0x00 を書き込み 同時に3byte読み出し
read_data=pi.spi_xfer(h, b'\x41\x09\x00')
print(f'# {read_data=}')
# read_data=(3, bytearray(b'\x00\x00\xFF'))
```
[戻る](#目次)

## おまけ1：UART制御
ラズベリーパイでUART制御が一筋縄でいかなかったので、下記記事にまとめた。

https://qiita.com/tapitapi/items/a12192d034afac5e8dd2

[戻る](#目次)

## おまけ2：bytearray型の扱いについて
bytearray⇔HEX文字列の変換例を下記に示す。
```python:変換
#HEX文字列 → bytearray 変換
hex_string='ABCDEF01ABCDEF01ABCDEF01'
binary=bytes.fromhex(hex_string)
print(type(binary),binary)
# <class 'bytes'> b'\xab\xcd\xef\x01\xab\xcd\xef\x01\xab\xcd\xef\x01'

#bytearray → HEX文字列変換
binary=b'\xab\xcd\xef\x01\xab\xcd\xef\x01\xab\xcd\xef\x01'
hex_string=binary.hex()
print(type(hex_string),hex_string)
# <class 'str'> abcdef01abcdef01abcdef01
```

その他、数値関連の変換は下記にまとめる。参考に。

https://qiita.com/tapitapi/items/e8ea6eb9b23785c42f8f

[戻る](#目次)

以上