RaspberryPi で ValueError: A different mode has already been set!

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ナンバリングを使うのは全然問題がありませんでした。