2025-08-06 4:07 PM
I am trying to get UIDs returned for an PICOPASS ISO15693 iClass card. I am using the default analogConfig.h generated for a ST25R3916B (though am using a ST2525R3917B). I can read ISO14443A/B and non-picopass ISO15693 cards without any issues, but am unable to read this iClass card. My understanding is that iClass cards use PICOPASS, which requires me to send:
In order to do this, I am doing the following to initialize RFAL in PICOPASS:
ReturnCode err;
err = rfalSetMode(RFAL_MODE_POLL_PICOPASS, RFAL_BR_26p48, RFAL_BR_26p48);
if (err != ERR_NONE) {
LOG_ERR("Failed to set mode for NFC PICO-15693 (err %d)", err);
return -EIO;
}
rfalSetErrorHandling(RFAL_ERRORHANDLING_NONE);
rfalSetGT(RFAL_GT_PICOPASS);
rfalSetFDTListen(RFAL_FDT_LISTEN_PICOPASS_POLLER);
rfalSetFDTPoll(RFAL_FDT_POLL_PICOPASS_POLLER);
I will then turn on my RF field and try to POLL for the UID using:
static int st25r_nfc_pico15693_poll(const struct device *dev, struct nfc_id *device_ids,
uint8_t count)
{
int ret;
LOG_DBG("Polling for NFC PICO-15693");
uint8_t tx_buff[16] = {0};
uint8_t rx_buff[16] = {0};
uint16_t rx_len = 0;
uint32_t start_time = k_uptime_get_32();
do {
tx_buff[0] = 0x0A;
ret = st25r_nfc_pico15693_rxtx(dev, tx_buff, nfc_byte2bit(1), rx_buff,
sizeof(rx_buff), &rx_len);
if (ret) {
LOG_ERR("Failed to send ACTALL command (err %d)", ret);
}
tx_buff[0] = 0x0C;
ret = st25r_nfc_pico15693_rxtx(dev, tx_buff, nfc_byte2bit(1), rx_buff,
sizeof(rx_buff), &rx_len);
if (ret) {
LOG_ERR("Failed to send IDENTIFY command (err %d)", ret);
ret = 0;
continue;
}
tx_buff[0] = 0x81;
memcpy(&tx_buff[1], rx_buff, 8); /* load the hashed UID */
ret = st25r_nfc_pico15693_rxtx(dev, tx_buff, nfc_byte2bit(9), rx_buff,
sizeof(rx_buff), &rx_len);
if (ret) {
LOG_ERR("Failed to send SELECT command (err %d)", ret);
return 0;
} else {
break;
}
} while (k_uptime_get_32() - start_time < 500);
return 1;
}
/**
* @brief NFC PICO-15693 poller @p rxtx API function
* See @ref nfc_poller_api for poller API
*/
static int st25r_nfc_pico15693_rxtx(const struct device *dev, uint8_t *tx_data, uint16_t tx_bitlen,
uint8_t *rx_data, uint16_t rx_size, uint16_t *rx_bitlen)
{
ReturnCode err;
rfalTransceiveContext ctx = {
.txBuf = tx_data,
.txBufLen = tx_bitlen,
.rxBuf = rx_data,
.rxBufLen = nfc_byte2bit(rx_size),
.rxRcvdLen = rx_bitlen,
/* Flags get overriden in rfalStartTransceive() because in PICOPASS mode */
.flags = RFAL_TXRX_FLAGS_DEFAULT,
.fwt = rfalConvUsTo1fc(NFC_ST25R_DATA(dev)->timeout_us),
};
err = rfalStartTransceive(&ctx);
if (err != ERR_NONE) {
LOG_ERR("Failed to start transceive (err %d)", err);
return -EIO;
}
do {
rfalWorker();
err = rfalGetTransceiveStatus();
} while (rfalIsTransceiveInTx() && (err == ERR_BUSY));
if (rfalIsTransceiveInRx()) {
err = RFAL_ERR_NONE;
}
if (err != RFAL_ERR_NONE) {
LOG_ERR("Failed to transceive Tx data (err %d)", err);
return err;
}
/* Block until Rx is complete */
err = rfalTransceiveBlockingRx();
if (err == RFAL_ERR_CRC) {
LOG_WRN("CRC check failed");
/* continue */
} else if ((err >= RFAL_ERR_INCOMPLETE_BYTE) && (err <= RFAL_ERR_INCOMPLETE_BYTE_07)) {
LOG_WRN("Incomplete byte received");
/* continue */
} else if (err) {
LOG_ERR("Failed to receive Rx data (err %d)", err);
return -EIO;
}
LOG_INF("Received %d bits", *rx_bitlen);
LOG_HEXDUMP_INF(rx_data, nfc_bit2byte(*rx_bitlen), "Received data:");
return 0;
}
I've also tried adjusting the register manually prior to entering the do/while loop where I'm tried variations of:
err = st25r3916ChangeRegisterBits(ST25R3916_REG_MODE, ST25R3916_REG_MODE_tr_am,
ST25R3916_REG_MODE_tr_am_am);
if (err != ERR_NONE) {
LOG_ERR("Failed to set mode to AM for NFC PICO-15693 (err %d)", err);
return -EIO;
}
err = st25r3916ChangeRegisterBits(ST25R3916_REG_TX_DRIVER,
ST25R3916_REG_TX_DRIVER_am_mod_mask,
ST25R3916_REG_TX_DRIVER_am_mod_10percent);
if (err != ERR_NONE) {
LOG_ERR("Failed to set AM modulation to 10%% for NFC PICO-15693 (err %d)", err);
return -EIO;
}
I'm pretty confused how to go about this. The only documentation I have to go off is this from Proxmark which I'm not 100% sure is even for the same card.
For context, I am process of upgrading our architecture from using the ams AG 3911 library by Christian Eisendle (I'm just going based on the headers here, I don't know who actually released this) to using the RFAL library for the 3917B. I'm unsure what I'm missing here and am hoping ST can provide me some additional context or notes about possible register changes needed...
Solved! Go to Solution.
2025-08-07 2:38 AM
Dear kwolff,
Picopass is not an open NFC standard/protocol, and therefore one needs to implement the handling / state machine on top of the provided drivers accordingly.
In regards to RF technology (modulation scheme, coding, etc) Picopass is based/equivalent to ISO15693, with a few minor modifications, such as Command Set and CRC.
The RFAL library allows to set the ST25R Devices in the correct communication mode, and to freely exchange user defined payload with the cards. No need access registers/configurations directly.
Below an example flow:
uint8_t txBuf[20];
uint8_t rxBuf[20];
uint16_t actRxLen;
rfalInitialize();
rfalSetMode(RFAL_MODE_POLL_PICOPASS, RFAL_BR_26p48, RFAL_BR_26p48);
rfalSetFDTListen(RFAL_FDT_LISTEN_PICOPASS_POLLER);
rfalSetFDTPoll(RFAL_FDT_POLL_PICOPASS_POLLER);
rfalSetGT(RFAL_GT_PICOPASS);
rfalFieldOnAndStartGT();
txBuf[0] = xx; // Define command to be sent, e.g ACTALL
rfalTransceiveBlockingTxRx(txBuf, 1, rxBuf, sizeof(rxBuf), &actRxLen, (RFAL_TXRX_FLAGS_DEFAULT | RFAL_TXRX_FLAGS_CRC_TX_MANUAL | RFAL_TXRX_FLAGS_NFCV_FLAG_MANUAL), rfalConvMsTo1fc(20) );
Hope it helps
Kind regards
GP
2025-08-07 2:38 AM
Dear kwolff,
Picopass is not an open NFC standard/protocol, and therefore one needs to implement the handling / state machine on top of the provided drivers accordingly.
In regards to RF technology (modulation scheme, coding, etc) Picopass is based/equivalent to ISO15693, with a few minor modifications, such as Command Set and CRC.
The RFAL library allows to set the ST25R Devices in the correct communication mode, and to freely exchange user defined payload with the cards. No need access registers/configurations directly.
Below an example flow:
uint8_t txBuf[20];
uint8_t rxBuf[20];
uint16_t actRxLen;
rfalInitialize();
rfalSetMode(RFAL_MODE_POLL_PICOPASS, RFAL_BR_26p48, RFAL_BR_26p48);
rfalSetFDTListen(RFAL_FDT_LISTEN_PICOPASS_POLLER);
rfalSetFDTPoll(RFAL_FDT_POLL_PICOPASS_POLLER);
rfalSetGT(RFAL_GT_PICOPASS);
rfalFieldOnAndStartGT();
txBuf[0] = xx; // Define command to be sent, e.g ACTALL
rfalTransceiveBlockingTxRx(txBuf, 1, rxBuf, sizeof(rxBuf), &actRxLen, (RFAL_TXRX_FLAGS_DEFAULT | RFAL_TXRX_FLAGS_CRC_TX_MANUAL | RFAL_TXRX_FLAGS_NFCV_FLAG_MANUAL), rfalConvMsTo1fc(20) );
Hope it helps
Kind regards
GP
2025-08-07 8:57 AM
Thank you for the example! That worked for me. I believe I just had the wrong flags being set. Once I changed the flags to yours, everything work :)