dotnet / iot

This repo includes .NET Core implementations for various IoT boards, chips, displays and PCBs.
MIT License
2.15k stars 581 forks source link

RFID reader sample can't read card #1869

Open fadyanwar opened 2 years ago

fadyanwar commented 2 years ago

When running the sample found at https://github.com/dotnet/iot/blob/2cc943168d4b20339a4c3046a5c8a952b273afcb/src/devices/Mfrc522/samples/Program.cs

using the below shown command, no result was shown when card was presented to RFID reader

dotnet run --project iot/src/devices/Mfrc522/samples/Mfrc522.Sample.csproj

.NET SDK (reflecting any global.json): Version: 6.0.300 Commit: 8473146e7d

Runtime Environment: OS Name: debian OS Version: 11 OS Platform: Linux RID: debian.11-arm64 Base Path: /home/pi/.dotnet/sdk/6.0.300/

Host (useful for support): Version: 6.0.5 Commit: 70ae3df4a6

.NET SDKs installed: 6.0.300 [/home/pi/.dotnet/sdk]

.NET runtimes installed: Microsoft.AspNetCore.App 6.0.5 [/home/pi/.dotnet/shared/Microsoft.AspNetCore.App] Microsoft.NETCore.App 6.0.5 [/home/pi/.dotnet/shared/Microsoft.NETCore.App]

To install additional .NET runtimes or SDKs: https://aka.ms/dotnet-download

joperezr commented 2 years ago

Thanks for logging the issue @fadyanwar. Can you please share details on which specific card reader are you trying to use? cc: @Ellerbach who wrote most of this code and who has tested it.

joperezr commented 2 years ago

Also did you try debugging it by any chance? If so, did you see if it was able to successfully connect to the device? From what i'm reading in that example there are several ways to connect to the reader, I'm curious as to which one you picked and to see if you have tried the others.

fadyanwar commented 2 years ago

Hello @joperezr The card reader is RF522, the card itself is a ISO14443A and I'm connecting over SPI. I did test using a python script using the same configuration and it's working fine. I've tried to take out segments of code to figure out which line is not working, I think it's the loop part found here https://github.com/dotnet/iot/blob/2cc943168d4b20339a4c3046a5c8a952b273afcb/src/devices/Mfrc522/samples/Program.cs#L113

Find below full output

Hello MFRC522! Do you want to use the MFRC522 on a device like a Raspberry Pi or through a FT4222?

  1. Raspberry Pi or equivalent
  2. FT4222 1 How do you want to connect MFRC5222?
  3. SPI
  4. I2C
  5. UART (Serial Port) 1 Version: 0.0, version should be 1 or 2. Some clones may appear with version 0 Place your Mifare or Ultralight card on the reader. The default B key for Mifare is set to 0xFF 0xFF 0xFF 0xFF 0xFF 0xFF will be used to read the card. The default password for Ultralight is set to 0xFF 0xFF 0xFF 0xFF and will be used if write permissions require authentication.
fadyanwar commented 2 years ago

@joperezr @Ellerbach After some further debugging, I found out that this call is timing out as it calls ReadRegister(Register.ComIrq) which ends up calling SpiReadRegister(Register register) https://github.com/dotnet/iot/blob/43e66f2118c6bc56adf8b234c4824d88a85ae474/src/devices/Mfrc522/Mfrc522.cs#L727 which returns zero, so WaitForCommandToComplete(byte waitIrq) will loop for 36 ms then times out causing the timeout to cascade all the way up to mfrc522.ListenToCardIso14443TypeA(out card, TimeSpan.FromSeconds(2)) call which also happens to have an inverted timeout condition, so it will continue looping without doing anything.

fadyanwar commented 2 years ago

I've created a PR to fix the inverted timeout condition but this doesn't fix the root cause issue as it was merely hiding it. https://github.com/dotnet/iot/pull/1871

krwq commented 2 years ago

Thank you a lot @fadyanwar!

As a side note if you put Fixes: https://github.com/dotnet/iot/issues/1869 or Fixes: #1869 in the first line of the pull request the issue gets auto closed when PR is merged. It's always a good idea to reference related issue in the description anyway. I've already edited your PR description to include that

fadyanwar commented 2 years ago

Thanks @krwq just be aware that this PR does not resolve the root cause as it fixes an issue that was hiding it, so even after it's merged the issue should stay open.

krwq commented 2 years ago

In such cases I'd just go ahead with Contributes to #xyz so we at least know it's related

fadyanwar commented 2 years ago

@krwq @joperezr I've traced the timeout bug to an ioctl Linux system call that returns 2 for a result and sets read and write buffer bytes to zeros which bubbles up through several calls to become a timeout. https://github.com/dotnet/iot/blob/1cae134bbd2958bb1fa7ac608169c33afdba3533/src/System.Device.Gpio/System/Device/Spi/Devices/UnixSpiDevice.cs#L291

As per ioctl man page, it should return zero on success and only few requests return a positive integer. So I was wondering if the SPI_IOC_MESSAGE_1 is one of those few requests or something is wrong here.

fadyanwar commented 2 years ago

Hi @krwq just wondering if anyone had a chance to look at this. Thanks.

joreg commented 1 year ago

same problem here. @fadyanwar did you find a workaround for this by any chance?

fadyanwar commented 1 year ago

@joreg Yes, I used Python.

masuar commented 1 year ago

Same problem here, nobody could have a look yet?

jdbruner commented 1 year ago

I had a similar problem, which I was able to work around by creating the SpiDevice directly rather than using board.CreateSpiDevice(connection). I examined the state of the pins with raspi-gpio. Prior to running the sample, the chip enable pin was configured as an output. After running the sample, it was reconfigured to be the alternate function for the chip enable. That seems like it should be correct - but it doesn't work. I have a setup where I can see activity on the pins via attached LEDs, and the chip enable doesn't change.

When I reconfigured it to an output with raspi-gpio and used SpiDevice.Create(connection), it worked.

pgrawehr commented 1 year ago

@jdbruner I've got an idea what this could be (Wrong operation mode of the CS pin). Can you provide the exact code you use to create the Spi device and the binding, please?

masuar commented 1 year ago

@jdbruner Thanks for your answer! Unfortunately, in my case either before and after execution, the SDA pin is configured as OUTPUT already. What is the alternate function for this pin? I'd like to try it...

jdbruner commented 1 year ago

My setup is a Raspberry Pi 4B with the RFC522 connected to logical pins 9 (MISO), 10 (MOSI), 11 (SCLK), and 7 (CE1) - although I've also tried it with 8 (CE0). Prior to running the sample, raspi-gpio get tells me (abbreviated):

BANK0 (GPIO 0 to 27):
GPIO 0: level=0 fsel=1 func=OUTPUT pull=UP
GPIO 1: level=1 fsel=0 func=INPUT pull=UP
GPIO 2: level=1 fsel=4 alt=0 func=SDA1 pull=UP
GPIO 3: level=1 fsel=4 alt=0 func=SCL1 pull=UP
GPIO 4: level=1 fsel=0 func=INPUT pull=NONE
GPIO 5: level=1 fsel=0 func=INPUT pull=UP
GPIO 6: level=1 fsel=0 func=INPUT pull=UP
GPIO 7: level=1 fsel=1 func=OUTPUT pull=UP
GPIO 8: level=1 fsel=1 func=OUTPUT pull=UP
GPIO 9: level=0 fsel=4 alt=0 func=SPI0_MISO pull=DOWN
GPIO 10: level=0 fsel=4 alt=0 func=SPI0_MOSI pull=DOWN
GPIO 11: level=0 fsel=4 alt=0 func=SPI0_SCLK pull=DOWN
...

When I run the sample as-is, it doesn't work. If I abort with ^C, raspi-gpio get now tells me

BANK0 (GPIO 0 to 27):
GPIO 0: level=0 fsel=1 func=OUTPUT pull=UP
GPIO 1: level=1 fsel=0 func=INPUT pull=UP
GPIO 2: level=1 fsel=4 alt=0 func=SDA1 pull=UP
GPIO 3: level=1 fsel=4 alt=0 func=SCL1 pull=UP
GPIO 4: level=1 fsel=0 func=INPUT pull=NONE
GPIO 5: level=1 fsel=0 func=INPUT pull=UP
GPIO 6: level=1 fsel=0 func=INPUT pull=UP
GPIO 7: level=1 fsel=4 alt=0 func=SPI0_CE1_N pull=UP
GPIO 8: level=1 fsel=1 func=OUTPUT pull=UP
GPIO 9: level=0 fsel=4 alt=0 func=SPI0_MISO pull=DOWN
GPIO 10: level=0 fsel=4 alt=0 func=SPI0_MOSI pull=DOWN
GPIO 11: level=0 fsel=4 alt=0 func=SPI0_SCLK pull=DOWN
...

(GPIO 7 has been reconfigured as CE1). Next, if I leave the pin as-is and change the sample to create the SPI device with

        SpiDevice spi = SpiDevice.Create(connection);

(which doesn't change the pin function) it still doesn't work. However, if I then reconfigure the pin back to an output using raspi-gpio set 7 op, then the sample works as expected.

pgrawehr commented 1 year ago

Have you tried using CE0 (pin8) and changing the line SpiConnectionSettings connection = new(0, 1); to SpiConnectionSettings connection = new(0, 0);? That would be the right combination.

Alternatively you can change the line to SpiConnectionSettings connection = new(0, -1);, thus disabling automatic CS handling and therefore also the change of the pin mode. As long as there's only one device connected to the SPI bus, the CS line has no real function anyway. You can just pull the CS input of your RFC controller to low to make it work. I presume that's why it works when the pin mode is "output" (and the output value is low).

It currently looks to me as if selecting CE1 isn't properly handled in the driver, but mostly because there seems to be no option to do that on the hardware level. The kernel API we use just doesn't offer that option (but due to very poor documentation, I'm not sure).

jdbruner commented 1 year ago

@pgrawehr are you asking me? In my case, when it is configured as an output, the pin is not being held low. My setup has LEDs associated with each GPIO pin, so I can visually see what is happening. When GPIO 7 is configured as an output and I run the sample, I can see the chip select changing state in the polling loop. When it's configured as CE1 there is no activity on that pin - and the MFRC522 is non-responsive.

I get the same result when I use CE0 (pin 8) and new SpiConnectionSettings(0, 0)

It is counter-intuitive to me that this doesn't work when the CE1 pin is configured as the CE1 function; nonetheless, that what I observe.

pgrawehr commented 1 year ago

@jdbruner Thanks for that detailed analysis.

I've found the cause: This documentation page specifies that the hardware CS lines are not used on the Raspi, because of some problems with the hardware. Instead, the driver actually uses the GPIO controller to handle the CS pin, which explains why it's mode must be set to output.

Should not be to difficult to implement this behavior in the RaspberryPiBoard handler.