RaspberryPi で、1602LCD用 Adafruit_CharLCD ライブラリ と ICカードリーダ用 pirc522 ライブラリを同時に使うと、
ValueError: A different mode has already been set!
エラーが出ます。
環境
- Raspbian GNU/Linux 10 (buster)
- Raspberry Pi 3 Model B
- Adafruit_CharLCD ライブラリ
- pirc522 ライブラリ
- Python 3.7.3
コード
from pirc522 import RFID
import Adafruit_CharLCD as LCD
rdr = RFID()
発生するエラー
Traceback (most recent call last):
File "read_once_with_key_LCD.py", line 4, in <module>
rdr = RFID()
File "/usr/local/lib/python3.7/dist-packages/pi_rc522-2.2.1-py3.7.egg/pirc522/rfid.py", line 88, in __init__
ValueError: A different mode has already been set!
原因
これは、 Adafruit_CharLCD.py で BCM番号、pirc522 で BOARD番号でGPIOを指定しているからです。
エラーの再現
pi@raspberrypi:~ $ python3
Python 3.7.3 (default, Dec 20 2019, 18:57:59)
[GCC 8.3.0] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> import RPi.GPIO as GPIO
>>> GPIO.setmode(GPIO.BCM)
>>> GPIO.setmode(GPIO.BOARD)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
ValueError: A different mode has already been set!
再現する。
cleanup でエラー回避
>>> import RPi.GPIO as GPIO
>>> GPIO.setmode(GPIO.BCM)
>>> GPIO.setup(11,GPIO.IN)
>>> GPIO.cleanup()
>>> GPIO.setmode(GPIO.BOARD)
cleanup するとエラーが出なくなる。ちなみに cleanup する前に一度適当なIO操作をしておかないとうまくいかない。上記の例ではGPIO.setup(11,GPIO.IN)
となっているところ。
cleanup の問題
cleanup すると GPIO.setmode がクリアされる。
それぞれのライブラリを呼び出すごとに cleanup すればと思ったが、ライブラリで使う setmode が消えてしまうので別の方法を考える。
pirc522 ライブラリ
読んでみました
class RFID(object):
pin_rst = 22
pin_ce = 0
pin_irq = 18
mode_idle = 0x00
・
・
・
def __init__(self, bus=0, device=0, speed=1000000, pin_rst=def_pin_rst,
pin_ce=0, pin_irq=def_pin_irq, pin_mode = def_pin_mode):
self.pin_rst = pin_rst
self.pin_ce = pin_ce
self.pin_irq = pin_irq
self.spi = SPIClass()
self.spi.open(bus, device)
if board == RASPBERRY:
self.spi.max_speed_hz = speed
else:
self.spi.mode = 0
self.spi.msh = speed
if pin_mode is not None:
GPIO.setmode(pin_mode)
となっていて、pin_mode は以下のようになっています。
import threading
RASPBERRY = object()
BEAGLEBONE = object()
board = RASPBERRY
try:
# Try with Raspberry PI imports first
import spidev
import RPi.GPIO as GPIO
SPIClass = spidev.SpiDev bus=0, device=0, speed=1000000, pin_rst=def_pin_rst,
pin_ce=0, pin_irq=def_pin_irq, pin_mode = def_pin_mode):
def_pin_rst = 22
def_pin_irq = 18
def_pin_mode = GPIO.BOARD
Adafruit_CharLCDライブラリ
読んでみました
class Adafruit_CharLCD(object):
"""Class to represent and interact with an HD44780 character LCD display."""
def __init__(self, rs, en, d4, d5, d6, d7, cols, lines, backlight=None,
invert_polarity=True,
enable_pwm=False,
gpio=GPIO.get_platform_gpio(),
pwm=PWM.get_platform_pwm(),
initial_backlight=1.0):
となっていますね。gpio=GPIO.get_platform_gpio()って何だ?
https://github.com/adafruit/Adafruit_Python_GPIO/blob/master/Adafruit_GPIO/GPIO.py
を見てみると、
def get_platform_gpio(**keywords):
"""Attempt to return a GPIO instance for the platform which the code is being
executed on. Currently supports only the Raspberry Pi using the RPi.GPIO
library and Beaglebone Black using the Adafruit_BBIO library. Will throw an
exception if a GPIO instance can't be created for the current platform. The
returned GPIO object is an instance of BaseGPIO.
"""
plat = Platform.platform_detect()
if plat == Platform.RASPBERRY_PI:
import RPi.GPIO
return RPiGPIOAdapter(RPi.GPIO, **keywords)
elif plat == Platform.BEAGLEBONE_BLACK:
となっていて、
class RPiGPIOAdapter(BaseGPIO):
"""GPIO implementation for the Raspberry Pi using the RPi.GPIO library."""
def __init__(self, rpi_gpio, mode=None):
self.rpi_gpio = rpi_gpio
# Suppress warnings about GPIO in use.
rpi_gpio.setwarnings(False)
# Setup board pin mode.
if mode == rpi_gpio.BOARD or mode == rpi_gpio.BCM:
rpi_gpio.setmode(mode)
elif mode is not None:
raise ValueError('Unexpected value for mode. Must be BOARD or BCM.')
else:
# Default to BCM numbering if not told otherwise.
rpi_gpio.setmode(rpi_gpio.BCM)
# Define mapping of Adafruit GPIO library constants to RPi.GPIO constants.
self._dir_mapping = { OUT: rpi_gpio.OUT,
IN: rpi_gpio.IN }
self._pud_mapping = { PUD_OFF: rpi_gpio.PUD_OFF,
PUD_DOWN: rpi_gpio.PUD_DOWN,
PUD_UP: rpi_gpio.PUD_UP }
self._edge_mapping = { RISING: rpi_gpio.RISING,
FALLING: rpi_gpio.FALLING,
BOTH: rpi_gpio.BOTH }
となっています。
それぞれ、デフォルトだとpirc522 ライブラリ
GPIO.BCM、 Adafruit_CharLCD(objeだと、GPIO.BORAD になってますね。
BCMに統一することにします。以下のように呼び出したらうまくいきました。
import sys
from pirc522 import RFID
import RPi.GPIO as GPIO
# rdr = RFID() # サンプルプログラムの書き方
# rdr = RFID(0,0,1000000,22,0,18,GPIO.BOARD) # GPIO.BOARD での指定
rdr = RFID(0,0,1000000,25,0,24,GPIO.BCM) # GPIO.BCM での指定
おまけ
上記は GPIO ライブラリを使う時の問題でした。
pirc522 では GPIO ライブラリを内部で使用、
Adafruit_CharLCD.py では Adafruit_GPIO ライブラリを使っていますがそこから更に GPIO ライブラリを呼んでいてそこで GPIO.setmode の競合が起こっているということでした。
あくまでこれは GPIO ライブラリの内部保持している情報に関わることで、RaspberryPiのハードウェアに由来する制限ではありません。 なので、上記のプログラムに更に WiringPi を混ぜ、異なるGPIOナンバリングを使うのは全然問題がありませんでした。