cancel
Showing results for 
Search instead for 
Did you mean: 

Unable to properly control ST25DV64KC read write permissions.

Jean4
Associate II

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.");
}
 
Any help would be appreciated.
9 REPLIES 9
Rene Lenerve
ST Employee

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.

ReneLenerve_0-1752744830777.png

I hope this can help you.

Kind Regards.

Hi Rene, What do you mean exactly by device code? As for the CC file, in both cases the access conditions and permissions block was written right? So there shouldn’t be a difference.

Would the following code fix the issues you mentioned? 

#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);

// I2C addresses
#define ST25DV_USER_ADDR 0x53  // For EEPROM/NDEF access
#define ST25DV_SYS_ADDR  0x57  // For system register access

#define I2C_NODE DT_NODELABEL(i2c2)

#define EEPROM_START_ADDR 0x0000

// System Register Addresses
#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;

// Write a single byte to a 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_SYS_ADDR);
}

// Write to EEPROM (user memory)
static int st25dv_write_eeprom(uint16_t addr, const uint8_t *data, size_t len)
{
    uint8_t buf[2 + 64] = { addr >> 8, addr & 0xFF };
    memcpy(&buf[2], data, len);
    return i2c_write(i2c_dev, buf, len + 2, ST25DV_USER_ADDR);
}

// Open I2C session with password
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_SYS_ADDR);
}

// Check if I2C session is open
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_SYS_ADDR, reg, sizeof(reg), &status, 1) < 0) {
        LOG_ERR("Failed to check session");
        return false;
    }
    return (status & 0x01);
}

// Set full Area1 by configuring all ENDAx to 0xFF
static int st25dv_set_full_area1(void)
{
    if (st25dv_write_sys(ENDA3_REG, 0xFF) < 0) return -1;
    k_msleep(5);
    if (st25dv_write_sys(ENDA2_REG, 0xFF) < 0) return -1;
    k_msleep(5);
    if (st25dv_write_sys(ENDA1_REG, 0xFF) < 0) return -1;
    k_msleep(5);
    return 0;
}

// Set RF write protection for Area1
static int st25dv_set_rf_write_protect(bool enable)
{
    if (!st25dv_is_session_open()) {
        LOG_ERR("Session not open");
        return -1;
    }

    if (st25dv_set_full_area1() < 0) {
        LOG_ERR("Failed to set full Area1");
        return -1;
    }

    // RFA1SS: 0x01 = write-protected, readable
    return st25dv_write_sys(RFA1SS_REG, enable ? 0x01 : 0x00);
}

void main(void)
{
    i2c_dev = DEVICE_DT_GET(I2C_NODE);
    if (!device_is_ready(i2c_dev)) {
        LOG_ERR("I2C device not ready");
        return;
    }

    LOG_INF("ST25DV64KC Zephyr Example");

    const uint8_t password[8] = { 0x00 }; // Default password (all zeros)

    // Capability Container (8 bytes)
    const uint8_t cc[8] = {
        0xE1, 0x40, 0x08, 0x0E,  // Magic, version, length=8, read-only access
        0x00, 0x00, 0x00, 0x00   // Reserved
    };

    // Example NDEF Text Record: "Hello World"
    const uint8_t ndef_msg[] = {
        0x03, 0x11,             // NDEF TLV (type 0x03), length 17
        0xD1, 0x01, 0x0D,       // NDEF header
        0x54,                   // Type = 'T'
        0x02,                   // UTF-8, lang="en"
        0x65, 0x6E,             // 'e', 'n'
        'H', 'e', 'l', 'l', 'o', ' ', 'W', 'o', 'r', 'l', 'd',
        0xFE                    // TLV terminator
    };

    // Open session
    LOG_INF("Opening I2C session...");
    if (st25dv_open_session(password) < 0) {
        LOG_ERR("Failed to open session");
        return;
    }
    k_msleep(10);

    if (!st25dv_is_session_open()) {
        LOG_ERR("Session not open");
        return;
    }

    LOG_INF("Session opened successfully");

    // Write CC
    LOG_INF("Writing Capability Container...");
    if (st25dv_write_eeprom(0x0000, cc, sizeof(cc)) < 0) {
        LOG_ERR("Failed to write CC");
        return;
    }
    k_msleep(20);

    // Write NDEF message at address 0x0008
    LOG_INF("Writing NDEF message...");
    if (st25dv_write_eeprom(0x0008, ndef_msg, sizeof(ndef_msg)) < 0) {
        LOG_ERR("Failed to write NDEF");
        return;
    }
    k_msleep(20);

    // Apply 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;
    }

    LOG_INF("Tag is now read-only over RF, but still writable over I2C.");
}
Rene Lenerve
ST Employee

Hi Jean4,

By device code I meant the I²C address of the tag, the user area and system area uses 2 different values of the device code to accessed it. Normaly your code should work.

For the CC file, if you read the tag with the MCU, you know the addresses of data, so you can read correctly the tag. But as you do not respect the NFC Forum Type5 standard if you try to read the tag with a smartphone (for example) through the NFC natively or with an app, the NDEF message will not be read.

The rule is if your NDEF message is lower than 2040 bytes then the CC file is on 4 bytes and the byte 2 (MLEN) can take values from 1 to 0xff (formula is : bytes = 8 * MLEN). If the NDEF message is higher than 2040 the CC file is on 8 bytes and the byte 2 values must be 0 then the size is coded on byte 6 and byte 7 (>0x0100).

The other bytes keep same position.

Kind Regards.

Jean4
Associate II

I tried this code, but it doesn’t work either. It either tells me that it cannot open a session or that it cannot enable write protection.

#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);

// I2C addresses
#define ST25DV_USER_ADDR 0x53 // For EEPROM/NDEF access
#define ST25DV_SYS_ADDR 0x57  // For system register access

#define I2C_NODE DT_NODELABEL(i2c2)

#define EEPROM_START_ADDR 0x0000

// System Register Addresses
#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_SYS_ADDR);
}

static int st25dv_write_eeprom(uint16_t addr, const uint8_t *data, size_t len)
{
    uint8_t buf[2 + 64];
    if (len > 64)
    {
        LOG_ERR("Write length exceeds maximum I2C burst size (64 bytes)");
        return -EINVAL;
    }
    buf[0] = addr >> 8;
    buf[1] = addr & 0xFF;
    memcpy(&buf[2], data, len);
    return i2c_write(i2c_dev, buf, len + 2, ST25DV_USER_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_USER_ADDR, reg, sizeof(reg), data, len);
}

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);

    int ret = i2c_write(i2c_dev, buf, sizeof(buf), ST25DV_SYS_ADDR);
    if (ret < 0) {
        LOG_ERR("i2c_write() failed: %d", ret);
    }
    return ret;
}


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_SYS_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)
{
    if (st25dv_write_sys(ENDA3_REG, 0xFF) < 0)
        return -1;
    k_msleep(5);
    if (st25dv_write_sys(ENDA2_REG, 0xFF) < 0)
        return -1;
    k_msleep(5);
    if (st25dv_write_sys(ENDA1_REG, 0xFF) < 0)
        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;
    }

    if (st25dv_set_full_area1() < 0)
    {
        LOG_ERR("Failed to set full Area1");
        return -1;
    }

    return st25dv_write_sys(RFA1SS_REG, enable ? 0x01 : 0x00);
}

void main(void)
{
    i2c_dev = DEVICE_DT_GET(I2C_NODE);
    if (!device_is_ready(i2c_dev))
    {
        LOG_ERR("I2C device not ready");
        return;
    }

    LOG_INF("ST25DV64KC Zephyr Example");

    const uint8_t password[8] = {0};  // Default password

    const uint8_t ndef_msg[] = {
        0x03,                         // NDEF TLV Type
        0x11,                         // TLV Length
        0xD1, 0x01, 0x0D,             // NDEF header
        0x54,                         // Type = 'T' (Text)
        0x02,                         // Status byte: UTF-8, lang="en"
        0x65, 0x6E,                   // 'e', 'n'
        'H', 'e', 'l', 'l', 'o', ' ', 'W', 'o', 'r', 'l', 'd',
        0xFE                          // TLV terminator
    };

    size_t ndef_actual_len = sizeof(ndef_msg) - 1;

    uint8_t cc[8];
    uint16_t mlc_bytes = ndef_actual_len;
    uint8_t mlen_value;

    if (mlc_bytes <= 2040) {
        mlen_value = (mlc_bytes + 7) / 8;
        if (mlen_value == 0) mlen_value = 1;
        if (mlen_value > 0xFF) mlen_value = 0xFF;
        cc[0] = 0xE1;
        cc[1] = 0x40;
        cc[2] = mlen_value;
        cc[3] = 0x0E;
        cc[4] = 0x00;
        cc[5] = 0x00;
        cc[6] = 0x00;
        cc[7] = 0x00;
    } else {
        cc[0] = 0xE1;
        cc[1] = 0x40;
        cc[2] = 0x00;
        cc[3] = 0x0E;
        cc[4] = 0x00;
        cc[5] = 0x00;
        cc[6] = (mlc_bytes >> 8) & 0xFF;
        cc[7] = mlc_bytes & 0xFF;
    }

    LOG_INF("Checking I2C session status...");
    if (!st25dv_is_session_open())
    {
        LOG_INF("Session not open. Attempting to open...");
        if (st25dv_open_session(password) < 0)
        {
            LOG_WRN("Session open failed, continuing without authentication...");
        }
        k_msleep(10);

        if (!st25dv_is_session_open())
        {
            LOG_WRN("Session still not open — some operations may fail.");
        }
        else
        {
            LOG_INF("Session opened successfully.");
        }
    }
    else
    {
        LOG_INF("Session already open.");
    }

    LOG_INF("Writing Capability Container...");
    if (st25dv_write_eeprom(0x0000, cc, sizeof(cc)) < 0)
    {
        LOG_ERR("Failed to write CC");
        return;
    }
    k_msleep(20);

    LOG_INF("Writing NDEF message...");
    if (st25dv_write_eeprom(0x0008, ndef_msg, sizeof(ndef_msg)) < 0)
    {
        LOG_ERR("Failed to write NDEF");
        return;
    }
    k_msleep(20);

    LOG_INF("Enabling RF write protection...");
    if (st25dv_set_rf_write_protect(true) < 0)
    {
        LOG_WRN("RF write protection failed (possibly due to session lock)");
    }
    else
    {
        LOG_INF("RF write protection enabled.");
    }

    LOG_INF("Reading back CC...");
    uint8_t read_buffer[64];
    if (st25dv_read_eeprom(0x0000, read_buffer, sizeof(cc)) == 0)
    {
        LOG_HEXDUMP_INF(read_buffer, sizeof(cc), "CC Read:");
    }
    else
    {
        LOG_ERR("Failed to read CC.");
    }

    LOG_INF("Reading back NDEF message...");
    if (st25dv_read_eeprom(0x0008, read_buffer, sizeof(ndef_msg)) == 0)
    {
        LOG_HEXDUMP_INF(read_buffer, sizeof(ndef_msg), "NDEF Read:");
    }
    else
    {
        LOG_ERR("Failed to read NDEF.");
    }
}

example output:

*** Booting nRF Connect SDK v3.0.2-89ba1294ac9b ***
*** Using Zephyr OS v4.0.99-f791c49f492c ***
[00:00:00.450,866] <inf> st25dv_demo: ST25DV64KC Zephyr Example
[00:00:00.450,866] <inf> st25dv_demo: Checking I2C session status...
[00:00:00.451,141] <inf> st25dv_demo: Session already open.
[00:00:00.451,141] <inf> st25dv_demo: Writing Capability Container...
[00:00:00.471,557] <inf> st25dv_demo: Writing NDEF message...
[00:00:00.492,309] <inf> st25dv_demo: Enabling RF write protection...
[00:00:00.492,706] <err> st25dv_demo: Failed to set full Area1
[00:00:00.492,736] <wrn> st25dv_demo: RF write protection failed (possibly due to session lock)
[00:00:00.492,736] <inf> st25dv_demo: Reading back CC...
[00:00:00.493,164] <inf> st25dv_demo: CC Read:
e1 40 03 0e 00 00 00 00 |.@......
[00:00:00.493,164] <inf> st25dv_demo: Reading back NDEF message...
[00:00:00.493,865] <inf> st25dv_demo: NDEF Read:
03 11 d1 01 0d 54 02 65 6e 48 65 6c 6c 6f 20 57 |.....T.e nHello W
6f 72 6c 64 fe |orld.

Rene Lenerve
ST Employee

Hi Jean4,

I think that your issue is due to the I2C_SSO_DYN register address, you set it to 0x2000 but the correct address is 0x2004. Your code reads first the GPO_CTRL_Dyn register and think that the session is open (the session cannot be open if you didn't enter the password first) => message Session already open.

I hope this can help you.

Kind Regards

I still get the same output 

*** Booting nRF Connect SDK v3.0.2-89ba1294ac9b ***
*** Using Zephyr OS v4.0.99-f791c49f492c ***
[00:00:00.457,519] <inf> st25dv_demo: ST25DV64KC Zephyr Example
[00:00:00.457,519] <inf> st25dv_demo: Checking I2C session status...
[00:00:00.457,794] <inf> st25dv_demo: Session already open.
[00:00:00.457,794] <inf> st25dv_demo: Writing Capability Container...
[00:00:00.478,210] <inf> st25dv_demo: Writing NDEF message...
[00:00:00.498,962] <inf> st25dv_demo: Enabling RF write protection...
[00:00:00.499,359] <err> st25dv_demo: Failed to set full Area1
[00:00:00.499,389] <wrn> st25dv_demo: RF write protection failed (possibly due to session lock)
[00:00:00.499,389] <inf> st25dv_demo: Reading back CC...
[00:00:00.499,816] <inf> st25dv_demo: CC Read:
e1 40 03 0e 00 00 00 00 |.@......
[00:00:00.499,816] <inf> st25dv_demo: Reading back NDEF message...
[00:00:00.500,518] <inf> st25dv_demo: NDEF Read:
03 11 d1 01 0d 54 02 65 6e 48 65 6c 6c 6f 20 57 |.....T.e nHello W
6f 72 6c 64 fe |orld.

 

#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);

// I2C addresses
#define ST25DV_USER_ADDR 0x53 // For EEPROM/NDEF access
#define ST25DV_SYS_ADDR 0x57  // For system register access

#define I2C_NODE DT_NODELABEL(i2c2)

#define EEPROM_START_ADDR 0x0000

// System Register Addresses
#define I2C_PWD_REG 0x09B0
#define I2C_SSO_DYN 0x2004
#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_SYS_ADDR);
}

static int st25dv_write_eeprom(uint16_t addr, const uint8_t *data, size_t len)
{
    uint8_t buf[2 + 64];
    if (len > 64)
    {
        LOG_ERR("Write length exceeds maximum I2C burst size (64 bytes)");
        return -EINVAL;
    }
    buf[0] = addr >> 8;
    buf[1] = addr & 0xFF;
    memcpy(&buf[2], data, len);
    return i2c_write(i2c_dev, buf, len + 2, ST25DV_USER_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_USER_ADDR, reg, sizeof(reg), data, len);
}

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);

    int ret = i2c_write(i2c_dev, buf, sizeof(buf), ST25DV_SYS_ADDR);
    if (ret < 0) {
        LOG_ERR("i2c_write() failed: %d", ret);
    }
    return ret;
}


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_SYS_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)
{
    if (st25dv_write_sys(ENDA3_REG, 0xFF) < 0)
        return -1;
    k_msleep(5);
    if (st25dv_write_sys(ENDA2_REG, 0xFF) < 0)
        return -1;
    k_msleep(5);
    if (st25dv_write_sys(ENDA1_REG, 0xFF) < 0)
        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;
    }

    if (st25dv_set_full_area1() < 0)
    {
        LOG_ERR("Failed to set full Area1");
        return -1;
    }

    return st25dv_write_sys(RFA1SS_REG, enable ? 0x01 : 0x00);
}

void main(void)
{
    i2c_dev = DEVICE_DT_GET(I2C_NODE);
    if (!device_is_ready(i2c_dev))
    {
        LOG_ERR("I2C device not ready");
        return;
    }

    LOG_INF("ST25DV64KC Zephyr Example");

    const uint8_t password[8] = {0};  // Default password

    const uint8_t ndef_msg[] = {
        0x03,                         // NDEF TLV Type
        0x11,                         // TLV Length
        0xD1, 0x01, 0x0D,             // NDEF header
        0x54,                         // Type = 'T' (Text)
        0x02,                         // Status byte: UTF-8, lang="en"
        0x65, 0x6E,                   // 'e', 'n'
        'H', 'e', 'l', 'l', 'o', ' ', 'W', 'o', 'r', 'l', 'd',
        0xFE                          // TLV terminator
    };

    size_t ndef_actual_len = sizeof(ndef_msg) - 1;

    uint8_t cc[8];
    uint16_t mlc_bytes = ndef_actual_len;
    uint8_t mlen_value;

    if (mlc_bytes <= 2040) {
        mlen_value = (mlc_bytes + 7) / 8;
        if (mlen_value == 0) mlen_value = 1;
        if (mlen_value > 0xFF) mlen_value = 0xFF;
        cc[0] = 0xE1;
        cc[1] = 0x40;
        cc[2] = mlen_value;
        cc[3] = 0x0E;
        cc[4] = 0x00;
        cc[5] = 0x00;
        cc[6] = 0x00;
        cc[7] = 0x00;
    } else {
        cc[0] = 0xE1;
        cc[1] = 0x40;
        cc[2] = 0x00;
        cc[3] = 0x0E;
        cc[4] = 0x00;
        cc[5] = 0x00;
        cc[6] = (mlc_bytes >> 8) & 0xFF;
        cc[7] = mlc_bytes & 0xFF;
    }

    LOG_INF("Checking I2C session status...");
    if (!st25dv_is_session_open())
    {
        LOG_INF("Session not open. Attempting to open...");
        if (st25dv_open_session(password) < 0)
        {
            LOG_WRN("Session open failed, continuing without authentication...");
        }
        k_msleep(10);

        if (!st25dv_is_session_open())
        {
            LOG_WRN("Session still not open — some operations may fail.");
        }
        else
        {
            LOG_INF("Session opened successfully.");
        }
    }
    else
    {
        LOG_INF("Session already open.");
    }

    LOG_INF("Writing Capability Container...");
    if (st25dv_write_eeprom(0x0000, cc, sizeof(cc)) < 0)
    {
        LOG_ERR("Failed to write CC");
        return;
    }
    k_msleep(20);

    LOG_INF("Writing NDEF message...");
    if (st25dv_write_eeprom(0x0008, ndef_msg, sizeof(ndef_msg)) < 0)
    {
        LOG_ERR("Failed to write NDEF");
        return;
    }
    k_msleep(20);

    LOG_INF("Enabling RF write protection...");
    if (st25dv_set_rf_write_protect(true) < 0)
    {
        LOG_WRN("RF write protection failed (possibly due to session lock)");
    }
    else
    {
        LOG_INF("RF write protection enabled.");
    }

    LOG_INF("Reading back CC...");
    uint8_t read_buffer[64];
    if (st25dv_read_eeprom(0x0000, read_buffer, sizeof(cc)) == 0)
    {
        LOG_HEXDUMP_INF(read_buffer, sizeof(cc), "CC Read:");
    }
    else
    {
        LOG_ERR("Failed to read CC.");
    }

    LOG_INF("Reading back NDEF message...");
    if (st25dv_read_eeprom(0x0008, read_buffer, sizeof(ndef_msg)) == 0)
    {
        LOG_HEXDUMP_INF(read_buffer, sizeof(ndef_msg), "NDEF Read:");
    }
    else
    {
        LOG_ERR("Failed to read NDEF.");
    }
}


[00:00:00.476,013] <inf> st25dv_demo: I2C_SSO_EN_REG (0x0003) = 0x00

Rene Lenerve
ST Employee

Hi Jean4,

Your code is still returning that the session is already open, but no password has been provided before. Check what is the return value of st25dv_is_session_open(), check that the condition is working correctly (depending on bool typedef's "!st25dv_is_session_open()" can return different values, e.g. if bool is an int !0 = -1 and !1 = -2). If you have the possibility to spy the I²C communication it could help to debug also.

I don't know why you added this log afterwards ([00:00:00.476,013] <inf> st25dv_demo: I2C_SSO_EN_REG (0x0003) = 0x00), but register 0003 is for RF_MGNT and not I2C_SSO and its value is 0 by default.

For the CC file you split it in 2 cases, which is correct now but in the case the NDEF file is lower than 2040 the CC file is on 4 bytes an so you need to write the NDEF message at the offset 4 (and not the offset 8).

Kind Regards.

Sorry for the late response, i have adjusted the code accordingly and it still doesnt work, it is as if the chip is soft locked or something 

*** Booting nRF Connect SDK v3.0.2-89ba1294ac9b ***
*** Using Zephyr OS v4.0.99-f791c49f492c ***
[00:00:00.477,264] <inf> st25dv_demo: ST25DV64KC Zephyr Example
[00:00:00.477,264] <inf> st25dv_demo: Checking I2C session status...
[00:00:00.477,539] <inf> st25dv_demo: I2C_SSO_DYN (0x2004) = 0xFF -> Session is OPEN
[00:00:00.477,569] <inf> st25dv_demo: Session already open.
[00:00:00.477,569] <inf> st25dv_demo: Writing Capability Container...
[00:00:00.498,016] <inf> st25dv_demo: Writing NDEF message...
[00:00:00.518,798] <inf> st25dv_demo: Enabling RF write protection...
[00:00:00.519,104] <inf> st25dv_demo: I2C_SSO_DYN (0x2004) = 0xFF -> Session is OPEN
[00:00:00.519,287] <err> st25dv_demo: Sys write failed at 0x0009
[00:00:00.519,317] <err> st25dv_demo: Setting full Area1 failed
[00:00:00.519,317] <wrn> st25dv_demo: RF write protection failed
[00:00:00.519,348] <inf> st25dv_demo: Reading back CC...
[00:00:00.519,744] <inf> st25dv_demo: CC Read:
e1 40 03 0e 03 11 d1 01 |.@......
[00:00:00.519,775] <inf> st25dv_demo: Reading back NDEF message...
[00:00:00.520,477] <inf> st25dv_demo: NDEF Read:
03 11 d1 01 0d 54 02 65 6e 48 65 6c 6c 6f 20 57 |.....T.e nHello W
6f 72 6c 64 fe |orld.

#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);

// I2C addresses
#define ST25DV_USER_ADDR 0x53 // EEPROM/NDEF
#define ST25DV_SYS_ADDR  0x57 // System registers

#define I2C_NODE DT_NODELABEL(i2c2)

// System Register Addresses
#define I2C_PWD_REG   0x09B0
#define I2C_SSO_DYN   0x2004
#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};
    int ret = i2c_write(i2c_dev, buf, sizeof(buf), ST25DV_SYS_ADDR);
    if (ret < 0)
        LOG_ERR("Sys write failed at 0x%04X", reg);
    return ret;
}

static int st25dv_write_eeprom(uint16_t addr, const uint8_t *data, size_t len)
{
    if (len > 64)
    {
        LOG_ERR("Max I2C burst size exceeded");
        return -EINVAL;
    }
    uint8_t buf[2 + 64];
    buf[0] = addr >> 8;
    buf[1] = addr & 0xFF;
    memcpy(&buf[2], data, len);
    return i2c_write(i2c_dev, buf, len + 2, ST25DV_USER_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_USER_ADDR, reg, sizeof(reg), data, len);
}

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);
    int ret = i2c_write(i2c_dev, buf, sizeof(buf), ST25DV_SYS_ADDR);
    if (ret < 0)
        LOG_ERR("Password write failed: %d", ret);
    return ret;
}

static bool st25dv_is_session_open(void)
{
    uint8_t reg[2] = {I2C_SSO_DYN >> 8, I2C_SSO_DYN & 0xFF};
    uint8_t status = 0;

    if (i2c_write_read(i2c_dev, ST25DV_SYS_ADDR, reg, sizeof(reg), &status, 1) < 0)
    {
        LOG_ERR("Failed to read I2C_SSO_DYN");
        return false;
    }

    bool open = (status & 0x01) ? true : false;
    LOG_INF("I2C_SSO_DYN (0x%04X) = 0x%02X -> Session is %s", I2C_SSO_DYN, status, open ? "OPEN" : "CLOSED");
    return open;
}

static int st25dv_set_full_area1(void)
{
    if (st25dv_write_sys(ENDA3_REG, 0xFF) < 0) return -1;
    k_msleep(5);
    if (st25dv_write_sys(ENDA2_REG, 0xFF) < 0) return -1;
    k_msleep(5);
    if (st25dv_write_sys(ENDA1_REG, 0xFF) < 0) 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 for RF protection");
        return -1;
    }

    if (st25dv_set_full_area1() < 0)
    {
        LOG_ERR("Setting full Area1 failed");
        return -1;
    }

    return st25dv_write_sys(RFA1SS_REG, enable ? 0x01 : 0x00);
}

void main(void)
{
    i2c_dev = DEVICE_DT_GET(I2C_NODE);
    if (!device_is_ready(i2c_dev))
    {
        LOG_ERR("I2C device not ready");
        return;
    }

    LOG_INF("ST25DV64KC Zephyr Example");

    const uint8_t password[8] = {0};  // Default password

    const uint8_t ndef_msg[] = {
        0x03,                         // NDEF TLV Type
        0x11,                         // TLV Length
        0xD1, 0x01, 0x0D,             // NDEF header
        0x54,                         // Type = 'T' (Text)
        0x02,                         // Status byte: UTF-8, lang="en"
        0x65, 0x6E,                   // 'e', 'n'
        'H', 'e', 'l', 'l', 'o', ' ', 'W', 'o', 'r', 'l', 'd',
        0xFE                          // TLV terminator
    };

    size_t ndef_actual_len = sizeof(ndef_msg) - 1;

    uint8_t cc[8];
    uint16_t mlc_bytes = ndef_actual_len;
    uint8_t mlen_value;
    uint16_t ndef_offset;

    if (mlc_bytes <= 2040)
    {
        mlen_value = (mlc_bytes + 7) / 8;
        if (mlen_value == 0) mlen_value = 1;
        if (mlen_value > 0xFF) mlen_value = 0xFF;

        cc[0] = 0xE1;
        cc[1] = 0x40;
        cc[2] = mlen_value;
        cc[3] = 0x0E;
        cc[4] = cc[5] = cc[6] = cc[7] = 0x00;

        ndef_offset = 0x0004;
    }
    else
    {
        cc[0] = 0xE1;
        cc[1] = 0x40;
        cc[2] = 0x00;
        cc[3] = 0x0E;
        cc[4] = cc[5] = 0x00;
        cc[6] = (mlc_bytes >> 8) & 0xFF;
        cc[7] = mlc_bytes & 0xFF;

        ndef_offset = 0x0008;
    }

    LOG_INF("Checking I2C session status...");
    if (!st25dv_is_session_open())
    {
        LOG_INF("Session not open. Opening session...");
        if (st25dv_open_session(password) < 0)
        {
            LOG_WRN("Failed to open session with password.");
        }

        k_msleep(10);

        if (!st25dv_is_session_open())
        {
            LOG_WRN("Session still not open — operations may fail.");
        }
        else
        {
            LOG_INF("Session opened successfully.");
        }
    }
    else
    {
        LOG_INF("Session already open.");
    }

    LOG_INF("Writing Capability Container...");
    if (st25dv_write_eeprom(0x0000, cc, sizeof(cc)) < 0)
    {
        LOG_ERR("CC write failed");
        return;
    }
    k_msleep(20);

    LOG_INF("Writing NDEF message...");
    if (st25dv_write_eeprom(ndef_offset, ndef_msg, sizeof(ndef_msg)) < 0)
    {
        LOG_ERR("NDEF write failed");
        return;
    }
    k_msleep(20);

    LOG_INF("Enabling RF write protection...");
    if (st25dv_set_rf_write_protect(true) < 0)
    {
        LOG_WRN("RF write protection failed");
    }
    else
    {
        LOG_INF("RF write protection enabled");
    }

    LOG_INF("Reading back CC...");
    uint8_t read_buffer[64];
    if (st25dv_read_eeprom(0x0000, read_buffer, sizeof(cc)) == 0)
    {
        LOG_HEXDUMP_INF(read_buffer, sizeof(cc), "CC Read:");
    }
    else
    {
        LOG_ERR("Failed to read CC.");
    }

    LOG_INF("Reading back NDEF message...");
    if (st25dv_read_eeprom(ndef_offset, read_buffer, sizeof(ndef_msg)) == 0)
    {
        LOG_HEXDUMP_INF(read_buffer, sizeof(ndef_msg), "NDEF Read:");
    }
    else
    {
        LOG_ERR("Failed to read NDEF.");
    }
}