nRF Connect Can't Communicate Properly with CY15B108QN FRAM Infineon

Hi All,

Anyone has experience in FRAM?

I have a project which need to integrate the FRAM CY15B108QN with nRF52840.

I got a weird result by using nRF silicone, but can communicate perfectly using other sillicone (ESP32).

(1)NCS Zephyr Result(2)ESP Result using RAK lib

(1) result when use zephyr; (2) result when use ESP32

Both sending 0x9F but having different response. The ESP32 result is matched with documentation, the zephyr one is not matched but consistently resulting the same value.

I've tried:

1. using SPI mode 0 and 3

2. vary the frequency

3. manually control the CS line

4. byte by byte transfer

5. using SPI 1 and SPI 3

6. using EEPROM SPI API by modify the fujitsu,mb85rsxx

7. raw simple SPI transaction

8. using SPI and SPIM

All have output the same result.

Appreciate your help.

Thanks

Parents Reply Children
  • sure, please check this:

    I shared the board overlay and FRAM driver I used. I haven't reach the write and read memory, just read the RDID only in the init function for testing. Because if RDID still have issue, the other functionality may also not working properly.

    Do you have any idea whats going on?

    cy15b108qn.h

    /*
     * Copyright (c) 2025 Medtech Sherpa LLC
     * Copyright (c) 2024 Antmicro <www.antmicro.com> (Original MB85RSXX driver)
     *
     * SPDX-License-Identifier: Apache-2.0
     *
     * CY15B108QN FRAM Driver for Zephyr RTOS
     * Based on Zephyr's MB85RSXX EEPROM driver with Infineon ID support added.
     *
     * Supports both:
     * - Fujitsu MB85RSXX series (Manufacturer ID: 0x04)
     * - Infineon CY15B108QN series (Manufacturer ID: 0x02)
     */
    
    #define DT_DRV_COMPAT infineon_cy15b108qn
    
    #include <zephyr/device.h>
    #include <zephyr/drivers/eeprom.h>
    #include <zephyr/drivers/gpio.h>
    #include <zephyr/drivers/spi.h>
    #include <zephyr/kernel.h>
    #include <zephyr/sys/byteorder.h>
    
    #include <zephyr/logging/log.h>
    LOG_MODULE_REGISTER(cy15b108qn, CONFIG_CY15B108QN_LOG_LEVEL);
    
    /* FRAM SPI instruction set (common across Fujitsu/Infineon) */
    #define FRAM_CMD_WREN   0x06U  /* Set Write Enable Latch */
    #define FRAM_CMD_WRDI   0x04U  /* Reset Write Enable Latch */
    #define FRAM_CMD_RDSR   0x05U  /* Read Status Register */
    #define FRAM_CMD_WRSR   0x01U  /* Write Status Register */
    #define FRAM_CMD_READ   0x03U  /* Read Memory */
    #define FRAM_CMD_WRITE  0x02U  /* Write Memory */
    #define FRAM_CMD_RDID   0x9F  /* Read Device ID */
    #define FRAM_CMD_RUID   0x4C  /* Read Unique ID */
    #define FRAM_CMD_FSTRD  0x0BU  /* Fast Read Memory */
    #define FRAM_CMD_SLEEP  0xB9U  /* Sleep Mode */
    
    /* Manufacturer IDs */
    #define FRAM_MFR_FUJITSU    0x04U
    #define FRAM_MFR_INFINEON   0x03U  /* Cypress/Infineon CY15B series */
    
    /* Fujitsu MB85RSXX expected ID pattern */
    #define FUJITSU_CON_CODE    0x7FU
    #define FUJITSU_PROD_ID     0x20U
    #define FUJITSU_PROD_MASK   0xE0U  /* GENMASK(7, 5) */
    #define FUJITSU_PROD_ID2    0x03U
    
    struct cy15b108qn_config {
        struct spi_dt_spec spi;
        size_t size;
        bool readonly;
    };
    
    struct cy15b108qn_data {
        struct k_mutex lock;
    };
    
    /* ============================================================================
     * CS Timing Control
     * ============================================================================ */
    
    /* CS timing delays (in microseconds) - from CY15B108QN datasheet */
    #define CS_SETUP_DELAY_US    0   /* tCSS: CS low to first clock (min 5ns) */
    #define CS_HOLD_DELAY_US     0   /* tCSH: last clock to CS high (min 5ns) */
    #define CS_DESELECT_DELAY_US 0   /* tCSD: CS high between commands (min 25ns) */
    
    static inline void fram_cs_assert(const struct cy15b108qn_config *config)
    {
        gpio_pin_set_dt(&config->spi.config.cs.gpio, 1);  /* Active LOW: set=1 means LOW */
        // k_busy_wait(CS_SETUP_DELAY_US);                   /* Wait for CS setup time */
    }
    
    static inline void fram_cs_deassert(const struct cy15b108qn_config *config)
    {
        // k_busy_wait(CS_HOLD_DELAY_US);                    /* Wait for CS hold time */
        gpio_pin_set_dt(&config->spi.config.cs.gpio, 0);  /* Active LOW: set=0 means HIGH */
        // k_busy_wait(CS_DESELECT_DELAY_US);                /* Wait for CS deselect time */
    }
    
    /* ============================================================================
     * SPI Operations
     * ============================================================================ */
    
    static int fram_read(const struct device *dev, off_t offset, void *buf, size_t len)
    {
        const struct cy15b108qn_config *config = dev->config;
        struct cy15b108qn_data *data = dev->data;
        uint8_t cmd[4] = {FRAM_CMD_READ, 0, 0, 0};
        uint8_t *paddr = &cmd[1];
        int err;
    
        if (offset + len > config->size) {
            LOG_ERR("attempt to read past device boundary");
            return -EINVAL;
        }
    
        if (!len) {
            return 0;
        }
    
        /* Populate 24-bit address */
        *paddr++ = (offset >> 16) & 0xFF;
        *paddr++ = (offset >> 8) & 0xFF;
        *paddr++ = offset & 0xFF;
    
        const struct spi_buf tx_buf = {
            .buf = cmd,
            .len = sizeof(cmd),
        };
        const struct spi_buf_set tx = {
            .buffers = &tx_buf,
            .count = 1,
        };
        const struct spi_buf rx_bufs[2] = {
            {
                .buf = NULL,
                .len = sizeof(cmd),
            },
            {
                .buf = buf,
                .len = len,
            },
        };
        const struct spi_buf_set rx = {
            .buffers = rx_bufs,
            .count = ARRAY_SIZE(rx_bufs),
        };
    
        /* SPI config without automatic CS */
        struct spi_config spi_cfg = config->spi.config;
        spi_cfg.cs.gpio.port = NULL;
    
        k_mutex_lock(&data->lock, K_FOREVER);
    
        fram_cs_assert(config);
        err = spi_transceive(config->spi.bus, &spi_cfg, &tx, &rx);
        fram_cs_deassert(config);
    
        k_mutex_unlock(&data->lock);
    
        if (err < 0) {
            LOG_ERR("failed to read FRAM (err %d)", err);
        }
    
        return err;
    }
    
    static int fram_wren(const struct device *dev)
    {
        const struct cy15b108qn_config *config = dev->config;
        uint8_t cmd = FRAM_CMD_WREN;
        int err;
    
        const struct spi_buf tx_buf = {
            .buf = &cmd,
            .len = sizeof(cmd),
        };
        const struct spi_buf_set tx = {
            .buffers = &tx_buf,
            .count = 1,
        };
    
        /* SPI config without automatic CS */
        struct spi_config spi_cfg = config->spi.config;
        spi_cfg.cs.gpio.port = NULL;
    
        fram_cs_assert(config);
        err = spi_write(config->spi.bus, &spi_cfg, &tx);
        fram_cs_deassert(config);
    
        return err;
    }
    
    static int fram_wrdi(const struct device *dev)
    {
        const struct cy15b108qn_config *config = dev->config;
        uint8_t cmd = FRAM_CMD_WRDI;
        int err;
    
        const struct spi_buf tx_buf = {
            .buf = &cmd,
            .len = sizeof(cmd),
        };
        const struct spi_buf_set tx = {
            .buffers = &tx_buf,
            .count = 1,
        };
    
        /* SPI config without automatic CS */
        struct spi_config spi_cfg = config->spi.config;
        spi_cfg.cs.gpio.port = NULL;
    
        fram_cs_assert(config);
        err = spi_write(config->spi.bus, &spi_cfg, &tx);
        fram_cs_deassert(config);
    
        return err;
    }
    
    static int fram_write(const struct device *dev, off_t offset, const void *buf, size_t len)
    {
        const struct cy15b108qn_config *config = dev->config;
        struct cy15b108qn_data *data = dev->data;
        uint8_t cmd[4] = {FRAM_CMD_WRITE, 0, 0, 0};
        uint8_t *paddr = &cmd[1];
        int err;
    
        if (config->readonly) {
            LOG_ERR("attempt to write to read-only device");
            return -EACCES;
        }
    
        if (offset + len > config->size) {
            LOG_ERR("attempt to write past device boundary");
            return -EINVAL;
        }
    
        /* Populate 24-bit address */
        *paddr++ = (offset >> 16) & 0xFF;
        *paddr++ = (offset >> 8) & 0xFF;
        *paddr++ = offset & 0xFF;
    
        const struct spi_buf tx_bufs[2] = {
            {
                .buf = cmd,
                .len = sizeof(cmd),
            },
            {
                .buf = (void *)buf,
                .len = len,
            },
        };
        const struct spi_buf_set tx = {
            .buffers = tx_bufs,
            .count = ARRAY_SIZE(tx_bufs),
        };
    
        /* SPI config without automatic CS */
        struct spi_config spi_cfg = config->spi.config;
        spi_cfg.cs.gpio.port = NULL;
    
        k_mutex_lock(&data->lock, K_FOREVER);
    
        err = fram_wren(dev);
        if (err < 0) {
            LOG_ERR("failed to enable write (err %d)", err);
            k_mutex_unlock(&data->lock);
            return err;
        }
    
        fram_cs_assert(config);
        err = spi_write(config->spi.bus, &spi_cfg, &tx);
        fram_cs_deassert(config);
    
        if (err < 0) {
            LOG_ERR("failed to write to FRAM (err %d)", err);
            k_mutex_unlock(&data->lock);
            return err;
        }
    
        err = fram_wrdi(dev);
        if (err < 0) {
            LOG_ERR("failed to disable write (err %d)", err);
        }
    
        k_mutex_unlock(&data->lock);
    
        return err;
    }
    
    static size_t fram_size(const struct device *dev)
    {
        const struct cy15b108qn_config *config = dev->config;
        return config->size;
    }
    
    static int fram_rdid(const struct device *dev)
    {
        const struct cy15b108qn_config *config = dev->config;
        struct cy15b108qn_data *data = dev->data;
        uint8_t cmd = FRAM_CMD_RDID;
        uint8_t id[9] = {0};
        int err;
    
        /* TX: just the command byte */
        const struct spi_buf tx_buf = {
            .buf = &cmd,
            .len = 1,
        };
        const struct spi_buf_set tx = {
            .buffers = &tx_buf,
            .count = 1,
        };
    
        /* RX: discard during cmd TX, then receive 9 ID bytes */
        const struct spi_buf rx_bufs[2] = {
            { .buf = NULL, .len = 1 },           /* Discard during command TX */
            { .buf = id, .len = sizeof(id) },    /* Receive ID bytes */
        };
        const struct spi_buf_set rx = {
            .buffers = rx_bufs,
            .count = ARRAY_SIZE(rx_bufs),
        };
    
        /* SPI config without automatic CS (we control CS manually) */
        struct spi_config spi_cfg = config->spi.config;
        spi_cfg.cs.gpio.port = NULL;  /* Disable automatic CS */
    
        k_mutex_lock(&data->lock, K_FOREVER);
    
        /* Manual CS control with proper timing */
        fram_cs_assert(config);
        err = spi_transceive(config->spi.bus, &spi_cfg, &tx, &rx);
        // err = spi_write(config->spi.bus, &spi_cfg, &tx);
        // k_busy_wait(1);  /* Small delay between TX and RX */
        // err = spi_read(config->spi.bus, &spi_cfg, &rx);
        fram_cs_deassert(config);
    
        k_mutex_unlock(&data->lock);
    
        if (err < 0) {
            LOG_ERR("failed to read RDID (err %d)", err);
            return err;
        }
    
        LOG_INF("Device ID: %02X %02X %02X %02X %02X %02X %02X %02X %02X",
                id[0], id[1], id[2], id[3], id[4], id[5], id[6], id[7], id[8]);
    
        /* Unknown manufacturer - check if device responds at all */
        bool all_ff = true, all_00 = true;
        for (int i = 0; i < 4; i++) {
            if (id[i] != 0xFF) all_ff = false;
            if (id[i] != 0x00) all_00 = false;
        }
    
        if (all_ff || all_00) {
            LOG_ERR("FRAM not responding (ID all %s)", all_ff ? "0xFF" : "0x00");
            return -EIO;
        }
    
        /* Unknown but responding device - warn but allow */
        LOG_WRN("Unknown FRAM manufacturer ID: %02X, proceeding anyway", id[0]);
        return 0;
    }
    
    static int fram_ruid(const struct device *dev)
    {
        const struct cy15b108qn_config *config = dev->config;
        struct cy15b108qn_data *data = dev->data;
        uint8_t cmd = FRAM_CMD_RUID;
        uint8_t uid[8] = {0};
        int err;
    
        /* TX: just the command byte */
        const struct spi_buf tx_buf = {
            .buf = &cmd,
            .len = 1,
        };
        const struct spi_buf_set tx = {
            .buffers = &tx_buf,
            .count = 1,
        };
    
        /* RX: discard during cmd TX, then receive 8 UID bytes */
        const struct spi_buf rx_bufs[2] = {
            { .buf = NULL, .len = 1 },
            { .buf = uid, .len = sizeof(uid) },
        };
        const struct spi_buf_set rx = {
            .buffers = rx_bufs,
            .count = ARRAY_SIZE(rx_bufs),
        };
    
        /* SPI config without automatic CS */
        struct spi_config spi_cfg = config->spi.config;
        spi_cfg.cs.gpio.port = NULL;
    
        k_mutex_lock(&data->lock, K_FOREVER);
    
        fram_cs_assert(config);
        err = spi_transceive(config->spi.bus, &spi_cfg, &tx, &rx);
        fram_cs_deassert(config);
    
        k_mutex_unlock(&data->lock);
    
        if (err < 0) {
            LOG_ERR("failed to read RUID (err %d)", err);
            return err;
        }
    
        LOG_INF("Unique ID: %02X %02X %02X %02X %02X %02X %02X %02X",
                uid[0], uid[1], uid[2], uid[3], uid[4], uid[5], uid[6], uid[7]);
    
        return 0;
    }
    
    
    /* ============================================================================
     * Driver Initialization
     * ============================================================================ */
    
    static int cy15b108qn_init(const struct device *dev)
    {
        const struct cy15b108qn_config *config = dev->config;
        struct cy15b108qn_data *data = dev->data;
        int err;
    
        k_mutex_init(&data->lock);
    
        LOG_INF("Initializing FRAM");
    
        if (!spi_is_ready_dt(&config->spi)) {
            LOG_ERR("SPI bus not ready");
            return -EINVAL;
        }
    
        /* Configure CS GPIO for manual control */
        if (!gpio_is_ready_dt(&config->spi.config.cs.gpio)) {
            LOG_ERR("CS GPIO not ready");
            return -EINVAL;
        }
    
        err = gpio_pin_configure_dt(&config->spi.config.cs.gpio, GPIO_OUTPUT_INACTIVE);
        if (err < 0) {
            LOG_ERR("Failed to configure CS GPIO (err %d)", err);
            return err;
        }
    
        /* Ensure CS is high (inactive) initially */
        gpio_pin_set_dt(&config->spi.config.cs.gpio, 0);  /* 0 = HIGH for active-low */
        k_busy_wait(CS_DESELECT_DELAY_US);
    
        err = fram_rdid(dev);
        if (err < 0) {
            LOG_ERR("FRAM initialization failed (err %d)", err);
            return err;
        }
    
        k_sleep(K_MSEC(10));
    
        err = fram_ruid(dev);
        if (err < 0) {
            LOG_ERR("Failed to read Unique ID (err %d)", err);
            return err;
        }
    
        LOG_INF("FRAM initialized: %zu KB", config->size / 1024);
        return 0;
    }
    
    /* ============================================================================
     * EEPROM API Implementation
     * ============================================================================ */
    
    static DEVICE_API(eeprom, cy15b108qn_eeprom_api) = {
        .read = fram_read,
        .write = fram_write,
        .size = fram_size,
    };
    
    /* ============================================================================
     * Device Instantiation
     * ============================================================================ */
    
    /* CS GPIO is obtained from SPI spec (SPI_DT_SPEC_INST_GET extracts cs-gpios) */
    #define CY15B108QN_INIT(inst)                                                     \
        static struct cy15b108qn_data cy15b108qn_data_##inst;                         \
                                                                                       \
        static const struct cy15b108qn_config cy15b108qn_config_##inst = {            \
            .spi = SPI_DT_SPEC_INST_GET(                                              \
                inst, SPI_OP_MODE_MASTER | SPI_TRANSFER_MSB | SPI_WORD_SET(8), 0),   \
            .size = DT_INST_PROP(inst, size),                                         \
            .readonly = DT_INST_PROP_OR(inst, read_only, false),                      \
        };                                                                             \
                                                                                       \
        DEVICE_DT_INST_DEFINE(inst, cy15b108qn_init, NULL,                            \
                              &cy15b108qn_data_##inst,                                \
                              &cy15b108qn_config_##inst,                              \
                              POST_KERNEL,                                            \
                              CONFIG_EEPROM_INIT_PRIORITY,                            \
                              &cy15b108qn_eeprom_api);
    
    DT_INST_FOREACH_STATUS_OKAY(CY15B108QN_INIT)
    

    ubx_evkninab3_nrf52840.overlay

  • Hi, I hope you are doing well,

    just add a comparison when I use ESP32 with ESP-IDF. Just using simple SPI master:

    #include <stdio.h>
    
    #include "driver/spi_master.h"
    #include "driver/gpio.h"
    #include "esp_log.h"
    #include "freertos/FreeRTOS.h"
    #include "freertos/task.h"
    
    #define PIN_NUM_MISO 19
    #define PIN_NUM_MOSI 23
    #define PIN_NUM_CLK 18
    #define PIN_NUM_CS 5
    #define PIN_NUM_WP 22
    
    #define RDID_CMD 0x9F
    
    static const char *TAG = "fram_rdid";
    
    static void log_help(void)
    {
        ESP_LOGI(TAG, "CY15B108QN RDID test over VSPI");
        ESP_LOGI(TAG, "Pins: SCK=GPIO%d MISO=GPIO%d MOSI=GPIO%d CS=GPIO%d",
                 PIN_NUM_CLK, PIN_NUM_MISO, PIN_NUM_MOSI, PIN_NUM_CS);
        ESP_LOGI(TAG, "WP pin: GPIO%d (held high)", PIN_NUM_WP);
        ESP_LOGI(TAG, "SPI mode 0, ~2 MHz, full duplex");
        ESP_LOGI(TAG, "RDID command 0x%02X, expect 3 bytes: [Manufacturer, Product MSB, Product LSB]",
                 RDID_CMD);
    }
    
    static esp_err_t fram_read_rdid(spi_device_handle_t dev, uint8_t rdid[3])
    {
        uint8_t tx_data[4] = { RDID_CMD, 0x00, 0x00, 0x00 };
        uint8_t rx_data[4] = { 0 };
        spi_transaction_t t = { 0 };
    
        t.length = 8 * sizeof(tx_data);
        t.tx_buffer = tx_data;
        t.rx_buffer = rx_data;
    
        esp_err_t err = spi_device_transmit(dev, &t);
        if (err != ESP_OK) {
            return err;
        }
    
        rdid[0] = rx_data[1];
        rdid[1] = rx_data[2];
        rdid[2] = rx_data[3];
        return ESP_OK;
    }
    
    void app_main(void)
    {
        log_help();
    
        gpio_config_t wp_cfg = {
            .pin_bit_mask = 1ULL << PIN_NUM_WP,
            .mode = GPIO_MODE_OUTPUT,
            .pull_up_en = GPIO_PULLUP_DISABLE,
            .pull_down_en = GPIO_PULLDOWN_DISABLE,
            .intr_type = GPIO_INTR_DISABLE
        };
        ESP_ERROR_CHECK(gpio_config(&wp_cfg));
        ESP_ERROR_CHECK(gpio_set_level(PIN_NUM_WP, 1));
    
        spi_bus_config_t buscfg = {
            .miso_io_num = PIN_NUM_MISO,
            .mosi_io_num = PIN_NUM_MOSI,
            .sclk_io_num = PIN_NUM_CLK,
            .quadwp_io_num = -1,
            .quadhd_io_num = -1,
            .max_transfer_sz = 4
        };
    
        spi_device_interface_config_t devcfg = {
            .clock_speed_hz = 2 * 1000 * 1000,
            .mode = 0,
            .spics_io_num = PIN_NUM_CS,
            .queue_size = 1
        };
    
        ESP_ERROR_CHECK(spi_bus_initialize(SPI3_HOST, &buscfg, SPI_DMA_CH_AUTO));
    
        spi_device_handle_t dev = NULL;
        ESP_ERROR_CHECK(spi_bus_add_device(SPI3_HOST, &devcfg, &dev));
    
        while (true) {
            uint8_t rdid[3] = { 0 };
            esp_err_t err = fram_read_rdid(dev, rdid);
            if (err == ESP_OK) {
                ESP_LOGI(TAG, "RDID: 0x%02X 0x%02X 0x%02X", rdid[0], rdid[1], rdid[2]);
            } else {
                ESP_LOGE(TAG, "RDID read failed: %s", esp_err_to_name(err));
            }
            vTaskDelay(pdMS_TO_TICKS(1000));
        }
    }
    

    The result is aligned with the CY15B108QN documentation:

    I (339) main_task: Calling app_main()
    I (339) fram_rdid: CY15B108QN RDID test over VSPI
    I (339) fram_rdid: Pins: SCK=GPIO18 MISO=GPIO19 MOSI=GPIO23 CS=GPIO5
    I (349) fram_rdid: WP pin: GPIO22 (held high)
    I (359) fram_rdid: SPI mode 0, ~2 MHz, full duplex
    I (359) fram_rdid: RDID command 0x9F, expect 3 bytes: [Manufacturer, Product MSB, Product LSB]
    I (369) gpio: GPIO[22]| InputEn: 0| OutputEn: 1| OpenDrain: 0| Pullup: 0| Pulldown: 0| Intr:0 
    I (379) fram_rdid: RDID: 0x03 0x2E 0xC2
    I (1379) fram_rdid: RDID: 0x03 0x2E 0xC2
    I (2379) fram_rdid: RDID: 0x03 0x2E 0xC2
    I (3379) fram_rdid: RDID: 0x03 0x2E 0xC2
    I (4379) fram_rdid: RDID: 0x03 0x2E 0xC2
    I (5379) fram_rdid: RDID: 0x03 0x2E 0xC2
    I (6379) fram_rdid: RDID: 0x03 0x2E 0xC2
    I (7379) fram_rdid: RDID: 0x03 0x2E 0xC2

    I have tried also using nRF52840DK (different board) with ZephyrRTOS again but using simple SPI master, it still got the same response as the image logic analyzer (does not align with FRAM documentation).

    Please help :)

    Thank you.

  • This issue has been solved.

    I only need to add this to the pinctrl:

    nordic,drive-mode = <NRF_DRIVE_H0H1>;

  • zuhdalfan said:
    nordic,drive-mode = <NRF_DRIVE_H0H1>;

    Hello Zuhdi,

    Thanks for letting me know.

    Adding this in the device tree 

    nordic,drive-mode = <NRF_DRIVE_H0H1>; 

    resolved the communication issue with the CY15B108QN FRAM on nRF52840. It seems the root cause was very likely related to the default GPIO drive strength (SOS1 provides lower output device current) used by the nRF device. FRAM is a high speed (SPI clock up to 40 MHz) external SPIM devices so it can reduce timing margin and leads to sample incorrect data. 

    Did you try the default SPI3 pins mentioned in our board file?

Related