2
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

gpiozero、smbus、python3-serialを使ってRaspberryPi5のGPIOを色々と触ってみたメモ

Last updated at Posted at 2025-08-23

はじめに

実行環境

  • RaspberryPi 5 8GB
  • RaspberryPi OS 12(Bookworm) x64
    • CLIしか使わなかったけどwith desktopの方

おことわり

基本的にはClaude Sonnet 4にコードを書いてもらい、一部微修正するスタイルと取っています。

gpiozeroについて

GitHub: https://github.com/gpiozero/gpiozero
公式ドキュメント: https://gpiozero.readthedocs.io/en/stable/

  • Ben Nuttall氏とDave Jones氏が主にメンテナンスされている
  • RaspberryPi OSのデフォルトで入っている
  • when_pressedwhen_pressed便利 
    • Whileループの外に定義して、割り込み的にボタンが実行できていることを確認
  • デフォルトがプルアップなのは素敵な気がする(ほかのモジュールもそうかも):https://gpiozero.readthedocs.io/en/stable/api_input.html#button
  • pigpioみたいにi2cは負荷(smbusなどで代用)

PWM(LED)とButton押下で割り込み実行するサンプル

コードはとてもシンプル。PWMをsleepつかって無茶な実行するとかしていないのは嬉しい。

#!/usr/bin/env python3
from gpiozero import PWMLED, Button
from time import sleep

# ボタン押下時の処理
def say_hello():
    print("Hello!")

# GPIO17ピンにボタン接続()
button = Button(17)
button.when_pressed = say_hello

# GPIO18ピンにLEDを接続
led = PWMLED(18)

print("PWM LEDテスト開始...")

try:
    while True:
        # 0から1まで徐々に明るく
        for brightness in range(0, 101):
            led.value = brightness / 100.0
            sleep(0.02)

        # 1から0まで徐々に暗く
        for brightness in range(100, -1, -1):
            led.value = brightness / 100.0
            sleep(0.02)

except KeyboardInterrupt:
    print("\n終了します")
    led.close()

サーボモーターを回すだけのサンプル

Servoむっちゃいい。パルスの最大値と最小値、パルス周期をそのまま引数で指定してできる。特に中央取りようのメソッドがすでに用意されているのは魅力的

ただ、ソフトウェアPWMを使っていてジッターは健在なので注意。
(一応PiGPIOFactoryをServopin_factory引数にわたせればGPIO12,13,18,19でハードウェアPWMは得られるはず。:https://gpiozero.readthedocs.io/en/stable/api_pins.html#gpiozero.pins.pigpio.PiGPIOFactory)

from gpiozero import Servo
from time import sleep

# GPIO18ピンにサーボを接続
servo = Servo(27, min_pulse_width=0.6/1000, max_pulse_width=2.4/1000, frame_width=>

try:
    while True:
        print("最小位置へ移動")
        servo.min()
        sleep(1)

        print("中央位置へ移動")
        servo.mid()
        sleep(1)

        print("最大位置へ移動")
        servo.max()
        sleep(1)

smbus(i2c)

  • gpiozeroだとi2cを触れなかったのでこちらをインストール
    • sudo raspi-configでi2cを有効化して下記実行
sudo apt install python3-smbus i2c-tools

大気圧センサの値取得コード

コードはあまり精査していないので注意。
取り合えず値が取れたことだけ確認

#!/usr/bin/env python3
import smbus
import time

class SMPB02E:
    def __init__(self, i2c_address=0x56, i2c_bus=1):
        """
        2SMPB-02Eセンサを初期化
        """
        self.bus = smbus.SMBus(i2c_bus)
        self.address = i2c_address
        
        # 補償係数を保存する変数
        self.coef = {}
        
        print(f"センサアドレス: 0x{self.address:02X}")
        self.init_sensor()
        self.read_calibration_coefficients()
    
    def init_sensor(self):
        """センサの初期化"""
        print("センサ初期化を開始...")
        
        # チップID確認
        chip_id = self.bus.read_byte_data(self.address, 0xD1)
        if chip_id != 0x5C:
            raise ValueError(f"想定外のチップID: {chip_id:02X}")
        print(f"   ✓ チップID: {chip_id:02X}")
        
        # IIRフィルタ設定
        self.bus.write_byte_data(self.address, 0xF1, 0x00)  # IIRフィルタOFF
        print("   ✓ 初期化完了")
    
    def read_calibration_coefficients(self):
        """補償係数の読み取り(データシート準拠)"""
        print("補償係数を読み取り中...")
        
        # 補償係数レジスタを読み取り(0xA0-0xB8)
        coef_data = []
        for addr in range(0xA0, 0xB9):
            coef_data.append(self.bus.read_byte_data(self.address, addr))
        
        # データシートに基づいて係数を計算
        # b00 (20ビット)
        b00_0 = coef_data[0x01]  # 0xA1
        b00_1 = coef_data[0x00]  # 0xA0
        b00_ex = coef_data[0x18] >> 4  # 0xB8の上位4ビット
        b00_raw = (b00_1 << 12) | (b00_0 << 4) | b00_ex
        if b00_raw & 0x80000:
            b00_raw -= 0x100000
        self.coef['b00'] = b00_raw / 16.0
        
        # a0 (20ビット)
        a0_0 = coef_data[0x13]  # 0xB3
        a0_1 = coef_data[0x12]  # 0xB2
        a0_ex = coef_data[0x18] & 0x0F  # 0xB8の下位4ビット
        a0_raw = (a0_1 << 12) | (a0_0 << 4) | a0_ex
        if a0_raw & 0x80000:
            a0_raw -= 0x100000
        self.coef['a0'] = a0_raw / 16.0
        
        # 16ビット係数の計算
        coef_16bit = {
            'bt1': (0x02, 0x03, -6.3E-03, 4.3E-04),  # 0xA2, 0xA3
            'bt2': (0x04, 0x05, 1.2E-08, 1.2E-06),   # 0xA4, 0xA5
            'bp1': (0x06, 0x07, 3.3E-02, 1.9E-02),   # 0xA6, 0xA7
            'b11': (0x08, 0x09, 2.1E-07, 1.4E-07),   # 0xA8, 0xA9
            'bp2': (0x0A, 0x0B, -6.3E-10, 3.5E-10),  # 0xAA, 0xAB
            'b12': (0x0C, 0x0D, 2.9E-13, 7.6E-13),   # 0xAC, 0xAD
            'b21': (0x0E, 0x0F, 2.1E-15, 1.2E-14),   # 0xAE, 0xAF
            'bp3': (0x10, 0x11, 1.3E-16, 7.9E-17),   # 0xB0, 0xB1
            'a1': (0x14, 0x15, -6.3E-03, 4.3E-04),   # 0xB4, 0xB5
            'a2': (0x16, 0x17, -1.9E-11, 1.2E-10),   # 0xB6, 0xB7
        }
        
        for name, (idx0, idx1, a_val, s_val) in coef_16bit.items():
            raw_val = (coef_data[idx1] << 8) | coef_data[idx0]
            if raw_val & 0x8000:
                raw_val -= 0x10000
            self.coef[name] = (a_val + s_val * raw_val / 32767.0)
        
        print("   ✓ 補償係数読み取り完了")
    
    def trigger_measurement(self):
        """測定開始"""
        # 強制測定開始 (temp_ave=2, press_ave=8, forced mode)
        self.bus.write_byte_data(self.address, 0xF4, 0x51)  # 0101 0001
        time.sleep(0.02)  # 測定完了待機
    
    def read_raw_data(self):
        """生データの読み取り"""
        # 温度データ
        temp_data = []
        for addr in [0xFA, 0xFB, 0xFC]:  # TEMP_TXD2, TXD1, TXD0
            temp_data.append(self.bus.read_byte_data(self.address, addr))
        
        # 圧力データ
        press_data = []
        for addr in [0xF7, 0xF8, 0xF9]:  # PRESS_TXD2, TXD1, TXD0
            press_data.append(self.bus.read_byte_data(self.address, addr))
        
        # 24ビットデータに結合
        raw_temp = (temp_data[0] << 16) | (temp_data[1] << 8) | temp_data[2]
        raw_press = (press_data[0] << 16) | (press_data[1] << 8) | press_data[2]
        
        # データシートに従い2^23を減算
        dt = raw_temp - (1 << 23)
        dp = raw_press - (1 << 23)
        
        return dt, dp
    
    def calculate_temperature(self, dt):
        """温度計算(データシート準拠)"""
        # Tr = a0 + a1*Dt + a2*Dt²
        a0 = self.coef['a0']
        a1 = self.coef['a1']
        a2 = self.coef['a2']
        
        tr = a0 + a1 * dt + a2 * (dt ** 2)
        temperature_c = tr / 256.0  # データシートに従い256で除算
        
        return temperature_c, tr
    
    def calculate_pressure(self, dp, tr):
        """圧力計算(データシート準拠)"""
        # Pr = b00 + bt1*Tr + bp1*Dp + b11*Tr*Dp + bt2*Tr² + bp2*Dp² + b12*Tr²*Dp + b21*Tr*Dp² + bp3*Dp³
        b00 = self.coef['b00']
        bt1 = self.coef['bt1']
        bp1 = self.coef['bp1']
        b11 = self.coef['b11']
        bt2 = self.coef['bt2']
        bp2 = self.coef['bp2']
        b12 = self.coef['b12']
        b21 = self.coef['b21']
        bp3 = self.coef['bp3']
        
        pressure_pa = (b00 + 
                      bt1 * tr + 
                      bp1 * dp + 
                      b11 * tr * dp + 
                      bt2 * (tr ** 2) + 
                      bp2 * (dp ** 2) + 
                      b12 * (tr ** 2) * dp + 
                      b21 * tr * (dp ** 2) + 
                      bp3 * (dp ** 3))
        
        return max(pressure_pa, 0)  # 負の値を防止
    
    def read_pressure_temperature(self):
        """大気圧と温度を読み取り"""
        self.trigger_measurement()
        
        # 生データ取得
        dt, dp = self.read_raw_data()
        
        # 計算
        temperature, tr = self.calculate_temperature(dt)
        pressure = self.calculate_pressure(dp, tr)
        
        return pressure, temperature, dt, dp
    
    def close(self):
        """リソースの解放"""
        self.bus.close()


def main():
    """メイン処理"""
    sensor = None
    try:
        # センサの初期化
        sensor = SMPB02E()
        print("2SMPB-02E 大気圧センサを初期化しました")
        print("大気圧の測定を開始します... (Ctrl+Cで終了)\n")
        
        # 連続測定ループ
        measurement_count = 0
        while True:
            try:
                # 大気圧と温度を測定
                pressure_pa, temperature_c, dt, dp = sensor.read_pressure_temperature()
                pressure_hpa = pressure_pa / 100.0  # hPa変換
                
                measurement_count += 1
                print(f"[{measurement_count:3d}] 大気圧: {pressure_pa:.1f} Pa ({pressure_hpa:.2f} hPa) | 温度: {temperature_c:.1f}°C")
                print(f"      生データ: Dt={dt} Dp={dp}")
                
                time.sleep(2)  # 2秒間隔
                
            except KeyboardInterrupt:
                print("\n測定を終了します")
                break
            except Exception as e:
                print(f"測定エラー: {e}")
                time.sleep(1)
    
    except Exception as e:
        print(f"センサ初期化エラー: {e}")
    
    finally:
        if sensor:
            sensor.close()

if __name__ == "__main__":
    main()

とりあえず値を取れてるみたい。i2cの動作確認ができれば一旦OK。

image.png

python3-serial(UART)

  • sudo raspi-configにてSerial PortをEnable, Serial LoginをDisableに設定
  • sudo apt install python3-serialにてインストール
  • GPIO14とGPIO15をループバックにて接続

簡単なUARTループバックコード

#!/usr/bin/env python3
import time
import serial

# Raspberry Pi 5では ttyAMA0 を使用
uart = serial.Serial('/dev/ttyAMA0', baudrate=9600, timeout=1)

for i in range(5):
    message = f"Test{i}"
    print(f"送信: {message}")
    
    # 送信
    uart.write(message.encode('utf-8'))
    time.sleep(0.1)
    
    # 受信
    if uart.in_waiting > 0:
        received = uart.read(uart.in_waiting).decode('utf-8')
        print(f"受信: {received}")
    else:
        print("受信なし")
    
    time.sleep(1)

uart.close()
print("テスト完了")

image.png

まとめ

  • PWM, I2C, UARTを一通り試してみた
  • gpiozeroはメソッドがきれいに抽象化されているので可読性が前に比べて良くなったかも
  • I2CとUARTはgpiozeroにて実装できないため、smbus並びにpython3-serialなど別モジュールが必要
  • claude sonnet 4は神
2
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?