Beware that this post is related to an SDK in maintenance mode
More Info: Consider nRF Connect SDK for new designs

Sending I2S data via BLE using queue

Hi,

I am writing an application that reads data from the I2S interface, adds it to a queue and sends it via BLE (the code I have implemented so far is added at the end of this post).

In a previous thread (https://devzone.nordicsemi.com/f/nordic-q-a/87656/create-custom-event-to-trigger-updating-data-in-gatt-table), I have reached a state where I am able to send arbitrary data via BLE. I also know that reading data from the I2S works. However, integrating those parts into one application causes the application to run into an error (all 4 LEDs on my nRF52 DK are on).

EDIT: For clarification: the nRF is advertising and I can connect to it from nRF Connect for Desktop and Mobile. The problem starts when I enable notifications and the code, thereby, enters the if-clause in line 46 in my main function (i2s_transfer becomes true).

The code is based on the ble_app_template and the I2S loopback example. I'm using the nRF52 DK, nRF5 SDK 17.1.0 with S132 and Segger emStudio 5.42a.

Could there be too many interrupts that call the data_handler so that the rest of the code cannot be executed (I'm sampling with ~ 44 kHz, so the buffer of 256 bytes will be filled quickly)? I'm also thinking that the allocated RAM could be insufficient (I attach my section placement macros below).

Can anybody suggest a fix that I can try or an alternative implementation to achieve what I have mentioned above? Any help is appreciated.

CODE:

The section placement macros:

FLASH_PH_START=0x0
FLASH_PH_SIZE=0x80000
RAM_PH_START=0x20000000
RAM_PH_SIZE=0x10000
FLASH_START=0x26000
FLASH_SIZE=0x5a000
RAM_START=0x20002ad8
RAM_SIZE=0xd528

Here are the relevant declarations:

#define I2S_DATA_BLOCK_WORDS    256

static uint32_t m_buffer_rx[2][I2S_DATA_BLOCK_WORDS];
static uint32_t m_buffer_tx[2][I2S_DATA_BLOCK_WORDS];

NRF_QUEUE_DEF(uint8_t, m_queue, 3072, NRF_QUEUE_MODE_OVERFLOW);

uint8_t m_array[244] = {0};

Here is my main function:

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

    // Initialize.
    log_init();
    timers_init();
    buttons_leds_init(&erase_bonds);
    power_management_init();

    // Initialize TWI/ I2C & Setup ADC
    twi_adc_configuration();

    // Initialize i2s
    err_code = i2s_init();
    if (err_code == NRF_SUCCESS)
    {
        NRF_LOG_INFO("I2S successfully initialized.");
    }
    else
    {
        NRF_LOG_INFO("Error initializing I2S.");
    }

    // Initialize BLE
    ble_stack_init();
    gap_params_init();
    gatt_init();
    services_init();
    advertising_init();
    conn_params_init();
    peer_manager_init();

    // Start execution.
    NRF_LOG_INFO("Template example started.");

    // Start advertising
    advertising_start(erase_bonds);

    // Enter main loop.
    for (;;)
    {
        if (i2s_transfer) 
        {
            // start i2s
            err_code = start_i2s();
            if (err_code == NRF_SUCCESS) 
            {
                NRF_LOG_INFO("I2S started");
            }
            else
            {
                NRF_LOG_INFO("Failed starting I2S");
            }

            if (nrf_queue_utilization_get(&m_queue) >= 244)
            {
                // get data from the queue
                err_code = nrf_queue_read(&m_queue, &m_array, sizeof(m_array));
                if ((err_code != NRF_SUCCESS) &&
                        (err_code != NRF_ERROR_INVALID_STATE) &&
                        (err_code != NRF_ERROR_RESOURCES) &&
                        (err_code != NRF_ERROR_BUSY) &&
                        (err_code != BLE_ERROR_GATTS_SYS_ATTR_MISSING)
                       )
                {
                    APP_ERROR_CHECK(err_code);
                }

                // send data via BLE
                err_code = ble_aas_value_update(&m_aas, &m_array);
                if ((err_code != NRF_SUCCESS) &&
                    (err_code != NRF_ERROR_INVALID_STATE) &&
                    (err_code != NRF_ERROR_RESOURCES) &&
                    (err_code != NRF_ERROR_BUSY) &&
                    (err_code != BLE_ERROR_GATTS_SYS_ATTR_MISSING)
                   )
                {
                    APP_ERROR_CHECK(err_code);
                }

                if (err_code == NRF_ERROR_RESOURCES)
                {
                    NRF_LOG_DEBUG("error resources");
                    if (ble_ready != true)
                    {
                        idle_state_handle();
                    }
                    ble_ready = false;
                }
            }
        }
    }   
     
    nrf_drv_i2s_stop();

    NRF_LOG_FLUSH();

    bsp_board_leds_off();
}

My start_i2s function:

int start_i2s()
{
    uint32_t err_code = NRF_SUCCESS;

    nrf_drv_i2s_buffers_t const initial_buffers = {
        .p_tx_buffer = NULL,
        .p_rx_buffer = m_buffer_rx[0],
    };

    err_code = nrf_drv_i2s_start(&initial_buffers, I2S_DATA_BLOCK_WORDS, 0);
    APP_ERROR_CHECK(err_code);

    return err_code;
}

and my data handler:

static void data_handler(nrf_drv_i2s_buffers_t const * p_released,
                         uint32_t                      status)
{
    // 'nrf_drv_i2s_next_buffers_set' is called directly from the handler
    // each time next buffers are requested, so data corruption is not
    // expected.
    ASSERT(p_released);

    // When the handler is called after the transfer has been stopped
    // (no next buffers are needed, only the used buffers are to be
    // released), there is nothing to do.
    if (!(status & NRFX_I2S_STATUS_NEXT_BUFFERS_NEEDED))
    {
        return;
    }

    // First call of this handler occurs right after the transfer is started.
    // No data has been transferred yet at this point, so there is nothing to
    // check. Only the buffers for the next part of the transfer should be
    // provided.
    if (!p_released->p_rx_buffer)
    {
        // .p_tx_buffer = m_buffer_tx[1] changed to .p_tx_buffer = NULL, since we only receive data
        nrf_drv_i2s_buffers_t const next_buffers = {
            .p_rx_buffer = m_buffer_rx[1],
            .p_tx_buffer = NULL,
        };
        APP_ERROR_CHECK(nrf_drv_i2s_next_buffers_set(&next_buffers));

    }
    else
    {
        uint16_t i;
        uint32_t const * p_word = NULL;
        uint8_t sample[3];

        for (i = 0; i < I2S_DATA_BLOCK_WORDS; ++i)
        {
            p_word = &mp_block_to_check[i];
            //decompose 32-bit sample and discard msb
            sample[0] = ((uint8_t const *)p_word)[0];
            sample[1] = ((uint8_t const *)p_word)[1];
            sample[2] = ((uint8_t const *)p_word)[2];
            printf("\n %2x%2x%2x", sample[0], sample[1], sample[2]);

            // copy data to the queue
            ret_code_t err_code;
            err_code = nrf_queue_write(&m_queue, &sample, sizeof(sample));
            NRF_LOG_DEBUG("data added to queue.");
        }
       
        // The driver has just finished accessing the buffers pointed by
        // 'p_released'. They can be used for the next part of the transfer
        // that will be scheduled now.
        APP_ERROR_CHECK(nrf_drv_i2s_next_buffers_set(p_released));

    }
}

Parents
  • Hello,

    You an look at this blog post (+) An introduction to error handling in nRF5 projects - Blogs - Nordic Blog - Nordic DevZone (nordicsemi.com) . It is a bit old but still relevant for debugging on segger. 

    You said that you application causes the application to run into an error while all 4 LEDs on your nRf52 DK are on. So, I guess APP_ERROR_CHECK() has been called with an error code. You should make sure you do a debug build (most SES example projects lets you select ''Debug'' from the build target from drop down menue. Then if you use logging, the log should state which error code was detected in which file at which line. From that point you know, were you should look further. If you do not use logging you can put a breakpoint in the error handler and check with a debugger.

    Thanks.

    Best Regars,

    Kazi Afroza Sultana

  • Hi,

    thanks for the link. I wasn't aware of this article. However, I'm using the the Debug build configuration from the beginning. After reading the article, I also defined the DEBUG flag in the project options (in the common build configuration -> Preprocessor -> Preprocessor Definitions). However, this did not result in any error being printed to the debug console of SES. Also my NRF_LOG_INFO and NRF_LOG_DEBUG were not printed (at least most of them). Even if this is not the topic of this post, I would like to understand why (NRF_LOG_BACKEND_RTT_ENABLED  is set to 1 in sdk_config.h)?

    Back to programming: There were multiple errors in the code I posted above. I will highlight the main ones for anybody else who will read this post.

    1. Within the endless loop in my main function, i2s_start() will be called in every iteration, which could already be enough reason to cause the application to crash.

    2. nrf_queue_read will return NRF_ERROR_NOT_FOUND the first time it is called, since there is no data available yet. This needs to be handled.

    3. in my data_handler() I access the volatile pointer mp_block_to_check without assigning any value to it. I missed 'mp_block_to_check = p_released->p_rx_buffer;', like it is also done in the i2s example of the SDK.

    4. This is rather a question to you : If I understood correctly, the data_handler is called by the i2s driver when new data is available. So, if I have a loop in the data_handler (like in the one I have above), blocking the data_handler, could this cause trouble the next time the i2s driver calls the data handler? I assumed, yes, and removed any unnecessary code from the data handler.

    And, another question: when the data_handler writes new data to mp_block_to_check, it overwrites all the data in the variable, even if I'm still processing parts of that data, correct?

    Many questions, I hope I have expressed myself clear enough. If not, let me know Slight smile

Reply
  • Hi,

    thanks for the link. I wasn't aware of this article. However, I'm using the the Debug build configuration from the beginning. After reading the article, I also defined the DEBUG flag in the project options (in the common build configuration -> Preprocessor -> Preprocessor Definitions). However, this did not result in any error being printed to the debug console of SES. Also my NRF_LOG_INFO and NRF_LOG_DEBUG were not printed (at least most of them). Even if this is not the topic of this post, I would like to understand why (NRF_LOG_BACKEND_RTT_ENABLED  is set to 1 in sdk_config.h)?

    Back to programming: There were multiple errors in the code I posted above. I will highlight the main ones for anybody else who will read this post.

    1. Within the endless loop in my main function, i2s_start() will be called in every iteration, which could already be enough reason to cause the application to crash.

    2. nrf_queue_read will return NRF_ERROR_NOT_FOUND the first time it is called, since there is no data available yet. This needs to be handled.

    3. in my data_handler() I access the volatile pointer mp_block_to_check without assigning any value to it. I missed 'mp_block_to_check = p_released->p_rx_buffer;', like it is also done in the i2s example of the SDK.

    4. This is rather a question to you : If I understood correctly, the data_handler is called by the i2s driver when new data is available. So, if I have a loop in the data_handler (like in the one I have above), blocking the data_handler, could this cause trouble the next time the i2s driver calls the data handler? I assumed, yes, and removed any unnecessary code from the data handler.

    And, another question: when the data_handler writes new data to mp_block_to_check, it overwrites all the data in the variable, even if I'm still processing parts of that data, correct?

    Many questions, I hope I have expressed myself clear enough. If not, let me know Slight smile

Children
No Data
Related