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,

    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.

    Have you looked at the I2S example in the SDK? Could you please tell us how your start_i2s() function works. It may be something similar to the nrf_drv_is2_start () used in the main loop of that i2s example. What does the return of this function, anything other than NRF_SUCCESS ?

    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.

    As you said there is no data so nrf_queue_read() function can return an error. We do not have this function in I2S example, so this is something you have to handle yourself. 

    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.

    what is this pointer mp_block_to_check and what is the use of it? We do not have something in I2S example.

    4. This is rather a question to you Kazi Afroza Sultana : 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.

    Inside the data handler, when you are blocking new interrupts, you should spend as little time as possible inside the handler. The factors impact here are the size of buffers and the time it would take it to fill up an trigger a new interrupt. 

    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?

    Could you please tell a bit details about this error? 

    Thanks.

    Best Regards,

    Kazi Afroza Sultana

  • Hi, thank you for your response.

    1. this is solved. I used nrf_drv_i2s_start() similarly to the i2s example, but since i do continuous i2s transfer (without stopping the i2s at the end of each loop), I cannot call nrf_drv_i2s_start() at the beginning of each loop (which I did initially). this problem is solved now.

    2. also solved now.

    3. mp_block_to check is declared as

    static uint32_t const * volatile mp_block_to_check = NULL;
    in your i2s example. I used it in exactly the same way as in the example.

    4. Thank you, I will keep the activity in the data handler as low as possible.

    There is no error related to mp_block_to_check or the data_handler. This was a general question: Imagine I have a loop that iterates over an array of data and while I'm doing that, the i2s interrupt calls the data_handler() which is writing to that array. In that case, the data in the array would be overwritten by the data_handler() even if my loop is still iterating over the array?

    In fact my code is executed without errors now. Now, I'm facing the problem that the data I'm expecting is not received correctly. Could be a hardware issue or caused by the data_handler overwriting the data in mp_block_to_check. Hence, my question above. If you want, I can share my project, so you get a better understanding?

    Regards

    Moritz

  • Hello Moritz,

    Yes, You can send your full project so that we can look at.

    Since it is summer vacation, it may take a bit time to reply. I will be in vacation for next 2 week. Sorry for this inconvenience. 

    Thanks.

    Best Regards,

    Kazi Afroza Sultana

Reply Children
  • Hi Kazi,

    thank you for the reply and your offer about looking at my project. I have solved the problem which is stated above. The problem was

    NRF_QUEUE_DEF(uint8_t, m_queue, 3072, NRF_QUEUE_MODE_OVERFLOW);

    It seems the buffer was full very often and appending partially new data, but not all of them (i.e. dropping one part of the data, while keeping another part) led to corrupted data. Changing it to NRF_QUEUE_MODE_NO_OVERFLOW improved the program a lot. Finally, I realized that

    #define I2S_DATA_BLOCK_WORDS    256

    also has an influence on how fast the buffer gets filled up. In between I was using I2S_DATA_BLOCK_WORDS 1024, which led to the buffer being full all the time. Currently, I'm at 64, but I'm still experimenting which setting is the best.

    In the meantime I'm facing a new issue. Now my data handler is not called as often as it should be. This leads to ending up with significantly less samples as I'm expecting. But I guess I will open another thread regarding this after my holiday and link the question here. I will also close this thread then!

    Thanks a lot for your support!

  • Hello,

    You are welcome. Yes, You can open new case.

    Thanks.

    Best Regards,

    Kazi Afroza Sultana

Related