2025-07-13 7:01 AM - last edited on 2025-07-17 1:32 AM by Brian TIDAL
Hi! I am trying to control the read write permission of my st25dv nfc tag with a nordic chip, but i am having a hard time doing that. I am able to write to it and everything using i2c and nfc, and with the following code
#include <zephyr/kernel.h>
#include <zephyr/device.h>
#include <zephyr/logging/log.h>
#include <zephyr/drivers/i2c.h>
#include <string.h>
LOG_MODULE_REGISTER(st25dv_demo, LOG_LEVEL_INF);
#define ST25DV_ADDR 0x53
#define I2C_NODE DT_NODELABEL(i2c2)
#define EEPROM_START_ADDR 0x0000
#define EEPROM_LEN 16
#define I2C_PWD_REG 0x09B0
#define I2C_SSO_DYN 0x2000
#define RFA1SS_REG 0x0004
#define ENDA1_REG 0x0005
#define ENDA2_REG 0x0007
#define ENDA3_REG 0x0009
static const struct device *i2c_dev;
static int st25dv_write_sys(uint16_t reg, uint8_t data)
{
uint8_t buf[3] = { reg >> 8, reg & 0xFF, data };
return i2c_write(i2c_dev, buf, sizeof(buf), ST25DV_ADDR);
}
static int st25dv_open_session(const uint8_t *pwd)
{
uint8_t buf[10] = { I2C_PWD_REG >> 8, I2C_PWD_REG & 0xFF };
memcpy(&buf[2], pwd, 8);
return i2c_write(i2c_dev, buf, sizeof(buf), ST25DV_ADDR);
}
static bool st25dv_is_session_open(void)
{
uint8_t reg[2] = { I2C_SSO_DYN >> 8, I2C_SSO_DYN & 0xFF };
uint8_t status;
if (i2c_write_read(i2c_dev, ST25DV_ADDR, reg, sizeof(reg), &status, 1) < 0) {
LOG_ERR("Failed to check session");
return false;
}
return (status & 0x01);
}
static int st25dv_set_full_area1(void)
{
// Set ENDA3 = 0xFF first (to max)
if (st25dv_write_sys(ENDA3_REG, 0xFF)) return -1;
k_msleep(5);
// Set ENDA2 = 0xFF
if (st25dv_write_sys(ENDA2_REG, 0xFF)) return -1;
k_msleep(5);
// Set ENDA1 = 0xFF (Area1 = entire memory)
if (st25dv_write_sys(ENDA1_REG, 0xFF)) return -1;
k_msleep(5);
return 0;
}
static int st25dv_set_rf_write_protect(bool enable)
{
if (!st25dv_is_session_open()) {
LOG_ERR("Session not open");
return -1;
}
// Set full Area1 before protection
if (st25dv_set_full_area1() < 0) {
LOG_ERR("Failed to set full Area1");
return -1;
}
// Write RFA1SS: 0x05 = write-protected, read allowed
return st25dv_write_sys(RFA1SS_REG, enable ? 0x05 : 0x00);
}
static int st25dv_write_eeprom(uint16_t addr, const uint8_t *data, size_t len)
{
uint8_t buf[2 + EEPROM_LEN] = { addr >> 8, addr & 0xFF };
memcpy(&buf[2], data, len);
return i2c_write(i2c_dev, buf, len + 2, ST25DV_ADDR);
}
static int st25dv_read_eeprom(uint16_t addr, uint8_t *data, size_t len)
{
uint8_t reg[2] = { addr >> 8, addr & 0xFF };
return i2c_write_read(i2c_dev, ST25DV_ADDR, reg, sizeof(reg), data, len);
}
void main(void)
{
i2c_dev = DEVICE_DT_GET(I2C_NODE);
if (!device_is_ready(i2c_dev)) {
LOG_ERR("I2C not ready");
return;
}
uint8_t password[8] = {0}; // Default password (8 bytes of 0x00)
uint8_t data[EEPROM_LEN];
LOG_INF("ST25DV64KC Zephyr Example");
// Open session
LOG_INF("Opening I2C session...");
st25dv_open_session(password);
k_sleep(K_MSEC(10));
LOG_INF("Session is %s", st25dv_is_session_open() ? "open" : "closed");
// Enable RF write protection on Area 1
LOG_INF("Enabling RF write protection on full memory...");
if (st25dv_set_rf_write_protect(true) == 0)
LOG_INF("RF write protection enabled");
// Read back EEPROM to confirm it’s still accessible via I2C
LOG_INF("Reading EEPROM Area 1 via I2C...");
memset(data, 0, EEPROM_LEN);
st25dv_read_eeprom(EEPROM_START_ADDR, data, EEPROM_LEN);
for (int i = 0; i < EEPROM_LEN; i++)
printk("0x%02X ", data[i]);
printk("\n");
}
I am able to enable write protection, but apparently that affects also i2c because I am unable to set a new NDEF message after that. All i want is to allow the user to enter something in the tag, once that is confirmed, the MCU enables write protection over RF but still allows read. That's basically it. This is the code with which i am trying to turn on read only, but it doesn't seem to be working
#include <zephyr/kernel.h>
#include <zephyr/device.h>
#include <zephyr/logging/log.h>
#include <zephyr/drivers/i2c.h>
#include <string.h>
LOG_MODULE_REGISTER(st25dv_demo, LOG_LEVEL_INF);
#define ST25DV_ADDR 0x53
#define I2C_NODE DT_NODELABEL(i2c2)
#define EEPROM_START_ADDR 0x0000
static const struct device *i2c_dev;
// Write single byte to system register
static int st25dv_write_sys(uint16_t reg, uint8_t data)
{
uint8_t buf[3] = { reg >> 8, reg & 0xFF, data };
return i2c_write(i2c_dev, buf, sizeof(buf), ST25DV_ADDR);
}
// Open session with 8-byte password
static int st25dv_open_session(const uint8_t *pwd)
{
uint8_t buf[10] = { 0x09, 0xB0 }; // I2C_PWD_REG high byte, low byte
memcpy(&buf[2], pwd, 8);
return i2c_write(i2c_dev, buf, sizeof(buf), ST25DV_ADDR);
}
// Check if session is open
static bool st25dv_is_session_open(void)
{
uint8_t reg[2] = { 0x20, 0x00 }; // I2C_SSO_DYN
uint8_t status;
if (i2c_write_read(i2c_dev, ST25DV_ADDR, reg, sizeof(reg), &status, 1) < 0) {
LOG_ERR("Failed to check session");
return false;
}
return (status & 0x01);
}
// Enable RF write protection on area 1
static int st25dv_set_rf_write_protect(bool enable)
{
if (!st25dv_is_session_open()) {
LOG_ERR("Session not open");
return -1;
}
return st25dv_write_sys(0x0004, enable ? 0x01 : 0x00); // RFA1SS_REG
}
// Write bytes to EEPROM starting at addr
static int st25dv_write_eeprom(uint16_t addr, const uint8_t *data, size_t len)
{
uint8_t buf[2 + 64] = {0};
buf[0] = addr >> 8;
buf[1] = addr & 0xFF;
memcpy(&buf[2], data, len);
return i2c_write(i2c_dev, buf, len + 2, ST25DV_ADDR);
}
void main(void)
{
i2c_dev = DEVICE_DT_GET(I2C_NODE);
if (!device_is_ready(i2c_dev)) {
LOG_ERR("I2C not ready");
return;
}
LOG_INF("ST25DV64KC Zephyr Example");
const uint8_t password[8] = { 0x00 }; // Default password
// Capability Container (CC) bytes (8 bytes)
const uint8_t cc[8] = {
0xE1, 0x40, 0x06, 0x0E, // CC bytes: Magic, version, size, RW flag (read-only)
0x00, 0x00, 0x00, 0x00 // Reserved bytes
};
// NDEF message TLV (21 bytes)
const uint8_t ndef_msg[21] = {
0x03, 0x11, // NDEF TLV, length=17 bytes
0xD1, 0x01, 0x0D, // NDEF Record Header, type length=1, payload length=13
0x54, // Type = 'T' (Text)
0x02, // Status byte: UTF-8 + lang length=2
0x65, 0x6E, // 'en' language code
'H', 'e', 'l', 'l', 'o', ' ', 'W', 'o', 'r', 'l', 'd',
0xFE // Terminator TLV
};
LOG_INF("Opening I2C session...");
if (st25dv_open_session(password) < 0) {
LOG_ERR("Failed to open session");
return;
}
k_sleep(K_MSEC(10));
if (!st25dv_is_session_open()) {
LOG_ERR("Session not open");
return;
}
LOG_INF("Session is open");
// Write CC to address 0x0000
LOG_INF("Writing Capability Container (CC)...");
if (st25dv_write_eeprom(0x0000, cc, sizeof(cc)) < 0) {
LOG_ERR("Failed to write CC");
return;
}
k_msleep(20);
// Write NDEF message starting at 0x0008
LOG_INF("Writing NDEF message...");
if (st25dv_write_eeprom(0x0008, ndef_msg, sizeof(ndef_msg)) < 0) {
LOG_ERR("Failed to write NDEF message");
return;
}
k_msleep(20);
// Enable RF write protection
LOG_INF("Enabling RF write protection...");
if (st25dv_set_rf_write_protect(true) < 0) {
LOG_ERR("Failed to enable RF write protection");
return;
}
k_msleep(50);
LOG_INF("NDEF message successfully written and tag is read-only over RF.");
}
2025-07-17 2:35 AM
Hi Jean4,
Reading your code I'm not sure that you are changing the I²C device code while switching between system and user area. Please refer to the Datasheet section 6.3 Device addressing and note the 2 addresses for user and system memory in the table.
Another point I noticed, is the CC file in Type V can have a length of 4 or 8 bytes. in your case you have defined a message len (byte 2 of CC file) of 6 bytes so that implies the CC file length is of 4 bytes only.
I hope this can help you.
Kind Regards.