class MFRC522

Constants

AnalogTestReg
AutoTestReg
BitFramingReg
CRCResultRegH

PCD Configuration Registers

CRCResultRegL
CWGsPReg
CollReg
ComIEnReg
ComIrqReg
CommandReg

PCD Command and Status Registers

ControlReg
DemodReg
DivIEnReg
DivIrqReg
ErrorReg
FIFODataReg
FIFOLevelReg
GsNReg
MfRxReg
MfTxReg
ModGsPReg
ModWidthReg
ModeReg

PCD Command Registers

PCD_CalcCRC
PCD_GenRandomID
PCD_Idle

PCD commands

PCD_MFAuthent
PCD_Mem
PCD_NoCmdChange
PCD_Receive
PCD_SoftReset
PCD_Transceive
PCD_Transmit
PICC_CT
PICC_HLTA
PICC_MF_ACK
PICC_REQA

PICC commands used by the PCD to manage communication with several PICCs (ISO 14443-3, Type A, section 6.4)

PICC_SEL_CL1
PICC_SEL_CL2
PICC_SEL_CL3
PICC_WUPA
RFCfgReg
RxModeReg
RxSelReg
RxThresholdReg
SerialSpeedReg
Status1Reg
Status2Reg
TCounterValueRegH
TCounterValueRegL
TModeReg
TPrescalerReg
TReloadRegH
TReloadRegL
TestADCReg
TestBusReg
TestDAC1Reg
TestDAC2Reg
TestPinEnReg
TestPinValueReg
TestSel1Reg

PCD Test Registers

TestSel2Reg
TxASKReg
TxControlReg
TxModeReg
TxSelReg
VersionReg
WaterLevelReg

Public Class Methods

new(nrstpd = 24, chip = 0, spd = 1000000, timer = 256) click to toggle source
# File lib/mfrc522.rb, line 101
def initialize(nrstpd = 24, chip = 0, spd = 1000000, timer = 256)
  chip_option = { 0 => PiPiper::Spi::CHIP_SELECT_0,
                  1 => PiPiper::Spi::CHIP_SELECT_1,
                  2 => PiPiper::Spi::CHIP_SELECT_BOTH,
                  3 => PiPiper::Spi::CHIP_SELECT_NONE }
  @spi_chip = chip_option[chip]
  @spi_spd = spd
  @timer = timer

  # Power it up
  @nrstpd = nrstpd
  nrstpd_pin = PiPiper::Pin.new(pin: nrstpd, direction: :out)
  nrstpd_pin.on
  sleep 1.0 / 20.0 # Wait 50ms

  soft_reset # Perform software reset

  pcd_config_reset # Set default setting

  antenna_on # Turn antenna on. They were disabled by the reset.
end

Public Instance Methods

antenna_gain(level = nil) click to toggle source

Modify and show antenna gain level level = 1: 18dB, 2: 23dB, 3: 33dB, 4: 38dB, 5: 43dB, 6: 48dB

# File lib/mfrc522.rb, line 192
def antenna_gain(level = nil)
  unless level.nil?
    level = 1 if level > 6 || level < 1
    write_spi_set_bitmask(RFCfgReg, ((level + 1) << 4))
  end
  (read_spi(RFCfgReg) & 0x70) >> 4
end
antenna_off() click to toggle source

Turn antenna off

# File lib/mfrc522.rb, line 186
def antenna_off
  write_spi_clear_bitmask(TxControlReg, 0x03)
end
antenna_on() click to toggle source

Turn antenna on

# File lib/mfrc522.rb, line 181
def antenna_on
  write_spi_set_bitmask(TxControlReg, 0x03)
end
identify_model(sak) click to toggle source

Lookup PICC name using sak

# File lib/mfrc522.rb, line 392
def identify_model(sak)
  # SAK coding separation reference:
  # http://cache.nxp.com/documents/application_note/AN10833.pdf
  # http://www.nxp.com/documents/application_note/130830.pdf
  if sak & 0x04 != 0
    return :picc_uid_not_complete
  end

  if sak & 0x02 != 0
    return :picc_reserved_future_use
  end

  if sak & 0x08 != 0
    if sak & 0x10 != 0
      return :picc_mifare_4k
    end

    if sak & 0x01 != 0
      return :picc_mifare_mini
    end
    
    return :picc_mifare_1k
  end

  if sak & 0x10 != 0
    if sak & 0x01 != 0
      return :picc_mifare_plus_4k_sl2
    end
      
    return :picc_mifare_plus_2k_sl2
  end

  if sak == 0x00
    return :picc_mifare_ultralight
  end

  if sak & 0x20 != 0
    return :picc_iso_14443_4
  end

  if sak & 0x40 != 0
    return :picc_iso_18092
  end

  return :picc_unknown
end
internal_timer(timer = nil) click to toggle source

Control transceive timeout value

# File lib/mfrc522.rb, line 158
def internal_timer(timer = nil)
  if timer
    write_spi(TReloadRegH, (timer >> 8) & 0xFF)
    write_spi(TReloadRegL, (timer & 0xFF))
  end
  (read_spi(TReloadRegH) << 8) | read_spi(TReloadRegL)
end
mifare_crypto1_authenticate(command, block_addr, sector_key, uid) click to toggle source

Start Crypto1 communication between reader and Mifare PICC

PICC must be selected before calling for authentication Remember to deauthenticate after communication, or no new communication can be made

Accept PICC_MF_AUTH_KEY_A or PICC_MF_AUTH_KEY_B command Checks datasheets for block address numbering of your PICC

# File lib/mfrc522.rb, line 447
def mifare_crypto1_authenticate(command, block_addr, sector_key, uid)
  # Buffer[12]: {command, block_addr, sector_key[6], uid[4]}
  buffer = [command, block_addr]
  buffer.concat(sector_key[0..5])
  buffer.concat(uid[0..3])

  communicate_with_picc(PCD_MFAuthent, buffer)

  # Check MFCrypto1On bit
  (read_spi(Status2Reg) & 0x08) != 0
end
mifare_crypto1_deauthenticate() click to toggle source

Stop Crypto1 communication

# File lib/mfrc522.rb, line 460
def mifare_crypto1_deauthenticate
  write_spi_clear_bitmask(Status2Reg, 0x08) # Clear MFCrypto1On bit
end
pcd_config_reset() click to toggle source

Reset PCD config to default

# File lib/mfrc522.rb, line 145
def pcd_config_reset
  # Clear ValuesAfterColl bit
  write_spi_clear_bitmask(CollReg, 0x80)

  # Reset transceiver baud rate to 106 kBd
  transceiver_baud_rate(:tx, 0)
  transceiver_baud_rate(:rx, 0)

  # Set PCD timer value for 302us default timer
  internal_timer(@timer)
end
picc_halt() click to toggle source

Instruct PICC in ACTIVE state go to HALT state

# File lib/mfrc522.rb, line 211
def picc_halt
  buffer = append_crc([PICC_HLTA, 0])

  status, _received_data, _valid_bits = communicate_with_picc(PCD_Transceive, buffer)

  # PICC in HALT state will not respond
  # If PICC sent reply, means it didn't acknowledge the command we sent
  status == :status_picc_timeout
end
picc_request(picc_command) click to toggle source

Wakes PICC from HALT or IDLE to ACTIVE state Accept PICC_REQA and PICC_WUPA command

# File lib/mfrc522.rb, line 202
def picc_request(picc_command)
  pcd_config_reset

  status, _received_data, valid_bits = communicate_with_picc(PCD_Transceive, picc_command, 0x07)

  status == :status_ok && valid_bits == 0 # REQA or WUPA command return 16 bits(full byte)
end
picc_select() click to toggle source

Select PICC for further communication

PICC must be in state ACTIVE

# File lib/mfrc522.rb, line 224
def picc_select
  #  Description of buffer structure:
  #
  #  Byte 0: SEL   Indicates the Cascade Level: PICC_CMD_SEL_CL1, PICC_CMD_SEL_CL2 or PICC_CMD_SEL_CL3
  #  Byte 1: NVB   Number of Valid Bits (in complete command, not just the UID): High nibble: complete bytes, Low nibble: Extra bits.
  #  Byte 2: UID-data or Cascade Tag
  #  Byte 3: UID-data
  #  Byte 4: UID-data
  #  Byte 5: UID-data
  #  Byte 6: Block Check Character - XOR of bytes 2-5
  #  Byte 7: CRC_A
  #  Byte 8: CRC_A
  #  The BCC and CRC_A are only transmitted if we know all the UID bits of the current Cascade Level.
  #
  #  Description of bytes 2-5
  #
  #  UID size  Cascade level Byte2 Byte3 Byte4 Byte5
  #  ========  ============= ===== ===== ===== =====
  #   4 bytes        1       uid0  uid1  uid2  uid3
  #   7 bytes        1       CT    uid0  uid1  uid2
  #                  2       uid3  uid4  uid5  uid6
  #  10 bytes        1       CT    uid0  uid1  uid2
  #                  2       CT    uid3  uid4  uid5
  #                  3       uid6  uid7  uid8  uid9
  pcd_config_reset

  cascade_levels = [PICC_SEL_CL1, PICC_SEL_CL2, PICC_SEL_CL3]
  uid = []
  sak = 0

  cascade_levels.each do |cascade_level|
    buffer = [cascade_level]
    current_level_known_bits = 0
    received_data = []
    valid_bits = 0
    timeout = true

    # Maxmimum loop count is defined in ISO spec
    32.times do
      if current_level_known_bits >= 32 # Prepare to do a complete select if we knew everything
        # Validate buffer content against non-numeric classes and incorrect size
        buffer = buffer[0..5]
        dirty_buffer = buffer.size != 6
        dirty_buffer ||= buffer.any? do |byte|
          if Gem::Version.new(RUBY_VERSION) >= Gem::Version.new('2.4.0')
            !byte.is_a?(Numeric)
          else
            !byte.is_a?(Fixnum)
          end
        end

        # Retry reading UID when buffer is dirty, but don't reset loop count to prevent infinite loop
        if dirty_buffer
          # Reinitialize all variables
          buffer = [cascade_level]
          current_level_known_bits = 0
          received_data = []
          valid_bits = 0

          # Continue to next loop
          next
        end

        tx_last_bits = 0
        buffer[1] = 0x70 # NVB - We're sending full length byte[0..6]
        buffer[6] = (buffer[2] ^ buffer[3] ^ buffer[4] ^ buffer[5]) # Block Check Character

        # Append CRC to buffer
        buffer = append_crc(buffer)
      else
        tx_last_bits = current_level_known_bits % 8
        uid_full_byte = current_level_known_bits / 8
        all_full_byte = 2 + uid_full_byte # length of SEL + NVB + UID
        buffer[1] = (all_full_byte << 4) + tx_last_bits # NVB

        buffer_length = all_full_byte + (tx_last_bits > 0 ? 1 : 0)
        buffer = buffer[0...buffer_length]
      end

      framing_bit = (tx_last_bits << 4) + tx_last_bits

      # Select it
      status, received_data, valid_bits = communicate_with_picc(PCD_Transceive, buffer, framing_bit)

      if status != :status_ok && status != :status_collision
        raise CommunicationError, status
      end

      if received_data.empty?
        raise UnexpectedDataError, 'Received empty UID data'
      end

      # Append received UID into buffer if not doing full select
      if current_level_known_bits < 32
        # Check for last collision
        if tx_last_bits != 0
          buffer[-1] |= received_data.shift
        end

        buffer += received_data
      end

      # Handle collision
      if status == :status_collision
        collision = read_spi(CollReg)

        # CollPosNotValid - We don't know where collision happened
        raise CollisionError if (collision & 0x20) != 0
        
        collision_position = collision & 0x1F
        collision_position = 32 if collision_position == 0 # Values 0-31, 0 means bit 32
        raise CollisionError if collision_position <= current_level_known_bits

        # Calculate positioin
        current_level_known_bits = collision_position
        uid_bit = (current_level_known_bits - 1) % 8

        # Mark the collision bit
        buffer[-1] |= (1 << uid_bit)
      else
        if current_level_known_bits >= 32
          timeout = false
          break
        end
        current_level_known_bits = 32 # We've already known all bits, loop again for a complete select
      end 
    end

    # Handle timeout after 32 loops
    if timeout
      raise UnexpectedDataError, 'Keep receiving incomplete UID until timeout'
    end

    # We've finished current cascade level
    # Check and collect all uid stored in buffer

    # Append UID
    uid << buffer[2] if buffer[2] != PICC_CT
    uid << buffer[3] << buffer[4] << buffer[5]

    # Check the result of full select
    # Select Acknowledge is 1 byte + CRC16
    raise UnexpectedDataError, 'Unknown SAK format' if received_data.size != 3 || valid_bits != 0 
    raise IncorrectCRCError unless check_crc(received_data)

    sak = received_data[0]
    break if (sak & 0x04) == 0 # No more cascade level
  end

  return uid, sak
end
picc_transceive(send_data, accept_timeout = false) click to toggle source

Append CRC to buffer and check CRC or Mifare acknowledge

# File lib/mfrc522.rb, line 465
def picc_transceive(send_data, accept_timeout = false)
  send_data = append_crc(send_data)

  puts "Sending Data: #{send_data.map{|x|x.to_s(16).rjust(2,'0').upcase}}" if ENV['DEBUG']

  # Transfer data
  status, received_data, valid_bits = communicate_with_picc(PCD_Transceive, send_data)
  return [] if status == :status_picc_timeout && accept_timeout
  raise PICCTimeoutError if status == :status_picc_timeout
  raise CommunicationError, status if status != :status_ok

  puts "Received Data: #{received_data.map{|x|x.to_s(16).rjust(2,'0').upcase}}" if ENV['DEBUG']

  # Data exists, check CRC and return
  if received_data.size > 1
    raise IncorrectCRCError unless check_crc(received_data)

    return received_data[0..-3]
  end

  raise UnexpectedDataError, 'Incorrect Mifare ACK format' if received_data.size != 1 || valid_bits != 4 # ACK is 4 bits long
  raise MifareNakError, received_data[0] if received_data[0] != PICC_MF_ACK

  received_data
end
reestablish_picc_communication(uid) click to toggle source

Trying to restart picc

# File lib/mfrc522.rb, line 377
def reestablish_picc_communication(uid)
  picc_halt
  picc_request(PICC_WUPA)

  begin
    new_uid, _new_sak = picc_select
    status = true
  rescue CommunicationError
    status = false
  end

  status && uid == new_uid
end
shutdown() click to toggle source

Shutdown

# File lib/mfrc522.rb, line 124
def shutdown
  antenna_off
  # TODO: handle other platform?
  if PiPiper::Platform.driver == PiPiper::Bcm2835
    PiPiper::Platform.driver.unexport_pin(@nrstpd)
  end
end
soft_reset() click to toggle source

PCD software reset

# File lib/mfrc522.rb, line 133
def soft_reset
  write_spi(CommandReg, PCD_SoftReset)
  sleep 1.0 / 20.0 # wait 50ms

  write_spi(TModeReg, 0x87) # Start timer by setting TAuto=1, and higher part of TPrescalerReg
  write_spi(TPrescalerReg, 0xFF) # Set lower part of TPrescalerReg, and results in 302us timer (f_timer = 13.56 MHz / (2*TPreScaler+1))
  
  write_spi(TxASKReg, 0x40) # Default 0x00. Force a 100 % ASK modulation independent of the ModGsPReg register setting
  write_spi(ModeReg, 0x3D) # Default 0x3F. Set the preset value for the CRC coprocessor for the CalcCRC command to 0x6363 (ISO 14443-3 part 6.2.4)
end
transceiver_baud_rate(direction, value = nil) click to toggle source

Control transceiver baud rate value = 0: 106kBd, 1: 212kBd, 2: 424kBd, 3: 848kBd

# File lib/mfrc522.rb, line 168
def transceiver_baud_rate(direction, value = nil)
  reg = {tx: TxModeReg, rx: RxModeReg}

  if value
    value <<= 4
    value |= 0x80 if value != 0
    write_spi(reg.fetch(direction), value)
  end

  (read_spi(reg.fetch(direction)) >> 4) & 0x07
end

Private Instance Methods

append_crc(data) click to toggle source
# File lib/mfrc522.rb, line 601
def append_crc(data)
  data + calculate_crc(data)
end
calculate_crc(data) click to toggle source
# File lib/mfrc522.rb, line 580
def calculate_crc(data)
  write_spi(CommandReg, PCD_Idle)               # Stop any active command.
  write_spi(DivIrqReg, 0x04)                    # Clear the CRCIRq interrupt request bit
  write_spi_set_bitmask(FIFOLevelReg, 0x80)     # FlushBuffer = 1, FIFO initialization
  write_spi(FIFODataReg, data)                  # Write data to the FIFO
  write_spi(CommandReg, PCD_CalcCRC)            # Start the calculation

  # Wait for the command to complete
  i = 5000
  loop do
    irq = read_spi(DivIrqReg)
    break if (irq & 0x04) != 0
    raise PCDTimeoutError, 'Error calculating CRC' if i == 0
    i -= 1
  end

  write_spi(CommandReg, PCD_Idle)               # Stop calculating CRC for new content in the FIFO.

  [read_spi(CRCResultRegL), read_spi(CRCResultRegH)]
end
check_crc(data) click to toggle source
# File lib/mfrc522.rb, line 605
def check_crc(data)
  raise UnexpectedDataError, 'Data too short for CRC check' if data.size < 3

  data = data.dup
  crc = data.pop(2)

  crc == calculate_crc(data)
end
communicate_with_picc(command, send_data, framing_bit = 0) click to toggle source

PCD transceive helper

# File lib/mfrc522.rb, line 535
def communicate_with_picc(command, send_data, framing_bit = 0)
  wait_irq = 0x00
  wait_irq = 0x10 if command == PCD_MFAuthent
  wait_irq = 0x30 if command == PCD_Transceive

  write_spi(CommandReg, PCD_Idle)               # Stop any active command.
  write_spi(ComIrqReg, 0x7F)                    # Clear all seven interrupt request bits
  write_spi_set_bitmask(FIFOLevelReg, 0x80)     # FlushBuffer = 1, FIFO initialization
  write_spi(FIFODataReg, send_data)             # Write sendData to the FIFO
  write_spi(BitFramingReg, framing_bit)         # Bit adjustments
  write_spi(CommandReg, command)                # Execute the command
  if command == PCD_Transceive
    write_spi_set_bitmask(BitFramingReg, 0x80)  # StartSend=1, transmission of data starts
  end

  # Wait for the command to complete
  i = 2000
  loop do
    irq = read_spi(ComIrqReg)
    break if (irq & wait_irq) != 0
    return :status_picc_timeout if (irq & 0x01) != 0
    return :status_pcd_timeout if i == 0
    i -= 1
  end

  # Check for error
  error = read_spi(ErrorReg)
  return :status_error if (error & 0x13) != 0 # BufferOvfl ParityErr ProtocolErr

  # Receiving data
  received_data = []
  data_length = read_spi(FIFOLevelReg)
  while data_length > 0 do
    data = read_spi(FIFODataReg)
    received_data << data
    data_length -=1
  end
  valid_bits = read_spi(ControlReg) & 0x07

  status = :status_ok
  status = :status_collision if (error & 0x08) != 0 # CollErr

  return status, received_data, valid_bits
end
read_spi(reg) click to toggle source

Read from SPI communication

# File lib/mfrc522.rb, line 494
def read_spi(reg)
  output = 0
  PiPiper::Spi.begin do |spi|
    spi.chip_select_active_low(true)
    spi.bit_order PiPiper::Spi::MSBFIRST
    spi.clock @spi_spd

    spi.chip_select(@spi_chip) do
      spi.write((reg << 1) & 0x7E | 0x80)
      output = spi.read
    end
  end
  output
end
write_spi(reg, values) click to toggle source

Write to SPI communication

# File lib/mfrc522.rb, line 510
def write_spi(reg, values)
  PiPiper::Spi.begin do |spi|
    spi.chip_select_active_low(true)
    spi.bit_order PiPiper::Spi::MSBFIRST
    spi.clock @spi_spd

    spi.chip_select(@spi_chip) do
      spi.write((reg << 1) & 0x7E, *values)
    end
  end
end
write_spi_clear_bitmask(reg, mask) click to toggle source

Clear bits by mask

# File lib/mfrc522.rb, line 529
def write_spi_clear_bitmask(reg, mask)
  value = read_spi(reg)
  write_spi(reg, value & (~mask))
end
write_spi_set_bitmask(reg, mask) click to toggle source

Set bits by mask

# File lib/mfrc522.rb, line 523
def write_spi_set_bitmask(reg, mask)
  value = read_spi(reg)
  write_spi(reg, value | mask)
end