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 ?