2025-02-12 9:19 AM
We are using an ST25R3916B to read and write to both ST25TV512C tags and Mifare Classic 1K cards. While we have successfully communicated with ST25 tags, we are encountering issues at the authentication stage with Mifare Classic.
Steps We Are Following:
Before sending authentication commands, we ensure the reader is in the RFAL_NFC_STATE_ACTIVATED state.
Mifare Classic authentication follows a challenge-response mechanism. To begin, we must send the authentication command and obtain a 4-byte random challenge from the card.
The command consists of:
uint8_t authCmd[12];
authCmd[0] = 0x60; // Authenticate using Key A
authCmd[1] = block; // Block number
memcpy(&authCmd[2], keyA, 6); // Copy the 6-byte key
memcpy(&authCmd[8], device.nfcid, device.nfcidLen); // Copy the UID
We then transmit this using rfalTransceiveBlockingTxRx():
status = driver.rfalTransceiveBlockingTxRx(
authCmd, // Transmit buffer
sizeof(authCmd), // Transmit length
response, // Receive buffer
sizeof(response), // Max receive length
&responseLen, // Actual response length
RFAL_TXRX_FLAGS_DEFAULT,
RFAL_FWT_NONE
);
Current Issue
In the code below, I attempt to send only 4 bytes for authentication, which includes CRC values (D1 and 3D) for 0x60 and 0x04:
uint8_t authCmd[4];
authCmd[0] = 0x60; // Authentication command (Key A)
authCmd[1] = 0x04; // Block number
authCmd[2] = 0xD1; // CRC for 60 and 04
authCmd[3] = 0x3D; // CRC
status = driver.rfalTransceiveBlockingTxRx(
authCmd, // Transmit buffer
sizeof(authCmd), // Transmit length
response, // Receive buffer
sizeof(response), // Max receive length
&responseLen, // Actual response length
RFAL_TXRX_FLAGS_DEFAULT,
RFAL_FWT_NONE
);
What I Need Help With
Any help would be greatly appreciated.
Thanks in advance.
2025-02-13 12:58 AM
Hi AMCI,
you can find some hints here: nfc0541-read-mifare-classic-1k .
Concerning the ERR_IO: No idea really, please debug through and isolate the code line. There should only be very few ERR_IO and in normal operation this error code shouldn't appear.
Regards, Ulysses
2025-03-05 2:28 PM
I have the same issue with not being able to receive the NONCE upon calling the code here. After powering up the ST25R3916B, we set it to 3.3V in register 0x01H by setting bit 7 high Updated value of register 0x1 (Binary): 0b10000000.
With or without the register setting, I can successfully get the device, identify it as Mifare Classic 1K (ATQA: 04 00
SAK: 08) and then proceed to the authentication using the code below.
In some other threads, I read that I should be using bit-oriented calls vs. byte. Does that apply to the call to which the device responds with the nonce. I'm not able to get the nonce. My ESP32S3 MCU goes to watchdog and reboots.
2025-03-06 12:28 AM
Hello,
I think this 60 command is in reality much shorter. Please check against the specs of the cards in use. To my knowledge it can be sent using normal mode. First verify that. And once you get a response - as subsequent commands need to be sent in low level mode (SW generated parity and CRC)- it will be a good idea to already exercise with this command the transmission and reception of low level frames.
A similar approach with a T2T command was discussed under keyword "template" in the mentioned other community thread.
BR, Ulysses
2025-03-23 10:05 PM
Still no progress in getting a nonce from Mifare Classic 1K. Once I get the UID, I reverted to going directly to the registers, please see my mifare_authenticate function from which I do all the low level stuff. NOTHING.
1. Tap a tag to read its content␍␊
ISO14443A/NFC-A card found. UID: 77B2FCFF␍␊
ATQA: 04 00␊
SAK: 08␊
Detected MIFARE Classic 1K.␍␊
:locked_with_key: Starting MIFARE authentication sequence...␍␊
🧹 Resetting FIFO...␍␊
:wrench: Configuring ST25R3916 for ISO14443A...␍␊
:outbox_tray: Auth Frame: 60 04 A0 A1 A2 A3 A4 A5 FF FC B2 77 ␍␊
:inbox_tray: Writing frame to FIFO...␍␊
:satellite_antenna: Sending TRANSMIT_WITH_CRC command...␍␊
:hourglass_not_done: Waiting for IRQ response...␍␊
IRQ_MAIN: 0x7F␍␊
IRQ_ERROR: 0x7F␍␊
:white_heavy_check_mark: IRQ received: Card responded.␍␊
:package: FIFO Length: 0␊
:warning: FIFO empty, no response from card.␍␊
:incoming_envelope: Reading nonce from FIFO...␍␊
:locked_with_key: Nonce (Nt): 00 00 00 00 ␍␊
:rocket: Authentication phase 1 complete.␍␊
Operation completed␍␊
Tag can be removed from the field␍␊
1. Tap a tag to read its content␍␊
ISO14443A/NFC-A card found. UID: 77B2FCFF␍␊
ATQA: 04 00␊
SAK: 08␊
Detected MIFARE Classic 1K.␍␊
:locked_with_key: Starting MIFARE authentication sequence...␍␊
🧹 Resetting FIFO...␍␊
:wrench: Configuring ST25R3916 for ISO14443A...␍␊
:outbox_tray: Auth Frame: 60 04 A0 A1 A2 A3 A4 A5 FF FC B2 77 ␍␊
:inbox_tray: Writing frame to FIFO...␍␊
:satellite_antenna: Sending TRANSMIT_WITH_CRC command...␍␊
:hourglass_not_done: Waiting for IRQ response...␍␊
IRQ_MAIN: 0x7F␍␊
IRQ_ERROR: 0x7F␍␊
:white_heavy_check_mark: IRQ received: Card responded.␍␊
:package: FIFO Length: 0␊
:warning: FIFO empty, no response from card.␍␊
:incoming_envelope: Reading nonce from FIFO...␍␊
:locked_with_key: Nonce (Nt): 00 00 00 00 ␍␊
:rocket: Authentication phase 1 complete.␍␊
Operation completed␍␊
Tag can be removed from the field␍␊
1. Tap a tag to read its content␍␊
#define ST25R3916_REG_IO_CONF1 0x04
#define ST25R3916_REG_FIFO_LENGTH1 0x1C
#define ST25R3916_REG_FIFO_LENGTH2 0x1D
#define ST25R3916_REG_FIFO_CONTROL 0x18
#define ST25R3916_REG_FIFO_DATA 0x1F
#define ST25R3916_REG_COMMAND 0x00
#define ST25R3916_CMD_TRANSMIT_WITH_CRC 0x1A
#define ST25R3916_REG_OP_CONTROL 0x01
#define ST25R3916_REG_NFCIP1_BIT_RATE 0x08
#define ST25R3916_REG_IRQ_MAIN 0x0D
#define ST25R3916_REG_IRQ_ERROR 0x0E
#define ST25R3916_REG_FIFO_STATUS 0x1C
#define ST25R3916_REG_FIFO_LENGTH 0x1D
#define ST25R3916_REG_FIFO 0x1F
#define ST25R3916_REG_OPERATION_CONTROL 0x02
#define ST25R3916_REG_IRQ_ERROR 0x0C
#define ST25R3916_REG_IRQ_MASK_ERROR 0x17
void getTagASystemInfo(const rfalNfcDevice *device)
{
const rfalNfcaListenDevice &nfca = device->dev.nfca;
uint8_t atqa_0 = nfca.sensRes.anticollisionInfo;
uint8_t atqa_1 = nfca.sensRes.platformInfo;
uint8_t sak = nfca.selRes.sak;
USBSerial.printf("ATQA: %02X %02X\n", atqa_0, atqa_1);
USBSerial.printf("SAK: %02X\n", sak);
if (atqa_0 == 0x44 && atqa_1 == 0x00 && sak == 0x00)
{
USBSerial.println("Detected ST25TN01K Tag!");
}
else if (atqa_0 == 0x04 && atqa_1 == 0x00 && sak == 0x08)
{
USBSerial.println("Detected MIFARE Classic 1K.");
mifare_authenticate(0x04, device->nfcid, true);
}
else
{
USBSerial.println("Unknown NFC-A tag detected.");
}
}
uint8_t readRegister(uint8_t regAddress)
{
uint8_t readCommand = regAddress | 0x40; // Set the read bit
// Prepare to send the SPI command and receive the data
uint8_t result = 0;
// Enable the device by pulling CS low
digitalWrite(CS_PIN, LOW);
// Begin the SPI transaction with 1 MHz, MSB first, and SPI mode 1
SPI.beginTransaction(spiSettings);
// Send the read command
SPI.transfer(readCommand); // Send the command byte (readCommand)
// Receive the register value
result = SPI.transfer(0x00); // Send dummy data to clock in the register value
// End the transaction
SPI.endTransaction();
// Deselect the device by pulling CS high
digitalWrite(CS_PIN, HIGH);
// Return the read register value
return result;
}
void writeRegister(uint8_t regAddress, uint8_t value)
{
uint8_t writeCommand = regAddress & 0x7F; // Clear bit 7
digitalWrite(CS_PIN, LOW);
SPI.beginTransaction(spiSettings);
SPI.transfer(writeCommand); // Send register address
SPI.transfer(value); // Send the value to write
SPI.endTransaction();
digitalWrite(CS_PIN, HIGH);
}
void modifyRegisterBit(uint8_t regAddress, uint8_t bitPosition, bool setBit)
{
uint8_t value = readRegister(regAddress);
if (setBit)
value |= (1 << bitPosition);
else
value &= ~(1 << bitPosition);
digitalWrite(CS_PIN, LOW);
SPI.beginTransaction(spiSettings);
SPI.transfer(regAddress & 0x7F); // Clear MSB for write
SPI.transfer(value);
SPI.endTransaction();
digitalWrite(CS_PIN, HIGH);
}
void setup3v3()
{
uint8_t regAddress = 0x01;
modifyRegisterBit(regAddress, 7, true); // Set bit 7 to 1 for 3.3V mode
delay(10); // Optional safety delay
uint8_t updatedValue = readRegister(regAddress);
USBSerial.print(":gear: 0x01 register (binary): 0b");
for (int i = 7; i >= 0; --i)
USBSerial.print((updatedValue >> i) & 0x01);
USBSerial.println();
}
bool checkIrqStatus()
{
uint8_t irqMain = readRegister(ST25R3916_REG_IRQ_MAIN);
uint8_t irqError = readRegister(ST25R3916_REG_IRQ_ERROR);
USBSerial.print("IRQ_MAIN: 0x");
USBSerial.println(irqMain, HEX);
USBSerial.print("IRQ_ERROR: 0x");
USBSerial.println(irqError, HEX);
if (irqMain & 0x08)
{
// RX IRQ — indicates a response received
return true;
}
if (irqError != 0x00)
{
// Something went wrong
USBSerial.println(":cross_mark: Error occurred during authentication.");
return false;
}
USBSerial.println(":hourglass_not_done: No response yet...");
return false;
}
void reset_fifo() {
USBSerial.println("🧹 Resetting FIFO...");
writeRegister(ST25R3916_REG_FIFO_CONTROL, 0x80);
}
void configure_mode() {
USBSerial.println(":wrench: Configuring ST25R3916 for ISO14443A...");
writeRegister(ST25R3916_REG_MODE, 0x00); // ISO14443A framing
writeRegister(ST25R3916_REG_ISO14443A_NFC, 0x02); // Short frames
writeRegister(ST25R3916_REG_OPERATION_CONTROL, 0x3C); // TX + RX
writeRegister(ST25R3916_REG_OPERATION_CONTROL, 0x80); // RF field on
delay(5);
}
void clear_irqs() {
writeRegister(ST25R3916_REG_IRQ_MASK_MAIN, 0xF0);
writeRegister(ST25R3916_REG_IRQ_MASK_ERROR, 0xFF);
writeRegister(ST25R3916_REG_IRQ_MAIN, 0x7F);
writeRegister(ST25R3916_REG_IRQ_ERROR, 0x7F);
}
void print_auth_frame(const uint8_t *frame, size_t len) {
USBSerial.print(":outbox_tray: Auth Frame: ");
for (size_t i = 0; i < len; ++i) {
USBSerial.printf("%02X ", frame[i]);
}
USBSerial.println();
}
void write_auth_frame(const uint8_t *frame, size_t len) {
USBSerial.println(":inbox_tray: Writing frame to FIFO...");
for (size_t i = 0; i < len; ++i) {
writeRegister(ST25R3916_REG_FIFO_DATA, frame[i]);
}
}
bool wait_for_irq() {
USBSerial.println(":hourglass_not_done: Waiting for IRQ response...");
for (int i = 0; i < 10; ++i) {
if (checkIrqStatus()) {
USBSerial.println(":white_heavy_check_mark: IRQ received: Card responded.");
return true;
}
delay(10);
}
USBSerial.println(":cross_mark: No IRQ detected. Authentication failed or timed out.");
return false;
}
void read_and_print_fifo() {
uint8_t fifoLen = readRegister(ST25R3916_REG_FIFO_LENGTH);
USBSerial.printf(":package: FIFO Length: %d\n", fifoLen);
if (fifoLen == 0) {
USBSerial.println(":warning: FIFO empty, no response from card.");
return;
}
if (fifoLen == 1) {
uint8_t nak = readRegister(ST25R3916_REG_FIFO_DATA);
USBSerial.printf(":cross_mark: NAK Byte Received: 0x%02X\n", nak);
}
USBSerial.print(":incoming_envelope: FIFO Data: ");
digitalWrite(CS_PIN, LOW);
SPI.beginTransaction(spiSettings);
SPI.transfer(ST25R3916_REG_FIFO | 0x40);
for (int i = 0; i < fifoLen; ++i) {
USBSerial.printf("%02X ", SPI.transfer(0x00));
}
SPI.endTransaction();
digitalWrite(CS_PIN, HIGH);
USBSerial.println();
}
bool isFieldOn()
{
uint8_t status = readRegister(ST25R3916_REG_OPERATION_CONTROL);
return (status & 0x80); // Bit 7 = RF field status
}
void enableRfField()
{
USBSerial.println(":high_voltage: Enabling RF field...");
writeRegister(ST25R3916_REG_OPERATION_CONTROL, 0x80);
delay(10); // Give the field some time to stabilize
}
void mifare_authenticate(uint8_t block_number, uint8_t *uid, bool use_key_a) {
size_t short_frame_len = 2;
size_t long_frame_len = 12;
USBSerial.println(":locked_with_key: Starting MIFARE authentication sequence...");
uint8_t cmd = use_key_a ? 0x60 : 0x61;
uint8_t key[6] = {0xA0, 0xA1, 0xA2, 0xA3, 0xA4, 0xA5};
uint8_t auth_frame[12] = {cmd, block_number};
memcpy(&auth_frame[2], key, 6);
auth_frame[8] = uid[3]; auth_frame[9] = uid[2];
auth_frame[10] = uid[1]; auth_frame[11] = uid[0];
reset_fifo();
configure_mode();
clear_irqs();
size_t send_frame_len = long_frame_len;
print_auth_frame(auth_frame, send_frame_len);
write_auth_frame(auth_frame, send_frame_len);
USBSerial.println(":satellite_antenna: Sending TRANSMIT_WITH_CRC command...");
writeRegister(ST25R3916_REG_COMMAND, 0x0C);
delay(20);
if (!wait_for_irq()) return;
read_and_print_fifo();
USBSerial.println(":incoming_envelope: Reading nonce from FIFO...");
uint8_t nonce[4] = {0};
digitalWrite(CS_PIN, LOW);
SPI.beginTransaction(spiSettings);
SPI.transfer(ST25R3916_REG_FIFO | 0x40);
for (int i = 0; i < 4; i++) {
nonce[i] = SPI.transfer(0x00);
}
SPI.endTransaction();
digitalWrite(CS_PIN, HIGH);
USBSerial.print(":locked_with_key: Nonce (Nt): ");
for (int i = 0; i < 4; i++) {
USBSerial.printf("%02X ", nonce[i]);
}
USBSerial.println();
USBSerial.println(":rocket: Authentication phase 1 complete.");
}
void setup()
{
USBSerial.begin(115200);
configureSPI();
if (rfal_nfc.rfalNfcInitialize() == ERR_NONE)
{
discParam.compMode = RFAL_COMPLIANCE_MODE_NFC;
discParam.devLimit = 1U;
discParam.nfcfBR = RFAL_BR_212;
discParam.ap2pBR = RFAL_BR_424;
ST_MEMCPY(&discParam.nfcid3, NFCID3, sizeof(NFCID3));
ST_MEMCPY(&discParam.GB, GB, sizeof(GB));
discParam.GBLen = sizeof(GB);
discParam.notifyCb = NULL;
discParam.totalDuration = 1000U;
discParam.wakeupEnabled = false;
discParam.wakeupConfigDefault = true;
discParam.techs2Find = (RFAL_NFC_POLL_TECH_A | RFAL_NFC_POLL_TECH_B | RFAL_NFC_POLL_TECH_F | RFAL_NFC_POLL_TECH_V | RFAL_NFC_POLL_TECH_ST25TB);
discParam.techs2Find |= RFAL_NFC_POLL_TECH_AP2P;
state = DEMO_ST_START_DISCOVERY;
}
}
2025-03-24 1:08 AM
Hi,
as hinted above ("I think this 60 command is in reality much shorter"). I am pretty sure that your auth command content is incorrect. No need to go on register level.
Regards, Ulysses
2025-03-24 7:42 AM
The 60 command is either 2 (60, <Block>) or 12 (60, <Block> <KEY_A or KEY_B>, <UID LAST_FOUR>). I've tried it both ways via RFAL and also via Registers (my code earlier). In all cases, I do not receive a NONCE from the device. In fact, I am not sure that I'm even communicating with the device at all. The closest I've come is probably the IRQ value after sending. I must be missing something in what I'm doing and, perhaps, completely missing your hints to succeed with getting the device to respond.
2025-03-24 7:58 AM
Hi,
in your code I don't find writing the Tx length register, so I think the command does not get transmitted correctly.
Pls send the 2 bytes command through RFAL and if not working, then provide a logic analyzer trace (SPI+IRQ).
BR, Ulysses
2025-03-24 8:54 AM
Thanks. Will let you know.
2025-03-24 10:30 AM
Sadly, no change in response. I tried the calls via RFAL numerous times always with the TX lengths included. Now that I'm at the register level and specifying the bit counts for TX length, I want to make sure I have the all the register numbers and the commands correct.