Piicodev RFID in CircuitPython

I’m using a Piicodev RFID reader on a Pi Pico. Based on the tag id read by the reader, I’d like to send various strings to my computer via usb hid (keyboard). I know CircuitPython can send strings as a keyboard emulator, but is the Piicodev RFID reader library compatible with CircuitPython?

I’ve done the demo of the RFID reader in micropython, but that doesn’t seem to have usb hid functionalities.

Any suggestions for doing both things?

2 Likes

Check if there are other RFID libraries for CircuitPython that support USB HID functionality. You might find a different RFID library that meets your requirements and is compatible with CircuitPython.

2 Likes

I’d be really surprised if Adafruit (CircuitPython) had a driver/library for a CoreElectronics (Piicodev) product.
Did you have something specific in mind?

2 Likes

CircuitPython may have a library that works with the MFRC522 which is the chip used on the piicodev module. If that’s the case then you can do your whole project in CircuitPython instead

Here’s one i found on github
You may just have to choose the right I2C pins and device address

1 Like

Hi Michael,
Yes, searching for the chip (MFRC522) was the key to finding something for this. However, all of the libraries I’ve found for CircuitPython including the one you referenced use the SPI interface rather than I2C like the PiicoDev RFID reader uses. They are all based off of this one as well. I tried changing the interface, but have not gotten any cards to read.

I really like the PiicoDev RFID port even though it’s written in Micropython. The programming style is much clearer. So I tried porting this to CircuitPython myself. There appear to be only small changes needed. But still no card read. Once you get past the core read/write functions, the code is largely the same between the PiicoDev library and the domdfcoding library, but some of the values are different (i.e. different initialization). The PiicoDev library did work under microPython. I’ve never gotten the domdfcoding library to work under CircuitPython.

Here are the changes I’ve made. Really just to substitute I2C for SPI and doing separate write statements one byte at a time instead of using the write_to_mem function that CircuitPython doesn’t have.

def __init__(self, bus=None, sda=None, scl=None, address=_I2C_ADDRESS, asw=None):
        scl = board.GP9
        sda = board.GP8

        self.i2c = busio.I2C(scl, sda)
        
        if type(asw) is list: # determine address from ASW switch positions (if provided)
            assert max(asw) <= 1 and min(asw) >= 0 and len(asw) is 2, "asw must be a list of 1/0, length=2"
            self.address=_I2C_ADDRESS+asw[0]+2*asw[1]
        else:
            self.address = address # fall back on using address argument
        print(f'Address: {address:02X}')
        self.rfid = I2CDevice(self.i2c, self.address)
        
        self.reset()
        self.sleep_ms(50)
        self._wreg(_REG_T_MODE, 0x80)
...and a bunch more reads and writes that are identical but I took out of this code for readability...
        self.antennaOn()
        print('init finished')
    
    # I2C write to register
    def _wreg(self, reg, val):
        with self.rfid:
            self.rfid.write(bytes([reg]))
            self.rfid.write(bytes([val]))
        print(f'wreg: {reg:02X} val: {val:02X}')

    # I2C write to FIFO buffer
    def _wfifo(self, reg, val):
        with self.rfid:
            self.rfid.write(bytes([reg]))
            self.rfid.write(bytes([val]))
        print(f'wfifo: {reg:02X} val: {val:02X}')

    # I2C read from register
    def _rreg(self, reg):
        with self.rfid:
            self.rfid.write(bytes([reg]))
            val = bytearray(2)
            self.rfid.readinto(val)
            print(f'rreg: {reg:02X} val: {val[0]:X} {val[1]:X}')
            return val[0]

It seems to read from the 522 ok, but I’m not sure that it’s writing. For example, when I write to

# Turns the antenna on
    def antennaOn(self):
        if ~(self._rreg(_REG_TX_CONTROL) & 0x03):
            self._sflags(_REG_TX_CONTROL, 0x83)
            self._rreg(_REG_TX_CONTROL)

The read after the write is the same as the original read in the ‘if’ statement, so I’m not convinced it’s writing.

Any help greatly appreciated.
Thanks, Cyle

1 Like

Here’s some sample output. After this, it just repeats the last bit.
This morning, I thought the 522 might have been in some unstable state, so I power cycled it. Same response.

Address: 2C
wreg: 01 val: 0F
wreg: 2A val: 80
wreg: 2B val: A9
wreg: 2C val: 03
wreg: 2D val: E8
wreg: 15 val: 40
wreg: 11 val: 3D
wreg: 03 val: 80
wreg: 02 val: 20
rreg: 14 val: 80 80
rreg: 14 val: 80 80
wreg: 14 val: 83
rreg: 14 val: 80 80
init finished
tag present?
readTagID
detectTag
request
wreg: 0D val: 07
tocard
wreg: 01 val: 00
wreg: 04 val: 7F
rreg: 0A val: 0 0
wreg: 0A val: 80
wfifo: 09 val: 26
rreg: 0D val: 0 0
wreg: 0D val: 00
wreg: 01 val: 0C
rreg: 0D val: 0 0
wreg: 0D val: 80
rreg: 04 val: 14 14
rreg: 0D val: 0 0
wreg: 0D val: 00
rreg: 06 val: 0 0
rreg: 0A val: 0 0
rreg: 0C val: 10 10
rreg: 09 val: 0 0
detectTag
request
wreg: 0D val: 07
tocard
wreg: 01 val: 00
wreg: 04 val: 7F
rreg: 0A val: 0 0
wreg: 0A val: 80
wfifo: 09 val: 26
rreg: 0D val: 0 0
wreg: 0D val: 00
wreg: 01 val: 0C
rreg: 0D val: 0 0
wreg: 0D val: 80
rreg: 04 val: 14 14
rreg: 0D val: 0 0
wreg: 0D val: 00
rreg: 06 val: 0 0
rreg: 0A val: 0 0
rreg: 0C val: 10 10
rreg: 09 val: 0 0
got one? {'type': '', 'id_formatted': '', 'success': False, 'id_integers': [0]}
tag present?

This is my code.py. It’s essentially setting up the reader object and then calling TagPresent() repeatedly since it never finds one.

rfid = PiicoDev_RFID()	#Initialise the RFID module

def sleep_ms(t):
        sleep(t/1000)

while True:    
    if rfid.tagPresent():    # if an RFID tag is present
#        id = rfid.readID()   # get the id
        id = rfid.readID(detail=True) # gets more details eg. tag type

        print(id)            # print the id

    sleep_ms(1000)
1 Like

This is a great attempt so far @Cyle258478 :smiley:

The following set off a couple alarm bells and might be worth investigating.

Is this performing two separate write transactions? if so, this might be where the problem is. Two separate transactions means that:

  • reg gets written as the destination register, and then the transaction is aborted.
  • Then val gets written as the destination register, and then the transaction is aborted. here val is the first byte in a transaction following the device address, which by convention is value for the destination register.

unless write is a low-level operation that doesn’t send a stop bit or wait for an ack from the device, but i don’t think this is the case. It’s set up to read a buffer object which can contain multiple bytes.

my intuition tells me you might want to send the register and the value in the same transaction as follows:
self.rfid.write(bytes([reg, val]))

disclaimer, i haven’t delved into the CPython docs so this might be irrelevant. I had a stab at finding how the write method is defined but got lost in the massive docs library

1 Like

That’s a great idea. At least it’s something to try cause I was stumped!

I’ll let you know how it goes this evening.

Cyle

1 Like

Well, that’s progress. I’m getting something different when the tag is present now which tells me I’m on the right track.
Now when we turn on the antenna, it actually happens.

rreg: 14 val: 80 80
rreg: 14 val: 80 80
wreg: 14 val: 83
rreg: 14 val: 83 83
init finished

I didn’t have time last night to do more than just the simple changed you suggested Michael. I’ll keep looking. Seems like multiple bytes are being sent to read the fifo buffer.

tag present?
readTagID
detectTag
request
wreg: 0D val: 07
tocard
wreg: 01 val: 00
wreg: 04 val: 7F
rreg: 0A val: 0 0
wreg: 0A val: 80
rreg: 0D val: 7 7
wreg: 0D val: 07
wreg: 01 val: 0C
rreg: 0D val: 7 7
wreg: 0D val: 87
rreg: 04 val: 64 64
rreg: 0D val: 7 7
wreg: 0D val: 07
rreg: 06 val: 0 0
rreg: 0A val: 2 2
rreg: 0C val: 10 10
rreg: 09 val: 44 0
rreg: 09 val: 9F 9F
wreg: 0D val: 00
tocard
wreg: 01 val: 00
wreg: 04 val: 7F
rreg: 0A val: 0 0
wreg: 0A val: 80
Traceback (most recent call last):
  File "<stdin>", line 10, in <module>
  File "PiicoDev_RFID_CircuitPython.py", line 339, in tagPresent
  File "PiicoDev_RFID_CircuitPython.py", line 322, in readTagID
  File "PiicoDev_RFID_CircuitPython.py", line 254, in _readTagID
  File "PiicoDev_RFID_CircuitPython.py", line 222, in _anticoll
  File "PiicoDev_RFID_CircuitPython.py", line 145, in _tocard
  File "PiicoDev_RFID_CircuitPython.py", line 107, in _wfifo
TypeError: can't convert list to int

This seems more like a python error and not an RFID error - still learning python.
The low level i2c is using busio: busio – Hardware accelerated external bus access — Adafruit CircuitPython 9.0.0-beta.0 documentation
then that’s wrapped in: i2cDevice: adafruit_bus_device.i2c_device - I2C Bus Device — Adafruit CircuitPython Bus Device 1.0 documentation
There may be a limitation in i2cDevice and I’ll have to fall back to using busio.i2c and do the bus locking myself.
Thanks,
Cyle

1 Like

Nice @Cyle258478 :slight_smile: with enough incremental project it’ll just work at some point. Am i correct in interpreting that you queriedd the card with readID(detail=True) and multiple bytes are coming back? If so that’s great - it’s likely the extra data is metadata about the card.

As I’m sure you can appreciate, it’s tricky to follow along at home without replicating the setup.
However, a general roadmap for success would be to start small and build up which it looks like you’re doing.

  • Get bus transactions working reliably. These should be unquestionably good at writing single or multiple bytes before moving on.
  • Detect a tag is present (and what type)
  • port the ability to read a tag ID
  • port the ability to read arbitrary data from the tag (data might be all zeros at first though so perhaps unhelpful )
  • port the ability to write a small amount (1 page) of data
  • port the ability to write multiple pages
1 Like

The problem is that the RC522 chip documentation doesn’t give a good example of how to do setup and read operations. It describes all of the registers individually, but not the required sequence. So, I’m relying on the old libraries for this - the PiicoDev RFID one and the domdfcoding one linked above.

Regarding my last post, and the error about converting list to int. This was coming from _tocard where it was being sent a [list] but trying to write a byte. The domdfcoding library (which was for CircuitPython) used a little loop in _tocard to send the list elements individually.

for c in send:                                                 # send to the FIFO
        self._wreg(_REG_FIFO_DATA, c)        # reg_fifo_data
self._wreg(_REG_COMMAND, cmd)           # reg_command

This seemed to work and now things are running without python errors.
I am getting a different response when a card is held by the reader, but I’m never getting what seems like a valid card read.

detectTag
Start _request
Start _tocard- cmd:12 send:[38]
tocard - wait for interrupt
From tocard - Stat:2 Recv:[] Bits:0
got one? {'type': '', 'id_formatted': '', 'success': False, 'id_integers': [0]}
# tag presented here...
tag present?
readTagID
detectTag
Start _request
Start _tocard- cmd:12 send:[38]
tocard - wait for interrupt
From tocard - Stat:1 Recv:[32] Bits:0
detectTag
Start _request
Start _tocard- cmd:12 send:[38]
tocard - wait for interrupt
From tocard - Stat:1 Recv:[32] Bits:0
got one? {'type': '', 'id_formatted': '', 'success': False, 'id_integers': [0]}
tag present?
readTagID
detectTag
Start _request
Start _tocard- cmd:12 send:[38]
tocard - wait for interrupt
From tocard - Stat:1 Recv:[68, 4] Bits:16
readTagID - Tag present
Start _anticoll
Start _tocard- cmd:12 send:[147, 32]
tocard - wait for interrupt
From tocard - Stat:1 Recv:[136, 24, 101, 5, 5] Bits:40
Finish anticoll - Stat:3 Recv:[136, 24, 101, 5, 5]
got one? {'type': '', 'id_formatted': '', 'success': False, 'id_integers': [0]}

Python is giving me fits with print formatting and integers or hex formatting, so these are mixed.
This pattern repeats while a card is in proximity.
Receive [32], then [68, 4] then [136, 24, 101, 5, 5]. However, it’s never recognised as a valid tag and never returns ‘Success’ or any formatted reply from the card.
None of this part of code has changed from the original PiicoDev RFID library (which worked in uPython), so I can only assume there’s still something that CircuitPython is doing to mangle the response.

I appreciate the steps to success Michael, but without a better understanding of how the 522 works and starting from scratch, I’m not sure that approach is possible.

As a solution, I really only need to be able to read the tag identifier and that is all. Then send that over the USB HID (keyboard) - which should be relatively easy…where have I heard that before?

1 Like

The problem is here in _anticoll

if stat == self.OK:
            if len(recv) == 5:
                print(f'5 bytes received in anticoll. recv={recv} bits={bits}')
                for i in range(4):
                    ser_chk = ser_chk ^ recv[i]
                print(f'ser_chk: {ser_chk} recv[4]:{recv[4]}')
                if ser_chk != recv[4]:
                    stat = self.ERR
            else:
                stat = self.ERR

Here is the output:

From tocard - Stat:1 Recv:[136, 24, 101, 5, 5] Bits:40
5 bytes received in anticoll. recv=[136, 24, 101, 5, 5] bits=40
ser_chk: 240 recv[4]:5
Finish anticoll - Stat:3 Recv:[136, 24, 101, 5, 5]

The 5 bytes are getting x-or’d in sequence and compared to byte 4. They aren’t equal, so status is changed to Error.

1 Like

How deep the rabbit hole goes… This undertaking certainly isn’t for the faint-hearted. It may be more worth your time to make a complete swap to CPython and use a SPI RFID reader…

The effort is admirable and i appreciate your kind words about the PiicoDev ecosystem - but i respect that this is kind of getting in the way of your project. Might it be time to take the easy option?

Perhaps another (software) alternative is to use a COM port to HID translator program
then you can use the RFID reader using working libraries (PiicoDev & MicroPython)
and the Pico can print over USB (or UART adapter for a bit more software simplicity) to the computer com port - which receives and interprets commands with the HID software…

What do you think of these ideas?


In any case, that program looks like a very interesting tool to have in one’s toolbox

1 Like

I was really discouraged at the end of the day yesterday having spent many hours going down that rabbit hole. Today with a shower thought - aren’t the best ideas always from the shower? - I decided to reflash with uPython and get a detailed dump of what the original (working) library was doing and compare this with my modified CircuitPython version. And there it was…

Reading 2 bytes in _rreg was usually giving me the same byte twice, but when it came to reading the FIFO, it was discarding every other byte which make the xor comparison fail. Changing that one character from a 2 to a 1 solved it. Then it was only about an hour to implement the HID keyboard and have the project finished.

There were several other steps to get here though - thanks Michael for the tip about using the list to write both reg and val in the same call.

Anyway, here are the changes necessary to make the PiicoDev RFID library work in CircuitPython:

I’m not using the Unified part, so I put all the i2c stuff in the main file.

_init(...)
        scl = board.GP9
        sda = board.GP8
        self.i2c = busio.I2C(scl, sda)
        self.rfid = I2CDevice(self.i2c, self.address)
...the rest is unchanged...

# I2C write to register
    def _wreg(self, reg, val):
        with self.rfid:
            self.rfid.write(bytes([reg, val]))
                    

    # I2C write to FIFO buffer
    def _wfifo(self, reg, val):
        with self.rfid:
            self.rfid.write(bytes([reg, val]))
                    

    # I2C read from register
    def _rreg(self, reg):
        with self.rfid:
            self.rfid.write(bytes([reg]))
            val = bytearray(1)
            self.rfid.readinto(val)
            return val[0]

...and then down in _tocard where the line is:
        self._wfifo(_REG_FIFO_DATA, send)        # Write to the FIFO
...replace that with this:
        for c in send:                           # send to the FIFO
            self._wreg(_REG_FIFO_DATA, c)        # reg_fifo_data
        self._wreg(_REG_COMMAND, cmd)            # reg_command

Anyway, thanks for sticking with me and hopefully this is useful for someone in the future.

2 Likes

Hey, it was all you @Cyle258478 - what a great result :partying_face::partying_face::partying_face: I’m so glad you got over the last hurdle.

This is a really awesome and flexible project. It’s such a great foundation for so many applications and I think it would be a perfect project entry. I urge you to consider doing a full writeup because this one’s a winner and I’m sure there would be a little store credit in there to sweeten the deal :wink:

(to prove this project has legs, we actually use a few Picos and PiicoDev RFID readers for our own internal operations :smiley: but we rely on capturing the output over a COM port, we don’t use them as HID devices.)

2 Likes

@Cyle258478 Thank you so much for sharing your solution! Works great on Qt Py RP2040!

3 Likes

I’m glad it was useful to you. Great to know all that work paid off for someone else too. We’ve been using ours every week with no issues. Happy days :slight_smile:

3 Likes