HiPi
Perl Modules for Raspberry Pi
Version 0.92 - released 28 March 2024

HiPi::Interface::MFRC522

This module provides an interface to the common MFCR522 NFC module.

Supported tag types are MiFare Classic 1k and 4k, 4 byte UID and 7 byte UID.

It is a port of the Arduino MFRC522 library from https://github.com/miguelbalboa/rfid

Examples

The examples below are also part of the source distribution.

Module Info

Display information about the MFRC522 module.

#!/usr/bin/perl
use strict;
use warnings;
use HiPi qw( :rpi :mfrc522);
use HiPi::Interface::MFRC522;

my $resetpin = RPI_PIN_38; # the pin connected to reset

my $rfid = HiPi::Interface::MFRC522->new( reset_pin => $resetpin, devicename => '/dev/spidev0.1' );

my $versionstring = $rfid->get_firmware_version_string;
print qq(Module Version : $versionstring\n);

if( $rfid->self_test_ok ) {
    print qq(Self Test Result : SUCCESS\n);
} else {
    print qq(Self Test Result : FAILED\n);
}

# after self test we have to re init
$rfid->init;

1;

Dump Tag Info

Wait for tags to be presented and display basic info

#!/usr/bin/perl
use strict;
use warnings;
use HiPi qw( :rpi :mfrc522);
use HiPi::Interface::MFRC522;

my $resetpin = RPI_PIN_38; # the pin connected to reset

my $rfid = HiPi::Interface::MFRC522->new( reset_pin => $resetpin, devicename => '/dev/spidev0.1' );

sub handle_scan {
    my $continue = 1;
    my( $uid, $uidstring) = @_;
    my $output = $rfid->picc_dump_details( $uid );
    print $output . qq(\n);
    $rfid->picc_end_session;
    return $continue;
}

sub handle_timeout {
    warn q(timeout called);
    return 1;
}

$rfid->scan( \&handle_scan, \&handle_timeout, 10 );


1;

Dump all Tag Blocks

Print all tag block information to the console.

#!/usr/bin/perl
use strict;
use warnings;
use HiPi qw( :rpi :mfrc522);
use HiPi::Interface::MFRC522;

my $resetpin = RPI_PIN_38; # the pin connected to reset

my $rfid = HiPi::Interface::MFRC522->new( reset_pin => $resetpin, devicename => '/dev/spidev0.1' );

$rfid->scan( \&handle_scan );

sub handle_scan {
    my $continue = 1;
    my( $uid, $uidstring) = @_;
    print qq(\nReading Tag UID $uidstring Blocks\n);
    print qq(   do not remove tag from field ....\n);
    my $output = $rfid->picc_dump_tag_info( $uid );
    print $output;
    # set the card inactive
    $rfid->picc_end_session;
    print qq(\nTag $uidstring read complete\n\n);
    return $continue;
}


1;

Read and Write

Write data to a tag block and retrieve information

#!/usr/bin/perl
use strict;
use warnings;
use HiPi qw( :rpi :mfrc522);
use HiPi::Interface::MFRC522;

my $resetpin = RPI_PIN_38; # the pin connected to reset

my $rfid = HiPi::Interface::MFRC522->new( reset_pin => $resetpin, devicename => '/dev/spidev0.1' );

my $uidswritten = {};  # keep a record of if we have written to a tag in current invocation
my $infoblock  = 2;    # the block we will write to / read from 
my $infostring = 'HiPi Block Test';

my $key = $rfid->get_default_key;  # change if you changed it

sub handle_read_write {
    my( $uid, $uidstring) = @_;
    
    print qq(Tag ID   : $uidstring\n);
    my $picctype = $rfid->picc_get_type( $uid->{'sak'} );
    my $piccname = $rfid->picc_get_type_name( $picctype );
    print qq(Tag Type : $piccname\n);
    
    my $continue = 1;
    
    if(exists($uidswritten->{$uidstring})) {
        # read the data from info block
        my ( $bdstatus, $blockdata )  = $rfid->read_block_data( $infoblock, $uid, $key );
        if( $bdstatus == MFRC522_STATUS_OK ) {
            my $stringdata = '';
            my $bldata = '';
            for my $byte ( @$blockdata ) {
                $bldata .= ' ' if $bldata;
                $bldata .= sprintf('%02X', $byte);
                $stringdata .= chr($byte) if $byte; # ignore 0x00 == NULL - this is text
            }
            print qq(READ BLOCK $infoblock DATA : $bldata\n);
            print qq(BLOCK $infoblock STRING : $stringdata\n\n);
            $continue = 1; # wait for next tag
            print qq(present next tag ...\n\n);
        } else {
            print $rfid->get_status_code_name( $bdstatus ) . qq(\n);
        }
    } else {
        # write the data to the block
        my $writestring = $infostring;
        my @chars = split(//, $writestring);
        my @writedata = ();
        for (my $i = 0; $i < 16; $i ++) {  # block is 16 bytes
            my $char = $chars[$i];
            if(defined($char)) {
                $writedata[$i] = ord($char);
            } else {
                $writedata[$i] = 0;
            }
        }
        
        my $bdstatus = $rfid->write_block_data( $infoblock, $uid, \@writedata, $key );
        print qq(WRITE BLOCK $infoblock DATA RESULT : ) . $rfid->get_status_code_name( $bdstatus ) . qq(\n\n);
        if( $bdstatus == MFRC522_STATUS_OK ) {
            $uidswritten->{$uidstring} = 1;
            print qq(re-present tag to read block ....\n\n);
        }
        $continue = 1;
    }
    # end session so we can start communicating with same tag or new tag
    $rfid->picc_end_session;
    return $continue;    
}

$rfid->scan( \&handle_read_write );

1;

Change Keys

Chaneg keys and possibly access permissions on tag.

#!/usr/bin/perl
use strict;
use warnings;
use HiPi qw( :rpi :mfrc522);
use HiPi::Interface::MFRC522;

my $resetpin = RPI_PIN_38; # the pin connected to reset

my $rfid = HiPi::Interface::MFRC522->new( reset_pin => $resetpin, devicename => '/dev/spidev0.1' );

my $reverse = 0;  # change $reverse to 1 to switch back a tag to defaults
                  # ( the current key must be the one in $replaceA below, of course);

my $defaultkey = $rfid->get_default_key;
my $replaceA   = [ 0xA1, 0xA2, 0xA3, 0xA4, 0xA5, 0xA6 ]; # New Key A
my $replaceB   = [ 0xB1, 0xB2, 0xB3, 0xB4, 0xB5, 0xB6 ]; # New Key B

my $blockswritten = {};

$rfid->scan( \&handle_scan );

sub handle_scan {
    my $continue = 1;
    my( $uid, $uidstring) = @_;
    print qq(Tag UID: $uidstring\n);
    
    # record which blocks we wrote successfully so
    # that we can retry other blocks without failing
    # authentication.
    # This only lasts while the script is running 
    
    if(!exists($blockswritten->{$uidstring})) {
        $blockswritten->{$uidstring} = {};
    }
    
    # These are the default existing access bits.
    # We won't pass this value and existing bits will remain
    
    my $accessbits = [
        0b000, 0b000, 0b000, 0b001
    ];
    
    # General Purpose Bit. We won't pass this value and all existing
    # values will remain
    my $gpb = 0x69;
    
    my $key  = ( $reverse ) ? $replaceA : $defaultkey;
    my $newkeyA = ( $reverse ) ? $defaultkey : $replaceA;
    my $newkeyB = ( $reverse ) ? $defaultkey : $replaceB;
    
    my $picctype = $rfid->picc_get_type( $uid->{'sak'} );
    my $blocks   = $rfid->get_sector_trailer_blocks( $picctype );
    
    my $success = 1;
    
    for my $block( sort { $a <=> $b } ( keys %$blocks ) ) {
        if($blockswritten->{$uidstring}->{$block}) {
            print qq(skipping written block $block\n);
            next;
        }
        my $status = $rfid->write_sector_trailer( $block, $key, $uid, $newkeyA, $newkeyB, undef, undef  );
        if( $status == MFRC522_STATUS_OK ) {
            $blockswritten->{$uidstring}->{$block} = 1;
        } else {
            $success = 0;
        }
        
        print qq(result sector trailer write $block : ) . $rfid->get_status_code_name( $status ) . qq(\n);
        # now read back in
        my( $rstatus, $rdata ) = $rfid->read_block_data( $block, $uid, $newkeyA );
        if( $rstatus == MFRC522_STATUS_OK ) {
            printf(qq(result sector trailer read  %03d  %02X %02X %02X %02X %02X %02X %02X %02X %02X %02X %02X %02X %02X %02X %02X %02X\n), $block, @$rdata );
        } else {
            print  qq(result sector trailer read  $block : ) . $rfid->get_status_code_name( $rstatus ) . qq(\n);
        }
        
        $rfid->sleep_milliseconds(1);
    }

    if( $success ) {
        my $setype = ( $reverse ) ? 'Default' : 'New Custom';
        print qq(Tag Access Set to $setype Key On All Sectors\n);
    } else {
        print qq(Failed - 1 or more sector trailers were not written. Re-present tag / card\n);
    }
    
    $rfid->picc_end_session;
    return $continue;
}


1;