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_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(spi_bus: 0, spi_chip: 0, spi_spd: 1_000_000, spi_delay: 1, pcd_timer: 256) click to toggle source
# File lib/mfrc522.rb, line 100
def initialize(spi_bus: 0, spi_chip: 0, spi_spd: 1_000_000, spi_delay: 1, pcd_timer: 256)
  @spi_driver = Fubuki::SPI.new(spi_bus, spi_chip)
  @spi_speed = spi_spd
  @spi_delay = spi_delay
  @pcd_timer = pcd_timer

  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 182
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 176
def antenna_off
  write_spi_clear_bitmask(TxControlReg, 0x03)
end
antenna_on() click to toggle source

Turn antenna on

# File lib/mfrc522.rb, line 171
def antenna_on
  write_spi_set_bitmask(TxControlReg, 0x03)
end
buffer_size() click to toggle source
# File lib/mfrc522.rb, line 190
def buffer_size
  64
end
communicate_with_picc(command, send_data, framing_bit = 0) click to toggle source

PCD transceive helper

# File lib/mfrc522.rb, line 472
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_buffer_overflow if (error & 0x10) != 0
  return :status_crc_error if (error & 0x04) != 0
  return :status_parity_error if (error & 0x02) != 0
  return :status_protocol_error if (error & 0x01) != 0

  # Receiving data
  received_data = []
  data_length = read_spi(FIFOLevelReg)
  if data_length > 0
    received_data = read_spi_bulk(Array.new(data_length, FIFODataReg))
  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
internal_timer(timer = nil) click to toggle source

Control transceive timeout value

# File lib/mfrc522.rb, line 145
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 389
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 402
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 126
def pcd_config_reset
  # Stop current command
  write_spi(CommandReg, PCD_Idle)

  # Stop crypto1 communication
  mifare_crypto1_deauthenticate

  # 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(@pcd_timer)
end
picc_halt() click to toggle source

Instruct PICC in ACTIVE state go to HALT state

# File lib/mfrc522.rb, line 205
def picc_halt
  buffer = [PICC_HLTA, 0].append_crc16

  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 196
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(disable_anticollision = false) click to toggle source

Select PICC for further communication

PICC must be in state ACTIVE

# File lib/mfrc522.rb, line 218
def picc_select(disable_anticollision = false)
  #  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?{|byte| !byte.is_a?(Integer) }

        # 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_crc16
      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
      elsif status == :status_collision && disable_anticollision
        raise CollisionError
      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 received_data.check_crc16(true)

    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 407
def picc_transceive(send_data, accept_timeout = false)
  send_data = send_data.dup
  send_data.append_crc16 if @built_in_crc_disabled

  puts "Sending Data: #{send_data.to_bytehex}" 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.to_bytehex}" if ENV['DEBUG']
  puts "Valid bits: #{valid_bits}" if ENV['DEBUG']

  # Data exists, check CRC
  if received_data.size > 2 && @built_in_crc_disabled
    raise IncorrectCRCError unless received_data.check_crc16(true)
  end

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

Read from SPI communication

# File lib/mfrc522.rb, line 431
def read_spi(reg)
  read_spi_bulk(reg).first
end
read_spi_bulk(*regs) click to toggle source
# File lib/mfrc522.rb, line 435
def read_spi_bulk(*regs)
  regs.flatten!

  payload = regs.map{ |reg| ((reg & 0x3F) << 1) | 0x80 }
  payload << 0x00

  result = @spi_driver.transfer(payload, @spi_speed, @spi_delay)

  # discard first byte
  result.shift

  result
end
reestablish_picc_communication(uid) click to toggle source

Trying to restart picc

# File lib/mfrc522.rb, line 367
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
soft_reset() click to toggle source

PCD software reset

# File lib/mfrc522.rb, line 114
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 155
def transceiver_baud_rate(direction, value = nil)
  reg = {tx: TxModeReg, rx: RxModeReg}
  mod = {0 => 0x26, 1 => 0x15, 2 => 0x0A, 3 => 0x05}

  if value
    @built_in_crc_disabled = (value == 0)
    write_spi(ModWidthReg, mod.fetch(value))
    value <<= 4
    value |= 0x80 unless @built_in_crc_disabled
    write_spi(reg.fetch(direction), value)
  end

  (read_spi(reg.fetch(direction)) >> 4) & 0x07
end
write_spi(reg, values) click to toggle source

Write to SPI communication

# File lib/mfrc522.rb, line 450
def write_spi(reg, values)
  spi_addr = (reg & 0x3F) << 1
  payload = [spi_addr, *values]

  @spi_driver.transfer(payload, @spi_speed, @spi_delay)

  true
end
write_spi_clear_bitmask(reg, mask) click to toggle source

Clear bits by mask

# File lib/mfrc522.rb, line 466
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 460
def write_spi_set_bitmask(reg, mask)
  value = read_spi(reg)
  write_spi(reg, value | mask)
end