Repository: gassajor000/pn532pi

Issue:
[BUG] - Allow adjusting SPI bus speed to fix buggy pn532 modules
### What happened?

Many people appear to be struggling to get the pn532 module to work over SPI (myself included). I'm using the elechous pn532 v3 with a rpi 5. I was only able to get it to work by decreasing the SPI bus speed in a rather hacky way.

The solution for me was adding `nfc._interface._spi.max_speed_hz = 100000` after `nfc.begin()`. Otherwise the device would never connect.

Hope that helps someone. Maybe we can make 100kz the default speed or provide an option to adjust the speed in the constructor?

### Reproducibility

Always (100% of runs)

### Steps to Reproduce

1. Connect elechous pn532 v3 module to rpi 5 SPI
2. Run SPI example
3. See that device is not detected


### Code to reproduce

```Python
# Use examples/iso14443a_uid.py
```


### Host Controller

Raspberry Pi 5

### Python Version

3.11.2

### Interface Mode

SPI

### Power Supply

Pi 3.3v or 5v

### Additional Context

_No response_

Hints from issue comments:
@gassajor000: Yes that does seem to be an oversight in the original arduino library. I'll can push a patch to specify a bus speed. 100 KHz seems like a pretty significant speed reduction though. Have you tried anything between 1 and 5 MHz?@gassajor000: Haven't heard anything back so I am just going to lower the default speed to 4 MHz.@ebradbury: Thanks for adjusting this!

Solution patch:
diff --git a/pn532pi/interfaces/pn532spi.py b/pn532pi/interfaces/pn532spi.py
index 92f0626..9c546d1 100644
--- a/pn532pi/interfaces/pn532spi.py
+++ b/pn532pi/interfaces/pn532spi.py
@@ -45,17 +45,20 @@ class Pn532Spi(Pn532Interface):
         data_out = list(_reverse_bits([STATUS_READ, 0]))
         return _reverse_bits(self._spi.xfer2(data_out))[1]
 
-    def __init__(self, ss: int):
+    def __init__(self, ss: int, speed_hz: int=4_000_000):
+        """Pass in slave select pin and optional speed (4MHz default, 5MHz max)"""
         self._command = 0
         self._ss = ss
         self._spi = SpiDev()
+        assert speed_hz <= 5_000_000, "SPI Bus speed must be <= 5MHz"
+        self._speed = speed_hz
         assert ss in [1, 0], 'Chip select must be 1 or 0'
 
     def begin(self):
         self._spi.open(RPI_BUS0, self._ss)
         self._spi.mode = SPI_MODE0  # PN532 only supports mode0
         self._spi.cshigh = False  # Active low
-        self._spi.max_speed_hz = 5000000 # 5 MHz
+        self._spi.max_speed_hz = self._speed
 
     def wakeup(self) -> None:
         # Chip select controlled by driver
diff --git a/setup.py b/setup.py
index c554a65..f06158e 100644
--- a/setup.py
+++ b/setup.py
@@ -5,7 +5,7 @@ with open("README.md", "r") as fh:
 
 setuptools.setup(
     name="pn532pi",
-    version="1.5",
+    version="1.6",
     author="gassajor000",
     author_email="lgassjsg@example.com",
     description="PN532 library for Raspberry Pi",


Test patch:
diff --git a/test/unit/test_pn532spi.py b/test/unit/test_pn532spi.py
index 30ade7f..0a6b1d0 100644
--- a/test/unit/test_pn532spi.py
+++ b/test/unit/test_pn532spi.py
@@ -49,7 +49,7 @@ class TestPn532spi(TestCase):
 
         self.assertEqual(0, MOCK_SPI.mode, "spi mode not set to 0!")
         self.assertEqual(False, MOCK_SPI.cshigh, "spi cshigh not set to False!")
-        self.assertLessEqual(5000000, MOCK_SPI.max_speed_hz, "spi max_speed_hz greater than 5MHz!")
+        self.assertLessEqual(MOCK_SPI.max_speed_hz, 5000000, "spi max_speed_hz greater than 5MHz!")
         MOCK_SPI.open.assert_called_once_with(0, 0)     # SPI Bus 0, SS 0
 
         MOCK_SPI.reset_mock()
@@ -57,6 +57,18 @@ class TestPn532spi(TestCase):
         pn532.begin()
 
         MOCK_SPI.open.assert_called_once_with(0, 1)  # SPI Bus 0, SS 1
+        assert MOCK_SPI.max_speed_hz == 4_000_000
+
+        MOCK_SPI.reset_mock()
+        pn532 = Pn532Spi(ss=Pn532Spi.SS1_GPIO7, speed_hz=int(1e6))
+        pn532.begin()
+
+        assert MOCK_SPI.max_speed_hz == 1_000_000
+
+        MOCK_SPI.reset_mock()
+        with self.assertRaises(AssertionError):
+            pn532 = Pn532Spi(ss=Pn532Spi.SS1_GPIO7, speed_hz=int(10e6))
+
 
     def test_invalidSlaveSelect(self):
         """pn532spi accepts only valid slave select values"""