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
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(spi_bus: 0, spi_chip: 0, spi_spd: 1_000_000, spi_delay: 1, pcd_timer: 256) ⇒ MFRC522

shows the value of ADC I and Q channels


100
101
102
103
104
105
106
107
108
109
110
111
# 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

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


182
183
184
185
186
187
188
# 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_offObject

Turn antenna off


176
177
178
# File 'lib/mfrc522.rb', line 176

def antenna_off
  write_spi_clear_bitmask(TxControlReg, 0x03)
end

#antenna_onObject

Turn antenna on


171
172
173
# File 'lib/mfrc522.rb', line 171

def antenna_on
  write_spi_set_bitmask(TxControlReg, 0x03)
end

#buffer_sizeObject


190
191
192
# File 'lib/mfrc522.rb', line 190

def buffer_size
  64
end

#communicate_with_picc(command, send_data, framing_bit = 0) ⇒ Object

PCD transceive helper


472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
# 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) ⇒ Object

Control transceive timeout value


145
146
147
148
149
150
151
# 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) ⇒ 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


389
390
391
392
393
394
395
396
397
398
399
# 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_deauthenticateObject

Stop Crypto1 communication


402
403
404
# File 'lib/mfrc522.rb', line 402

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

#pcd_config_resetObject

Reset PCD config to default


126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
# 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_haltObject

Instruct PICC in ACTIVE state go to HALT state


205
206
207
208
209
210
211
212
213
# 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) ⇒ Object

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


196
197
198
199
200
201
202
# 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) ⇒ Object

Select PICC for further communication

PICC must be in state ACTIVE


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

Append CRC to buffer and check CRC or Mifare acknowledge

Raises:


407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
# 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) ⇒ Object

Read from SPI communication


431
432
433
# File 'lib/mfrc522.rb', line 431

def read_spi(reg)
  read_spi_bulk(reg).first
end

#read_spi_bulk(*regs) ⇒ Object


435
436
437
438
439
440
441
442
443
444
445
446
447
# 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) ⇒ Object

Trying to restart picc


367
368
369
370
371
372
373
374
375
376
377
378
379
# 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_resetObject

PCD software reset


114
115
116
117
118
119
120
121
122
123
# 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) ⇒ Object

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


155
156
157
158
159
160
161
162
163
164
165
166
167
168
# 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) ⇒ Object

Write to SPI communication


450
451
452
453
454
455
456
457
# 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) ⇒ Object

Clear bits by mask


466
467
468
469
# 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) ⇒ Object

Set bits by mask


460
461
462
463
# File 'lib/mfrc522.rb', line 460

def write_spi_set_bitmask(reg, mask)
  value = read_spi(reg)
  write_spi(reg, value | mask)
end