Hello,
I´m working on my audio project and I try to stream some audio data (currently a 1 kHz square wave signal with 3.3 V peak) over I2S. The audio signal is sampled by an ADC in the free-running mode.
#include "app_error.h" #include "nrf_drv_ppi.h" #include "nrf_drv_i2s.h" #include "nrf_drv_saadc.h" #include "nrf_drv_timer.h" #include "nrf_gpio.h" #include "nrf_log.h" #include "nrf_log_ctrl.h" #include "nrf_log_default_backends.h" #define LED_0 17 #define BUTTON_0 13 #define BUTTON_1 14 #define TRIGGER 31 #define SAMPLES 128 static bool IsStarted = false; static volatile uint32_t* I2S_BlockToFill = NULL; static volatile uint32_t* ADC_BlockToFill = NULL; static uint32_t I2S_Buffer[2][SAMPLES]; static nrf_saadc_value_t ADC_Buffer[2][SAMPLES]; static nrf_ppi_channel_t PPI_Channel; static const nrf_drv_timer_t Timer = NRF_DRV_TIMER_INSTANCE(0); void on_Timer_Handler(nrf_timer_event_t EventType, void* p_Context) { } void on_SAADC_Handler(nrf_drv_saadc_evt_t const* p_Event) { if(p_Event->type == NRF_DRV_SAADC_EVT_DONE) { APP_ERROR_CHECK(nrf_drv_saadc_buffer_convert(p_Event->data.done.p_buffer, SAMPLES)); ADC_BlockToFill = (uint32_t*)p_Event->data.done.p_buffer; } } static void DataHandler(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_tx_buffer) { nrf_drv_i2s_buffers_t const NextBuffer = { .p_tx_buffer = I2S_Buffer[1], .p_rx_buffer = NULL, }; APP_ERROR_CHECK(nrf_drv_i2s_next_buffers_set(&NextBuffer)); nrf_gpio_pin_clear(TRIGGER); I2S_BlockToFill = I2S_Buffer[1]; } else { // 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)); // The pointer needs to be typecasted here, so that it is possible to // modify the content it is pointing to (it is marked in the structure // as pointing to constant data because the driver is not supposed to // modify the provided data). I2S_BlockToFill = (uint32_t*)p_Released->p_tx_buffer; } } void SAADC_Init(void) { APP_ERROR_CHECK(nrf_drv_saadc_init(NULL, on_SAADC_Handler)); APP_ERROR_CHECK(nrf_drv_ppi_init()); nrf_drv_timer_config_t timer_cfg = NRF_DRV_TIMER_DEFAULT_CONFIG; timer_cfg.bit_width = NRF_TIMER_BIT_WIDTH_32; APP_ERROR_CHECK(nrf_drv_timer_init(&Timer, &timer_cfg, on_Timer_Handler)); nrf_drv_timer_extended_compare(&Timer, NRF_TIMER_CC_CHANNEL0, nrf_drv_timer_us_to_ticks(&Timer, 23), NRF_TIMER_SHORT_COMPARE0_CLEAR_MASK, false); nrf_drv_timer_enable(&Timer); APP_ERROR_CHECK(nrf_drv_ppi_channel_alloc(&PPI_Channel)); APP_ERROR_CHECK(nrf_drv_ppi_channel_assign(PPI_Channel, nrf_drv_timer_compare_event_address_get(&Timer, NRF_TIMER_CC_CHANNEL0), nrf_drv_saadc_sample_task_get())); nrf_saadc_channel_config_t Channel0 = NRF_DRV_SAADC_DEFAULT_CHANNEL_CONFIG_SE(NRF_SAADC_INPUT_AIN1); Channel0.acq_time = NRF_SAADC_ACQTIME_10US; APP_ERROR_CHECK(nrf_drv_saadc_channel_init(0, &Channel0)); APP_ERROR_CHECK(nrf_drv_saadc_buffer_convert(ADC_Buffer[0], SAMPLES)); APP_ERROR_CHECK(nrf_drv_saadc_buffer_convert(ADC_Buffer[1], SAMPLES)); APP_ERROR_CHECK(nrf_drv_ppi_channel_enable(PPI_Channel)); } int main(void) { APP_ERROR_CHECK(NRF_LOG_INIT(NULL)); NRF_LOG_DEFAULT_BACKENDS_INIT(); SAADC_Init(); nrf_gpio_cfg_input(BUTTON_0, NRF_GPIO_PIN_PULLUP); nrf_gpio_cfg_input(BUTTON_1, NRF_GPIO_PIN_PULLUP); nrf_gpio_cfg_output(TRIGGER); nrf_gpio_cfg_output(LED_0); nrf_gpio_pin_clear(TRIGGER); nrf_gpio_pin_set(LED_0); nrf_drv_i2s_config_t I2S_Config = NRF_DRV_I2S_DEFAULT_CONFIG; I2S_Config.sdin_pin = NRFX_I2S_PIN_NOT_USED; I2S_Config.sdout_pin = 27; I2S_Config.lrck_pin = 26; I2S_Config.sck_pin = 25; I2S_Config.mck_pin = 2; I2S_Config.mck_setup = NRF_I2S_MCK_32MDIV8; I2S_Config.ratio = NRF_I2S_RATIO_96X; I2S_Config.channels = NRF_I2S_CHANNELS_STEREO; APP_ERROR_CHECK(nrf_drv_i2s_init(&I2S_Config, DataHandler)); nrf_drv_i2s_buffers_t const InitialBuffer = { .p_tx_buffer = I2S_Buffer[0], .p_rx_buffer = NULL, }; while(1) { if(nrf_gpio_pin_read(BUTTON_0) == false) { if(IsStarted == false) { APP_ERROR_CHECK(nrf_drv_i2s_start(&InitialBuffer, SAMPLES, 0)); NRF_LOG_INFO("I2S sender started..."); IsStarted = true; nrf_gpio_pin_set(TRIGGER); nrf_gpio_pin_clear(LED_0); } } else if(nrf_gpio_pin_read(BUTTON_1) == false) { if(IsStarted) { nrf_drv_i2s_stop(); NRF_LOG_INFO("I2S sender stopped..."); IsStarted = false; nrf_gpio_pin_clear(TRIGGER); nrf_gpio_pin_set(LED_0); } } if(I2S_BlockToFill && ADC_BlockToFill) { for(uint32_t i = 0x00; i < SAMPLES; i++) { //int16_t Sample = (int16_t)(32768 * sin(2.0 * 3.14 * (double)i / ((double)SAMPLES))); I2S_BlockToFill[i] = (uint32_t)(0x0000 | (ADC_BlockToFill[i] & 0xFFFF)); } I2S_BlockToFill = NULL; ADC_BlockToFill = NULL; } if(NRF_LOG_PROCESS()) { NRF_LOG_FLUSH(); } } }
The code from above will give a pretty poor signal when I use the ADC.
I have tried to figure out the reason for this and replaced the line
I2S_BlockToFill[i] = (uint32_t)(0x0000 | (ADC_BlockToFill[i] & 0xFFFF));
with
I2S_BlockToFill[i] = (uint32_t)(0x0000 | (i & 0xFFFF));
to produce data from a ramp signal and the signal looks much better.
So it looks like the problem is based on the ADC. I have tried to use two buffers to use the same swapping buffer system as in the I2S example.
How can I improve this result?