L14-Click 1.0
STM32WLE5CC LoRaWAN Sensor Platform
Loading...
Searching...
No Matches
nfc_st25dv16kc.c
Go to the documentation of this file.
1/*
2 * nfc_st25dv16kc.c
3 *
4 * Created on: 9. 3. 2026
5 * Author: Milan
6 *
7 * ST25DV16KC – Dynamic NFC/RFID Tag with 16Kbit EEPROM.
8 *
9 * I2C communication uses 16-bit register addressing throughout.
10 * All dynamic registers (0x2000-0x2107) are in the User memory address
11 * space (I2C 0xA6). System registers require password presentation before
12 * writing.
13 *
14 * Default I2C password: 8 x 0x00.
15 */
16
17#include "i2c.h"
18#include "nfc_st25dv16kc.h"
19#include <string.h>
20
21/* ---------------------------------------------------------------------------
22 * I2C addresses (8-bit, shifted for HAL)
23 * --------------------------------------------------------------------------- */
24#define ST25DV16KC_I2C_ADDR_USER (0x53 << 1) /**< User memory / EEPROM */
25#define ST25DV16KC_I2C_ADDR_SYSTEM (0x57 << 1) /**< System configuration */
26
27/* ---------------------------------------------------------------------------
28 * Dynamic registers – accessible via User I2C address (16-bit addressing)
29 * --------------------------------------------------------------------------- */
30#define ST25DV16KC_REG_GPO_CTRL_DYN 0x2000U /**< GPO control (dynamic) */
31#define ST25DV16KC_REG_EH_CTRL_DYN 0x2001U /**< Energy-harvesting ctrl (dyn) */
32#define ST25DV16KC_REG_RF_MNGT_DYN 0x2002U /**< RF management (dynamic) */
33#define ST25DV16KC_REG_I2C_SSO_DYN 0x2003U /**< I2C security-session open */
34#define ST25DV16KC_REG_MB_CTRL_DYN 0x2006U /**< Mailbox control (dynamic) */
35#define ST25DV16KC_REG_MB_LEN_DYN 0x2007U /**< Mailbox message length (dyn) */
36#define ST25DV16KC_REG_MB_DATA 0x2008U /**< Mailbox RAM start (256 bytes)*/
37
38/* ---------------------------------------------------------------------------
39 * System registers – accessible via System I2C address (16-bit addressing)
40 * (require I2C password presentation for write access)
41 * --------------------------------------------------------------------------- */
42#define ST25DV16KC_SYS_REG_RF_MNGT 0x0003U /**< RF_MNGT static register */
43#define ST25DV16KC_SYS_REG_MB_MODE 0x000DU /**< Mailbox enable (static) */
44#define ST25DV16KC_SYS_REG_I2C_PWD 0x0900U /**< I2C password register */
45
46/* ---------------------------------------------------------------------------
47 * GPO_CTRL_DYN bit masks
48 * --------------------------------------------------------------------------- */
49#define ST25DV16KC_GPO_EN 0x80U /**< GPO output enable */
50#define ST25DV16KC_GPO_MSG_READY_EN 0x40U /**< GPO on mailbox message-ready */
51#define ST25DV16KC_GPO_RF_ACTIVITY 0x20U /**< GPO on RF activity */
52#define ST25DV16KC_GPO_RF_INTERRUPT 0x10U /**< GPO on RF interrupt */
53#define ST25DV16KC_GPO_FIELD_CHANGE 0x08U /**< GPO on RF field change */
54#define ST25DV16KC_GPO_RF_PUT_MSG 0x04U /**< GPO when RF writes to mailbox */
55#define ST25DV16KC_GPO_RF_GET_MSG 0x02U /**< GPO when RF reads mailbox */
56#define ST25DV16KC_GPO_RF_WRITE 0x01U /**< GPO on RF EEPROM write */
57
58/** GPO value used by nfc_st25dv16kc_On(): all events enabled */
59#define ST25DV16KC_GPO_ALL_EN (ST25DV16KC_GPO_EN | ST25DV16KC_GPO_MSG_READY_EN | \
60 ST25DV16KC_GPO_FIELD_CHANGE | ST25DV16KC_GPO_RF_WRITE)
61
62/** GPO value used by nfc_st25dv16kc_INT_On(): field-change interrupt only */
63#define ST25DV16KC_GPO_INT_EN (ST25DV16KC_GPO_EN | ST25DV16KC_GPO_FIELD_CHANGE)
64
65/* ---------------------------------------------------------------------------
66 * MB_CTRL_DYN bit masks
67 * --------------------------------------------------------------------------- */
68#define ST25DV16KC_MB_EN 0x01U /**< Enable mailbox */
69#define ST25DV16KC_MB_RF_PUT_MSG 0x02U /**< RF has written to mailbox */
70#define ST25DV16KC_MB_I2C_PUT_MSG 0x04U /**< I2C host has written to mailbox*/
71
72/* ---------------------------------------------------------------------------
73 * RF_MNGT_DYN bit masks
74 * --------------------------------------------------------------------------- */
75#define ST25DV16KC_RF_SLEEP 0x01U /**< Put RF analog block to sleep */
76#define ST25DV16KC_RF_DISABLE 0x02U /**< Fully disable RF interface */
77
78/* ---------------------------------------------------------------------------
79 * EH_CTRL_DYN bit masks
80 * --------------------------------------------------------------------------- */
81#define ST25DV16KC_EH_FIELD_ON 0x04U /**< RF field is currently detected */
82
83/* ---------------------------------------------------------------------------
84 * RF busy-wait parameters
85 * --------------------------------------------------------------------------- */
86/** Maximum number of 5 ms wait cycles before giving up (100 ms total). */
87#define ST25DV16KC_RF_BUSY_TIMEOUT 20U
88
89/* ---------------------------------------------------------------------------
90 * Mailbox size
91 * --------------------------------------------------------------------------- */
92#define ST25DV16KC_MB_SIZE 256U /**< Mailbox buffer size in bytes */
93
94/* ---------------------------------------------------------------------------
95 * Module-private state
96 * --------------------------------------------------------------------------- */
97
98/** 1 when the chip is present and successfully initialised, 0 otherwise. */
99static int8_t _isNfc = 0;
100static int8_t _isOnOff = 1; // cumulative on/off - on start is on
101#define EEPROM_TIMEOUT 5000 // because of phone and conflict with read/write the timeout must be longer
102/* ---------------------------------------------------------------------------
103 * Static (private) helpers
104 * --------------------------------------------------------------------------- */
105
106/**
107 * @brief Present the default I2C password to open a security session.
108 * The payload is: [0x09 validation code] + [8-byte pwd] + [8-byte pwd].
109 * Default password is 8 x 0x00.
110 * @param hi2c Pointer to I2C handle.
111 * @retval HAL_StatusTypeDef result of the I2C write.
112
113static HAL_StatusTypeDef nfc_st25dv16kc_PresentPassword(I2C_HandleTypeDef *hi2c)
114{
115 HAL_StatusTypeDef status;
116 uint8_t pwd_payload[17];
117 pwd_payload[0] = 0x09; // Validation code
118
119 memset(&pwd_payload[1], 0x00, 16); // 8-byte pwd repeated twice (default)
120
121 status = HAL_I2C_Mem_Write(hi2c, ST25DV16KC_I2C_ADDR_SYSTEM, ST25DV16KC_SYS_REG_I2C_PWD, I2C_MEMADD_SIZE_16BIT, pwd_payload, 17, 200);
122 return status;
123}
124 */
125/**
126 * @brief Write a value to GPO_CTRL_DYN with retry to tolerate transient NACKs
127 * from the RF field.
128 * @param hi2c Pointer to I2C handle.
129 * @param value Byte value to write to GPO_CTRL_DYN.
130 * @retval HAL_StatusTypeDef result of the last I2C write attempt.
131 */
132static HAL_StatusTypeDef nfc_st25dv16kc_WriteGPO(I2C_HandleTypeDef *hi2c, uint8_t value)
133{
134 HAL_StatusTypeDef status = HAL_ERROR;
135 uint8_t reg_val = value;
136 uint8_t i;
137
138 for (i = 0; i < 5; i++)
139 {
140 status = HAL_I2C_Mem_Write(hi2c, ST25DV16KC_I2C_ADDR_USER, ST25DV16KC_REG_GPO_CTRL_DYN, I2C_MEMADD_SIZE_16BIT, &reg_val, 1, 100);
141 if (status == HAL_OK)
142 break;
143 HAL_Delay(10);
144 }
145 /* If repeated failures, the chip is no longer responding */
146 _isNfc = (status == HAL_OK);
147 return status;
148}
149
150/* ---------------------------------------------------------------------------
151 * Public API implementation
152 * --------------------------------------------------------------------------- */
153
154int8_t nfc_st25dv16kc_Is(I2C_HandleTypeDef *hi2c, int8_t tryInit)
155{
156 if (!_isNfc && tryInit)
158 return _isNfc;
159}
160
161HAL_StatusTypeDef nfc_st25dv16kc_Init(I2C_HandleTypeDef *hi2c)
162{
163 HAL_StatusTypeDef status;
164 uint8_t reg_val;
165 uint8_t i;
166
167 do
168 {
169 /* 1. Verify the chip is on the I2C bus */
170 status = I2C_IsDeviceReadyMT(hi2c, ST25DV16KC_I2C_ADDR_USER, 10, 100);
171 if (status != HAL_OK)
172 break;
173
174 /* 2. Enable the Mailbox (MB_EN bit in MB_CTRL_DYN).
175 * Use a retry loop because an active RF field may cause a NACK. */
176 reg_val = ST25DV16KC_MB_EN;
177 for (i = 0; i < 5; i++)
178 {
179 status = HAL_I2C_Mem_Write(hi2c, ST25DV16KC_I2C_ADDR_USER,
180 ST25DV16KC_REG_MB_CTRL_DYN, I2C_MEMADD_SIZE_16BIT, &reg_val, 1, 100);
181 if (status == HAL_OK)
182 break;
183 HAL_Delay(10);
184 }
185 if (status != HAL_OK)
186 break;
187
188 /* 3. Wait for the mailbox to stabilise */
189 HAL_Delay(50);
190
191 /* 4. Clear / disable the mailbox to start from a known state */
192 reg_val = 0x00;
193 status = HAL_I2C_Mem_Write(hi2c, ST25DV16KC_I2C_ADDR_USER, ST25DV16KC_REG_MB_CTRL_DYN, I2C_MEMADD_SIZE_16BIT, &reg_val, 1, 100);
194 if (status != HAL_OK)
195 break;
196
197 HAL_Delay(20);
198
199 /* 5. Mark as present and switch GPO off (lowest-power default) */
200 _isNfc = 1;
201 nfc_st25dv16kc_Off(hi2c);
202
203 } while (0);
204
205 return status;
206}
207
208HAL_StatusTypeDef nfc_st25dv16kc_IsOn(I2C_HandleTypeDef *hi2c, uint8_t *onOff)
209{
210 HAL_StatusTypeDef status = HAL_ERROR;
211
212 if (_isNfc)
213 {
214 uint8_t reg_val = 0;
215 status = HAL_I2C_Mem_Read(hi2c, ST25DV16KC_I2C_ADDR_USER, ST25DV16KC_REG_GPO_CTRL_DYN, I2C_MEMADD_SIZE_16BIT, &reg_val, 1, 100);
216 if (status == HAL_OK && onOff != NULL)
217 *onOff = (reg_val & ST25DV16KC_GPO_EN) ? 1U : 0U;
218 }
219 return status;
220}
221
222HAL_StatusTypeDef nfc_st25dv16kc_On(I2C_HandleTypeDef *hi2c)
223{
224 HAL_StatusTypeDef status = HAL_ERROR;
225
226 if (_isNfc)
227 {
228 status = HAL_OK;
229 if (_isOnOff++ == 0)
230 {
231 /* Enable GPO with all relevant event bits:
232 * GPO_EN | MSG_READY | FIELD_CHANGE | RF_WRITE = 0xC9 */
234 }
235 }
236 return status;
237}
238
239HAL_StatusTypeDef nfc_st25dv16kc_Off(I2C_HandleTypeDef *hi2c)
240{
241 HAL_StatusTypeDef status = HAL_ERROR;
242
243 if (_isNfc)
244 {
245 status = HAL_OK;
246
247 if (_isOnOff > 0)
248 {
249 if (--_isOnOff == 0)
250 {
251 /* Disable GPO output */
252 //status = nfc_st25dv16kc_WriteGPO(hi2c, 0x00); //leave all to by availble on phone
253 status = nfc_st25dv16kc_WriteGPO(hi2c, ST25DV16KC_GPO_EN); // GPO-INT
254
255 /* Put RF to sleep for minimum current consumption (~1 µA).
256 * RF_MNGT_DYN is a dynamic register – no password required. */
257 if (status == HAL_OK)
258 {
259 uint8_t rf_val = ST25DV16KC_RF_SLEEP;
260 status = HAL_I2C_Mem_Write(hi2c, ST25DV16KC_I2C_ADDR_USER, ST25DV16KC_REG_RF_MNGT_DYN, I2C_MEMADD_SIZE_16BIT, &rf_val, 1, 100);
261 }
262 }
263 }
264 }
265 return status;
266}
267
268#if 0
269int8_t nfc_st25dv16kc_IsRFBusy(I2C_HandleTypeDef *hi2c)
270{
271 uint8_t reg_val = 0;
272
273 if (_isNfc)
274 {
275
276 /* Read RF_MNGT_DYN (0x2002); bit 1 = FIELD_ON – RF field is present.
277 * If the I2C read fails assume the field is not active. */
278 if (HAL_I2C_Mem_Read(hi2c, ST25DV16KC_I2C_ADDR_USER, ST25DV16KC_REG_RF_MNGT_DYN, // <-- was REG_EH_CTRL_DYN
279 I2C_MEMADD_SIZE_16BIT, &reg_val, 1, 50) != HAL_OK)
280 return 0;
281
282 /* FIELD_ON is bit 1 of RF_MNGT_DYN */
283 }
284 return (reg_val & 0x02U) ? 1 : 0; // <-- was 0x04 on EH_CTRL_DYN
285}
286#endif
287
288HAL_StatusTypeDef nfc_st25dv16kc_WriteEEPROM(I2C_HandleTypeDef *hi2c, uint16_t addr, uint8_t *pData, uint16_t len)
289{
290 HAL_StatusTypeDef status = HAL_ERROR;
291
292 if (_isNfc)
293 {
294 status = HAL_OK;
295
296 /* Wait for RF write to complete
297 uint8_t retry_count = 0;
298 while (nfc_st25dv16kc_IsRFBusy(hi2c))
299 {
300 HAL_Delay(5);
301 if (++retry_count >= ST25DV16KC_RF_BUSY_TIMEOUT)
302 {
303 status = HAL_TIMEOUT;
304 break;
305 }
306 }*/
307 if (status == HAL_OK)
308 do
309 {
310 uint16_t i = 0;
311 while (i < len)
312 {
313 /* ST25DV page size is 4 bytes for write operations */
314 /* Clip chunk to: remaining bytes, max 4, and must not cross 4-byte page */
315 uint16_t page_offset = (addr + i) % 4; // offset within current 4-byte page
316 uint16_t max_in_page = 4 - page_offset; // bytes left in this page
317 uint16_t remaining = len - i;
318 uint16_t chunk = remaining < max_in_page ? remaining : max_in_page;
319
320 /* Poll for ACK — device NACKs during internal write cycle */
321 uint8_t retry = 0;
322 do
323 {
324 if ((status = HAL_I2C_Mem_Write(hi2c, ST25DV16KC_I2C_ADDR_USER, addr + i, I2C_MEMADD_SIZE_16BIT, &pData[i], chunk, EEPROM_TIMEOUT)) == HAL_OK)
325 break; // break on HAL_OK, do no need more attempts
326 HAL_Delay(2);
327 } while (++retry < 10);
328
329 if (status != HAL_OK)
330 break;
331 i += chunk;
332 HAL_Delay(6); // tW: ensure write cycle completes before next page
333 }
334 } while (0);
335 }
336 return status;
337}
338
339HAL_StatusTypeDef nfc_st25dv16kc_ReadEEPROM(I2C_HandleTypeDef *hi2c, uint16_t addr, uint8_t *pData, uint16_t len)
340{
341 HAL_StatusTypeDef status = HAL_ERROR;
342
343 if (_isNfc)
344 status = HAL_I2C_Mem_Read(hi2c, ST25DV16KC_I2C_ADDR_USER, addr, I2C_MEMADD_SIZE_16BIT, pData, len, EEPROM_TIMEOUT);
345 return status;
346}
347
348HAL_StatusTypeDef nfc_st25dv16kc_ProcessMailBox(I2C_HandleTypeDef *hi2c)
349{
350 uint8_t mb_ctrl = 0;
351 uint8_t mb_len = 0;
352
353 if (!_isNfc)
354 return HAL_ERROR;
355
356 /* Read mailbox control register to check for an RF-sourced message */
357 if (HAL_I2C_Mem_Read(hi2c, ST25DV16KC_I2C_ADDR_USER,
358 ST25DV16KC_REG_MB_CTRL_DYN, I2C_MEMADD_SIZE_16BIT, &mb_ctrl, 1, 100) != HAL_OK)
359 return HAL_ERROR;
360
361 if (mb_ctrl & ST25DV16KC_MB_RF_PUT_MSG)
362 {
363 /* Get message length (register value = length - 1) */
364 if (HAL_I2C_Mem_Read(hi2c, ST25DV16KC_I2C_ADDR_USER,
365 ST25DV16KC_REG_MB_LEN_DYN, I2C_MEMADD_SIZE_16BIT, &mb_len, 1, 100) != HAL_OK)
366 return HAL_ERROR;
367
368 uint16_t msg_len = (uint16_t) mb_len + 1U;
369 if (msg_len > ST25DV16KC_MB_SIZE)
370 msg_len = ST25DV16KC_MB_SIZE;
371
372 uint8_t buffer[ST25DV16KC_MB_SIZE];
373 if (HAL_I2C_Mem_Read(hi2c, ST25DV16KC_I2C_ADDR_USER,
374 ST25DV16KC_REG_MB_DATA, I2C_MEMADD_SIZE_16BIT, buffer, msg_len, 300) != HAL_OK)
375 return HAL_ERROR;
376
377 nfc_st25dv16kc_OnMailboxData(buffer, msg_len);
378 return HAL_OK;
379 }
380
381 return HAL_ERROR; /* No RF message pending */
382}
383
384HAL_StatusTypeDef nfc_st25dv16kc_WriteMailBox(I2C_HandleTypeDef *hi2c, uint8_t *pData, uint16_t len)
385{
386 HAL_StatusTypeDef status = HAL_ERROR;
387 uint8_t reg_val;
388
389 if (_isNfc)
390 {
391 if (len > ST25DV16KC_MB_SIZE)
392 len = ST25DV16KC_MB_SIZE;
393
394 do
395 {
396 /* Enable mailbox before writing */
397 reg_val = ST25DV16KC_MB_EN;
398 if ((status = HAL_I2C_Mem_Write(hi2c, ST25DV16KC_I2C_ADDR_USER, ST25DV16KC_REG_MB_CTRL_DYN, I2C_MEMADD_SIZE_16BIT, &reg_val, 1, 100)) != HAL_OK)
399 break;
400
401 /* Write payload into mailbox RAM */
402 if ((status = HAL_I2C_Mem_Write(hi2c, ST25DV16KC_I2C_ADDR_USER, ST25DV16KC_REG_MB_DATA, I2C_MEMADD_SIZE_16BIT, pData, len, 300)) != HAL_OK)
403 break;
404 }while(0);
405 }
406 return status;
407}
408
409__weak void nfc_st25dv16kc_OnMailboxData(uint8_t *data, uint16_t len)
410{
411 /* Override in application code to handle received mailbox messages */
412 (void) data;
413 (void) len;
414}
This file contains all the function prototypes for the i2c.c file.
HAL_StatusTypeDef I2C_IsDeviceReadyMT(I2C_HandleTypeDef *hi2c, uint16_t DevAddress, uint32_t Trials, uint32_t Timeout)
Wrapper around HAL_I2C_IsDeviceReady() that recovers from a busy bus. If the HAL I2C bus is in a BUSY...
Definition i2c.c:158
HAL_StatusTypeDef nfc_st25dv16kc_WriteMailBox(I2C_HandleTypeDef *hi2c, uint8_t *pData, uint16_t len)
Write raw data into the Mailbox so the RF reader (phone) can retrieve it.
#define ST25DV16KC_MB_SIZE
#define ST25DV16KC_GPO_ALL_EN
HAL_StatusTypeDef nfc_st25dv16kc_ReadEEPROM(I2C_HandleTypeDef *hi2c, uint16_t addr, uint8_t *pData, uint16_t len)
Read data from the EEPROM user memory.
#define ST25DV16KC_REG_RF_MNGT_DYN
#define ST25DV16KC_REG_MB_DATA
HAL_StatusTypeDef nfc_st25dv16kc_On(I2C_HandleTypeDef *hi2c)
Turn the NFC GPO ON (all events: MsgReady, FieldChange, RFWrite). Enables host-side reading/writing v...
#define ST25DV16KC_RF_SLEEP
HAL_StatusTypeDef nfc_st25dv16kc_ProcessMailBox(I2C_HandleTypeDef *hi2c)
Process incoming data from the RF Mailbox. Checks MB_CTRL_DYN for an RF message; if present reads it ...
#define ST25DV16KC_REG_MB_LEN_DYN
#define EEPROM_TIMEOUT
#define ST25DV16KC_I2C_ADDR_USER
HAL_StatusTypeDef nfc_st25dv16kc_Off(I2C_HandleTypeDef *hi2c)
Turn the NFC GPO OFF and enable GPO RF to achieve minimum current (~1 µA).
__weak void nfc_st25dv16kc_OnMailboxData(uint8_t *data, uint16_t len)
Weak callback invoked by nfc_st25dv16kc_ProcessMailBox() when new data has arrived in the Mailbox....
HAL_StatusTypeDef nfc_st25dv16kc_IsOn(I2C_HandleTypeDef *hi2c, uint8_t *onOff)
#define ST25DV16KC_REG_MB_CTRL_DYN
int8_t nfc_st25dv16kc_Is(I2C_HandleTypeDef *hi2c, int8_t tryInit)
Check whether the ST25DV16KC has been successfully initialised.
HAL_StatusTypeDef nfc_st25dv16kc_WriteEEPROM(I2C_HandleTypeDef *hi2c, uint16_t addr, uint8_t *pData, uint16_t len)
Write data to the EEPROM user memory (4-byte page writes).
#define ST25DV16KC_MB_EN
HAL_StatusTypeDef nfc_st25dv16kc_Init(I2C_HandleTypeDef *hi2c)
Check the I2C bus and try to find the NFC chip. Enables the mailbox, clears it, and turns GPO off (lo...
static HAL_StatusTypeDef nfc_st25dv16kc_WriteGPO(I2C_HandleTypeDef *hi2c, uint8_t value)
Present the default I2C password to open a security session. The payload is: [0x09 validation code] +...
#define ST25DV16KC_GPO_EN
#define ST25DV16KC_MB_RF_PUT_MSG
static int8_t _isNfc
static int8_t _isOnOff
#define ST25DV16KC_REG_GPO_CTRL_DYN
void HAL_Delay(__IO uint32_t Delay)
Definition sys_app.c:369