This post is older than 2 years and might not be relevant anymore
More Info: Consider searching for newer posts

STALLSTAT on SPIM3 writes to flash IC

Our application has a bug that I am trying to track down, but it's being a little tricky.  We are writing a good deal of data to a NAND flash IC over SPIM.  Right now the SPIM is set to 1MHz and we are using SPIM3.  Each write is one page, which is 0x0880 bytes.  Everything is being done by J-Link in SES.

I keep running into STALLSTAT being set for TX when I perform writes.  If I perform a bunch of writes it usually happens around page 0x70.  I can also very reliably get the behavior by performing a read, then a write, then a read while stepping through my code - if I simply step over my read, write, and read functions in that order, I get a TX STALLSTAT every time.  I have this code in a test application I made to just verify that the flash ICs are reading and writing properly.  If I step through the code one line at a time, I get no stall.

I'm not really understanding what is causing this behavior.  In the file transfer I assumed I would find a place where I am writing to the TX buffer before it is emptied.  But in the test application I have a page write buffer that is filled with some nonsense data to write to the flash, then I have a different page read buffer for reading out the data.  When I do the write, I am only pointing the SPIM to the page write buffer, which isn't ever changed.  Because I get a stall, I am led to think that the read that I perform immediately after the write steps on the memory that the DMA is using for the write, but that is in a different location!

Anyway, I'm a little confused, which leads me to believe that I don't entirely understand the bus contention scenarios that can cause stalls.  Is it possible that the debugger itself is somehow involved?

Edit: Here is the code that runs in my test application, it's very minimal.  The flash_mem api is just a wrapper of nrfx_spim_xfer with the commands appropriate to our flash IC.

main.c:

#include <stdbool.h>
#include <stdint.h>
#include <string.h>

#include "flash_mem.h"

flash_mem_t m_flash;

void flash_mem_handler(flash_mem_command_t p_cmd)
{
}

/**@brief Function for application main entry.
 */
int main(void)
{
    nrfx_err_t err_code;

    NRF_POWER->DCDCEN  = POWER_DCDCEN_DCDCEN_Enabled;
    NRF_POWER->DCDCEN0 = POWER_DCDCEN0_DCDCEN_Enabled;
    /* Set internal REG0 to 3V */
    if (NRF_UICR->REGOUT0 != UICR_REGOUT0_VOUT_3V0)
    {
        /* Switch to Write access for NVMC */
        NRF_NVMC->CONFIG |= (NVMC_CONFIG_WEN_Wen << NVMC_CONFIG_WEN_Pos);
        while (NRF_NVMC->READY == NVMC_READY_READY_Busy);

        NRF_UICR->REGOUT0 = UICR_REGOUT0_VOUT_3V0;

        /* Switch back to Read access for NVMC */
        NRF_NVMC->CONFIG = NVMC_CONFIG_WEN_Ren << NVMC_CONFIG_WEN_Pos;
        while (NRF_NVMC->READY == NVMC_READY_READY_Busy);
    }

    NRF_RNG->CONFIG = RNG_CONFIG_DERCEN_Enabled;

    uint8_t buff[0x0880];
    for (int j = 0; j < 0x0880; j++)
    {
        NRF_RNG->TASKS_START = RNG_TASKS_START_TASKS_START_Trigger;
        while (!NRF_RNG->EVENTS_VALRDY) {}
        NRF_RNG->EVENTS_VALRDY = 0;
        buff[j] = NRF_RNG->VALUE;
    }
    uint8_t rbuff[FLASH_MEM_PAGE_SIZE + 4] = { 0 };

    err_code = flash_mem_init(&m_flash, flash_mem_handler);

    err_code = flash_mem_read_page_blocking(&m_flash, 0x00000000, rbuff, FLASH_MEM_PAGE_SIZE + FLASH_MEM_COMMAND_SIZE);
    err_code = flash_mem_write_page(&m_flash, 0x00000000, buff, FLASH_MEM_PAGE_SIZE);
    err_code = flash_mem_read_page_blocking(&m_flash, 0x00000000, rbuff, FLASH_MEM_PAGE_SIZE + FLASH_MEM_COMMAND_SIZE);
    //flash_mem_erase_block(&m_flash, 0x0);
    err_code = flash_mem_read_page_blocking(&m_flash, 0x00000880, rbuff, FLASH_MEM_PAGE_SIZE + FLASH_MEM_COMMAND_SIZE);
    err_code = flash_mem_read_page_blocking(&m_flash, 0x00008800, rbuff, FLASH_MEM_PAGE_SIZE + FLASH_MEM_COMMAND_SIZE);
    err_code = flash_mem_read_page_blocking(&m_flash, 0x0000AA00, rbuff, FLASH_MEM_PAGE_SIZE + FLASH_MEM_COMMAND_SIZE);

    // Enter main loop.
    for (;;)
    {
        // Wait for an event.
        __WFE();
        // Clear the internal event register.
        __SEV();
        __WFE();
    }
}

flash_mem.h:

#ifndef FLASH_MEM_H__
#define FLASH_MEM_H__

#ifdef __cplusplus
extern "C" {
#endif

#include <string.h>
#include "spim.h"

#define FLASH_MEM_PAGE_SIZE       0x0880
#define FLASH_MEM_PAGES_PER_BLOCK 0x40
#define FLASH_MEM_BLOCK_SIZE      FLASH_MEM_PAGE_SIZE * FLASH_MEM_PAGES_PER_BLOCK
#define FLASH_MEM_BLOCKS_PER_IC   0x0400
#define FLASH_MEM_COMMAND_SIZE    4

#define FLASH_MEM_TX_CMD_SIZE           1
#define FLASH_MEM_TX_ADDR_SIZE          3
#define FLASH_MEM_TX_CACHE_ADDR_SIZE    2
#define FLASH_MEM_WP_PIN                NRF_GPIO_PIN_MAP(1, 05)
#define FLASH_MEM_HOLD_PIN              NRF_GPIO_PIN_MAP(1, 06)

#define FLASH_MEM_DUMMY_BYTE      0xFF


/**
 * @brief Flash memory commands
 */
typedef enum
{
    FLASH_MEM_PROGRAM_1X              = 0x02,
    FLASH_MEM_WRITE_DISABLE           = 0x04,
    FLASH_MEM_WRITE_ENABLE            = 0x06,
    FLASH_MEM_READ_FROM_CACHE_1X      = 0x0B,
    FLASH_MEM_GET_FEATURES            = 0x0F,
    FLASH_MEM_PROGRAM_EXECUTE         = 0x10,
    FLASH_MEM_PAGE_READ               = 0x13,
    FLASH_MEM_SET_FEATURES            = 0x1F,
    FLASH_MEM_PERMANENT_BLOCK_LOCK    = 0x2C,
    FLASH_MEM_READ_PAGE_CACHE_RANDOM  = 0x30,
    FLASH_MEM_PROGRAM_4X              = 0x32,
    FLASH_MEM_PROGRAM_RAND_4X         = 0x34,
    FLASH_MEM_READ_FROM_CACHE_2X      = 0x3B,
    FLASH_MEM_READ_PAGE_CACHE_LAST    = 0x3F,
    FLASH_MEM_READ_FROM_CACHE_4X      = 0x6B,
    FLASH_MEM_PROGRAM_RAND_1X         = 0x84,
    FLASH_MEM_FEATURE_BLOCK_LOCK      = 0xA0,
    FLASH_MEM_FEATURE_CONFIGURATION   = 0xB0,
    FLASH_MEM_READ_FROM_CACHE_DUAL    = 0xBB,
    FLASH_MEM_FEATURE_STATUS          = 0xC0,
    FLASH_MEM_FEATURE_DIE_SELECT      = 0xD0,
    FLASH_MEM_BLOCK_ERASE             = 0xD8,
    FLASH_MEM_READ_FROM_CACHE_QUAD    = 0xEB,
    FLASH_MEM_RESET                   = 0xFF,
} flash_mem_command_t;


/**
 * @brief Flash memory statuses (stati?)
 */
typedef enum
{
    FLASH_MEM_IDLE,
    FLASH_MEM_BUSY,
} flash_mem_status_t;


/**
 * @brief Flash memory state structure
 */
typedef struct
{
    nrfx_spim_t *             spim;       // Pointer to spim struct
    uint32_t                  wp_pin;     // Write-protect pin
    uint32_t                  hold_pin;   // Hold pin
    nrfx_drv_state_t          init_state; // State of flash_mem driver
} flash_mem_t;


/** @brief Default flash memory driver instance */
#define FLASH_MEM_DRIVER_INSTANCE               \
{                                               \
    .spim       = NULL,                         \
    .wp_pin     = 0,                            \
    .hold_pin   = 0,                            \
    .init_state = NRFX_DRV_STATE_UNINITIALIZED  \
}


/**
 * @brief Flash memory driver event handler type.
 */
typedef void (*flash_mem_event_handler_t)(flash_mem_command_t p_cmd);

/**
 *  @brief Function for initializing flash memory driver.
 *
 *  @params[in] mem                     Pointer to flash memory struct
 *  @params[in] flash_mem_event_handler Pointer to event handler
 */
nrfx_err_t flash_mem_init(flash_mem_t * mem, flash_mem_event_handler_t);


/**
 *  @brief Function to de-initialize audio driver.
 *
 *  @params[in] audio     Pointer to audio driver struct
 */
void flash_mem_deinit(flash_mem_t * mem);


/**
 * @brief Function for blocking mode flash memory read on page boundary
 *
 * @params[in] mem                    Flash memory struct
 * @params[in] address                Read start address
 * @params[out] read_buffer           Receive buffer pointer
 * @params[in] read_buffer_size       Receive buffer length
 */
nrfx_err_t flash_mem_read_page_blocking(flash_mem_t  *mem,
                                        uint32_t      page_address,
                                        uint8_t      *read_buffer,
                                        uint16_t      read_buffer_size);


/**
 * @brief Function for flash memory read on page boundary
 *
 * @params[in] mem                    Flash memory struct
 * @params[in] address                Read start address
 * @params[out] read_buffer           Receive buffer pointer
 * @params[in] read_buffer_size       Receive buffer length
 */
nrfx_err_t flash_mem_read_page(flash_mem_t  *mem,
                               uint32_t      page_address,
                               uint8_t      *read_buffer,
                               uint16_t      read_buffer_size);


/**
 * @brief Function for flash memory read
 *
 * @params[in] mem                    Flash memory struct
 * @params[in] address                Read start address
 * @params[out] read_buffer           Receive buffer pointer
 * @params[in] read_buffer_size       Receive buffer length
 */
nrfx_err_t flash_mem_read(flash_mem_t *mem,
                          uint32_t     page_address,
                          uint16_t     cache_address,
                          uint8_t     *read_buffer,
                          uint16_t     read_buffer_size);


/**
 * @brief Function for blocking mode flash memory read
 *
 * @params[in] mem                    Flash memory struct
 * @params[in] address                Read start address
 * @params[out] read_buffer           Receive buffer pointer
 * @params[in] read_buffer_size       Receive buffer length
 */
nrfx_err_t flash_mem_read_blocking(flash_mem_t  *mem,
                                   uint32_t      page_address,
                                   uint16_t      cache_address,
                                   uint8_t      *read_buffer,
                                   uint16_t      read_buffer_size);


/**
 * @brief Common code for blocking mode flash memory read
 *
 * @params[in] mem                    Flash memory struct
 * @params[in] address                Read start address
 * @params[out] read_buffer           Receive buffer pointer
 * @params[in] read_buffer_size       Receive buffer length
 */
nrfx_err_t flash_mem_read_common(flash_mem_t  *mem,
                                 uint32_t      page_address,
                                 uint16_t      cache_address,
                                 uint8_t      *read_buffer,
                                 uint16_t      read_buffer_size,
                                 bool          blocking);


/**
 * @brief Function for flash memory write on page boundary
 *
 * @params[in] mem                    Flash memory struct
 * @params[in] address                Write start address
 * @params[i] read_buffer            Transmit buffer pointer
 * @params[in] read_buffer_size       Transmit buffer length
 */
nrfx_err_t flash_mem_write_page(flash_mem_t *mem,
                                uint32_t     address,
                                uint8_t     *write_buffer,
                                uint16_t     write_buffer_size);


/**
 * @brief Function for flash memory write
 *
 * @params[in] mem                    Flash memory struct
 * @params[in] address                Write start address
 * @params[i] read_buffer            Transmit buffer pointer
 * @params[in] read_buffer_size       Transmit buffer length
 */
nrfx_err_t flash_mem_write(flash_mem_t  *mem,
                           uint32_t      page_address,
                           uint16_t      cache_address,
                           uint8_t      *write_buffer,
                           uint16_t      write_buffer_size);


/**
 *  @brief Function to wait for flash memory to go idle.
 *
 *  @params[in] mem                 Flash memory struct
 */
void flash_mem_wait(flash_mem_t * mem);


/**
 *  @brief Function to erase a block of flash memory.
 *
 *  @params[in] mem                 Flash memory struct
 */
nrfx_err_t flash_mem_erase_block(flash_mem_t * mem, uint8_t block);


/**
 * @brief Finction to erase entire flash memory.
 *
 *  @params[in] mem                 Flash memory struct
 */
nrfx_err_t flash_mem_erase_all(flash_mem_t * mem);


/**
 * @brief Function to perform a SPIM tx/rx to flash memory.
 *        This is the blocking mode function: the SPIM handler is
 *        bypassed and instead we wait for the NRFX_SPIM_EVENT_DONE
 *        flag to flip.
 *
 * @params[in] mem                Flash memory struct
 * @params[in] tx_buffer          Transmit buffer pointer
 * @params[in] tx_len             Transmit buffer length
 * @params[in] rx_buffer          Receive buffer pointer
 * @params[in] rx_len             Receive buffer length
 */
static nrfx_err_t flash_mem_tr_rx_blocking(flash_mem_t *mem,
                                           uint8_t     *tx_buffer,
                                           uint16_t     tx_len,
                                           uint8_t     *rx_buffer,
                                           uint16_t     rx_len);


/**
 * @brief Function to perform a SPIM tx/rx to flash memory.
 *        This is the non-blocking mode function: the SPIM handler is
 *        called when the NRFX_SPIM_EVENT_DONE flag to flips.
 *
 * @params[in] mem                Flash memory struct
 * @params[in] tx_buffer          Transmit buffer pointer
 * @params[in] tx_len             Transmit buffer length
 * @params[in] rx_buffer          Receive buffer pointer
 * @params[in] rx_len             Receive buffer length
 */
static nrfx_err_t flash_mem_tr_rx(flash_mem_t *mem,
                                  uint8_t     *tx_buffer,
                                  uint16_t     tx_len,
                                  uint8_t     *rx_buffer,
                                  uint16_t     rx_len);


/**
 * @brief Function to check if flash memory is busy.
 *
 * @params[in] mem                Flash memory struct
 */
static bool flash_mem_is_busy(flash_mem_t * mem);


/**
 * @brief Function to enable writes to flash memory.
 *
 * @params[in] mem                Flash memory struct
 */
static nrfx_err_t flash_mem_write_enable(flash_mem_t * mem);


/**
 * @brief Function to reset flash memory IC.
 *
 * @params[in] mem                Flash memory struct
 */
nrfx_err_t flash_mem_reset(flash_mem_t * mem);


/**
 * @brief Function to unlock flash memory after power on.
 *
 * @params[in] mem                Flash memory struct
 */
static nrfx_err_t flash_mem_unlock(flash_mem_t * mem);


/**
 * @brief Function to unlock flash memory after power on.
 *
 * @params[in] mem                Flash memory struct
 */
static nrfx_err_t flash_mem_ecc_disable(flash_mem_t * mem);


/**
 *  @brief Function to handle SPIM callbacks.
 *
 *  @params[in] p_event     Pointer to SPIM event struct
 */
static void spim_event_handler(nrfx_spim_evt_t const * p_event, void * p_context);



/** @} */
#ifdef __cplusplus
}
#endif

#endif /* FLASH_MEM_H__ */

flash_mem.c:

#include "flash_mem.h"

static nrfx_spim_t  m_spim3 = NRFX_SPIM_INSTANCE(3);
static flash_mem_event_handler_t  m_flash_mem_event_handler = NULL;
static flash_mem_status_t m_busy_state = FLASH_MEM_IDLE;
static flash_mem_command_t m_command;
static bool m_flash_is_alive = false;

nrfx_err_t flash_mem_init(flash_mem_t               *mem,
                          flash_mem_event_handler_t flash_mem_event_handler)
{
    //ASSERT(mem->init_state != NRFX_DRV_STATE_INITIALIZED);
    nrfx_err_t ret_code = spi_init(spim_event_handler, &m_spim3);
    if (ret_code != NRFX_SUCCESS)
            return ret_code;

    mem->spim       = &m_spim3;
    mem->init_state = NRFX_DRV_STATE_INITIALIZED;
    mem->wp_pin     = FLASH_MEM_WP_PIN;
    mem->hold_pin   = FLASH_MEM_HOLD_PIN;

    m_flash_mem_event_handler   = flash_mem_event_handler;
    m_busy_state = FLASH_MEM_IDLE;

    nrf_gpio_pin_set(mem->wp_pin);
    nrf_gpio_cfg_output(mem->wp_pin);
    nrf_gpio_pin_set(mem->hold_pin);
    nrf_gpio_cfg_output(mem->hold_pin);

    while (!m_flash_is_alive)
    {
        static uint_fast8_t fail_counter = 0;
        // Check and clear STALLSTAT
        if (NRF_SPIM3->STALLSTAT > 0)
            NRF_SPIM3->STALLSTAT = 0;
        ret_code = flash_mem_reset(mem);
        if (ret_code != NRFX_SUCCESS && ret_code != NRFX_ERROR_BUSY)
            return ret_code;
        if (ret_code == NRFX_ERROR_BUSY)
            ++fail_counter;
        if (fail_counter > 50)
        {
           fail_counter = 0;
           return ret_code;
        }
        if (ret_code == NRFX_SUCCESS)
            fail_counter = 0;
    }

    ret_code = flash_mem_ecc_disable(mem);
    if (ret_code != NRFX_SUCCESS)
            return ret_code;

    ret_code = flash_mem_unlock(mem);

    return ret_code;
}


void flash_mem_deinit(flash_mem_t * mem)
{
    ASSERT(mem->init_state != NRFX_DRV_STATE_UNINITIALIZED);
    nrfx_spim_uninit(mem->spim);
    mem->init_state = NRFX_DRV_STATE_UNINITIALIZED;
}


static nrfx_err_t flash_mem_spim_xfer(flash_mem_t * mem,
                             uint8_t * tx_buffer,
                             uint16_t tx_len,
                             uint8_t * rx_buffer,
                             uint16_t rx_len,
                             uint32_t flags)
{
    nrfx_spim_xfer_desc_t desc;
    nrfx_err_t ret_code;

    desc.p_tx_buffer  = tx_buffer;
    desc.tx_length    = tx_len;
    desc.p_rx_buffer  = rx_buffer;
    desc.rx_length    = rx_len;

    do
    {
        ret_code = nrfx_spim_xfer(mem->spim, &desc, flags);
    } while(ret_code == NRFX_ERROR_BUSY);

    return ret_code;
}


static nrfx_err_t flash_mem_tr_rx_blocking(
    flash_mem_t * mem,
    uint8_t * tx_buffer,
    uint16_t tx_len,
    uint8_t * rx_buffer,
    uint16_t rx_len)
{
    /* Set busy se we know to check status reg */
    if (tx_buffer[0] == (uint8_t)FLASH_MEM_PROGRAM_EXECUTE         ||
        tx_buffer[0] == (uint8_t)FLASH_MEM_PAGE_READ               ||
        tx_buffer[0] == (uint8_t)FLASH_MEM_READ_PAGE_CACHE_LAST    ||
        tx_buffer[0] == (uint8_t)FLASH_MEM_BLOCK_ERASE             ||
        tx_buffer[0] == (uint8_t)FLASH_MEM_READ_PAGE_CACHE_RANDOM  ||
        tx_buffer[0] == (uint8_t)FLASH_MEM_RESET)
    {
        m_busy_state = FLASH_MEM_BUSY;
    }
    uint_fast8_t busy_count = 0;
    nrfx_err_t ret_code = flash_mem_spim_xfer(
        mem,
        tx_buffer,
        tx_len,
        rx_buffer,
        rx_len,
        NRFX_SPIM_FLAG_NO_XFER_EVT_HANDLER);
    if (ret_code != NRFX_SUCCESS)
        return ret_code;


    while ((!mem->spim->p_reg->EVENTS_END) && (busy_count++ < 64))
    {}

    if (busy_count >= 64)
    {
        
    }
    //return NRFX_ERROR_TIMEOUT; // TODO Reset?

    return ret_code;
}


static nrfx_err_t flash_mem_tr_rx(
    flash_mem_t * mem,
    uint8_t * tx_buffer,
    uint16_t tx_len,
    uint8_t * rx_buffer,
    uint16_t rx_len)
{
    /* Set busy se we know to check status reg */
    if (tx_buffer[0] == (uint8_t)FLASH_MEM_PROGRAM_EXECUTE         ||
        tx_buffer[0] == (uint8_t)FLASH_MEM_PAGE_READ               ||
        tx_buffer[0] == (uint8_t)FLASH_MEM_READ_PAGE_CACHE_LAST    ||
        tx_buffer[0] == (uint8_t)FLASH_MEM_BLOCK_ERASE             ||
        tx_buffer[0] == (uint8_t)FLASH_MEM_READ_PAGE_CACHE_RANDOM  ||
        tx_buffer[0] == (uint8_t)FLASH_MEM_RESET)
    {
        m_busy_state = FLASH_MEM_BUSY;
    }

    /* Set the command here for the event handler
     * SPIM event doesn't contain the command
     */
    m_command = (flash_mem_command_t)tx_buffer[0];
    
    nrfx_err_t ret_code = flash_mem_spim_xfer(
        mem,
        tx_buffer,
        tx_len,
        rx_buffer,
        rx_len,
        0);
    
    return ret_code;
}

static nrfx_err_t flash_mem_write_enable(flash_mem_t * mem)
{
    /* Check status */
    flash_mem_wait(mem);
    return flash_mem_tr_rx_blocking(mem,
                                    (uint8_t []){ (uint8_t)FLASH_MEM_WRITE_ENABLE },
                                    1,
                                    NULL,
                                    0);
}

nrfx_err_t flash_mem_erase_block(flash_mem_t * mem, uint8_t block)
{
    nrfx_err_t ret_code;

    uint32_t address = block * FLASH_MEM_BLOCK_SIZE;
    uint8_t erase_cmd[] =
    {
        (uint8_t)FLASH_MEM_BLOCK_ERASE,
        (uint8_t)((address >> 16) & 0xFF),
        (uint8_t)((address >>  8) & 0xFF),
        (uint8_t)((address >>  0) & 0xFF),
    };

    /* Check status */
    flash_mem_wait(mem);

    /* Write enable */
    ret_code = flash_mem_write_enable(mem);
    if (ret_code != NRFX_SUCCESS)
        return ret_code;

    ret_code = flash_mem_tr_rx_blocking(mem,
                                        erase_cmd,
                                        sizeof(erase_cmd),
                                        NULL,
                                        0);
    if (ret_code != NRFX_SUCCESS)
        return ret_code;

    /* Check status */
    flash_mem_wait(mem);

    return ret_code;
}

nrfx_err_t flash_mem_erase_all(flash_mem_t * mem)
{
    nrfx_err_t ret_code;

    /* Check status */
    flash_mem_wait(mem);

    for (uint16_t i = 0; i < FLASH_MEM_BLOCKS_PER_IC; i++)
    {
        uint32_t address = i * FLASH_MEM_BLOCK_SIZE;
        uint8_t erase_cmd[] =
        {
            (uint8_t)FLASH_MEM_BLOCK_ERASE,
            (uint8_t)((address >> 16) & 0xFF),
            (uint8_t)((address >>  8) & 0xFF),
            (uint8_t)((address >>  0) & 0xFF),
        };
        
        
        /* Write enable */
        ret_code = flash_mem_write_enable(mem);
        if (ret_code != NRFX_SUCCESS)
            return ret_code;

        /* Run in blocking mode until the last time so we don't trigger the
         * handler in the file driver.  This is fine since we are plugged
         * into USB => BLE is off. - W.J.
         */
        if (i < FLASH_MEM_BLOCKS_PER_IC - 1)
        {
            ret_code = flash_mem_tr_rx_blocking(mem,
                                                erase_cmd,
                                                sizeof(erase_cmd),
                                                NULL,
                                                0);
            if (ret_code != NRFX_SUCCESS)
                return ret_code;
            /* Check status */
            flash_mem_wait(mem);
        }
        else
        {
            ret_code = flash_mem_tr_rx(mem,
                                       erase_cmd,
                                       sizeof(erase_cmd),
                                       NULL,
                                       0);
            if (ret_code != NRFX_SUCCESS)
                return ret_code;
        }
    }
    return ret_code;
}

static nrfx_err_t flash_mem_unlock(flash_mem_t * mem)
{
    uint8_t tx_status_buff[3];
    nrfx_err_t ret_code;

    nrfx_spim_xfer_desc_t desc;

    tx_status_buff[0] = (uint8_t)FLASH_MEM_SET_FEATURES;
    tx_status_buff[1] = (uint8_t)FLASH_MEM_FEATURE_BLOCK_LOCK;
    tx_status_buff[2] = 0x00;

    ret_code = flash_mem_tr_rx_blocking(mem,
                                     tx_status_buff,
                                     sizeof(tx_status_buff),
                                     NULL,
                                     0);

    return ret_code;
}

static nrfx_err_t flash_mem_ecc_disable(flash_mem_t * mem)
{
    uint8_t tx_status_buff[3];
    nrfx_err_t ret_code;

    nrfx_spim_xfer_desc_t desc;

    tx_status_buff[0] = (uint8_t)FLASH_MEM_SET_FEATURES;
    tx_status_buff[1] = (uint8_t)FLASH_MEM_FEATURE_CONFIGURATION;
    tx_status_buff[2] = 0x00;

    ret_code = flash_mem_tr_rx_blocking(mem,
                                     tx_status_buff,
                                     sizeof(tx_status_buff),
                                     NULL,
                                     0);

    return ret_code;
}

static bool flash_mem_is_busy(flash_mem_t * mem)
{
    uint8_t tx_status_buff[2];
    uint8_t rx_status_buff[2 + 1];
    nrfx_err_t ret_code;

    nrfx_spim_xfer_desc_t desc;

    tx_status_buff[0] = (uint8_t)FLASH_MEM_GET_FEATURES;
    tx_status_buff[1] = (uint8_t)FLASH_MEM_FEATURE_STATUS;
    memset(rx_status_buff, 0, sizeof(rx_status_buff));

    ret_code = flash_mem_tr_rx_blocking(mem,
                                     tx_status_buff,
                                     sizeof(tx_status_buff),
                                     rx_status_buff,
                                     sizeof(rx_status_buff));
    if (ret_code != NRFX_SUCCESS)
        return ret_code;

    return (bool)(rx_status_buff[2] & (1 << 0));
}

void flash_mem_wait(flash_mem_t * mem)
{
    if (m_busy_state == FLASH_MEM_BUSY)
        while (flash_mem_is_busy(mem))
        {
            if (!m_flash_is_alive)
                m_flash_is_alive = true;
        }
    m_busy_state = FLASH_MEM_IDLE;
}

nrfx_err_t flash_mem_read_common(flash_mem_t  *mem,
                                 uint32_t      page_address,
                                 uint16_t      cache_address,
                                 uint8_t      *read_buffer,
                                 uint16_t      read_buffer_size,
                                 bool          blocking)
{
    /* N.B. This command sequence has a component (Page Read to Cache) that
     * operates in a blocking manner.  It should complete in under 70us, and
     * seems to complete on my copy in under 45us.  This seems acceptable, but
     * for better performance, move the flash_mem_wait to a SPIM DMA with SHORTS
     * enabled, and handle read from cache in interrupt. - W.J.
     */
    nrfx_err_t ret_code;

    /* Check status */
    flash_mem_wait(mem);

    uint8_t tx_buffer[FLASH_MEM_TX_CMD_SIZE + FLASH_MEM_TX_ADDR_SIZE];

    tx_buffer[0] = (uint8_t)FLASH_MEM_PAGE_READ;
    tx_buffer[1] = (uint8_t)(page_address >> 16);
    tx_buffer[2] = (uint8_t)(page_address >>  8);
    tx_buffer[3] = (uint8_t)(page_address >>  0);

    /* Read page into cache */
    m_busy_state = FLASH_MEM_BUSY;
    ret_code = flash_mem_tr_rx_blocking(mem,
                                        tx_buffer,
                                        FLASH_MEM_TX_CMD_SIZE + FLASH_MEM_TX_ADDR_SIZE,
                                        NULL,
                                        0);
    if (ret_code != NRFX_SUCCESS)
        return ret_code;

    /* Setup the read from cache first so we dont have to wait once it's ready */
    tx_buffer[0] = (uint8_t)FLASH_MEM_READ_FROM_CACHE_1X;
    tx_buffer[1] = (uint8_t)(cache_address >> 8); // Read cache starting form byte 0
    tx_buffer[2] = (uint8_t)(cache_address >> 0);

    /* Wait for read to cache to complete < 70us */
    flash_mem_wait(mem);

    /* Read page data from cache */
    if (!blocking)
        ret_code = flash_mem_tr_rx(mem,
                                   tx_buffer,
                                   FLASH_MEM_TX_CMD_SIZE + FLASH_MEM_TX_CACHE_ADDR_SIZE,
                                   read_buffer,
                                   read_buffer_size);
    else
        ret_code = flash_mem_tr_rx_blocking(mem,
                                            tx_buffer,
                                            FLASH_MEM_TX_CMD_SIZE + FLASH_MEM_TX_CACHE_ADDR_SIZE,
                                            read_buffer,
                                            read_buffer_size);
    return ret_code;
}

nrfx_err_t flash_mem_read(flash_mem_t *mem,
                          uint32_t     page_address,
                          uint16_t     cache_address,
                          uint8_t     *read_buffer,
                          uint16_t     read_buffer_size)
{
    nrfx_err_t ret_code;
    ret_code = flash_mem_read_common(mem,
                                     page_address,
                                     cache_address,
                                     read_buffer,
                                     read_buffer_size,
                                     false);
    return ret_code;
}

nrfx_err_t flash_mem_read_blocking(flash_mem_t  *mem,
                                   uint32_t      page_address,
                                   uint16_t      cache_address,
                                   uint8_t      *read_buffer,
                                   uint16_t      read_buffer_size)
{
    nrfx_err_t ret_code;
    ret_code = flash_mem_read_common(mem,
                                     page_address,
                                     cache_address,
                                     read_buffer,
                                     read_buffer_size,
                                     true);
    return ret_code;
}

nrfx_err_t flash_mem_read_page_blocking(flash_mem_t *mem,
                                        uint32_t     page_address,
                                        uint8_t     *read_buffer,
                                        uint16_t     read_buffer_size)
{
    nrfx_err_t ret_code;
    ret_code = flash_mem_read_common(mem,
                                     page_address,
                                     (uint16_t)0,
                                     read_buffer,
                                     read_buffer_size,
                                     true);
    return ret_code;
}

nrfx_err_t flash_mem_read_page(flash_mem_t  *mem,
                               uint32_t      page_address,
                               uint8_t      *read_buffer,
                               uint16_t      read_buffer_size)
{
    nrfx_err_t ret_code;
    ret_code = flash_mem_read_common(mem,
                                     page_address,
                                     (uint16_t)0,
                                     read_buffer,
                                     read_buffer_size,
                                     false);
    return ret_code;
}


nrfx_err_t flash_mem_write_page(flash_mem_t *mem,
                                uint32_t     page_address,
                                uint8_t     *write_buffer,
                                uint16_t     write_buffer_size)
{
    nrfx_err_t ret_code;
    ret_code = flash_mem_write(mem,
                               page_address,
                               0,
                               write_buffer,
                               write_buffer_size);
    return ret_code;
}


nrfx_err_t flash_mem_write(flash_mem_t  *mem,
                           uint32_t      page_address,
                           uint16_t      cache_address,
                           uint8_t      *write_buffer,
                           uint16_t      write_buffer_size)
{
    nrfx_err_t ret_code = NRFX_SUCCESS;

    uint8_t tx_buffer[FLASH_MEM_TX_CMD_SIZE + FLASH_MEM_TX_ADDR_SIZE + FLASH_MEM_PAGE_SIZE];

    tx_buffer[0] = (uint8_t)FLASH_MEM_PROGRAM_1X;
    tx_buffer[1] = (uint8_t)(cache_address >> 8);
    tx_buffer[2] = (uint8_t)(cache_address >> 0);
    memcpy(tx_buffer + FLASH_MEM_TX_CMD_SIZE + FLASH_MEM_TX_CACHE_ADDR_SIZE,
           write_buffer,
           write_buffer_size);
    
    /* Check status */
    flash_mem_wait(mem);

    /* Write enable */
    ret_code = flash_mem_write_enable(mem);
    if (ret_code != NRFX_SUCCESS)
        return ret_code;

    /* Program load into cache */
    ret_code = flash_mem_tr_rx_blocking(mem,
                                        tx_buffer,
                                        FLASH_MEM_TX_CMD_SIZE + FLASH_MEM_TX_CACHE_ADDR_SIZE + write_buffer_size,
                                        NULL,
                                        0);
    if (ret_code != NRFX_SUCCESS)
        return ret_code;

    /* Setup the execute command */
    tx_buffer[0] = (uint8_t)FLASH_MEM_PROGRAM_EXECUTE;
    tx_buffer[1] = (uint8_t)(((page_address) >> 16) & 0xFF);
    tx_buffer[2] = (uint8_t)(((page_address) >>  8) & 0xFF);
    tx_buffer[3] = (uint8_t)(((page_address) >>  0) & 0xFF);

    /* Execute page program from cache to flash < 600us */
    ret_code = flash_mem_tr_rx(mem,
                               tx_buffer,
                               FLASH_MEM_COMMAND_SIZE,
                               NULL,
                               0);
    
    return ret_code;
}


nrfx_err_t flash_mem_reset(flash_mem_t * mem)
{
    nrfx_err_t ret_code;
    ret_code = flash_mem_tr_rx_blocking(mem,
                                        (uint8_t []){ FLASH_MEM_RESET },
                                        sizeof(uint8_t),
                                        NULL,
                                        0);
    if (ret_code != NRFX_SUCCESS)
        return ret_code;
    flash_mem_wait(mem);
    return ret_code;
}

static void spim_event_handler(nrfx_spim_evt_t const * p_event, void * p_context)
{
    switch(p_event->type)
    {
        case NRFX_SPIM_EVENT_DONE:
            m_flash_mem_event_handler(m_command);
            break;
    }
}

spim.h:

#ifndef SPIM_H__
#define SPIM_H__

#ifdef __cplusplus
extern "C" {
#endif

#include "nrf_gpio.h"
#include "nrfx_spim.h"

#define SPIM_BUFF_SIZE  4096


/**
 *  @brief Function for initializing SPIM driver.
 *
 *  @params[in] spim_event_handler     Function to handle SPIM Event callbacks
 *  @params[in] m_spim                 SPIM struct
 */
nrfx_err_t spi_init(void (*spim_event_handler)(nrfx_spim_evt_t const *, void *), nrfx_spim_t *);



/** @} */
#ifdef __cplusplus
}
#endif

#endif /* SPIM_H__ */

spim.c:

#include "spim.h"

nrfx_err_t spi_init(void (*spim_event_handler)(nrfx_spim_evt_t const *, void *), nrfx_spim_t * m_spim)
{
    nrfx_spim_config_t spim_cfg = NRFX_SPIM_DEFAULT_CONFIG;
    nrfx_err_t error;

    m_spim->p_reg->ENABLE = SPIM_ENABLE_ENABLE_Enabled;

    spim_cfg.frequency  = NRF_SPIM_FREQ_1M;
    spim_cfg.ss_pin     = NRF_GPIO_PIN_MAP(1, 12);
    spim_cfg.sck_pin    = NRF_GPIO_PIN_MAP(1, 15);
    spim_cfg.mosi_pin   = NRF_GPIO_PIN_MAP(1, 13);
    spim_cfg.miso_pin   = NRF_GPIO_PIN_MAP(1, 14);
    spim_cfg.mode       = NRF_SPIM_MODE_0;

    spim_cfg.use_hw_ss  = true;
    //spim_cfg.rx_delay   = 0x07;
    //spim_cfg.ss_duration = 0x07;
    error = nrfx_spim_init(m_spim, &spim_cfg, spim_event_handler, NULL);
    return error;
}

sdk_config:

#ifndef SDK_CONFIG_H
#define SDK_CONFIG_H
// <<< Use Configuration Wizard in Context Menu >>>\n
#ifdef USE_APP_CONFIG
#include "app_config.h"
#endif

#define NRF_LOG_ENABLED 0

// <e> NRFX_SPIM_ENABLED - nrfx_spim - SPIM peripheral driver
//==========================================================
#ifndef NRFX_SPIM_ENABLED
#define NRFX_SPIM_ENABLED 1
#endif

#ifndef NRFX_SPIM0_ENABLED
#define NRFX_SPIM0_ENABLED 0
#endif

#ifndef NRFX_SPIM1_ENABLED
#define NRFX_SPIM1_ENABLED 0
#endif

#ifndef NRFX_SPIM2_ENABLED
#define NRFX_SPIM2_ENABLED 0
#endif

#ifndef NRFX_SPIM3_ENABLED
#define NRFX_SPIM3_ENABLED 1
#endif

#ifndef NRFX_SPIM_EXTENDED_ENABLED
#define NRFX_SPIM_EXTENDED_ENABLED 1
#endif

#ifndef NRFX_SPIM_MISO_PULL_CFG
#define NRFX_SPIM_MISO_PULL_CFG 1
#endif

#ifndef NRFX_SPIM_DEFAULT_CONFIG_IRQ_PRIORITY
#define NRFX_SPIM_DEFAULT_CONFIG_IRQ_PRIORITY 6
#endif

// <e> NRFX_SPIM_CONFIG_LOG_ENABLED - Enables logging in the module.
//==========================================================
#ifndef NRFX_SPIM_CONFIG_LOG_ENABLED
#define NRFX_SPIM_CONFIG_LOG_ENABLED 0
#endif

#ifndef NRFX_SPIM_CONFIG_LOG_LEVEL
#define NRFX_SPIM_CONFIG_LOG_LEVEL 3
#endif

#ifndef NRFX_SPIM_CONFIG_INFO_COLOR
#define NRFX_SPIM_CONFIG_INFO_COLOR 0
#endif

#ifndef NRFX_SPIM_CONFIG_DEBUG_COLOR
#define NRFX_SPIM_CONFIG_DEBUG_COLOR 0
#endif

// </e>

// </e>
#endif //SDK_CONFIG_H

Preproc defs:

CONFIG_GPIO_AS_PINRESET
FLOAT_ABI_HARD
INITIALIZE_USER_SECTIONS
NO_VTOR_CONFIG
NRF52840_XXAA

Happens with or without optimization.  It shouldn't matter, but the flash IC is the Micron MT29F1G01ABAFDWB, the pins are in the spim.c code above.  Let me know if there is anything else I can get you.

Parents
  • Hi

    The STALLSTAT should only occur due to ASHB bus congestions (AHB bus masters trying to access the same AHB slave at the same time). An EasyDMA channel is an AHB master, so apparently it tries to use an already busy peripheral in this case. 

    It could be that the UARTE tries using the same bus for logging for example, or that you have another peripheral using it for some reason. So yes, if you use the UART backend for logging, I guess the debugger could be the peripheral conflicting with SPIM. You can try disabling logging altogether to see whether there are more successful writes on your flash.

    Best regards,

    Simon

Reply
  • Hi

    The STALLSTAT should only occur due to ASHB bus congestions (AHB bus masters trying to access the same AHB slave at the same time). An EasyDMA channel is an AHB master, so apparently it tries to use an already busy peripheral in this case. 

    It could be that the UARTE tries using the same bus for logging for example, or that you have another peripheral using it for some reason. So yes, if you use the UART backend for logging, I guess the debugger could be the peripheral conflicting with SPIM. You can try disabling logging altogether to see whether there are more successful writes on your flash.

    Best regards,

    Simon

Children
  • Hey Simonr, thanks!  In this application there is no logging and it has all been disabled, which was part of my confusion.  Is it possible that performing a SPIM txrx transaction while another is still in process not only returns a busy error but also causes a stall?

  • And actually, if you'll check the question, I've posted a minimal example that reproduces the behavior, as well as my compiler settings and sdk_config.

  • Try moving the buffer to a reserved SRAM area which no other code accesses, rather than just stuffing it on the stack which is the most memory-intensive SRAM area:

        uint8_t buff[0x0880];
    

    That way the buffer gets its own DMA AHB slave bus and doesn't have to share. Something like this, there are examples in the devzone:

    // Place following data in section .spim3 so it has private SRAM segment
    #pragma default_variable_attributes = @ ".spim3" // if using IAR
    // Reserved spim3 buffer
    volatile uint8_t buff[0x0880] __attribute__((section(".sect_spim3")));
    #pragma default_variable_attributes =            // if using IAR
    // End - Place following data in section .spim3
    
    // These are the SRAM areas:
    //
    //  RAM0 Section 0 0x2000 0000 - 0x2000 0FFF
    //       Section 1 0x2000 1000 - 0x2000 1FFF
    //  RAM1 Section 0 0x2000 2000 - 0x2000 2FFF
    //       Section 1 0x2000 3000 - 0x2000 3FFF
    //  RAM2 Section 0 0x2000 4000 - 0x2000 4FFF
    //       Section 1 0x2000 5000 - 0x2000 5FFF
    //  RAM3 Section 0 0x2000 6000 - 0x2000 6FFF
    //       Section 1 0x2000 7000 - 0x2000 7FFF
    //  RAM4 Section 0 0x2000 8000 - 0x2000 8FFF
    //       Section 1 0x2000 9000 - 0x2000 9FFF
    //  RAM5 Section 0 0x2000 A000 - 0x2000 AFFF
    //       Section 1 0x2000 B000 - 0x2000 BFFF
    //  RAM6 Section 0 0x2000 C000 - 0x2000 CFFF
    //       Section 1 0x2000 D000 - 0x2000 DFFF
    //  RAM7 Section 0 0x2000 E000 - 0x2000 EFFF
    //       Section 1 0x2000 F000 - 0x2000 FFFF

    Ensure the section .sect_spim3 is defined in the linker area as a separate SRAM area in SEGGER_Flash.icf file. Here is an example from Torbjorn: designate-one-ram-block-exclusively

    Note also using P1.nn pins is dodgy if you wish to boost to a much higher serial SPI clock, since those pins do not allow H0H1 drive settings, which you will need. Port P0.nn is preferred.

Related