Class: MFRC522

Inherits:
Object
  • Object
show all
Defined in:
lib/mfrc522.rb

Constant Summary collapse

PICC_REQA =

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

0x26
PICC_WUPA =

REQuest command, Type A. Invites PICCs in state IDLE to go to READY and prepare for anticollision or selection. 7 bit frame.

0x52
PICC_CT =

Wake-UP command, Type A. Invites PICCs in state IDLE and HALT to go to READY(*) and prepare for anticollision or selection. 7 bit frame.

0x88
PICC_SEL_CL1 =

Cascade Tag. Not really a command, but used during anti collision.

0x93
PICC_SEL_CL2 =

Anti collision/Select, Cascade Level 1

0x95
PICC_SEL_CL3 =

Anti collision/Select, Cascade Level 2

0x97
PICC_HLTA =

Anti collision/Select, Cascade Level 3

0x50
PICC_MF_ACK =

Mifare Acknowledge

0x0A
PCD_Idle =

PCD commands

0x00
PCD_Mem =

no action, cancels current command execution

0x01
PCD_GenRandomID =

stores 25 bytes into the internal buffer

0x02
PCD_CalcCRC =

generates a 10-byte random ID number

0x03
PCD_Transmit =

activates the CRC coprocessor or performs a self test

0x04
PCD_NoCmdChange =

transmits data from the FIFO buffer

0x07
PCD_Receive =

no command change, can be used to modify the CommandReg register bits without affecting the command, for example, the PowerDown bit

0x08
PCD_Transceive =

activates the receiver circuits

0x0C
PCD_MFAuthent =

transmits data from FIFO buffer to antenna and automatically activates the receiver after transmission

0x0E
PCD_SoftReset =

performs the MIFARE standard authentication as a reader

0x0F
CommandReg =

PCD Command and Status Registers

0x01
ComIEnReg =

starts and stops command execution

0x02
DivIEnReg =

enable and disable interrupt request control bits

0x03
ComIrqReg =

enable and disable interrupt request control bits

0x04
DivIrqReg =

interrupt request bits

0x05
ErrorReg =

interrupt request bits

0x06
Status1Reg =

error bits showing the error status of the last command executed

0x07
Status2Reg =

communication status bits

0x08
FIFODataReg =

receiver and transmitter status bits

0x09
FIFOLevelReg =

input and output of 64 byte FIFO buffer

0x0A
WaterLevelReg =

number of bytes stored in the FIFO buffer

0x0B
ControlReg =

level for FIFO underflow and overflow warning

0x0C
BitFramingReg =

miscellaneous control registers

0x0D
CollReg =

adjustments for bit-oriented frames

0x0E
ModeReg =

PCD Command Registers

0x11
TxModeReg =

defines general modes for transmitting and receiving

0x12
RxModeReg =

defines transmission data rate and framing

0x13
TxControlReg =

defines reception data rate and framing

0x14
TxASKReg =

controls the logical behavior of the antenna driver pins TX1 and TX2

0x15
TxSelReg =

controls the setting of the transmission modulation

0x16
RxSelReg =

selects the internal sources for the antenna driver

0x17
RxThresholdReg =

selects internal receiver settings

0x18
DemodReg =

selects thresholds for the bit decoder

0x19
MfTxReg =

defines demodulator settings

0x1C
MfRxReg =

controls some MIFARE communication transmit parameters

0x1D
SerialSpeedReg =

controls some MIFARE communication receive parameters

0x1F
CRCResultRegH =

PCD Configuration Registers

0x21
CRCResultRegL =

shows the MSB and LSB values of the CRC calculation

0x22
ModWidthReg =

controls the ModWidth setting?

0x24
RFCfgReg =

configures the receiver gain

0x26
GsNReg =

selects the conductance of the antenna driver pins TX1 and TX2 for modulation

0x27
CWGsPReg =

defines the conductance of the p-driver output during periods of no modulation

0x28
ModGsPReg =

defines the conductance of the p-driver output during periods of modulation

0x29
TModeReg =

defines settings for the internal timer

0x2A
TPrescalerReg =

the lower 8 bits of the TPrescaler value. The 4 high bits are in TModeReg.

0x2B
TReloadRegH =

defines the 16-bit timer reload value

0x2C
TReloadRegL =
0x2D
TCounterValueRegH =

shows the 16-bit timer value

0x2E
TCounterValueRegL =
0x2F
TestSel1Reg =

PCD Test Registers

0x31
TestSel2Reg =

general test signal configuration

0x32
TestPinEnReg =

general test signal configuration

0x33
TestPinValueReg =

enables pin output driver on pins D1 to D7

0x34
TestBusReg =

defines the values for D1 to D7 when it is used as an I/O bus

0x35
AutoTestReg =

shows the status of the internal test bus

0x36
VersionReg =

controls the digital self test

0x37
AnalogTestReg =

shows the software version

0x38
TestDAC1Reg =

controls the pins AUX1 and AUX2

0x39
TestDAC2Reg =

defines the test value for TestDAC1

0x3A
TestADCReg =

defines the test value for TestDAC2

0x3B

Instance Method Summary collapse

Constructor Details

#initialize(nrstpd = 24, chip = 0, spd = 1000000, timer = 256) ⇒ MFRC522

shows the value of ADC I and Q channels



103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
# File 'lib/mfrc522.rb', line 103

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_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

Instance Method Details

#antenna_gain(level = nil) ⇒ Object

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



184
185
186
187
188
189
190
# File 'lib/mfrc522.rb', line 184

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_offObject

Turn antenna off



178
179
180
# File 'lib/mfrc522.rb', line 178

def antenna_off
  write_spi_clear_bitmask(TxControlReg, 0x03)
end

#antenna_onObject

Turn antenna on



173
174
175
# File 'lib/mfrc522.rb', line 173

def antenna_on
  write_spi_set_bitmask(TxControlReg, 0x03)
end

#identify_model(sak) ⇒ Object

Lookup PICC name using sak



384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
# File 'lib/mfrc522.rb', line 384

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) ⇒ Object

Control transceive timeout value



150
151
152
153
154
155
156
# File 'lib/mfrc522.rb', line 150

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) ⇒ Object

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



439
440
441
442
443
444
445
446
447
448
449
# File 'lib/mfrc522.rb', line 439

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_deauthenticateObject

Stop Crypto1 communication



452
453
454
# File 'lib/mfrc522.rb', line 452

def mifare_crypto1_deauthenticate
  write_spi_clear_bitmask(Status2Reg, 0x08) # Clear MFCrypto1On bit
end

#pcd_config_resetObject

Reset PCD config to default



137
138
139
140
141
142
143
144
145
146
147
# File 'lib/mfrc522.rb', line 137

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_haltObject

Instruct PICC in ACTIVE state go to HALT state



203
204
205
206
207
208
209
210
211
# File 'lib/mfrc522.rb', line 203

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) ⇒ Object

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



194
195
196
197
198
199
200
# File 'lib/mfrc522.rb', line 194

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_selectObject

Select PICC for further communication

PICC must be in state ACTIVE



216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
# File 'lib/mfrc522.rb', line 216

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) ⇒ Object

Append CRC to buffer and check CRC or Mifare acknowledge

Raises:



457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
# File 'lib/mfrc522.rb', line 457

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) ⇒ Object

Trying to restart picc



369
370
371
372
373
374
375
376
377
378
379
380
381
# File 'lib/mfrc522.rb', line 369

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_resetObject

PCD software reset



125
126
127
128
129
130
131
132
133
134
# File 'lib/mfrc522.rb', line 125

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) ⇒ Object

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



160
161
162
163
164
165
166
167
168
169
170
# File 'lib/mfrc522.rb', line 160

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