cancel
Showing results for 
Search instead for 
Did you mean: 

Problems with ST25R300, Mismatched voltages, and RSSI 0

Matheus_Markies
Associate II

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?

 

1 ACCEPTED SOLUTION

Accepted Solutions
Brian TIDAL
ST Employee

Hi Matheus,

The ST25 embedded NFC library provides a collection of middleware that runs on the NUCLEO-L476RG board.

The X-CUBE-NFC12 package provides a polling example, which is identical to the polling example from the ST25 embedded NFC library. This example can be ported to any Nucleo board by using STM32CubeMX. For your needs, use the X-CUBE-NFC12 package and follow the X-CUBE-NFC12_GettingStarted guide in the documentation folder of the package.

Rgds

BT

In order to give better visibility on the answered topics, please click on Accept as Solution on the reply which solved your issue or answered your question.

View solution in original post

5 REPLIES 5
Brian TIDAL
ST Employee

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

In order to give better visibility on the answered topics, please click on Accept as Solution on the reply which solved your issue or answered your question.

Good morning, Brain! Thank you for your reply.
I think my best option right now is to use X-Nucleo instead of starting with my own design right away. That said, I bought an X-NUCLEO-NFC12A1 board to start testing.

WhatsApp Image 2026-01-10 at 10.51.19.jpeg

I didn't know about RFAL, I would like to use the polling example to test my board. But I don't know how to adapt it to use the example. In my case, instead of NUCLEO-L476RG, I have a NUCLEO F103RB. I tried to run the example located in "...\STMicroelectronics\ST25NFC_Embedded_Lib_ST25R500_1.8. 0\Projects\STM32L476RG-Nucleo\Applications\X-NUCLEO-NFC12A1\polling\STM32CubeIDE."

WhatsApp Image 2026-01-10 at 11.03.48.jpeg

However, I did not get the logs in the serial to know if it worked or not, but I think I did it wrong, since, as I mentioned, I tried to load the example made for the L476RG directly into my F103RB.

I would like to know how I can better use this middleware, first to make this polling example work on my Nucleo board. And later, how can I put this middleware on my custom board (I don't know how to create a project with it integrated).

Brian TIDAL
ST Employee

Hi Matheus,

The ST25 embedded NFC library provides a collection of middleware that runs on the NUCLEO-L476RG board.

The X-CUBE-NFC12 package provides a polling example, which is identical to the polling example from the ST25 embedded NFC library. This example can be ported to any Nucleo board by using STM32CubeMX. For your needs, use the X-CUBE-NFC12 package and follow the X-CUBE-NFC12_GettingStarted guide in the documentation folder of the package.

Rgds

BT

In order to give better visibility on the answered topics, please click on Accept as Solution on the reply which solved your issue or answered your question.

Good morning, Brian! Thank you very much! I finally got it working here.

WhatsApp Image 2026-01-12 at 10.37.53.jpeg

Now I have another question. Please correct me if I should open a new thread for this instead of posting it here.

My problem is increasing the EH voltage of the ST25. As I said earlier, the goal is to power a set of low-power chips with it. I measured it in an improvised way to get an idea and arrived at 10mV on the EH pin. I don't know if I did this measurement correctly.

WhatsApp Image 2026-01-12 at 10.38.00.jpeg

What can I do to increase this to perhaps 1V, for example? Would it be possible to reach up to 3V in EH?

Brian TIDAL
ST Employee

Hi Matheus,

please create a new post and mark this one as replied.

Thanks

Rgds

BT

 

In order to give better visibility on the answered topics, please click on Accept as Solution on the reply which solved your issue or answered your question.