Page 1 of 1

[SOLVED] ESP32 Arduino Framework. Proper I2S ADC DMA reading and Plotting question.

Posted: Tue Oct 01, 2019 11:34 am
by zekageri
Hello forumers!

I want to read a 4Khz analog signal from a signal generator via i2s dma with ESP32 on arduino framework.
My basic idea is this:


-Setup i2s DMA to put the reading to a buffer.
-Assign a read function/task to the core0 to read the values from the DMA buffer.
-Assign a "plotting" functon/task to a core to plot the readings to the web with ChartJS.


What have i done so far:

I created the i2s setup function and put it to the first place on the setup().
I created the reader task on the core0 for reading and plotting to the web.
I created a html file that i store on the SPIFFS file system on the ESP32.

-In the HTMl file i created a chart on a canvas using ChartJS javascript library.
-In the JavaScript file i created a websocket client that is connected to the websocket server on the esp32.
-In the JavaScript file, when the data comes from the esp32 i assign it to the chart and visualize it.


Everything is working fine, except one little problem.
My i2s dma buffer is overflowing and i can see it in the visualized data.


What i think is happening:
-While i read the entire DMA buffer, the i2s adc is filling up this buffer again and again even if i doesn't end up reading from the entire buffer. This will result on buffer overflow and my readed data does not mach the actual reading.

What i think is the solution:
-If there will be an interrupt on the i2s adc dma, if it is filled up the entire buffer i would switch to an other buffer while i read the first, and when i done with the reading i would read from the second while the adc filling up the first and so on...

The thing is i did not find anything from this interrupt on the Arduino core. The one thing that i found is an event solution, that i think is just a flag and does not work as expected.

So my question is, how can i avoid this buffer overflow or how can i assign an interrupt to the dma buffer, or how can it be done correctly.

Here is my i2s init code:

Code: Select all

void configure_i2s(){
  i2s_config_t i2s_config = 
    {
    .mode = (i2s_mode_t)(I2S_MODE_MASTER | I2S_MODE_RX | I2S_MODE_ADC_BUILT_IN),  // I2S receive mode with ADC
    .sample_rate = 9000,
    .bits_per_sample = I2S_BITS_PER_SAMPLE_16BIT,                  // 16 bit I2S
    .channel_format = I2S_CHANNEL_FMT_ALL_LEFT,                  // all the left channel, no other options works for me!
    .communication_format = (i2s_comm_format_t)(I2S_COMM_FORMAT_I2S | I2S_COMM_FORMAT_I2S_MSB),   // I2S format
    .intr_alloc_flags = 1,    // Tried buffer interrupt somehow.
    .dma_buf_count = 8,    // number of DMA buffers. ( Tried different options )
    .dma_buf_len = 1000,  // number of samples.  ( Tried different options )
    .use_apll = 0,              // no Audio PLL ( I dont need the adc to be accurate )
  };
  adc1_config_channel_atten(ADC_CHANNEL, ADC_ATTEN_11db);  // ADC_CHANNEL is ADC_CHANNEL_0 - GPIO34.
  adc1_config_width(ADC_WIDTH_12Bit);                                      // I need 0 - 4095.
  i2s_driver_install(I2S_NUM_0, &i2s_config, 0, NULL);                  // I2S_NUM_0 works for adc.
 
  i2s_set_adc_mode(ADC_UNIT_1, ADC_CHANNEL);
  SET_PERI_REG_MASK(SYSCON_SARADC_CTRL2_REG, SYSCON_SARADC_SAR1_INV);  // Inverse because fifo.
  i2s_adc_enable(I2S_NUM_0);
}
Here is my adc reader function on core 0:

Code: Select all

size_t bytes_read;
static const inline void ADC_Sampling(){
  uint16_t i2s_read_buff[NUM_SAMPLES];
  String data;
  //xQueueReceive(i2s_queue, &event, (2*samplingFrequency)/  portTICK_PERIOD_MS); // Tried some kind of an event thing. Restarts
  i2s_read(I2S_NUM_0, (char*)i2s_read_buff,NUM_SAMPLES * sizeof(uint16_t), &bytes_read, portMAX_DELAY);  // I2S read to buffer
  if(I2S_EVENT_RX_DONE){  // This is the flag that i mentioned. Does nothing if there are no events assigned based on my observation.
  
    //i2s_adc_disable(I2S_NUM_0);  // Some comment war
  //long Millis_Now = millis();
  //if (((Millis_Now - Start_Sending_Millis) >= DUMP_INTERVAL)&& Chart_is_ok_to_Send_Data)
  //{
    //Start_Sending_Millis = Millis_Now;
    
    for (int i=0;i<NUM_SAMPLES;i++) {
      data = String((i2s_read_buff[i])/(float)40.95);  // Actual read from buffer, i need the data in % format on the web.
      if(olvasunk_e){                                                // Want to send String.
      data += "}";
      webSocket.broadcastTXT(data.c_str(), data.length());  // Send the data with webSocket.
      }
    }
    
    //i2s_zero_dma_buffer(I2S_NUM_0);  // Comment war again.
    //i2s_adc_enable(I2S_NUM_0);
  //}
  
  }
}
The ADC_Sampling is looks like this on core0:

Code: Select all

static void loop0(void * pvParameters){
  for( ;; ){
    vTaskDelay(1);          // REQUIRED TO RESET THE WATCH DOG TIMER IF WORKFLOW DOES NOT CONTAIN ANY OTHER DELAY
    ADC_Sampling1();         // RETRIVE DATA FROM DMA BUFFER FOR CALCULATION AND VISUALIZATION
  }
}
My HTML and JavaScript code:

Code: Select all

<!DOCTYPE HTML>
<html>
<head>
<script src="https://code.jquery.com/jquery-3.4.1.min.js"></script>      
<script type = "text/javascript" >
  /** new chart  **/
var yVal , xVal = 0;
var updateCount = 0;
var dataPoints = [];
var chart;
window.onload = function () {
chart = new CanvasJS.Chart("chartContainer", {
	zoomEnabled: true,
     	rangeChanging: customInterval,
		title : {
			text : "Osc_Test"
		},
		data : [{
				type : "line",
				dataPoints : dataPoints
			}
		],
		
	});
chart.render();
}	

var updateChart = function () {
	updateCount++;
	dataPoints.push({
	y : yVal,
	x : xVal--
});
if (dataPoints.length >  500 )
	  {
		dataPoints.shift();
	}    
chart.options.title.text = "Update " + updateCount;
chart.render();   
};

webSocket1 = new WebSocket('ws://' + window.location.hostname + ':81/');
webSocket1.onmessage=function(a){
var t = a.data;
if(t.indexOf('}')>-1){
			var l = t.substring(0,t.length-1);
    		yVal = parseInt(l,10);
    		updateChart();
	   }
	};
</script>
<script type="text/javascript" src="https://cdnjs.cloudflare.com/ajax/libs/canvasjs/1.7.0/canvasjs.js"></script>
</head>
<style>
.button {
  background-color: #818a8a;
  color: #FFFFFF;
  float: right;
  padding: 10px;
  border-radius: 10px;
  -moz-border-radius: 10px;
  -webkit-border-radius: 10px;
}

#mydiv {
    position:fixed;
    top: 40%;
    left: 30%;
    margin-top: -9em; /*set to a negative number 1/2 of your height*/
    margin-left: -15em; /*set to a negative number 1/2 of your width*/
}

</style>
<body>
    <div style = "height: 400px; width: 70%;"id="mydiv">
<div id = "chartContainer" style = "height: 400px; width: 70%;"></div>
<div class="button" width="60" height="100"><a href="MeresStop">STOP PLOTTING</a></div>
<div class="button" width="60" height="100"><a href="MeresOk">START PLOTTING</a></div>
</div>
</body>
</html>
Here is a video URL from my plotted data on the WEB when the buffer overflow doesn't hit.:
https://streamable.com/9dngp
I attached some pictures from the readed data on the web.

Re: [SOLVED] ESP32 Arduino Framework. Proper I2S ADC DMA reading and Plotting question.

Posted: Wed Oct 09, 2019 7:04 am
by zekageri
My problem is partially solved.
The solution was that i had to separate my i2s reading and plotting function and put them on the two cores.

Now my i2s reading function on core 1 is looks like this:

Code: Select all

static const inline void Sampling(){
    i2s_read(I2S_NUM_0, (char*)i2s_read_buff,NUM_SAMPLES * sizeof(uint16_t), &bytes_read, portMAX_DELAY);
}
And my plotting function on core 0:

Code: Select all

static const inline void Plotting(uint16_t* Buffer){
   String data;
  if (Chart_is_ok_to_Send_Data)  // if the user on the chart page
  {
    for(int i = 0;i<NUM_SAMPLES;i++)
    {
      data = String((Buffer[i])/(float)40.95);  // float to see the data in % on the web
      if(olvasunk_e){                                     // There is a button on the web to stop just the plotting
      data += "}";
      webSocket.broadcastTXT(data.c_str(), data.length());
      }
    }
  }
}
And my i2s init code on the first place in the setup() :

Code: Select all

void configure_i2s(){
  i2s_config_t i2s_config = 
    {
    .mode = (i2s_mode_t)(I2S_MODE_MASTER | I2S_MODE_RX | I2S_MODE_ADC_BUILT_IN),  // I2S receive mode with ADC
    .sample_rate = samplingFrequency,                                             // 144000
    .bits_per_sample = I2S_BITS_PER_SAMPLE_16BIT,                                 // 16 bit I2S
    .channel_format = I2S_CHANNEL_FMT_ALL_LEFT,                                   // all the left channel
    .communication_format = (i2s_comm_format_t)(I2S_COMM_FORMAT_I2S | I2S_COMM_FORMAT_I2S_MSB),   // I2S format
    .intr_alloc_flags = 0,                                                        // none
    .dma_buf_count = 2,                                                           // number of DMA buffers 2 for fastness
    .dma_buf_len = 1024,                                                   // number of samples
    .use_apll = 0,                                                                // no Audio PLL
  };
  adc1_config_channel_atten(ADC_CHANNEL, ADC_ATTEN_11db);
  adc1_config_width(ADC_WIDTH_12Bit);
  i2s_driver_install(I2S_NUM_0, &i2s_config, 0, NULL);
 
  i2s_set_adc_mode(ADC_UNIT_1, ADC_CHANNEL);
  SET_PERI_REG_MASK(SYSCON_SARADC_CTRL2_REG, SYSCON_SARADC_SAR1_INV);
  i2s_adc_enable(I2S_NUM_0);
  vTaskDelay(1000);
}
It will be better if i would use queues but i can't find any usefull example about i2s queues on arduino core.
I attached some pictures from the visualized data on the web.