はじめに
Raspberry Pi5を購入したので、pigoioでGPIOの制御を使おうと思ったが対応していないらしい。pigpioの代わりに下記を使ってみた。使い分けは必要だが、同等のことができることは確認できた。
- GPIO制御:gpiozero
- I2C制御:smbus2
- SPI制御:spidev
- PWM制御:rpi_hardware_pwm
Raspberry Pi4やZero2であればpigpioを使えるので、下記記事を参照。
目次
Raspberry Piのピン配置
5Vと3.3Vの電源出力を備えている。GPIOは3.3V。GPIOはシリアル通信のI2C/SPI/UART/PWMを兼ねている。ピン配置はRaspberry Pi/Raspberry Pi Zero共通。下図は公式サイトより。
| 種別 | Pin.No | GPIO.No |
|---|---|---|
| I2C | Pin3:SDA Pin5:SCL |
GPIO2 GPIO3 |
| UART(シリアルコンソールと兼用) | Pin8:TXD Pin10:RXD |
GPIO14 GPIO15 |
| SPI | Pin19:MOSI Pin21:MISO Pin23:SCLK Pin24:CE0(チップセレクト0) Pin26:CE1(チップセレクト1) |
GPIO10 GPIO9 GPIO11 GPIO8 GPIO7 |
| GPIO | Pin.11 Pin.13 Pin.15 Pin.29 Pin.31 Pin.37 |
GPIO17 GPIO27 GPIO22 GPIO5 GPIO6 GPIO26 |
| GPIO | Pin.16 Pin.18 Pin.22 Pin.36 |
GPIO23 GPIO24 GPIO25 GPIO16 |
| PWM | Pin.32 Pin.33 |
PWM0 PWM1 |
ペリフェラル通信の設定
ペリフェラル通信の設定はsudo raspi-configの「3 Interface Options」で「I4 SPI」「I5 I2C」を<Yes>で設定する。
GPIO制御
GPIOはgpiozeroを使えばIN/OUT/割り込みができる。まずは、モジュールのインストールとpipを実施する。
sudo apt install python3-gpiozero
pip install gpiozero --break-system-packages
代表的なAPIは下記の通り
| API | 概要 |
|---|---|
| out = gpiozero.DigitalOutputDevice(pin=27,initial_value=0) | GPIO-OUTの設定。pinでGPIOを指定する。initial_valueで初期値を設定。 |
| in = gpiozero.DigitalInputDevice(pin=17,pull_up=True,bounce_time=0.05) | GPIO-INの設定。pinでGPIOを指定する。pull_upで内蔵プルアップの設定、bounce_timeでチャタリング除去の時間(sec)を設定できる。 |
| in.when_activated = callback() | GPIO-INでHigh検出時にコールするコールバック関数を設定する。 |
| in.when_deactivated = callback() | GPIO-INでLow検出時にコールするコールバック関数を設定する。 |
| out.value | 1,0を設定するとHigh,Low出力する。 |
| in.value | 1=High,0=Lowが読める。 |
GPIOの17と27を接続して下記を動作させると、0.5秒周期で状態変化がprint()で出力されるコード。
import gpiozero
import time
# PIN27をOUTで設定
pinO_27 = gpiozero.DigitalOutputDevice(pin=27,initial_value=0)
# PIN17をINで設定(プルアップ(pull_up=True)、チャタリング除去を50ms(bounce_time=0.05)で設定
PinI_17 = gpiozero.DigitalInputDevice(pin=17,pull_up=True,bounce_time=0.05)
# PIN17の割り込みコールバック関数
def pin_callback(pin,value):
print(f'{pin=},{value=}')
#割り込コールバック設定 lamdbaでコールバック関数を汎用化
PinI_17.when_activated = lambda:pin_callback(17,1) # High時にコール
PinI_17.when_deactivated = lambda:pin_callback(17,0) # Low時にコール
while(1):
pinO_27.value=1
time.sleep(0.5)
pinO_27.value=0
time.sleep(0.5)
pinO_27.close()
PinI_17.close()
I2C制御
I2Cはsmbus2がpigpioの代わりになる。まずは、モジュールのインストールとpipを実施する。
sudo apt install python3-smbus
pip install smbus2 --break-system-packages
代表的なAPIは下記の通り
| API | 概要 |
|---|---|
| h = SMBus(1) | I2C1のオープン |
| msg = i2c_msg.write(addr, list(data)) | I2C Writeするためのデータをmsgに設定する。dataはbytearray型 |
| h.i2c_rdwr(msg) | msgに設定されたデータをI2C Writeする。 |
| msg = i2c_msg.read(addr, length) | I2C Readするためのバッファをmsgに設定する。 |
| h.i2c_rdwr(msg) | I2C Readしたデータをmsgに詰める。 |
アドレス0x3CにWrite/Readする例。接続されているI2Cのアドレスはsudo i2cdetect -y 1で確認できる。
from smbus2 import SMBus, i2c_msg
h = SMBus(1)
#i2c write関数化
def i2c_write_device(h, addr, data):
msg = i2c_msg.write(addr, list(data))
h.i2c_rdwr(msg)
#i2c read関数化
def i2c_read_device(h, addr, length):
msg = i2c_msg.read(addr, length)
h.i2c_rdwr(msg)
return bytes(msg)
# 0x3Cに 0x00,0xA8,0x3Fを Writeする
i2c_write_device(h, 0x3C, b'\x00\xA8\x3F')
# 0x3Cから3byte Readする
data = i2c_read_device(h, 0x3C, 3)
SPI制御
SPIはspidevがpigpioの代わりになる。まずは、モジュールのpipを実施する。
pip install spidev --break-system-packages
代表的なAPIは下記の通り
| API | 概要 |
|---|---|
| SPI=spidev.SpiDev() SPI.open(0, 0) |
初期化 |
| SPI.mode=0 | SPIのモード設定0,1,2,3 |
| SPI.max_speed_hz=100000 | 通信周波数設定Hz単位 |
| rcv_data = SPI.xfer2(send_data) | SPIの送受信。send_dataに送信データ列を設定。rcv_dataに受信したデータ列が格納される。 |
import spidev
# SPI初期化-------------
# bus=0, device=0(/dev/spidev0.0)でモード0で10MHzで初期化
SPI=spidev.SpiDev()
SPI.open(0, 0)
SPI.mode=0
SPI.max_speed_hz=100000
# SPI送受信--------------
#送信データ binnary_array型で設定
send_data=bytes.fromhex('ABCDEF01ABCDEF01ABCDEF01')
#SPI送受信
rcv_data = SPI.xfer2(send_data)
#binarry列をHEX文字列に変換
print(rcv_data.hex().upper())
PWM制御
rpi_hardware_pwmを使えばHWでPWMができる。まずPWMを使う場合/boot/firmware/config.txtに下記行を追加し、PWM機能を有効にする。RaspberryPi5の場合、ch=2(GPIO18)、ch=3(GPIO19)となる点注意。
- GPIO12=ch0/GPIO13=ch1の場合
[all]
dtoverlay=pwm-2chan,pin=12,func=4,pin2=13,func2=4
- GPIO18=ch2/GPIO19=ch3の場合
[all]
dtoverlay=pwm-2chan
rpi_hardware_pwmモジュールをpipで取得する。
pip install rpi_hardware_pwm --break-system-packages
pwm = HardwarePWM()でPWMのチャネルと周波数を設定する。pwm.start()でデューティーを設定し出力開始する。pwm.change_frequency()で周波数の変更、pwm.change_duty_cycleでデューティーの変更ができる。
from rpi_hardware_pwm import HardwarePWM
import time
# GPIO12はPWMチャネル0に対応
pwm = HardwarePWM(pwm_channel=0, hz=48000) # 1Hz
pwm.start(10) # デューティ比10%
time.sleep(5)
pwm.change_frequency(10) # 10Hzに変更
time.sleep(5)
pwm.change_duty_cycle(50) #デューティー比50%に変更
time.sleep(5)
pwm.change_frequency(100) # 100Hzに変更
time.sleep(5)
pwm.stop()
rpi_hardware_pwm はDMAと組み合わせてPWMを制御することができない。そのため、pigpio の wave_chain() のように、事前にデューティ比の配列を用意してリアルタイムに切り替えるような使い方ができないのが惜しいところ。
動作確認
RaspberryPi5とEEPROM(24FC1025)、IOExpander(MPC23S08)、LED、SWを接続して確認してみた。
接続は下記の通り
- GPIO02:SDA :24FC1025 SDA
- GPIO03:SCL :24FC1025 SCL
- GPIO27:GPO :MPC23S08 CS
- GPIO22:GPO :MPC23S08 RST
- GPIO10:MOSI:MPC23S08 Rx
- GPIO09:MSIO:MPC23S08 Tx
- GPIO11:SCLK:MPC23S08 CLK
- GPIO06:GPI :SW
- GPIO13:PWM1:LED Yellow
- GPIO26:GPO :LED Blue
- GPIO12:PWM0:PinHeader
コードは下記の通り、PWM1を定期的にデューティーと周波数を変えながら、SW押下で、LED、24FC1025、MPC23S08にアクセスする。
from smbus2 import SMBus, i2c_msg
import gpiozero
from rpi_hardware_pwm import HardwarePWM
import spidev
import time
# sudo i2cdetect -y 1
# 0 1 2 3 4 5 6 7 8 9 a b c d e f
#00: -- -- -- -- -- -- -- --
#10: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
#20: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
#30: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
#40: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
#50: 50 -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
#60: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
#70: -- -- -- -- -- -- -- --
# GPIO02:SDA :24FC1025 SDA
# GPIO03:SCL :24FC1025 SCL
# GPIO27:GPO :MPC23S08 CS
# GPIO22:GPO :MPC23S08 RST
# GPIO10:MOSI:MPC23S08 Rx
# GPIO09:MSIO:MPC23S08 Tx
# GPIO11:SCLK:MPC23S08 CLK
# GPIO06:GPI :SW
# GPIO13:PWM1:LED Yellow
# GPIO26:GPO :LED Blue
# GPIO12:PWM0:PinHeader
def main():
# SWの割り込みコールバック関数
def pin_callback(pin,value):
if(value==0):
if(LED_B.value == 1):
LED_B.value = 0
#I2CWriteアクセス 24FC1025
i2c_write_device(I2C, 0x50, b'\x00\x00\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0A')
#SPI writeアクセス MPC23S08
spi_access(SPI, CS, b'\x40\x00\x00') # MPC23S08 OUTモードに設定
spi_access(SPI, CS, b'\x40\x09\x00') # MPC23S08 Low出力に設定
else:
LED_B.value = 1
#I2CWriteアクセス 24FC1025
i2c_write_device(I2C, 0x50, b'\x00\x00\x0A\x09\x08\x07\x06\x05\x04\x03\x02\x01')
#SPI writeアクセス MPC23S08
spi_access(SPI, CS, b'\x40\x00\x00') # MPC23S08 OUTモードに設定
spi_access(SPI, CS, b'\x40\x09\xFF') # MPC23S08 High出力に設定
else:
#I2CReadアクセス 24FC1025
i2c_write_device(I2C, 0x50, b'\x00\x00')
rcv_data = i2c_read_device(I2C, 0x50, 10)
print(f'I2C={rcv_data.hex().upper()}')
#SPI Readアクセス MPC23S08
rcv_data=spi_access(SPI, CS, b'\x41\x00\x00') # IN/OUTモードレジスタ
print(f'SPI={rcv_data.hex().upper()}')
rcv_data=spi_access(SPI, CS, b'\x41\x09\x00') # 出力状態
print(f'SPI={rcv_data.hex().upper()}')
#I2C write
def i2c_write_device(h, addr, data):
msg = i2c_msg.write(addr, list(data))
h.i2c_rdwr(msg)
#I2C Read
def i2c_read_device(h, addr, length):
msg = i2c_msg.read(addr, length)
h.i2c_rdwr(msg)
return bytes(msg)
#SPI
def spi_access(h,cs,send_data):
cs.value=0
rcv_data = h.xfer2(send_data)
cs.value=1
return bytes(rcv_data)
try:
#初期化
LED_B = gpiozero.DigitalOutputDevice(pin=26,initial_value=0)
SW = gpiozero.DigitalInputDevice(pin=6,pull_up=True,bounce_time=0.05)
PWM_LED_Y = HardwarePWM(pwm_channel=1, hz=1000)
CS = gpiozero.DigitalOutputDevice(pin=27,initial_value=1)
RST = gpiozero.DigitalOutputDevice(pin=22,initial_value=0)
I2C = SMBus(1)
SPI = spidev.SpiDev()
SPI.open(0, 0) # bus=0, device=0(/dev/spidev0.0)
SPI.mode = 0 # mode =0
SPI.max_speed_hz = 100000 # 10MHz
#割り込コールバック設定 lamdbaでコールバック関数を汎用化
SW.when_activated = lambda:pin_callback(26,0) # Low時にコール
SW.when_deactivated = lambda:pin_callback(26,1) # High時にコール
# MPC23S08 RST解除
RST.value=1
while(1):
PWM_LED_Y.start(10) # デューティ比10%
time.sleep(2)
PWM_LED_Y.change_frequency(10) # 10Hzに変更
time.sleep(2)
PWM_LED_Y.change_duty_cycle(50) #デューティー比50%に変更
time.sleep(2)
PWM_LED_Y.change_frequency(100) # 100Hzに変更
time.sleep(2)
except KeyboardInterrupt:
#終了処理
LED_B.close()
SW.close()
PWM_LED_Y.stop()
I2C.close()
SPI.close()
RST.close()
CS.close()
print('強制終了')
if __name__ == "__main__":
main()

