Problems with ST25R300, Mismatched voltages, and RSSI 0
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;
}




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?
