nfc: add ISO15693-3 and SLIX write-back support

Add Write Single Block poller functions for ISO 15693-3 and SLIX/SLIX2,
enable the Write scene for both protocols, and skip blocks that already
contain the correct data to minimise RF session time and avoid timeouts
on large tags (e.g. 80-block ICODE SLIX2).

Tested on NXP ICODE SLIX2 (UID E0 04 01 09 05 05 09 37, 80 x 4 byte blocks).
This commit is contained in:
DoniyorI
2026-03-31 22:05:59 -04:00
parent 5d3f1a6ad2
commit b9fa2943ed
11 changed files with 358 additions and 7 deletions
@@ -15,6 +15,11 @@ extern "C" {
#define ISO15693_3_FDT_LISTEN_FC (4320U)
#define ISO15693_3_POLL_POLL_MIN_US (1500U)
/* NVM write time varies by tag type (ICODE SLIX: 9.6ms, generic: up to 20ms).
* ISO 15693-3 write commands require a longer FWT than reads to account for
* EEPROM programming delays before the tag responds. 20ms covers all known tags. */
#define ISO15693_3_FDT_WRITE_POLL_FC (271200U)
#define ISO15693_3_REQ_FLAG_SUBCARRIER_1 (0U << 0)
#define ISO15693_3_REQ_FLAG_SUBCARRIER_2 (1U << 0)
#define ISO15693_3_REQ_FLAG_DATA_RATE_LO (0U << 1)
@@ -176,6 +176,25 @@ Iso15693_3Error
return ret;
}
Iso15693_3Error iso15693_3_write_block_response_parse(const BitBuffer* buf) {
Iso15693_3Error ret = Iso15693_3ErrorNone;
do {
if(iso15693_3_error_response_parse(&ret, buf)) break;
typedef struct {
uint8_t flags;
} WriteBlockResponseLayout;
if(bit_buffer_get_size_bytes(buf) != sizeof(WriteBlockResponseLayout)) {
ret = Iso15693_3ErrorUnexpectedResponse;
break;
}
} while(false);
return ret;
}
Iso15693_3Error iso15693_3_get_block_security_response_parse(
uint8_t* data,
uint16_t block_count,
@@ -28,6 +28,8 @@ Iso15693_3Error
Iso15693_3Error
iso15693_3_read_block_response_parse(uint8_t* data, uint8_t block_size, const BitBuffer* buf);
Iso15693_3Error iso15693_3_write_block_response_parse(const BitBuffer* buf);
Iso15693_3Error iso15693_3_get_block_security_response_parse(
uint8_t* data,
uint16_t block_count,
@@ -13,6 +13,14 @@ extern "C" {
*/
typedef struct Iso15693_3Poller Iso15693_3Poller;
/**
* @brief Get the Iso15693_3 data accumulated by the poller.
*
* @param[in] instance pointer to the Iso15693_3Poller instance.
* @return pointer to the Iso15693_3Data structure filled during activation.
*/
const Iso15693_3Data* iso15693_3_poller_get_data(Iso15693_3Poller* instance);
/**
* @brief Enumeration of possible Iso15693_3 poller event types.
*/
@@ -144,6 +152,40 @@ Iso15693_3Error iso15693_3_poller_get_blocks_security(
uint8_t* data,
uint16_t block_count);
/**
* @brief Write a single Iso15693_3 block.
*
* Must ONLY be used inside the callback function.
*
* @param[in, out] instance pointer to the instance to be used in the transaction.
* @param[in] data pointer to the buffer containing the data to write.
* @param[in] block_number block number to write.
* @param[in] block_size size of the block in bytes.
* @return Iso15693_3ErrorNone on success, an error code on failure.
*/
Iso15693_3Error iso15693_3_poller_write_block(
Iso15693_3Poller* instance,
const uint8_t* data,
uint8_t block_number,
uint8_t block_size);
/**
* @brief Write multiple consecutive Iso15693_3 blocks.
*
* Must ONLY be used inside the callback function.
*
* @param[in, out] instance pointer to the instance to be used in the transaction.
* @param[in] data pointer to the buffer containing the data to write.
* @param[in] block_count number of blocks to write.
* @param[in] block_size size of each block in bytes.
* @return Iso15693_3ErrorNone on success, an error code on failure.
*/
Iso15693_3Error iso15693_3_poller_write_blocks(
Iso15693_3Poller* instance,
const uint8_t* data,
uint16_t block_count,
uint8_t block_size);
#ifdef __cplusplus
}
#endif
@@ -234,6 +234,57 @@ Iso15693_3Error iso15693_3_poller_read_blocks(
return ret;
}
Iso15693_3Error iso15693_3_poller_write_block(
Iso15693_3Poller* instance,
const uint8_t* data,
uint8_t block_number,
uint8_t block_size) {
furi_assert(instance);
furi_assert(data);
bit_buffer_reset(instance->tx_buffer);
bit_buffer_reset(instance->rx_buffer);
bit_buffer_append_byte(
instance->tx_buffer, ISO15693_3_REQ_FLAG_SUBCARRIER_1 | ISO15693_3_REQ_FLAG_DATA_RATE_HI);
bit_buffer_append_byte(instance->tx_buffer, ISO15693_3_CMD_WRITE_BLOCK);
bit_buffer_append_byte(instance->tx_buffer, block_number);
bit_buffer_append_bytes(instance->tx_buffer, data, block_size);
Iso15693_3Error ret;
do {
ret = iso15693_3_poller_send_frame(
instance, instance->tx_buffer, instance->rx_buffer, ISO15693_3_FDT_WRITE_POLL_FC);
if(ret != Iso15693_3ErrorNone) break;
ret = iso15693_3_write_block_response_parse(instance->rx_buffer);
} while(false);
return ret;
}
Iso15693_3Error iso15693_3_poller_write_blocks(
Iso15693_3Poller* instance,
const uint8_t* data,
uint16_t block_count,
uint8_t block_size) {
furi_assert(instance);
furi_assert(data);
furi_assert(block_count);
furi_assert(block_size);
Iso15693_3Error ret = Iso15693_3ErrorNone;
for(uint32_t i = 0; i < block_count; ++i) {
ret = iso15693_3_poller_write_block(
instance, &data[block_size * i], (uint8_t)i, block_size);
if(ret != Iso15693_3ErrorNone) break;
}
return ret;
}
Iso15693_3Error iso15693_3_poller_get_blocks_security(
Iso15693_3Poller* instance,
uint8_t* data,
@@ -33,8 +33,6 @@ struct Iso15693_3Poller {
void* context;
};
const Iso15693_3Data* iso15693_3_poller_get_data(Iso15693_3Poller* instance);
#ifdef __cplusplus
}
#endif
+34
View File
@@ -118,6 +118,40 @@ SlixError slix_poller_set_password(
SlixPassword password,
SlixRandomNumber random_number);
/**
* @brief Write a single block to a Slix card.
*
* Must ONLY be used inside the callback function.
*
* @param[in, out] instance pointer to the instance to be used in the transaction.
* @param[in] data pointer to the buffer containing the data to write.
* @param[in] block_number block number to write.
* @param[in] block_size size of the block in bytes.
* @return SlixErrorNone on success, an error code on failure.
*/
SlixError slix_poller_write_block(
SlixPoller* instance,
const uint8_t* data,
uint8_t block_number,
uint8_t block_size);
/**
* @brief Write multiple consecutive blocks to a Slix card.
*
* Must ONLY be used inside the callback function.
*
* @param[in, out] instance pointer to the instance to be used in the transaction.
* @param[in] data pointer to the buffer containing the data to write.
* @param[in] block_count number of blocks to write.
* @param[in] block_size size of each block in bytes.
* @return SlixErrorNone on success, an error code on failure.
*/
SlixError slix_poller_write_blocks(
SlixPoller* instance,
const uint8_t* data,
uint16_t block_count,
uint8_t block_size);
#ifdef __cplusplus
}
#endif
+28
View File
@@ -128,3 +128,31 @@ SlixError slix_poller_set_password(
return error;
}
SlixError slix_poller_write_block(
SlixPoller* instance,
const uint8_t* data,
uint8_t block_number,
uint8_t block_size) {
furi_assert(instance);
furi_assert(data);
const Iso15693_3Error error =
iso15693_3_poller_write_block(instance->iso15693_3_poller, data, block_number, block_size);
return slix_process_iso15693_3_error(error);
}
SlixError slix_poller_write_blocks(
SlixPoller* instance,
const uint8_t* data,
uint16_t block_count,
uint8_t block_size) {
furi_assert(instance);
furi_assert(data);
furi_assert(block_count);
furi_assert(block_size);
const Iso15693_3Error error =
iso15693_3_poller_write_blocks(instance->iso15693_3_poller, data, block_count, block_size);
return slix_process_iso15693_3_error(error);
}