2025-09-16 5:09 AM - edited 2025-09-16 5:34 AM
Hello.
I am making a custom PCB with a custom antenna and ST25R on the board.
Antenna: single-ended (RF0, RF1).
Bundle: Nucleo-C031C6 with STM32 for communication via I2C with my PCB.
The antenna has a SWR at a frequency of 13.56MHz is about 2.2 (not ideal, but for testing it should work). The spectrum analyzer shows that the field is established by command cmd = 0xC8 and with a fairly strong signal. It seems to me that my problem is somewhere in the configuration or the commands used.
I will briefly describe the steps (the code is presented below):
STEP 1: Reset to default state with a command 0xC0.
STEP 2: Basic interface configuration
Write IO config1 (0x00) = 0x81 (Single antenna, sup3V, No LF clock)
Write IO config2 (0x01) = 0xA6 (sup3V, AAT EN, I2C, VDD_RF)
STEP 3: Automatic regulator adjustment wit ha command 0xD6.
STEP 4: Enable regulators and oscillator
Operation control (0x02) = 0x80 (bit EN = 1)
Delay 100ms
STEP 5: Configure operation mode
Mode definition (0x03) = 0x01 → Initiator ISO14443A
ISO14443A settings (0x05) = 0x01 → enable anticollision
STEP 7: Clear FIFO before new communication wit ha command 0xD8.
STEP 8: Turn on RF field wit ha command 0xC8.
Wait for IRQ I_APON flag
Wait for IRQ I_CAT flag
STEP 9: Send REQA like a command 0xC6.
STEP 10: Wait for RXE
As I understand 0x1D register should return I_wu_a as "1". But I got always "0"
And then STEP 11 Read ATQA from FIFO is also "0"
My code:
#define ST25R_ADDR (0x50 << 1) // HAL expects 8-bit
uint8_t buf[2];
uint8_t rxBuf[512];
uint8_t buttonState = 1;
uint8_t state = 1; //Used for initialization and normal work
uint8_t cmd = 0;
uint8_t irq = 0;
uint8_t regAdr = 0;
while (1)
{
buttonState = HAL_GPIO_ReadPin(User_Button_GPIO_Port, User_Button_Pin);
if (buttonState == 0 && state == 1) {
buttonState=1;
/* === STEP 1. Reset to default state === */
// Direct command: Set Default (0xC0)
cmd = 0xC0;
HAL_I2C_Master_Transmit(&hi2c1, ST25R_ADDR, &cmd, 1, 100);
/* === STEP 2. Basic interface configuration === */
// Write IO config1 (0x00) = 0x81 (Single antenna, sup3V, No LF clock)
buf[0] = 0x00; buf[1] = 0x81;
HAL_I2C_Master_Transmit(&hi2c1, ST25R_ADDR, buf, 2, 100);
// Write IO config2 (0x01) = 0xA6 (sup3V, AAT EN, I2C, VDD_RF)
buf[0] = 0x01; buf[1] = 0xA6;
HAL_I2C_Master_Transmit(&hi2c1, ST25R_ADDR, buf, 2, 100);
/* === STEP 3. Automatic regulator adjustment === */
// Direct command: Adjust regulators (0xD6)
cmd = 0xD6;
HAL_I2C_Master_Transmit(&hi2c1, ST25R_ADDR, &cmd, 1, 100);
/* === STEP 4. Enable regulators and oscillator === */
// Operation control (0x02) = 0x80 (EN bit = 1)
buf[0] = 0x02; buf[1] = 0x80;
HAL_I2C_Master_Transmit(&hi2c1, ST25R_ADDR, buf, 2, 100);
// Wait for IRQ that oscillator is stable (osc_ok)
HAL_Delay(100); Just 100ms delay instead IRQ read
/* === STEP 5. Configure operation mode === */
// Mode definition (0x03) = 0x01 → Initiator ISO14443A
buf[0] = 0x03; buf[1] = 0x01;
HAL_I2C_Master_Transmit(&hi2c1, ST25R_ADDR, buf, 2, 100);
// ISO14443A settings (0x05) = 0x01 → enable anticollision
buf[0] = 0x05; buf[1] = 0x01;
HAL_I2C_Master_Transmit(&hi2c1, ST25R_ADDR, buf, 2, 100);
buf[0] = 0x3F|(1 << 6);
HAL_I2C_Mem_Read(&hi2c1, ST25R_ADDR, buf[0], I2C_MEMADD_SIZE_8BIT, &irq, 1, 100);
/* === STEP 6. Go to the next state === */
state = 2;
regAdr = 0x00 | (1 << 6);
HAL_I2C_Mem_Read(&hi2c1, ST25R_ADDR, regAdr, 1, &irq, 1, 100);
regAdr = 0x01 | (1 << 6);
HAL_I2C_Mem_Read(&hi2c1, ST25R_ADDR, regAdr, 1, &irq, 1, 100);
HAL_Delay(1000);
}
if (buttonState == 0 && state == 2) {
buttonState=1;
/* === STEP 7. Clear FIFO before new communication === */
// Direct command: Clear FIFO (0xDB)
// Ensures no "old" data is left in the queue before sending REQA.
cmd = 0xDB;
HAL_I2C_Master_Transmit(&hi2c1, ST25R_ADDR, &cmd, 1, 100);
/* === STEP 8. Turn on RF field === */
// Direct command: NFC Initial Field ON (0xC8)
// Starts the antenna activation and collision check procedure.
cmd = 0xC8;
HAL_I2C_Master_Transmit(&hi2c1, ST25R_ADDR, &cmd, 1, 100);
// Wait for IRQ I_APON confirming that the field was turned on successfully.
irq = 0;
regAdr = 0x1D|(1 << 6);//0x5D
for (int tries = 0; tries < 20; tries++) {
HAL_Delay(1); // 1 ms пауза
HAL_I2C_Mem_Read(&hi2c1, ST25R_ADDR, regAdr, 1, &irq, 1, 100);
if (irq & (1 << 5)) {
break;
}
}
// Wait for IRQ I_CAT confirming that the field was turned on successfully.
irq = 0;
regAdr = 0x1B|(1 << 6);//0x5B
for (int tries = 0; tries < 20; tries++) {
HAL_Delay(1); // 1 ms пауза
HAL_I2C_Mem_Read(&hi2c1, ST25R_ADDR, regAdr, 1, &irq, 1, 100);
if (irq & (1 << 1)) {
break;
}
}
// === STEP 9. Send REQA ===
cmd = 0xC6;
HAL_I2C_Master_Transmit(&hi2c1, ST25R_ADDR, &cmd, 1, 100);
// up to here it works.
// === STEP 10. Wait for RXE ===
irq = 0;
regAdr = 0x1D | (1 << 6);
for (int tries = 0; tries < 250; tries++) {
HAL_Delay(1);
HAL_I2C_Mem_Read(&hi2c1, ST25R_ADDR, regAdr, 1, &irq, 1, 100);
if (irq != 0) {
break;
}
}
// === STEP 11. Read ATQA from FIFO ===
uint8_t atqa[2] = {0};
regAdr = 0x5F; // FIFO read
HAL_I2C_Mem_Read(&hi2c1, ST25R_ADDR, regAdr, I2C_MEMADD_SIZE_8BIT, atqa, 2, 100);
// Now atqa[0..1] contains the card response
Log from Logic Analyzer:
//Step 1
write to 0x50 nak
//Step 2
write to 0x50 ack data: 0x00 0x81
write to 0x50 ack data: 0x01 0xA6
//Step 3
write to 0x50 ack data: 0xD6
//Step 4
write to 0x50 ack data: 0x02 0x80
//Step 5
write to 0x50 ack data: 0x03 0x01
write to 0x50 ack data: 0x05 0x01
//Just read ID
write to 0x50 ack data: 0x7F
read to 0x50 ack data: 0x31
//Just check configuration registers
write to 0x50 ack data: 0x40
read to 0x50 ack data: 0x81
write to 0x50 ack data: 0x41
read to 0x50 ack data: 0xA6
//Step 7
write to 0x50 ack data: 0xDB
//Step 8
write to 0x50 ack data: 0xC8
//Waiting for I_APON
write to 0x50 ack data: 0x5D
read to 0x50 ack data: 0x20
/Waiting for I_CAT
...
write to 0x50 ack data: 0x5B
read to 0x50 ack data: 0x02
//Step 9
write to 0x50 ack data: 0xC6
//Step 10 - Waiting for I_wu_a*
write to 0x50 ack data: 0x5D
read to 0x50 ack data: 0x00
...
is always 0x00
//Step 11
read to 0x50 ack data: 0x00 0x00
2025-09-16 6:22 AM
Hi,
not sure what you are trying to achieve with this. There are some obvious errors in your code. But even if you fix them you are only at the point of getting the ATQA. There are many more things to discover before you can actually read an NFC-A card's UID. And this is then only the first step.
I recommend to use our RFAL - it implements all necessary procedures to be an NFC Reader conforming to the various standards.
BR, Ulysses
P.S. the immediate issues I see are no guard time being implemented, polling the wrong register for I_rxe and I_wu_a being related to card mode and not to R/W mode.
2025-09-16 7:35 AM
Oh. I didn't know about that. Could you suggest some good guide on how to use RFAL?
2025-09-16 7:46 AM
Hi,
best would be to start off with X-CUBE-NFC6 which has an integration into CubeMX.
Also available are the plain RFAL (STSW-ST25RFAL002) with an extensive User manual and ST25 embedded NFC lib (STSW-ST25R-LIB) which contains various demos based on RFAL.
BR, Ulysses