#include #include #include #include "rc522_internal.h" #include "rc522_types_internal.h" #include "rc522_helpers_internal.h" #include "rc522_pcd_internal.h" #include "rc522_picc_internal.h" RC522_LOG_DEFINE_BASE(); struct rc522_picc_transaction_context { const rc522_picc_transaction_t *transaction; uint8_t interrupts; bool completed; uint8_t error_reg; }; esp_err_t rc522_picc_send(const rc522_handle_t rc522, const rc522_picc_transaction_t *transaction, rc522_picc_transaction_context_t *out_context) { RC522_CHECK(rc522 == NULL); RC522_CHECK(transaction == NULL); RC522_CHECK_BYTES(&transaction->bytes); RC522_CHECK( transaction->pcd_command != RC522_PCD_TRANSCEIVE_CMD && transaction->pcd_command != RC522_PCD_MF_AUTH_CMD); RC522_CHECK(transaction->expected_interrupts == 0); rc522_picc_transaction_context_t context = { .transaction = transaction, }; // Prepare values for bit framing uint8_t bit_framing = (transaction->rx_align << 4) + transaction->valid_bits; if (RC522_LOG_LEVEL >= ESP_LOG_DEBUG) { RC522_LOGD("rx_align=%d,tx_last_bits=%d, bit_framing=%d", transaction->rx_align, transaction->valid_bits, bit_framing); char debug_buffer[64]; rc522_buffer_to_hex_str(transaction->bytes.ptr, transaction->bytes.length, debug_buffer, sizeof(debug_buffer)); RC522_LOGD("picc << %s", debug_buffer); } RC522_RETURN_ON_ERROR(rc522_pcd_stop_active_command(rc522)); RC522_RETURN_ON_ERROR(rc522_pcd_clear_all_com_interrupts(rc522)); RC522_RETURN_ON_ERROR(rc522_pcd_fifo_flush(rc522)); RC522_RETURN_ON_ERROR(rc522_pcd_fifo_write(rc522, &transaction->bytes)); RC522_RETURN_ON_ERROR(rc522_pcd_write(rc522, RC522_PCD_BIT_FRAMING_REG, bit_framing)); RC522_RETURN_ON_ERROR(rc522_pcd_write(rc522, RC522_PCD_COMMAND_REG, transaction->pcd_command)); if (transaction->pcd_command == RC522_PCD_TRANSCEIVE_CMD) { RC522_RETURN_ON_ERROR(rc522_pcd_start_data_transmission(rc522)); } // TAuto flag in TModeReg is set. // This means the timer automatically starts when the PCD stops transmitting. const uint32_t deadline = rc522_millis() + 36; do { RC522_RETURN_ON_ERROR(rc522_pcd_read(rc522, RC522_PCD_COM_INT_REQ_REG, &context.interrupts)); if (context.interrupts & transaction->expected_interrupts) { context.completed = true; break; } // Timer interrupt - nothing received if (context.interrupts & RC522_PCD_TIMER_IRQ_BIT) { RC522_LOGD("timer interrupt (irq=0x%02" RC522_X ")", context.interrupts); rc522_pcd_stop_active_command(rc522); return RC522_ERR_RX_TIMER_TIMEOUT; } rc522_delay_ms(2); } while (rc522_millis() < deadline); // Deadline reached and nothing happened (normal when no card is present). if (!context.completed) { rc522_pcd_stop_active_command(rc522); return RC522_ERR_RX_TIMEOUT; } // Stop now if any errors except collisions were detected. RC522_RETURN_ON_ERROR(rc522_pcd_read(rc522, RC522_PCD_ERROR_REG, &context.error_reg)); if (context.error_reg & RC522_PCD_BUFFER_OVFL_BIT) { return RC522_ERR_PCD_FIFO_BUFFER_OVERFLOW; } else if (context.error_reg & RC522_PCD_PARITY_ERR_BIT) { RC522_LOGD("parity error detected"); return RC522_ERR_PCD_PARITY_CHECK_FAILED; } else if (context.error_reg & RC522_PCD_PROTOCOL_ERR_BIT) { RC522_LOGD("protocol error detected"); return RC522_ERR_PCD_PROTOCOL_ERROR; } if (out_context) { memcpy(out_context, &context, sizeof(context)); } return ESP_OK; } static esp_err_t rc522_picc_receive(const rc522_handle_t rc522, const rc522_picc_transaction_context_t *context, rc522_picc_transaction_result_t *out_result) { RC522_CHECK(rc522 == NULL); RC522_CHECK(context == NULL); RC522_CHECK(context->transaction == NULL); RC522_CHECK(out_result == NULL); RC522_CHECK_BYTES(&context->transaction->bytes); uint8_t fifo_level = 0; RC522_RETURN_ON_ERROR(rc522_pcd_read(rc522, RC522_PCD_FIFO_LEVEL_REG, &fifo_level)); if (fifo_level < 1) { RC522_LOGD("fifo empty (irq=0x%02" RC522_X ")", context->interrupts); return RC522_ERR_PCD_FIFO_EMPTY; } RC522_CHECK(fifo_level > out_result->bytes.length); rc522_picc_transaction_result_t result = { .bytes = { .ptr = out_result->bytes.ptr, // Use buffer provided by caller .length = fifo_level, }, }; RC522_RETURN_ON_ERROR(rc522_pcd_fifo_read(rc522, &result.bytes)); if (RC522_LOG_LEVEL >= ESP_LOG_DEBUG) { char debug_buffer[64]; rc522_buffer_to_hex_str(result.bytes.ptr, result.bytes.length, debug_buffer, sizeof(debug_buffer)); RC522_LOGD("picc >> %s", debug_buffer); } if (context->transaction->rx_align) { RC522_LOGD("applying mask (rx_align=%d)", context->transaction->rx_align); // Apply mask for rx_align..7 of the first byte result.bytes.ptr[0] &= (0xFF << context->transaction->rx_align); } // RxLastBits[2:0] indicates the number of valid bits in the last received byte. // If this value is 0, the whole byte is valid. RC522_RETURN_ON_ERROR(rc522_pcd_read(rc522, RC522_PCD_CONTROL_REG, &result.valid_bits)); result.valid_bits &= 0x07; if (result.valid_bits) { RC522_LOGD("not full byte received, valid_bits=%d", result.valid_bits); } if (context->error_reg & RC522_PCD_COLL_ERR_BIT) { return RC522_ERR_COLLISION; } // Perform CRC_A validation if (context->transaction->check_crc) { // We need at least the CRC_A value // and all 8 bits of the last byte RC522_CHECK_AND_RETURN(result.bytes.length < 3, ESP_ERR_INVALID_STATE); RC522_CHECK_AND_RETURN(result.valid_bits != 0, ESP_ERR_INVALID_STATE); // Verify CRC_A rc522_pcd_crc_t crc = { 0 }; RC522_RETURN_ON_ERROR(rc522_pcd_calculate_crc(rc522, &(rc522_bytes_t) { .ptr = result.bytes.ptr, .length = result.bytes.length - 2 }, &crc)); if (memcmp(result.bytes.ptr + result.bytes.length - 2, &crc, sizeof(crc)) != 0) { return RC522_ERR_CRC_WRONG; } } memcpy(out_result, &result, sizeof(result)); return ESP_OK; } esp_err_t rc522_picc_transceive(const rc522_handle_t rc522, const rc522_picc_transaction_t *transaction, rc522_picc_transaction_result_t *out_result) { RC522_CHECK(rc522 == NULL); RC522_CHECK(transaction == NULL); rc522_picc_transaction_t transaction_clone = { 0 }; memcpy(&transaction_clone, transaction, sizeof(transaction_clone)); transaction_clone.pcd_command = RC522_PCD_TRANSCEIVE_CMD; transaction_clone.expected_interrupts = RC522_PCD_RX_IRQ_BIT | RC522_PCD_IDLE_IRQ_BIT; rc522_picc_transaction_context_t context = { 0 }; RC522_RETURN_ON_ERROR_SILENTLY(rc522_picc_send(rc522, &transaction_clone, &context)); if (out_result) { if (!(context.interrupts & RC522_PCD_RX_IRQ_BIT)) { return RC522_ERR_RX_TIMEOUT; } RC522_RETURN_ON_ERROR_SILENTLY(rc522_picc_receive(rc522, &context, out_result)); } return ESP_OK; } inline static esp_err_t rc522_picc_parse_atqa(uint16_t atqa, rc522_picc_atqa_desc_t *out_atqa) { RC522_CHECK(out_atqa == NULL); out_atqa->source = atqa; out_atqa->rfu4 = (atqa >> 12) & 0x0F; out_atqa->prop_coding = (atqa >> 8) & 0x0F; out_atqa->uid_size = (atqa >> 6) & 0x03; out_atqa->rfu1 = (atqa >> 5) & 0x01; out_atqa->anticollision = atqa & 0x1F; return ESP_OK; } static esp_err_t rc522_picc_reqa_or_wupa(const rc522_handle_t rc522, uint8_t picc_cmd, rc522_picc_atqa_desc_t *out_atqa) { RC522_CHECK(rc522 == NULL); RC522_CHECK(picc_cmd != RC522_PICC_CMD_REQA && picc_cmd != RC522_PICC_CMD_WUPA); RC522_CHECK(out_atqa == NULL); RC522_RETURN_ON_ERROR(rc522_pcd_clear_bits(rc522, RC522_PCD_COLL_REG, RC522_PCD_VALUES_AFTER_COLL_BIT)); uint8_t buffer[2] = { 0 }; rc522_picc_transaction_t transaction = { .bytes = { .ptr = &picc_cmd, .length = 1 }, .valid_bits = 7, // REQA and WUPA use short frame format }; rc522_picc_transaction_result_t transaction_result = { .bytes = { .ptr = buffer, .length = sizeof(buffer) }, }; esp_err_t ret = rc522_picc_transceive(rc522, &transaction, &transaction_result); if (ret != ESP_OK) { // Timeouts are expected if no PICC are in the field, log other errors if (ret != RC522_ERR_RX_TIMER_TIMEOUT && ret != RC522_ERR_RX_TIMEOUT) { RC522_LOGD("non-timeout error: %04" RC522_X, ret); } return ret; } if (transaction_result.bytes.length != 2 || transaction_result.valid_bits != 0) { return RC522_ERR_INVALID_ATQA; } uint16_t atqa = (buffer[0] << 8) | buffer[1]; RC522_RETURN_ON_ERROR(rc522_picc_parse_atqa(atqa, out_atqa)); return ESP_OK; } inline esp_err_t rc522_picc_reqa(const rc522_handle_t rc522, rc522_picc_atqa_desc_t *out_atqa) { RC522_CHECK(rc522 == NULL); RC522_CHECK(out_atqa == NULL); RC522_LOGD("REQA"); return rc522_picc_reqa_or_wupa(rc522, RC522_PICC_CMD_REQA, out_atqa); } inline esp_err_t rc522_picc_wupa(const rc522_handle_t rc522, rc522_picc_atqa_desc_t *out_atqa) { RC522_CHECK(rc522 == NULL); RC522_CHECK(out_atqa == NULL); RC522_LOGD("WUPA"); return rc522_picc_reqa_or_wupa(rc522, RC522_PICC_CMD_WUPA, out_atqa); } /** * Resolve collision and SELECT a PICC */ esp_err_t rc522_picc_select(const rc522_handle_t rc522, rc522_picc_uid_t *out_uid, uint8_t *out_sak, bool skip_anticoll) { RC522_CHECK(rc522 == NULL); RC522_CHECK(skip_anticoll && (out_uid == NULL || out_uid->length < RC522_PICC_UID_SIZE_MIN)); bool uid_complete; bool select_done; bool use_cascade_tag; uint8_t cascade_level = 1; esp_err_t ret; uint8_t count; uint8_t check_bit; uint8_t index; uint8_t uid_index; // The first index in uid->uidByte[] that is used in the current Cascade Level. int8_t current_level_known_bits; // The number of known UID bits in the current Cascade Level. uint8_t buffer[9]; // The SELECT/ANTICOLLISION commands uses a 7 byte standard frame + 2 bytes CRC_A uint8_t buffer_used; // The number of bytes used in the buffer, ie the number of bytes to transfer to the FIFO. uint8_t rx_align; // Used in BitFramingReg. Defines the bit position for the first bit received. uint8_t tx_last_bits = 0; // Used in BitFramingReg. The number of valid bits in the last transmitted byte. uint8_t *response_buffer; uint8_t response_length; rc522_picc_uid_t uid; if (skip_anticoll) { memcpy(&uid, out_uid, sizeof(rc522_picc_uid_t)); } else { memset(&uid, 0, sizeof(uid)); } uint8_t sak; // 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 CT See explanation below. CT means Cascade Tag. // Byte 3: UID-data // Byte 4: UID-data // Byte 5: UID-data // Byte 6: BCC 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: (Section 6.5.4 of the ISO/IEC 14443-3 draft: UID contents and cascade levels) // 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 // Prepare MFRC522 RC522_RETURN_ON_ERROR(rc522_pcd_clear_bits(rc522, RC522_PCD_COLL_REG, RC522_PCD_VALUES_AFTER_COLL_BIT)); // Repeat Cascade Level loop until we have a complete UID. uid_complete = false; while (!uid_complete) { RC522_LOGD("cascade_level=%d, uid.length=%d", cascade_level, uid.length); // Set the Cascade Level in the SEL byte, find out if we need to use the Cascade Tag in byte 2. switch (cascade_level) { case 1: buffer[0] = RC522_PICC_CMD_SEL_CL1; uid_index = 0; // When we know that the UID has more than 4 bytes use_cascade_tag = uid.length > 4; break; case 2: buffer[0] = RC522_PICC_CMD_SEL_CL2; uid_index = 3; // When we know that the UID has more than 7 bytes use_cascade_tag = uid.length > 7; break; case 3: buffer[0] = RC522_PICC_CMD_SEL_CL3; uid_index = 6; use_cascade_tag = false; // Never used in CL3. break; default: return ESP_FAIL; // TODO: use custom err break; } RC522_LOGD("cl=%d, uid_index=%d, use_cascade_tag=%d", cascade_level, uid_index, use_cascade_tag); // How many UID bits are known in this Cascade Level? current_level_known_bits = skip_anticoll ? (4 * 8) : 0; // Copy the known bits from uid.uidByte[] to buffer[] index = 2; // destination index in buffer[] if (use_cascade_tag) { buffer[index++] = RC522_PICC_CMD_CT; } // The number of bytes needed to represent the known bits for this level. uint8_t bytes_to_copy = current_level_known_bits / 8 + (current_level_known_bits % 8 ? 1 : 0); if (bytes_to_copy) { // Max 4 bytes in each Cascade Level. Only 3 left if we use the Cascade Tag uint8_t max_bytes = use_cascade_tag ? 3 : 4; if (bytes_to_copy > max_bytes) { bytes_to_copy = max_bytes; } for (count = 0; count < bytes_to_copy; count++) { buffer[index++] = uid.value[uid_index + count]; } } // Now that the data has been copied we need to include the 8 bits in CT in current_level_known_bits if (use_cascade_tag) { current_level_known_bits += 8; } // Repeat anti collision loop until we can transmit all UID bits + BCC and receive a SAK - max 32 iterations. select_done = false; while (!select_done) { // Find out how many bits and bytes to send and receive. if (current_level_known_bits >= 32) { // All UID bits in this Cascade Level are known. This is a SELECT. RC522_LOGD("SELECT (cl=%d)", cascade_level); // NVB - Number of Valid Bits: Seven whole bytes buffer[1] = 0x70; // Calculate BCC - Block Check Character buffer[6] = buffer[2] ^ buffer[3] ^ buffer[4] ^ buffer[5]; // Calculate CRC_A rc522_pcd_crc_t crc = { 0 }; RC522_RETURN_ON_ERROR( rc522_pcd_calculate_crc(rc522, &(rc522_bytes_t) { .ptr = buffer, .length = 7 }, &crc)); buffer[7] = crc.lsb; buffer[8] = crc.msb; tx_last_bits = 0; // 0 => All 8 bits are valid. buffer_used = 9; // Store response in the last 3 bytes of buffer (BCC and CRC_A - not needed after tx) response_buffer = &buffer[6]; response_length = 3; } else { // This is an ANTICOLLISION. RC522_LOGD("ANTICOLLISION (cl=%d)", cascade_level); tx_last_bits = current_level_known_bits % 8; count = current_level_known_bits / 8; // Number of whole bytes in the UID part. index = 2 + count; // Number of whole bytes: SEL + NVB + UIDs buffer[1] = (index << 4) + tx_last_bits; // NVB - Number of Valid Bits buffer_used = index + (tx_last_bits ? 1 : 0); // Store response in the unused part of buffer response_buffer = &buffer[index]; response_length = sizeof(buffer) - index; } // Set bit adjustments // Having a separate variable is overkill. But it makes the next line easier to read. rx_align = tx_last_bits; // RxAlign = BitFramingReg[6..4]. TxLastBits = BitFramingReg[2..0] RC522_RETURN_ON_ERROR(rc522_pcd_write(rc522, RC522_PCD_BIT_FRAMING_REG, (rx_align << 4) + tx_last_bits)); // Transmit the buffer and receive the response. rc522_picc_transaction_t transaction = { .bytes = { .ptr = buffer, .length = buffer_used }, .rx_align = rx_align, .valid_bits = tx_last_bits, }; rc522_picc_transaction_result_t transaction_result = { .bytes = { .ptr = response_buffer, .length = response_length }, }; ret = rc522_picc_transceive(rc522, &transaction, &transaction_result); if (ret == ESP_OK) { response_length = transaction_result.bytes.length; tx_last_bits = transaction_result.valid_bits; } if (ret == RC522_ERR_COLLISION) { // More than one PICC in the field => collision. RC522_LOGD("collision detected (cl=%d, skip_anticoll=%d)", cascade_level, skip_anticoll); if (skip_anticoll) { // If we are skipping anticoll, we should not have collisions RC522_LOGD("unexpected collision detected"); return ret; } // CollReg[7..0] bits are: ValuesAfterColl reserved CollPosNotValid CollPos[4:0] uint8_t value_of_coll_reg; rc522_pcd_read(rc522, RC522_PCD_COLL_REG, &value_of_coll_reg); if (value_of_coll_reg & RC522_PCD_COLL_POS_NOT_VALID_BIT) { // Without a valid collision position we cannot continue RC522_LOGD("collision position not valid, coll_poss[4:0] out of range"); return RC522_ERR_COLLISION_UNSOLVABLE; } uint8_t collision_pos = value_of_coll_reg & 0x1F; // Values 0-31, 0 means bit 32. if (collision_pos == 0) { collision_pos = 32; } if (collision_pos <= current_level_known_bits) { // No progress - should not happen RC522_LOGD("collision_pos (%d) <= current_level_known_bits (%d)", collision_pos, current_level_known_bits); return RC522_ERR_COLLISION_UNSOLVABLE; } // Choose the PICC with the bit set. current_level_known_bits = collision_pos; count = current_level_known_bits % 8; // The bit to modify check_bit = (current_level_known_bits - 1) % 8; index = 1 + (current_level_known_bits / 8) + (count ? 1 : 0); // First byte is index 0. buffer[index] |= (1 << check_bit); } else if (ret != ESP_OK) { RC522_LOGD("transceive failed"); return ret; } else { // ESP_OK if (current_level_known_bits >= 32) { // This was a SELECT. // No more anticollision // We continue below outside the while. select_done = true; } else { // This was an ANTICOLLISION. // We now have all 32 bits of the UID in this Cascade Level current_level_known_bits = 32; // Run loop again to do the SELECT. } } } // End of while (!selectDone) RC522_LOGD("SELECT (cl=%d) done", cascade_level); // We do not check the CBB - it was constructed by us above. // Copy the found UID bytes from buffer[] to uid.uidByte[] index = (buffer[2] == RC522_PICC_CMD_CT) ? 3 : 2; // source index in buffer[] bytes_to_copy = (buffer[2] == RC522_PICC_CMD_CT) ? 3 : 4; for (count = 0; count < bytes_to_copy; count++) { uid.value[uid_index + count] = buffer[index++]; } // Check response SAK (Select Acknowledge) if (response_length != 3 || tx_last_bits != 0) { // SAK must be exactly 24 bits (1 byte + CRC_A). RC522_LOGD("invalid sak"); return RC522_ERR_INVALID_SAK; } // Verify CRC_A - do our own calculation and store the control in buffer[2..3] - those bytes are not needed // anymore. // compiler complains about uninitialized response_buffer even is // no chance that response_buffer is NULL here, so ignore warning here #pragma GCC diagnostic ignored "-Wmaybe-uninitialized" rc522_pcd_crc_t crc = { 0 }; RC522_RETURN_ON_ERROR( rc522_pcd_calculate_crc(rc522, &(rc522_bytes_t) { .ptr = response_buffer, .length = 1 }, &crc)); if (memcmp(response_buffer + 1, &crc, sizeof(crc)) != 0) { RC522_LOGD("crc wrong"); return RC522_ERR_CRC_WRONG; } buffer[2] = crc.lsb; buffer[3] = crc.msb; if (response_buffer[0] & 0x04) { // Cascade bit set - UID not complete yes cascade_level++; } else { uid_complete = true; sak = response_buffer[0]; } #pragma GCC diagnostic pop } // End of while (!uidComplete) // Set correct uid.size uid.length = 3 * cascade_level + 1; RC522_LOGD("sak=0x%02" RC522_X, sak); if (out_uid) { memcpy(out_uid, &uid, sizeof(uid)); } if (out_sak) { *out_sak = sak; } return ESP_OK; } /** * Checks if PICC is still in the PCD field */ esp_err_t rc522_picc_heartbeat( const rc522_handle_t rc522, const rc522_picc_t *picc, rc522_picc_uid_t *out_uid, uint8_t *out_sak) { RC522_CHECK(rc522 == NULL); RC522_CHECK(picc == NULL); RC522_CHECK(picc->state != RC522_PICC_STATE_ACTIVE && picc->state != RC522_PICC_STATE_ACTIVE_H); esp_err_t ret = ESP_OK; const uint8_t retries = 5; uint8_t retry = 1; do { rc522_picc_atqa_desc_t atqa; if (retry <= 2) { if (picc->state == RC522_PICC_STATE_ACTIVE) { ret = rc522_picc_reqa(rc522, &atqa); } else if (picc->state == RC522_PICC_STATE_ACTIVE_H) { ret = rc522_picc_wupa(rc522, &atqa); } } else { if ((ret = rc522_picc_reqa(rc522, &atqa)) != ESP_OK) { ret = rc522_picc_wupa(rc522, &atqa); } } if (ret == ESP_OK) { break; } rc522_delay_ms(5); } while (retry++ < retries); if (ret != ESP_OK) { return ret; } rc522_picc_uid_t uid; uint8_t sak; memcpy(&uid, &picc->uid, sizeof(rc522_picc_uid_t)); ret = rc522_picc_select(rc522, &uid, &sak, true); if (ret != ESP_OK) { return ret; } if (picc->sak != sak) { return RC522_ERR_PICC_POST_HEARTBEAT_MISSMATCH; } for (uint8_t i = 0; i < uid.length; i++) { if (picc->uid.value[i] != uid.value[i]) { return RC522_ERR_PICC_POST_HEARTBEAT_MISSMATCH; } } if (out_uid) { memcpy(out_uid, &uid, sizeof(uid)); } if (out_sak) { *out_sak = sak; } return ESP_OK; } esp_err_t rc522_picc_uid_to_str(const rc522_picc_uid_t *uid, char *buffer, uint8_t buffer_size) { RC522_CHECK(uid == NULL); RC522_CHECK(buffer == NULL); RC522_CHECK(buffer_size < RC522_PICC_UID_STR_BUFFER_SIZE_MAX); return rc522_buffer_to_hex_str(uid->value, uid->length, buffer, buffer_size); } esp_err_t rc522_picc_halta(const rc522_handle_t rc522, rc522_picc_t *picc) { RC522_CHECK(rc522 == NULL); RC522_CHECK(picc == NULL); RC522_LOGD("HALTA"); uint8_t buffer[4] = { 0 }; buffer[0] = RC522_PICC_CMD_HLTA; buffer[1] = 0; rc522_pcd_crc_t crc = { 0 }; RC522_RETURN_ON_ERROR(rc522_pcd_calculate_crc(rc522, &(rc522_bytes_t) { .ptr = buffer, .length = 2 }, &crc)); buffer[2] = crc.lsb; buffer[3] = crc.msb; rc522_picc_transaction_t transaction = { .bytes = { .ptr = buffer, .length = sizeof(buffer) }, }; esp_err_t ret = rc522_picc_transceive(rc522, &transaction, NULL); // If the PICC responds with any modulation during a period of 1 ms after the HLTA, // response shall be interpreted as 'not acknowledge', so timeout is not an error. if (ret == ESP_OK) { return RC522_ERR_HLTA_NOT_ACKED; } else if (ret == RC522_ERR_RX_TIMER_TIMEOUT || ret == RC522_ERR_RX_TIMEOUT) { RC522_RETURN_ON_ERROR(rc522_picc_set_state(rc522, picc, RC522_PICC_STATE_HALT, true)); return ESP_OK; } return ret; } esp_err_t rc522_picc_set_state( const rc522_handle_t rc522, rc522_picc_t *picc, rc522_picc_state_t new_state, bool fire_event) { RC522_CHECK(rc522 == NULL); RC522_CHECK(picc == NULL); esp_err_t ret = ESP_OK; if (picc->state == new_state) { return ESP_OK; } RC522_LOGD("changing state from %d to %d (fire=%d)", picc->state, new_state, fire_event); rc522_picc_state_t old_state = picc->state; picc->state = new_state; if (fire_event) { rc522_picc_state_changed_event_t event_data = { .old_state = old_state, .picc = picc, }; if ((ret = rc522_dispatch_event(rc522, RC522_EVENT_PICC_STATE_CHANGED, &event_data, sizeof(event_data))) != ESP_OK) { RC522_LOGW("picc_state_changed event dispatch failed (err=%04" RC522_X ")", ret); } } return ret; } rc522_picc_type_t rc522_picc_get_type(const rc522_picc_t *picc) { RC522_CHECK(picc == NULL); uint8_t sak = picc->sak; // http://www.nxp.com/documents/application_note/AN10833.pdf // Section: Coding of Select Acknowledge (SAK) // ignore 8th (iso14443 starts with LSBit = bit 1) // fixes wrong type for manufacturer Infineon (http://nfc-tools.org/index.php?title=ISO14443A) sak &= 0x7F; switch (sak) { case 0x09: return RC522_PICC_TYPE_MIFARE_MINI; case 0x08: return RC522_PICC_TYPE_MIFARE_1K; case 0x18: return RC522_PICC_TYPE_MIFARE_4K; case 0x00: return RC522_PICC_TYPE_MIFARE_UL; case 0x10: case 0x11: return RC522_PICC_TYPE_MIFARE_PLUS; case 0x01: return RC522_PICC_TYPE_TNP3XXX; case 0x20: return picc->atqa.source == 0x4400 ? RC522_PICC_TYPE_MIFARE_DESFIRE : RC522_PICC_TYPE_ISO_14443_4; case 0x40: return RC522_PICC_TYPE_ISO_18092; default: return RC522_PICC_TYPE_UNKNOWN; } } char *rc522_picc_type_name(rc522_picc_type_t type) { switch (type) { /* SAK-determined */ case RC522_PICC_TYPE_ISO_14443_4: return "PICC compliant with ISO/IEC 14443-4"; case RC522_PICC_TYPE_ISO_18092: return "PICC compliant with ISO/IEC 18092 (NFC)"; case RC522_PICC_TYPE_MIFARE_MINI: return "MIFARE Mini, 320 bytes"; case RC522_PICC_TYPE_MIFARE_1K: return "MIFARE 1K"; case RC522_PICC_TYPE_MIFARE_4K: return "MIFARE 4K"; case RC522_PICC_TYPE_MIFARE_UL: // SAK=0x00 for both of these families return "MIFARE Ultralight or NXP NTAG"; case RC522_PICC_TYPE_MIFARE_PLUS: return "MIFARE Plus"; case RC522_PICC_TYPE_MIFARE_DESFIRE: return "MIFARE DESFire"; case RC522_PICC_TYPE_TNP3XXX: return "MIFARE TNP3XXX"; /* Further ID processing */ case RC522_PICC_TYPE_MIFARE_UL_: return "MIFARE Ultralight"; case RC522_PICC_TYPE_MIFARE_UL_C: return "MIFARE Ultralight C"; case RC522_PICC_TYPE_MIFARE_UL_EV1_1: return "MIFARE Ultralight EV1 (80 bytes)"; case RC522_PICC_TYPE_MIFARE_UL_EV1_2: return "MIFARE Ultralight EV1 (164 bytes)"; case RC522_PICC_TYPE_MIFARE_UL_NANO: return "MIFARE Ultralight NANO"; case RC522_PICC_TYPE_MIFARE_UL_AES: return "MIFARE Ultralight AES"; case RC522_PICC_TYPE_NTAG2xx: return "NTAG2xx"; case RC522_PICC_TYPE_NTAG213: return "NTAG213"; case RC522_PICC_TYPE_NTAG215: return "NTAG215"; case RC522_PICC_TYPE_NTAG216: return "NTAG216"; case RC522_PICC_TYPE_UNDEFINED: case RC522_PICC_TYPE_UNKNOWN: default: return "unknown"; } } esp_err_t rc522_picc_print(const rc522_picc_t *picc) { RC522_CHECK(picc == NULL); char uid_str[RC522_PICC_UID_STR_BUFFER_SIZE_MAX]; RC522_RETURN_ON_ERROR(rc522_picc_uid_to_str(&picc->uid, uid_str, sizeof(uid_str))); ESP_LOGI(TAG, ""); ESP_LOGI(TAG, "╔══════════════╗"); ESP_LOGI(TAG, "║ ║ Type: %s", rc522_picc_type_name(picc->type)); ESP_LOGI(TAG, "║ RFID ║ UID: %s", uid_str); ESP_LOGI(TAG, "║ CARD ║ ATQA: 0x%04" RC522_X, picc->atqa.source); ESP_LOGI(TAG, "║ ║ SAK: 0x%02" RC522_X, picc->sak); ESP_LOGI(TAG, "╚══════════════╝"); ESP_LOGI(TAG, ""); return ESP_OK; }