Skip to main content
Visitor II
December 26, 2025
Question

Problems with ST25R300, Mismatched voltages, and RSSI 0

  • December 26, 2025
  • 1 reply
  • 58 views

I am entering the world of NFC with a somewhat complex project, but I took the initiative to start it. The goal was to create a sensor that would work only with Energy Harvesting from the reader.

For this, I chose the ST25R300 chip to be the chip for my reader board and sensor power supply, mainly due to its high transmission power.

I am having difficulty getting it to work properly. Basically, I ran several tests with some DEBUG codes on the reader board. In these tests, I found some inconsistencies. The first is the VDD_A voltages, which come from a reading of the chip itself, which is registering almost 6V, and the other inconsistency is the RSSI giving 0.

I believe the error is in my antenna and matching circuit. I would like someone to help me improve this system by pointing out where I may have made a mistake in their design.

I made the antenna using the ST website (eDesignSuite antenna design tool). I also made the maching circuit using ST's eDesignSuite.

In addition, my circuit was largely based on the core board (X-NUCLEO-NFC12A1).

The main premise was to develop something small that could perform energy harvesting, so I made a 10-turn antenna, which is larger than the TAG antenna. I made a mistake in developing a single-ended antenna instead of a differential one, but I wanted to start with something simpler first before perfecting it.

Could the design of a 10-turn antenna for the ST25R300 prevent the RF driver from oscillating to the point where the RSSI is zero, or does the VDD_A value suggest a hardware failure in the analog stage?

Observations:

  • The chip responds to SPI and returns the correct ID (0x16).
  • When enabling the RF field, RSSI does not fluctuate and remains at 0.
  • The ADJUST_REGULATORS command was executed, but VDD_A remains abnormal.
  • I have already disconnected the antenna to try something more direct by removing resistor R5, but even so, RSSI remains at 0 and the voltage remains the same at VDD_A.
  • On the PCB, I removed the ground plane on the antenna side.

Below is the test code:

int main(void)
{

 /* USER CODE BEGIN 1 */

 /* USER CODE END 1 */

 /* MCU Configuration--------------------------------------------------------*/

 /* Reset of all peripherals, Initializes the Flash interface and the Systick. */
 HAL_Init();

 /* USER CODE BEGIN Init */
 nfc_reader.CS_port = GPIOB;
 nfc_reader.CS_pin = ST25_CS_Pin;
 nfc_reader.reset_port = GPIOA;
 nfc_reader.reset_pin = GPIO_PIN_3;
 nfc_reader.IRQ_port = GPIOA;
 nfc_reader.IRQ_pin = GPIO_PIN_0;
 nfc_reader.hSPIx = &hspi2;
 /* USER CODE END Init */

 /* Configure the system clock */
 SystemClock_Config();

 /* USER CODE BEGIN SysInit */

 /* USER CODE END SysInit */

 /* Initialize all configured peripherals */
 MX_GPIO_Init();
 MX_I2C2_Init();
 MX_SPI1_Init();
 MX_SPI2_Init();
 MX_USART1_UART_Init();
 /* USER CODE BEGIN 2 */
 /* USER CODE BEGIN 2 */
 if (ST25R300_Init(&nfc_reader)) {
 DEBUG_PRINT("ST25R300 inicializado com sucesso!\r\n");

 // Configura para modo RX
 if (ST25R300_ConfigureRxMode(&nfc_reader)) {
 DEBUG_PRINT("Modo RX configurado!\r\n");

 // Habilita RX
 if (ST25R300_EnableRx(&nfc_reader)) {
 DEBUG_PRINT("RX habilitado - Pronto para medir RSSI!\r\n");
 }
 }
 } else {
 DEBUG_PRINT("ERRO: Falha na inicialização do ST25R300!\r\n");
 }

 /* USER CODE END 2 */

 /* Infinite loop */
 /* USER CODE BEGIN WHILE */
 while (1) {
 static uint32_t last_read = 0;

 // Lê RSSI a cada 100ms
 if (HAL_GetTick() - last_read >= 100) {
 last_read = HAL_GetTick();

 if (ST25R300_ReadRSSI(&nfc_reader, &rssi_data)) {
 int db_int = (int)rssi_data.rssi_dbm;
 int db_dec = (int)((rssi_data.rssi_dbm - (float)db_int) * 10.0f);
 if (db_dec < 0) db_dec = -db_dec;
 const char* sign = (rssi_data.rssi_dbm < 0 && db_int == 0) ? "-" : "";

 DEBUG_PRINT("RSSI - I: %3d, Q: %3d, Total: %3d, dBm: %s%d.%d\r\n",
 rssi_data.rssi_i,
 rssi_data.rssi_q,
 rssi_data.rssi_total,
 sign,
 db_int,
 db_dec);
 } else {
 DEBUG_PRINT("ERRO: Falha na leitura do RSSI!\r\n");
 }
 }
 }
 /* USER CODE END 3 */
}
/*
 * st25r300.h
 *
 * Created on: Dec 25, 2025
 * Author: Matheus Markies
 */

#ifndef INC_ST25R300_H_
#define INC_ST25R300_H_

#include "stm32l0xx_hal.h" // Ajuste conforme seu MCU
#include <stdint.h>
#include <stdbool.h>

/* Estrutura de configuração do ST25R300 */
typedef struct ST25R300_setting {
 GPIO_TypeDef* CS_port;
 uint16_t CS_pin;
 GPIO_TypeDef* reset_port;
 uint16_t reset_pin;
 GPIO_TypeDef* IRQ_port;
 uint16_t IRQ_pin;
 SPI_HandleTypeDef* hSPIx;
} ST25R300;

/* Estrutura para dados de RSSI */
typedef struct {
 uint8_t rssi_i; // RSSI canal I (7 bits)
 uint8_t rssi_q; // RSSI canal Q (7 bits)
 uint8_t rssi_total; // RSSI combinado
 float rssi_dbm; // RSSI em dBm (aproximado)
} ST25R300_RSSI;

/* Definições de registros */
#define ST25R300_REG_OPERATION 0x00
#define ST25R300_REG_GENERAL_CONFIG 0x01
#define ST25R300_REG_RX_ANALOG_SETTINGS_1 0x09
#define ST25R300_REG_RX_ANALOG_SETTINGS_2 0x0A
#define ST25R300_REG_RX_DIGITAL_SETTINGS_1 0x0D
#define ST25R300_REG_PROTOCOL_1 0x14
#define ST25R300_REG_IRQ_STATUS_3 0x3E
#define ST25R300_REG_IC_IDENTITY 0x3F
#define ST25R300_REG_STATUS_1 0x40
#define ST25R300_REG_RSSI_DISPLAY_1 0x4A
#define ST25R300_REG_RSSI_DISPLAY_2 0x4B

/* Bits do Operation Register */
#define ST25R300_OP_EN (1 << 3) // Enable ready mode
#define ST25R300_OP_RX_EN (1 << 5) // Enable RX
#define ST25R300_OP_TX_EN (1 << 6) // Enable TX
#define ST25R300_OP_VDDDR_EN (1 << 4) // Enable VDD_DR regulator

/* Bits do Status Register 1 */
#define ST25R300_STATUS_OSC_OK (1 << 4) // Oscillator stable

/* Comandos diretos */
#define ST25R300_CMD_SET_DEFAULT 0x60
#define ST25R300_CMD_CLEAR_FIFO 0x64
#define ST25R300_CMD_ADJUST_REGULATORS 0x68
#define ST25R300_CMD_CALIBRATE_RC 0xEE

/* Timeouts */
#define ST25R300_TIMEOUT_MS 100
#define ST25R300_RESET_DELAY_MS 10
#define ST25R300_OSC_TIMEOUT_MS 50

/* Protótipos de funções */
bool ST25R300_Init(ST25R300 *dev);
bool ST25R300_Reset(ST25R300 *dev);
bool ST25R300_ConfigureRxMode(ST25R300 *dev);
bool ST25R300_EnableRx(ST25R300 *dev);
bool ST25R300_DisableRx(ST25R300 *dev);
bool ST25R300_ReadRSSI(ST25R300 *dev, ST25R300_RSSI *rssi);
bool ST25R300_ReadRegister(ST25R300 *dev, uint8_t reg_addr, uint8_t *data);
bool ST25R300_WriteRegister(ST25R300 *dev, uint8_t reg_addr, uint8_t data);
bool ST25R300_SendCommand(ST25R300 *dev, uint8_t cmd);
uint8_t ST25R300_ReadID(ST25R300 *dev);
bool ST25R300_WaitOscillatorReady(ST25R300 *dev);

#endif /* INC_ST25R300_H_ */
/*
 * st25r300.c
 *
 * Created on: Dec 25, 2025
 * Author: Matheus Markies
 */
#include "st25r300.h"
#include <math.h>

/* Macros auxiliares */
#define CS_LOW() HAL_GPIO_WritePin(dev->CS_port, dev->CS_pin, GPIO_PIN_RESET)
#define CS_HIGH() HAL_GPIO_WritePin(dev->CS_port, dev->CS_pin, GPIO_PIN_SET)
#define RESET_LOW() HAL_GPIO_WritePin(dev->reset_port, dev->reset_pin, GPIO_PIN_RESET)
#define RESET_HIGH() HAL_GPIO_WritePin(dev->reset_port, dev->reset_pin, GPIO_PIN_SET)

/**
 * @brief Lê um registro do ST25R300
 * @PAram dev Ponteiro para estrutura do dispositivo
 * @PAram reg_addr Endereço do registro (0x00-0x57)
 * @PAram data Ponteiro para armazenar o dado lido
 * @return true se sucesso, false se erro
 */
bool ST25R300_ReadRegister(ST25R300 *dev, uint8_t reg_addr, uint8_t *data) {
 if (!dev || !data || reg_addr > 0x57) return false;

 uint8_t tx_data[2] = {0x80 | reg_addr, 0xFF}; // Bit 7=1 para leitura
 uint8_t rx_data[2] = {0};

 CS_LOW();
 HAL_StatusTypeDef status = HAL_SPI_TransmitReceive(dev->hSPIx, tx_data, rx_data, 2, ST25R300_TIMEOUT_MS);
 CS_HIGH();

 if (status == HAL_OK) {
 *data = rx_data[1];
 return true;
 }
 return false;
}

/**
 * @brief Escreve em um registro do ST25R300
 * @PAram dev Ponteiro para estrutura do dispositivo
 * @PAram reg_addr Endereço do registro (0x00-0x57)
 * @PAram data Dado a ser escrito
 * @return true se sucesso, false se erro
 */
bool ST25R300_WriteRegister(ST25R300 *dev, uint8_t reg_addr, uint8_t data) {
 if (!dev || reg_addr > 0x57) return false;

 uint8_t tx_data[2] = {reg_addr, data}; // Bit 7=0 para escrita

 CS_LOW();
 HAL_StatusTypeDef status = HAL_SPI_Transmit(dev->hSPIx, tx_data, 2, ST25R300_TIMEOUT_MS);
 CS_HIGH();

 return (status == HAL_OK);
}

/**
 * @brief Envia um comando direto ao ST25R300
 * @PAram dev Ponteiro para estrutura do dispositivo
 * @PAram cmd Código do comando (0x60-0xF9)
 * @return true se sucesso, false se erro
 */
bool ST25R300_SendCommand(ST25R300 *dev, uint8_t cmd) {
 if (!dev) return false;

 CS_LOW();
 HAL_StatusTypeDef status = HAL_SPI_Transmit(dev->hSPIx, &cmd, 1, ST25R300_TIMEOUT_MS);
 CS_HIGH();

 return (status == HAL_OK);
}

/**
 * @brief Executa reset via pino RESET
 * @PAram dev Ponteiro para estrutura do dispositivo
 * @return true se sucesso
 */
bool ST25R300_Reset(ST25R300 *dev) {
 if (!dev) return false;

 // Pulso de reset (RESET high->low)
 RESET_HIGH();
 HAL_Delay(ST25R300_RESET_DELAY_MS);
 RESET_LOW();
 HAL_Delay(ST25R300_RESET_DELAY_MS);

 return true;
}

/**
 * @brief Lê o ID do chip
 * @PAram dev Ponteiro para estrutura do dispositivo
 * @return ID do chip (0x16 para ST25R300) ou 0 em caso de erro
 */
uint8_t ST25R300_ReadID(ST25R300 *dev) {
 uint8_t id = 0;
 if (ST25R300_ReadRegister(dev, ST25R300_REG_IC_IDENTITY, &id)) {
 return (id >> 3) & 0x1F; // Bits 7:3 contêm o IC type
 }
 return 0;
}

/**
 * @brief Aguarda o oscilador ficar estável
 * @PAram dev Ponteiro para estrutura do dispositivo
 * @return true se oscilador estável, false se timeout
 */
bool ST25R300_WaitOscillatorReady(ST25R300 *dev) {
 uint32_t start_tick = HAL_GetTick();
 uint8_t status;

 while ((HAL_GetTick() - start_tick) < ST25R300_OSC_TIMEOUT_MS) {
 if (ST25R300_ReadRegister(dev, ST25R300_REG_STATUS_1, &status)) {
 if (status & ST25R300_STATUS_OSC_OK) {
 return true;
 }
 }
 HAL_Delay(1);
 }
 return false;
}

/**
 * @brief Inicializa o ST25R300 para operação básica
 * @PAram dev Ponteiro para estrutura do dispositivo
 * @return true se sucesso, false se erro
 */
bool ST25R300_Init(ST25R300 *dev) {
 if (!dev || !dev->hSPIx) return false;

 // Reset do chip
 ST25R300_Reset(dev);
 HAL_Delay(10);

 // Verifica ID do chip
 uint8_t chip_id = ST25R300_ReadID(dev);
 if (chip_id != 0x16) { // ST25R300 ID = 0x16
 return false;
 }

 // Comando Set Default
 ST25R300_SendCommand(dev, ST25R300_CMD_SET_DEFAULT);
 HAL_Delay(5);

 // Habilita Ready Mode (oscilador)
 ST25R300_WriteRegister(dev, ST25R300_REG_OPERATION, ST25R300_OP_EN);

 // Aguarda oscilador estabilizar
 if (!ST25R300_WaitOscillatorReady(dev)) {
 return false;
 }

 // Limpa FIFO
 ST25R300_SendCommand(dev, ST25R300_CMD_CLEAR_FIFO);

 // Calibra RC para AWS
 ST25R300_SendCommand(dev, ST25R300_CMD_CALIBRATE_RC);
 HAL_Delay(5);

 return true;
}

/**
 * @brief Configura o ST25R300 para modo RX
 * @PAram dev Ponteiro para estrutura do dispositivo
 * @return true se sucesso, false se erro
 */
bool ST25R300_ConfigureRxMode(ST25R300 *dev) {
 if (!dev) return false;

 // Configura Rx Analog Settings 1
 // hpf_ctrl = 3 (380kHz), gain_boost = 0
 ST25R300_WriteRegister(dev, ST25R300_REG_RX_ANALOG_SETTINGS_1, 0x53);

 // Configura Rx Analog Settings 2
 // afe_gain_rw = 0 (sem redução de ganho inicial)
 ST25R300_WriteRegister(dev, ST25R300_REG_RX_ANALOG_SETTINGS_2, 0x00);

 // Configura Rx Digital Settings 1
 // AGC habilitado, filtros padrão
 ST25R300_WriteRegister(dev, ST25R300_REG_RX_DIGITAL_SETTINGS_1, 0x8F);

 // Configura Protocol Register 1 para NFC-A (modo padrão)
 // om = 0x1 (ISO14443-A/NFC-A)
 ST25R300_WriteRegister(dev, ST25R300_REG_PROTOCOL_1, 0x01);

 // Ajusta reguladores
 uint8_t op_reg;
 ST25R300_ReadRegister(dev, ST25R300_REG_OPERATION, &op_reg);
 ST25R300_WriteRegister(dev, ST25R300_REG_OPERATION,
 op_reg | ST25R300_OP_VDDDR_EN);
 HAL_Delay(1); // Aguarda 10us (1ms é mais que suficiente)

 // Ajusta reguladores
 ST25R300_SendCommand(dev, ST25R300_CMD_ADJUST_REGULATORS);
 HAL_Delay(5);

 return true;
}

/**
 * @brief Habilita o modo RX
 * @PAram dev Ponteiro para estrutura do dispositivo
 * @return true se sucesso, false se erro
 */
bool ST25R300_EnableRx(ST25R300 *dev) {
 if (!dev) return false;

 uint8_t op_reg;
 ST25R300_ReadRegister(dev, ST25R300_REG_OPERATION, &op_reg);

 // Habilita RX
 op_reg |= ST25R300_OP_RX_EN;

 return ST25R300_WriteRegister(dev, ST25R300_REG_OPERATION, op_reg);
}

/**
 * @brief Desabilita o modo RX
 * @PAram dev Ponteiro para estrutura do dispositivo
 * @return true se sucesso, false se erro
 */
bool ST25R300_DisableRx(ST25R300 *dev) {
 if (!dev) return false;

 uint8_t op_reg;
 ST25R300_ReadRegister(dev, ST25R300_REG_OPERATION, &op_reg);

 // Desabilita RX
 op_reg &= ~ST25R300_OP_RX_EN;

 return ST25R300_WriteRegister(dev, ST25R300_REG_OPERATION, op_reg);
}

/**
 * @brief Lê os valores de RSSI
 * @PAram dev Ponteiro para estrutura do dispositivo
 * @PAram rssi Ponteiro para estrutura de dados RSSI
 * @return true se sucesso, false se erro
 */
bool ST25R300_ReadRSSI(ST25R300 *dev, ST25R300_RSSI *rssi) {
 if (!dev || !rssi) return false;

 ST25R300_SendCommand(dev, 0xF9);
 HAL_Delay(1);

 uint8_t rssi_i_raw, rssi_q_raw;

 // Lê RSSI do canal I (registro 0x4A, bits 6:0)
 if (!ST25R300_ReadRegister(dev, ST25R300_REG_RSSI_DISPLAY_1, &rssi_i_raw)) {
 return false;
 }

 // Lê RSSI do canal Q (registro 0x4B, bits 6:0)
 if (!ST25R300_ReadRegister(dev, ST25R300_REG_RSSI_DISPLAY_2, &rssi_q_raw)) {
 return false;
 }

 // Extrai os 7 bits de dados (bit 7 é RFU)
 rssi->rssi_i = rssi_i_raw & 0x7F;
 rssi->rssi_q = rssi_q_raw & 0x7F;

 // Calcula RSSI total (magnitude vetorial)
 float i_float = (float)rssi->rssi_i;
 float q_float = (float)rssi->rssi_q;
 rssi->rssi_total = (uint8_t)sqrtf(i_float * i_float + q_float * q_float);

 // Conversão aproximada para dBm
 // Fórmula empírica: RSSI_dBm ≈ -90 + (RSSI_total * 0.5)
 // Ajuste conforme calibração real
 rssi->rssi_dbm = -90.0f + (rssi->rssi_total * 0.5f);

 return true;
}

Captura de tela 2025-12-26 105158.png

Captura de tela 2025-12-26 112739.png

Captura de tela 2025-10-06 191052.png

Captura de tela 2025-12-26 111836.png

Captura de tela 2025-09-24 104642.png

Abnormal VDD_A levels: Is it physically possible for the ST25R300's internal VDD_A regulator to report 5.66V when the main V_DD supply is only 3.25V? Could this be a symptom of RF energy reflecting back into the RFI pins due to antenna mismatch, or does it point to a specific fault in the internal LDO/ADC reference?

ADC RSSI saturation: My diagnostics show that the channel I ADC has reached its maximum of 255, but the RSSI remains at 0. This saturation usually indicates that the receiver stage is being “blinded” by self-interference. Is the high Q factor of my 10-turn antenna Q = 66 the likely culprit for this behavior in a single-ended configuration?

Are all these problems reflected by my antenna being small but with many turns? Even when I test without it in the circuit?

 

    This topic has been closed for replies.

    1 reply

    Technical Moderator
    January 5, 2026

    Hi Matheus,

    It is recommended to use the RFAL middleware for application development. The RFAL provides support for various NFC protocols and includes the ST25R300 low-level driver for register access.

    For information about the RSSI, see section 5.4.7 of the ST25R300 datasheet. In particular "The RSSI information can be read after a tag reply without any timing constraints."

    The nominal voltage of VDD_A is 3.0 V. The conversion factor is 5.9. The typo in the Datasheet DS14655 - Rev 2 has been reported internally and will be fixed in the next revision.

    Regarding the Design verification of the antenna, make sure to follow section 9 of AN6092 Antenna design for ST25R500 and ST25R300 devices.

    Also I suggest to run you code on X-NUCLEO-NFC12A1.

    Rgds

    BT