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

I2S clocks and softdevice

I am on Ubuntu 20.04 with SES 5.42c, SDK 16, and SoftDevice S140 version 7.0.1 I am using a Knowles SPH0645LM4H. I have put together a beacon that operates the microphone via I2S. I2S is configured in slave mode and i use PWM to generate SCK and LRCK for the microphone and the I2S peripheral. The microphone is operated for 100mS and a single value is produced for the beacon to advertise. The beacon advertises every 10 seconds and operates the microphone at 10 second intervals in between advertisements. The following screen shot of an Otii power profile illustrates the process.

  

Point1) Otii powers up firmware starts.

Point 2) main calls my I2S function in my i2s module. This function starts the PWM "clock" operation.

Point 3) The function stops the PWM and returns to main.

Point 4) main advertises the acoustic value. Repeat the process.

Unfortunately, this is a screen shot of just PWM in action. When I include the actual operation of I2S the result is shown in the next screen shot.

Points 1 and 2 are the same as the first screen shot.

Point 3 is where nrfx_i2s_start is called and Point 3 is where nrfx_i2s_stop is called.

As you can see main never advertises. From debugging it is the nrfx_i2s_stop function that "blows-up" timers across the firmware. Here is a zoom of the Point 3 to Point 4 area.

Point 1 main calls the function.

Point 2 The function starts PWM

Point 3 nrfx_i2s_start      Point 4 nrfx_i2s_stop

Point 5 shows the DMA of the I2S buffers. 

static nrfx_i2s_config_t m_i2s_config     = NRFX_I2S_DEFAULT_CONFIG;

#define NUM_I2S_MBUFS                    5
static uint8_t                          m_i2s_flag = 0;

static uint8_t                          m_i2s_buf_index = 0;
static uint32_t                         m_i2s_buf[NUM_I2S_MBUFS + 1][I2S_DATA_BLOCK_WORDS];

APP_TIMER_DEF(m_i2s_delay_timer_id);
#define I2S_DELAY_TIMER_INTERVAL          APP_TIMER_TICKS(500)

static uint32_t                          m_buffer_rx[2][I2S_DATA_BLOCK_WORDS];


static void i2s_data_handler(nrfx_i2s_buffers_t const * p_released,
                             uint32_t                      status)
{
    uint8_t     flag = 1;

    // 'nrf_drv_i2s_next_buffers_set' is called directly from the handler
    // each time next buffers are requested, so data corruption is not
    // expected. However, if p_released is NULL the driver is telling you
    // that it reused the rx buffer and you are missing data,(data corruption).
    //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. status is either zero or
    // NRFX_I2S_STATUS_NEXT_BUFFERS_NEEDED. As of SDK 17 it can't be
    // anything else.

    // 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 buffer for the next part of the transfer should be
    // provided.
    if (!p_released->p_rx_buffer)
    {

        nrfx_i2s_buffers_t const next_buffers =
        {
            .p_rx_buffer = m_buffer_rx[1],
            .p_tx_buffer = NULL,
        };
        APP_ERROR_CHECK(nrfx_i2s_next_buffers_set(&next_buffers));
        nrf_gpio_pin_toggle(LED_RED_PIN);

    }
    // If p_released->p_rx_buffer isn't NULL, you have a buffer of data from the microphone that
    // is ready to be used. status should also be equal to NRFX_I2S_STATUS_NEXT_BUFFERS_NEEDED.
    // At any rate you have to do something with the data and then make that buffer "re-available"
    // for the driver to use.
    else
    {

        memmove(m_i2s_buf[m_i2s_buf_index++], p_released->p_rx_buffer, (I2S_DATA_BLOCK_WORDS * sizeof(uint32_t)));
        // 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. If flag isn't set, the m_i2s_buffer_handler, is
        // telling us it has called i2s_drv_audio_stop because it has filled up it's
        // preset number of buffers. You will ASSERT on this nrfx_i2s_next_buffers_set
        // call if nrfx_i2s_stop has already been called.
        if (m_i2s_buf_index < NUM_I2S_MBUFS)
        {
            APP_ERROR_CHECK(nrfx_i2s_next_buffers_set(p_released));
        }
        else if (m_i2s_buf_index >= NUM_I2S_MBUFS)
        {
            m_i2s_flag = 0;
            nrf_gpio_pin_toggle(LED_RED_PIN);
        }

    }
}

uint32_t i2s_drv_audio_init(void)
{

    uint32_t err_code = NRF_SUCCESS;

    // The nRF52840 I2S specification:
    // https://infocenter.nordicsemi.com/index.jsp?topic=%2Fcom.nordic.infocenter.sdk5.v15.0.0%2Fgroup__nrfx__i2s.html
    // "When a pin is acquired by the I2S module, the direction of the pin (input or output) will be
    //  configured automatically, and any pin direction setting done in the GPIO module will be overridden."
    // So the following is supposedly unneccessary.
    //nrf_gpio_cfg_input(MY_I2S_CONFIG_SCK_PIN, NRF_GPIO_PIN_NOPULL);
    //nrf_gpio_cfg_input(MY_I2S_CONFIG_LRCK_PIN, NRF_GPIO_PIN_NOPULL);
    //nrf_gpio_cfg_input(MY_I2S_CONFIG_SDIN_PIN, NRF_GPIO_PIN_NOPULL);
    // Even though I've read in the Nordic devzone at least one nordic response that explicitly asked if
    // the GPIO pins had been correctly set as input or output prior to I2S initialization.

    // Define the I2S configuration; running in slave mode and using PWM outputs to provide synthetic SCK and LRCK

    m_i2s_config.sck_pin              = MY_I2S_CONFIG_SCK_PIN;
    m_i2s_config.lrck_pin             = MY_I2S_CONFIG_LRCK_PIN;
    m_i2s_config.sdin_pin             = MY_I2S_CONFIG_SDIN_PIN;
    m_i2s_config.sdout_pin            = NRFX_I2S_PIN_NOT_USED;
    m_i2s_config.mck_pin              = NRFX_I2S_PIN_NOT_USED;

    m_i2s_config.mck_setup            = NRF_I2S_MCK_DISABLED;
    m_i2s_config.mode                 = NRF_I2S_MODE_SLAVE;

    // The Knowles microphone has it's SEL pin grounded. According to Knowles that
    // makes it responsive only when it's LRCK input is low. Knowles says this
    // corresponds to the Left channel. But this setting is about the I2S peripheral
    // interpreting the data_in as mono and translating the data_in only when LRCK is
    // low.
    m_i2s_config.channels             = NRF_I2S_CHANNELS_LEFT;

    // I2S frame format. There are two choices, NRF_I2S_FORMAT_I2S, (Original I2S format), or
    // NRF_I2S_FORMAT_ALIGNED, (Alternate (left-aligned or right-aligned) format).
    //
    // This is a quote from the Knowles' representative, Erik Wiederholtz,
    // "I talked with our internal apps team and Nordic is one chipset that we know can have
    //  issues as they do not allow you to choose the decimation rate and left VS right
    //  justified alignment. So the data would not make sense in that case.
    //  We are left justified, 64x, 24 but depth.", [email protected]
    // So I use NRF_I2S_FORMAT_ALIGNED along with the .alignment member.
    m_i2s_config.format               = NRF_I2S_FORMAT_ALIGNED;

    // I2S alignments of sample within a frame. There are two choices, NRF_I2S_ALIGN_LEFT,
    // (Left-aligned), or NRF_I2S_ALIGN_RIGHT, (Right-aligned). I'll use NRF_I2S_ALIGN_LEFT
    // to follow the Knowles specification.
    m_i2s_config.alignment            = NRF_I2S_ALIGN_LEFT;

    // Per the Knowles specification.
    m_i2s_config.sample_width         = NRF_I2S_SWIDTH_24BIT;

    // .irq_priority and .ratio
    // Nordic documentation doesn't have a specification for .ratio when the MCK isn't used.
    // This is unlike the .mck_setup parameter which is closely related to the .ratio and is
    // provided with the Nordic supplied NRF_I2S_MCK_DISABLED value for non use of MCK.
    // So I leave it in default value as well as the .irq_priority member.


    err_code                    = nrfx_i2s_init(&m_i2s_config, i2s_data_handler);   // Initialize the I2S driver
    APP_ERROR_CHECK(err_code);

    return (err_code);

}


void i2s_drv_audio_init_drv(void)
{

    uint32_t err_code = NRF_SUCCESS;
    err_code = i2s_drv_audio_init();

}


uint32_t i2s_drv_audio_start(void)
{

    uint32_t err_code = NRF_SUCCESS;

    // The NULL p_tx_buffer member tells the driver that I2S is operating only in
    // receiving mode. As of SDK 17, the third parameter can only be zero.
    nrfx_i2s_buffers_t const initial_buffers =
    {
        .p_rx_buffer = m_buffer_rx[0],
        .p_tx_buffer = NULL,
    };
    err_code = nrfx_i2s_start(&initial_buffers, I2S_DATA_BLOCK_WORDS, 0);
    APP_ERROR_CHECK(err_code);

    return (err_code);

}


static void i2s_delay_timeout_handler(void * p_context)
{
    UNUSED_PARAMETER(p_context);

    m_i2s_flag = ~m_i2s_flag;

}


void i2s_mic_delay_timer_create(void)
{
    uint32_t err_code = 0;

    // Main has already done app_timer_init(); Create the i2s microphone's delay timer.
    // This timer controls the delay after power-up and start of the PWM synthetic clocks.
    // Gives the microphone time to stablelize.
    err_code = app_timer_create(&m_i2s_delay_timer_id, APP_TIMER_MODE_SINGLE_SHOT, i2s_delay_timeout_handler);
    APP_ERROR_CHECK(err_code);
}


void i2s_mic_generate_an_spl(void)
{

    uint32_t     err_code = 0;

    m_i2s_16_bit_signed_spl = 0;
    m_i2s_buf_index = 0;
    m_i2s_flag = 0;

    start_i2s_aux_clks ();
    err_code = app_timer_start(m_i2s_delay_timer_id, I2S_DELAY_TIMER_INTERVAL, NULL);
    APP_ERROR_CHECK(err_code);
    while (!m_i2s_flag);

    i2s_drv_audio_start();
    //err_code = app_timer_start(m_i2s_delay_timer_id, I2S_DELAY_TIMER_INTERVAL, NULL);
    while (m_i2s_flag);

    nrfx_i2s_stop();
    stop_i2s_aux_clks();
    nrf_delay_ms(1);
    i2s_audio_buffer_transfer();


}

This is the code that handles the I2S peripheral. I have confirmed that this all works in a non ble application. I am missing something fundamental concerning the I2S working in conjunction with a SoftDevice application. 

The following is what I think is the pertinent code from main.

static void timers_init(void)
{
    uint32_t err_code = 0;
    err_code = app_timer_init();
    APP_ERROR_CHECK(err_code);

    // Create the master timer. This timer controls everything.
    err_code = app_timer_create(&m_master_timer_id, APP_TIMER_MODE_REPEATED, master_timeout_handler);
    APP_ERROR_CHECK(err_code);
    // Create the ad timer. This timer controls interval between ads.
    err_code = app_timer_create(&m_AD_timer_id, APP_TIMER_MODE_SINGLE_SHOT, ad_timeout_handler);
    APP_ERROR_CHECK(err_code);
}


/**@brief Function to start the application's timers. */
static void application_timers_start(void)
{
    uint32_t    err_code = 0;

    err_code = app_timer_start(m_master_timer_id, MASTER_TIMER_INTERVAL, NULL);
    APP_ERROR_CHECK(err_code);
}


/**@brief Function for initializing power management.
 */
static void power_management_init(void)
{
    uint32_t   err_code;
    err_code = nrf_pwr_mgmt_init();
    APP_ERROR_CHECK(err_code);
}


/**@brief Function for initializing the BLE stack.
 *
 * @details Initializes the SoftDevice and the BLE event interrupt.
 */
static void ble_stack_init(void)
{
    ret_code_t err_code;

    err_code = nrf_sdh_enable_request();
    APP_ERROR_CHECK(err_code);

    // Configure the BLE stack using the default settings.
    // Fetch the start address of the application RAM.
    uint32_t ram_start = 0;
    err_code = nrf_sdh_ble_default_cfg_set(APP_BLE_CONN_CFG_TAG, &ram_start);
    APP_ERROR_CHECK(err_code);

    // Enable BLE stack.
    err_code = nrf_sdh_ble_enable(&ram_start);
    APP_ERROR_CHECK(err_code);

}


/**@brief Function for handling the idle state (main loop).
 *
 * @details If there is no pending log operation, then sleep until next the next event occurs.
 */
static void idle_state_handle(void)
{
#ifdef DEBUGGING
    if (NRF_LOG_PROCESS() == false)
#endif
        nrf_pwr_mgmt_run();

}


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

    short        soundmeas = 0;
    uint32_t     err_code = 0;

    uint8_t      beacon_sound_major_MSB, beacon_sound_major_LSB;
    uint16_t     beacon_sound_major;

    // Initialize.
#ifdef DEBUGGING
    log_init();  // Commenting this out saves power but is it dangerous?
#endif
    timers_init();
    power_management_init();
    ble_stack_init();
    //err_code = sd_power_dcdc_mode_set(NRF_POWER_DCDC_ENABLE);//
    //APP_ERROR_CHECK(err_code);//

    led_pin_init();
    init_i2s_aux_clks_pins();
    i2s_mic_delay_timer_create();
    i2s_drv_audio_init_drv();

    master_adv_handle = beacon_init(MASTER_TIMER_INTERVAL_IN_SEC, BEACON_TX_POWER);
    dfu_init(master_adv_handle);

    // Start execution.
    application_timers_start();

    // Enter main loop.
#ifdef DEBUGGING
    NRF_LOG_INFO("My Test Beacon executing.");
#endif
    for (; ; )
    {

        if (do_measurement  == 1)
        {

            do_measurement = 2;
            soundmeas = 0;

            beacon_sound_major_MSB = m_beacon_brdcst_counter;
            beacon_sound_major_LSB = BEACON_SOUND_MESSAGE_TYPE;
            beacon_sound_major = (beacon_sound_major_MSB << 8) | beacon_sound_major_LSB;

#ifdef MEASURE_SOUND
            i2s_mic_get_an_spl (&soundmeas);
            beacon_stop();
            beacon_advertising_re_init(beacon_sound_major, soundmeas, m_master_cntr);
            beacon_start();
            soundmeas = 0;
#endif         

            err_code = app_timer_start(m_AD_timer_id, AD_TIMER_INTERVAL, NULL);

        }
        else if (do_measurement == 3)
        {
            beacon_stop();
            do_measurement = 0;

        }
        else if (do_measurement == 12)
        {

            do_measurement = 0;
#ifdef MEASURE_SOUND
            i2s_mic_generate_an_spl();
#endif

        }

        idle_state_handle();

    }  //Infinite for loop

}

Can you tell me why the call to nrfx_i2s_stop() screws everything up? Can you use I2S in a BLE application? Am I missing something obvious in sdk_config.h ?

Parents
  • After I call nrfx_i2s_stop, an assertion is thrown in the I2S data handler. For some reason the data handler seems to be called by the I2S peripheral even after nrfx_i2s_stop is called. The assertion is NRF_ERROR 8 - Invalid State on a nrfx_i2s_next_buffers_set call. The invalid state is defined as either the buffers have already been supplied or the peripheral is currently being stopped. The way I fixed this was to wrap the code in the i2s_data_handler in an if (status & NRFX_t2S_STATUS_NEXT_BUFFERS_NEEDED) condition. 

    My understanding is that the data handler's 2nd in parameter, status, will be either 0 or NRFX_I2S_STATUS_NEXT_BUFFERS_NEEDED. Although I have read about a second value called NRFX_I2S_STATUS_TRANSFER_STOPPED in Drivers->nrfx v2.3. That value isn't available in my environment.

    So it works now and my problem had nothing to do with clocks or the softdevice. However, I have yet to figure out how the code worked in a none BLE application. Back to work.

Reply
  • After I call nrfx_i2s_stop, an assertion is thrown in the I2S data handler. For some reason the data handler seems to be called by the I2S peripheral even after nrfx_i2s_stop is called. The assertion is NRF_ERROR 8 - Invalid State on a nrfx_i2s_next_buffers_set call. The invalid state is defined as either the buffers have already been supplied or the peripheral is currently being stopped. The way I fixed this was to wrap the code in the i2s_data_handler in an if (status & NRFX_t2S_STATUS_NEXT_BUFFERS_NEEDED) condition. 

    My understanding is that the data handler's 2nd in parameter, status, will be either 0 or NRFX_I2S_STATUS_NEXT_BUFFERS_NEEDED. Although I have read about a second value called NRFX_I2S_STATUS_TRANSFER_STOPPED in Drivers->nrfx v2.3. That value isn't available in my environment.

    So it works now and my problem had nothing to do with clocks or the softdevice. However, I have yet to figure out how the code worked in a none BLE application. Back to work.

Children
No Data
Related