How to store the error info on internal flash for debugging before system reset

This blog is to show how to store / write the error information inside the flash. For example, in some cases, when the device is randomly got system reset, it may be caused by any hidden errors. If we can store the error information before the system reset into the internal flash first, after that we can read such error information back to narrow down the problem. It is one of the most effective debugging skill.

In this example, I would modify the app_error.c file from the Nordic SDK and add the flash (write) routine.

Define the address to store the error code

#define N52_FLASH_PAGESIZE 4096
#define N52_PAGE_NUM       100         // Address 0x64000

uint32_t pg_size = N52_FLASH_PAGESIZE;
uint32_t pg_num  = N52_PAGE_NUM;       // Use last page in flash
void app_error_save_and_stop(uint32_t id, uint32_t pc, uint32_t info)
{
    uint32_t err_code;
        /* static error variables - in order to prevent removal by optimizers */
        static volatile struct
        {
                uint32_t fault_id;
                uint32_t pc;
                uint32_t error_info;
                assert_info_t * p_assert_info;
                error_info_t  * p_error_info;
                ret_code_t err_code;
                uint32_t line_num;
                const uint8_t * p_file_name;
        } m_error_data = {0};

        uint32_t * flash_addr;
        uint32_t * path_addr;
        uint16_t store_length;

        // The following variable helps Keil keep the call stack visible, in addition, it can be set to
        // 0 in the debugger to continue executing code after the error check.
        volatile bool loop = true;
        UNUSED_VARIABLE(loop);

            err_code = nrf_sdh_disable_request();
//    APP_ERROR_CHECK(err_code);

//        softdevice_handler_sd_disable();
        __disable_irq();

        // Start address:
        flash_addr = (uint32_t *)(pg_size * pg_num);

        m_error_data.fault_id   = id;
        m_error_data.pc         = pc;
        m_error_data.error_info = info;

        switch (id)
        {
        case NRF_FAULT_ID_SDK_ASSERT:
                m_error_data.p_assert_info = (assert_info_t *)info;
                m_error_data.line_num      = m_error_data.p_assert_info->line_num;
                m_error_data.p_file_name   = m_error_data.p_assert_info->p_file_name;
                break;

        case NRF_FAULT_ID_SDK_ERROR:
                m_error_data.p_error_info = (error_info_t *)info;
                m_error_data.err_code     = m_error_data.p_error_info->err_code;
                m_error_data.line_num     = m_error_data.p_error_info->line_num;
                m_error_data.p_file_name  = m_error_data.p_error_info->p_file_name;
                path_addr=(uint32_t *)m_error_data.p_error_info->p_file_name;


                // Erase page:
                flash_page_erase(flash_addr);
                flash_word_write(flash_addr, m_error_data.err_code);
                flash_addr++;
                flash_word_write(flash_addr, m_error_data.line_num);
                flash_addr++;
                for(store_length=0; store_length<1000;)
                {

                        if(m_error_data.p_file_name[store_length]!=0)
                        {
                                flash_word_write(flash_addr, *path_addr);
                                flash_addr++;
                                path_addr++;
                                if(m_error_data.p_file_name[store_length+1]==0  \
                                  || m_error_data.p_file_name[store_length+2]==0 \
                                  || m_error_data.p_file_name[store_length+3]==0)
                                {
                                        break;
                                }
                                store_length+=4;
                        }
                        else
                        {
                                break;
                        }

                }

                break;
        }

        UNUSED_VARIABLE(m_error_data);

        // If printing is disrupted, remove the irq calls, 
        // or set the loop variable to 0 in the debugger.
        //__disable_irq();
        while (loop);

        __enable_irq();
}

After that, you can use nrfjprog to read the error information out.

For example, it reads the address 0x64000 with first 100 bytes.

nrfjprog --memrd 0x64000 --w 8 --n 100

The full source code is placed at here.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Google photo

You are commenting using your Google account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s

This site uses Akismet to reduce spam. Learn how your comment data is processed.