espressif / arduino-esp32

Arduino core for the ESP32
GNU Lesser General Public License v2.1
13.3k stars 7.35k forks source link

ESP32 Arduino Framework. Proper I2S ADC DMA reading and Plotting question. #3315

Closed zekageri closed 4 years ago

zekageri commented 4 years ago

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:

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:

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:

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

My HTML and JavaScript code:

<!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.

Képernyőfelvétel (21) Képernyőfelvétel (22) Képernyőfelvétel (23)

mehmetcanbalci commented 4 years ago

Hi ,where is your loop or setup functions ?

zekageri commented 4 years ago

There are a few things in my setup and in my loop. i have got a bunch of components attached to my esp. The i2s init function is my first thing to do on the setup.

zekageri commented 4 years ago

The problem partially solved.

I had to separate the reading and plotting part of my sampling function. The signals are good now, but the entire thing is slow. Btw my problem solved. Here is the reader and plotter functions, separated on the two cores of the esp.

Reading on core1 on loop:

static const inline void Sampling(){
    i2s_read(I2S_NUM_0, (char*)i2s_read_buff,NUM_SAMPLES * sizeof(uint16_t), &bytes_read, portMAX_DELAY);
}

I dont have to do anything else in here.

And my plotting function on core 0:

static const inline void Plotting(uint16_t* Buffer){
   String data;
  if (Chart_is_ok_to_Send_Data) // if the user investigated the chart page
  {
    for(int i = 0;i<NUM_SAMPLES;i++)
    {
      data = String((Buffer[i])/*/(float)40.95*/);
      if(olvasunk_e){  // there is a button on the page to stop the plotting but not the reading.
      data += "}";
      webSocket.broadcastTXT(data.c_str(), data.length());
      }
    }
  }
}

My i2s init is on the first place at the setup():

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,                                             // 144000hz
    .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 = 8,                                                           // number of DMA buffers ( maybe this is too much? )
    .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);
}

And thats all. It would be good if i was able to do the queue thing with the i2s but i can't find anything with arduino core. Btw this problem is solved.

Some pictures from the plotting on the web:

Képernyőfelvétel (26) Képernyőfelvétel (27)

ReinstatedSamit commented 2 years ago

Can I get the full code of it. I am facing similar problem but couldn’t understand how to run in seperate core as you mentioned.

zekageri commented 2 years ago

Sorry. I don't have the code anymore. I completelly ditched the project because the esp's analog read is a total garbage. I couldn't get even near of the desired accuracy.

VojtechBartoska commented 2 years ago

@PilnyTomas Just take a look on this closed issue, maybe it can be relevant a bit to your work.

zekageri commented 2 years ago

If you want accurate real time analog data visualization on the web, spare your time. It won't work.