atomic14 / esp32_audio

Sample code for reading audio samples from the ESP32 ADC using I2S
Creative Commons Zero v1.0 Universal
397 stars 109 forks source link

Very strange waveform Shape #20

Open Kabron287 opened 3 years ago

Kabron287 commented 3 years ago

Hello, I have reduced your example to a minimum to investigate in details. I changed output to Serial and observe the signals via SerialPlot SW. Input signal was Sine 1kHz, 2V Amp, 1.7V offset.

include "ADCSampler.h"

define SAMPLE_RATE 32000 //16000

define SAMPLES 16384

define TPIN 2

define t1 digitalWrite(TPIN, HIGH);

define t0 digitalWrite(TPIN, LOW);

define t digitalWrite(TPIN, !digitalRead(TPIN));

ADCSampler adcSampler = NULL; I2SSampler i2sSampler = NULL;

// i2s config for using the internal ADC i2s_config_t adcI2SConfig = { .mode = (i2s_mode_t)(I2S_MODE_MASTER | I2S_MODE_RX | I2S_MODE_ADC_BUILT_IN), .sample_rate = SAMPLE_RATE, //16000, .bits_per_sample = I2S_BITS_PER_SAMPLE_16BIT, // I2S_BITS_PER_SAMPLE_16BIT, .channel_format = I2S_CHANNEL_FMT_ONLY_LEFT, .communication_format = I2S_COMM_FORMAT_I2S_LSB, .intr_alloc_flags = ESP_INTR_FLAG_LEVEL1, .dma_buf_count = 4, .dma_buf_len = 1024, .use_apll = false, .tx_desc_auto_clear = false, .fixed_mclk = 0 };

void adcWriterTask(void param) { I2SSampler sampler = (I2SSampler )param; const TickType_t xMaxBlockTime = pdMS_TO_TICKS(100); while (true) { uint32_t ulNotificationValue = ulTaskNotifyTake(pdTRUE, xMaxBlockTime); if (ulNotificationValue > 0){ int8_t buffptr = (int8_t*)sampler->getCapturedAudioBuffer();

// for (int i = 0; i < sampler->getBufferSizeInBytes(); i=i+2) { // Serial.printf("%d\n\r", buffptr[i] + buffptr[i+1]256); // } Serial.write((char ) sampler->getCapturedAudioBuffer(), sampler->getBufferSizeInBytes()); } } }

void setup() { Serial.begin(115200); pinMode(TPIN, OUTPUT); while (!Serial);

adcSampler = new ADCSampler(ADC_UNIT_1, ADC1_CHANNEL_7);

TaskHandle_t adcWriterTaskHandle; xTaskCreatePinnedToCore(adcWriterTask, "ADC Writer Task", 4096, adcSampler, 1, &adcWriterTaskHandle, 1); adcSampler->start(I2S_NUM_0, adcI2SConfig, SAMPLES, adcWriterTaskHandle); }

void loop() { }

Below are screenshorts of the various I2S settings. Please, note how awful they looks. They should be smooth but they did not. Only at 32Bit 100kHz it looks acceptable. First time I met the problem on PDM example for M5 Core2. Note, also, that signals aquired from analogRead function are ideal. This sawtooth distortion leave no way to use FFT because they produce a lot of harmonics. Also, the same thing occured in you original code(Audacity picture). 16Bit_16kHz_1kHz_2V 16Bit_32kHz_1kHz_2V ) 32Bit_100kHz_1kHz_2V Audacity

Kabron287 commented 3 years ago

Actually, it is a weel known problem, e.g: https://www.esp32.com/viewtopic.php?t=5522 obviously caused by bug in esp library code. The simplest solution is to use only even or odd buffer values. More complicated is to split buffer in odd/even discretes and then rearrange them back into buffer. The purpose of my notice is to inform you, that you current realization does not produce correct data for further FFT analysis.

ReinstatedSamit commented 2 years ago

I am also facing a similar type of problem. I was trying to sample a sinusoidal using i2s ADC and observe it in the oscilloscope using DAC. But the DAC wasn't giving a sinusoidal. The signal given in the ADC input was only 600 Hz. I am providing the code here

include

include

include <driver/i2s.h>

include <driver/adc.h>

include <driver/dac.h>

include <soc/syscon_reg.h>

define FS_NO_GLOBALS

include

include "SPIFFS.h"

include

include

define ADC_CHANNEL ADC1_CHANNEL_4 //

define NUM_SAMPLES 1024 // number of sam

define I2S_NUM (0)

define DAC1 25

char payload[10000];

/**** Main Functions ****/

void configure_i2s(){ i2s_config_t i2s_config = { .mode = (i2s_mode_t)(I2S_MODE_MASTER | I2S_MODE_RX | I2S_MODE_TX | I2S_MODE_ADC_BUILT_IN), // I2S receive mode with ADC .sample_rate =44100 , // sample rate .bits_per_sample = I2S_BITS_PER_SAMPLE_16BIT, // 16 bit I2S .channel_format = I2S_CHANNEL_FMT_ALL_LEFT, // only 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 .dma_buf_len = NUM_SAMPLES, // number of samples .use_apll = 0, // no Audio PLL };

adc1_config_channel_atten(ADC_CHANNEL, ADC_ATTEN_6db); 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);

  //init DAC pad

i2s_set_dac_mode(I2S_DAC_CHANNEL_BOTH_EN);

i2s_adc_enable(I2S_NUM_0); dac_i2s_enable(); }

static const inline void ADC_Sampling(){ uint16_t i2s_read_buff[NUM_SAMPLES2]; //uint8_t i2s_write_buff[NUM_SAMPLES2]; size_t num_bytes_read,bytes_written; String data; uint8_t data2; uint16_t offset = (int)ADC_CHANNEL * 0x1000 + 0xFFF;

i2s_read( I2S_NUM_0, (void) i2s_read_buff, NUM_SAMPLES 2* sizeof (uint16_t), &num_bytes_read, portMAX_DELAY);

int NumSamps =  num_bytes_read/(2*sizeof (uint16_t));
for (int i=0;i<NumSamps;i++) {
  data = ( (int) (i2s_read_buff[i*2]& 0x0FFF)); //0x0FFF;
  data2 = (uint8_t)((i2s_read_buff[i*2]& 0x0FFF)*256/4096);

  sprintf(payload, "%s", data.c_str());

  Serial.print("Offset :");Serial.println(i2s_read_buff[i*2]>>12);
  Serial.println(payload);

  Serial.print("Dac :");Serial.print(data2);

  dacWrite(DAC1,data2);

}

// i2s_write( I2S_NUM_0, (void) i2s_write_buff, NUM_SAMPLES 2* sizeof (uint8_t), &bytes_written, portMAX_DELAY); i2s_zero_dma_buffer(I2S_NUM_0); i2s_adc_disable(I2S_NUM_0); dac_i2s_disable();

}

void setup() { Serial.begin(115200); pinMode(DAC1, OUTPUT);

configure_i2s();

}

void loop() { ADC_Sampling();

delay(150); }

cgreening commented 2 years ago

I'll hook up my signal generator and take a look.

LyricDev commented 2 years ago

I was facing similar problems to others here. My solution is as follows, for anyone coming across this in future:

The basic problem I was having was apparently not one of ADC/DAC performance, but rather improper numerical handling of the i2s data in my code. Not surprising, because the way the data is structured by the ESP32 drivers is mildly cretinous. This was using PlatformIO with ESP-IDF 4.3.1 (I believe some issues have been fixed since I last played with an ESP32, so updating may be worthwhile.)

Consider an example reading data from the ADC and pushing it directly out through the DAC. This is ideal if you want to quickly asses the ADC/DAC performance with a scope. The following configuration worked for me:

#define SAMPLE_RATE 100000 // realistically all that seems to happen beyond 200ksps is the frequency accuracy gets worse (10's of %), suspect either the DAC or ADC is hitting its limit and cummulative clock drift starts to become a big thing
#define CHUNKS_IN_I2S 2
#define SAMPLES_PER_CHUNK 128
i2s_port_t i2s_port = I2S_NUM_0; // I don't believe DAC/ADC DMA is possible on the other i2s port

void init()
{
        i2s_config_t i2s_config = {
            .mode = (i2s_mode_t)(I2S_MODE_MASTER | I2S_MODE_RX | I2S_MODE_TX | I2S_MODE_DAC_BUILT_IN | I2S_MODE_ADC_BUILT_IN),
            .sample_rate =  SAMPLE_RATE,
            .bits_per_sample = I2S_BITS_PER_SAMPLE_16BIT,
            .channel_format = I2S_CHANNEL_FMT_ONLY_LEFT, // I'm not sure if dual channel changes the max achievable sampling rate
            .communication_format = I2S_COMM_FORMAT_STAND_MSB,
            .intr_alloc_flags = 0, //? ESP_INTR_FLAG_LEVEL1
            .dma_buf_count = CHUNKS_IN_I2S, 
            .dma_buf_len = SAMPLES_PER_CHUNK, // Careful, this is in SAMPLES, not bytes!
            .use_apll = true, // this being true somehow seems to improve sampling synch, seems like a more steady clock, even if it is slightly less accurate overall (1.5% frequency error vs 0.8% error) 
            .tx_desc_auto_clear = false, // I think this controls how the DAC output buffer is handled if it doesn't receive a constant stream of data, but it does in this example
            .fixed_mclk = 1// when this is set to 0 it seems the sample rate cannot go above 44.1kHz 
        };
        //install and start i2s driver
        i2s_driver_install(i2s_port, &i2s_config, 0, NULL);
        //init DAC pad
        i2s_set_dac_mode(I2S_DAC_CHANNEL_BOTH_EN); // not sure how this interracts with the i2s channel_format
        //init ADC pad
        i2s_set_adc_mode(ADC_UNIT_1, ADC1_CHANNEL_4); //input on gpio32
        adc1_config_channel_atten(ADC1_CHANNEL_4, ADC_ATTEN_11db);
        adc1_config_width(ADC_WIDTH_12Bit);
        // start the adc
        i2s_adc_enable(i2s_port);
}

Then loop the following code forever to read/write the analog data (in a task or your main loop):

        // make a zeroed buffer to store a chunk of 12 bit adc data (stored in 16 bit uint)
        uint16_t* i2s_read_buff = (uint16_t*) calloc(SAMPLES_PER_CHUNK, sizeof(uint16_t));
        size_t bytes_read, bytes_written;

        // collect ADC data (length in bytes, and we need 2 bytes for each sample)
        i2s_read(i2s_port, (void*) i2s_read_buff, SAMPLES_PER_CHUNK * 2, &bytes_read, portMAX_DELAY);

        // scale the data into 8 bit DAC range (still represented using 16bit as this is what i2s expects)
        for (int i = 0; i < SAMPLES_PER_CHUNK; i++)
        {
            // i2s_read_buff[i] = ((i2s_read_buff[i]  & 0xfff) * 256) / 4096;
            i2s_read_buff[i] = i2s_read_buff[i] << 4; // apparently its the HIGHEST byte that should contain the DAC 8 bit value
        }

        i2s_write(i2s_port, i2s_read_buff, bytes_read, &bytes_written, portMAX_DELAY);

        free(i2s_read_buff);
        i2s_read_buff = NULL;

Rationale

The reason behind my original problem is the following: 1 - the data read from the ADC is 12 bits stored in 16bit (2byte) sections. Make sure you read the buffer returned from i2s properly! The structure of these 16 bit sections is: 4 bits of configuration data (garbage to us), followed by 12 bits of data. There is code around already for masking away the garbage to get sensible results.

I see some examples above clearly not doing this, e.g. ReinstatedSamit does:

i2s_read( I2S_NUM_0, (void*) i2s_read_buff, NUM_SAMPLES * 2* sizeof (uint16_t), &num_bytes_read, portMAX_DELAY);

which asks the i2s for a data length in BITS not in BYTES!

2 - the data sent to the DAC is 8 bits, but must also be packed in 16 bit sections (that's what i2s requires). The most confusing thing is that the DAC looks for the data in the MOST SIGNIFICANT BITS. This seems a bit nutty to me, as this is not the behaviour you'd have if you put your 0-255 into a uint16 array and passed it across - the most direct way most of us would do! Instead, you have a few options:

My ultimate conclusion is that many of these types of symptoms may well be the result of incorrect numerical handling in user code. For me, I initially experienced all sorts of octave / harmonic errors as well as weird amplitude effects until I realised these were more likely symptoms of periodic mishandling of the raw data than those of hardware non-linearities or ESP-IDF library code. Having said that, Espressif's documentation and data-model "design" choices in this implementation cost me a good day or two of my life!

Certainly, once I finally understood the data formats, I was immediately able to make my WROOM 32 clone read and write analog data quite satisfactorily at rates up to around 200ksps and, using the built in clock, frequency stable to within 1%. I'm now fairly satisfied it is suitable for my audio dsp use-case having battered it with some rather demanding square wave analysis. Now, I must go to design proper input and output filters - hopefully you are all also doing this, you won't get very far feeding naked analog audio signals in and out of any digital device!

Bartvelp commented 2 years ago

I fixed this in the following code using the "complicated" code suggested by @Kabron287 This is the code, fixAudioBuffer is the relevant function. This is arduino but esp idf should be the same (?)

#include "esp_a2dp_api.h"
#include "driver/i2s.h"
#include "freertos/queue.h"
#include "driver/adc.h"

// public config parameters
int sample_rate = 44100;
int dma_buf_count = 10;
int dma_buf_len = 512;
bool use_apll = false;

const i2s_port_t i2s_num = I2S_NUM_0; // Analog input only supports 0!

// select the analog pin e.g 32
adc_unit_t unit = ADC_UNIT_1;
adc1_channel_t channel = ADC1_GPIO34_CHANNEL;

// I2S config
i2s_config_t i2s_config = {
  .mode = (i2s_mode_t)(I2S_MODE_MASTER | I2S_MODE_RX | I2S_MODE_ADC_BUILT_IN | I2S_MODE_TX | I2S_MODE_DAC_BUILT_IN),
  .sample_rate = sample_rate,
  .bits_per_sample = I2S_BITS_PER_SAMPLE_16BIT,
  .channel_format = I2S_CHANNEL_FMT_ONLY_RIGHT,
  .communication_format = I2S_COMM_FORMAT_I2S_LSB,
  .intr_alloc_flags = ESP_INTR_FLAG_LEVEL1,
  .dma_buf_count = dma_buf_count,
  .dma_buf_len = dma_buf_len,
  .use_apll = use_apll,
  .tx_desc_auto_clear = false,
  .fixed_mclk = 0
};

void setup() {
  Serial.begin(115200);
  // setup config
  if (i2s_driver_install(i2s_num, &i2s_config, 0, NULL)!=ESP_OK){
      ESP_LOGE(ADC_TAG, "%s - %s", __func__, "i2s_driver_install");
  }      

  //init ADC pad
  if (i2s_set_adc_mode(unit, channel)!=ESP_OK) {
      ESP_LOGE(ADC_TAG, "%s - %s", __func__, "i2s_set_adc_mode");
  }

  // enable the ADC
  if (i2s_adc_enable(i2s_num)!=ESP_OK) {
      ESP_LOGE(ADC_TAG, "%s - %s", __func__, "i2s_adc_enable");
  }
  Serial.println("Done with setup");
}

#define audioSampleSize 100
uint16_t audioSamples[audioSampleSize];

void loop() {
  size_t bytes_read = 0;
  i2s_read(i2s_num, &audioSamples, sizeof(uint16_t) * audioSampleSize, &bytes_read, portMAX_DELAY);
  int samplesRead = bytes_read / sizeof(uint16_t);

  // Serial.println("read bytes: " + String(bytes_read) + " " + String(samplesRead));
  for (int i = 0; i < samplesRead; i++) audioSamples[i] = audioSamples[i] & 0x0FFF; // Only keep right 12 bits
  fixAudioBuffer(audioSamples, samplesRead);
  for (int i = 0; i < samplesRead; i++) Serial.println(audioSamples[i]);
  // WARNING: the i2s write function expects the values to be "wrong" again so
  //   fixAudioBuffer(audioSamples, samplesRead);
  // another time if you want to write it!
  // i2s_write(i2s_num, &audioBytes, sizeof(uint16_t) * audioBufSize, &bytes_read, portMAX_DELAY);
}

void fixAudioBuffer(uint16_t* audioSamples, size_t numSamples) {
  // [2, 0, 10, 8, 15, 13] -> [0, 2, 8, 10, 13, 15]
  for (int i = 0; i < numSamples - 1; i++) {
    if (i % 2 == 0) { // Even entry
      uint16_t leftVal = audioSamples[i];
      // Shift left value to the right one
      audioSamples[i] = audioSamples[i + 1];
      audioSamples[i + 1] = leftVal;
    }
  }
}
ytneu commented 2 years ago

Dear @LyricDev,

Thanks for your snippet, it was the first time I've run adc -> dac program on esp32 successfully.

I tried to modify it slightly to fit my case.

Simply, I wanted it to record sound from mic for a few seconds then play it back and repeat those two steps.

Here's the code:

#include <Arduino.h>
#include "driver/i2s.h"

#define MAX_SIZE 50000
#define SAVE_MAX_SIZE MAX_SIZE - 1000
#define SAMPLE_RATE 20000 // realistically all that seems to happen beyond 200ksps is the frequency accuracy gets worse (10's of %), suspect either the DAC or ADC is hitting its limit and cummulative clock drift starts to become a big thing
#define CHUNKS_IN_I2S 2
#define SAMPLES_PER_CHUNK 50
i2s_port_t i2s_port = I2S_NUM_0; // I don't believe DAC/ADC DMA is possible on the other i2s port

int16_t save_buf[MAX_SIZE];

uint16_t cnt = 0;
uint8_t record = 1;

void setup()
{
  Serial.begin(115200);
  Serial.println("Startin...");
  esp_err_t err = ESP_OK;
  i2s_config_t i2s_config = {
      .mode = (i2s_mode_t)(I2S_MODE_MASTER | I2S_MODE_RX | I2S_MODE_TX | I2S_MODE_DAC_BUILT_IN | I2S_MODE_ADC_BUILT_IN),
      .sample_rate = SAMPLE_RATE,
      .bits_per_sample = I2S_BITS_PER_SAMPLE_16BIT,
      .channel_format = I2S_CHANNEL_FMT_ONLY_LEFT, // I'm not sure if dual channel changes the max achievable sampling rate
      .communication_format = I2S_COMM_FORMAT_I2S_MSB,
      .intr_alloc_flags = 0, //? ESP_INTR_FLAG_LEVEL1
      .dma_buf_count = CHUNKS_IN_I2S,
      .dma_buf_len = SAMPLES_PER_CHUNK, // Careful, this is in SAMPLES, not bytes!
      .use_apll = true,                 // this being true somehow seems to improve sampling synch, seems like a more steady clock, even if it is slightly less accurate overall (1.5% frequency error vs 0.8% error)
      .tx_desc_auto_clear = false,      // I think this controls how the DAC output buffer is handled if it doesn't receive a constant stream of data, but it does in this example
      .fixed_mclk = 1                   // when this is set to 0 it seems the sample rate cannot go above 44.1kHz
  };
  //install and start i2s driver
  err = i2s_driver_install(i2s_port, &i2s_config, 0, NULL);
  if (err != ESP_OK)
  {
    Serial.println("i2s_driver_install err: err");
  }
  //init DAC pad
  err = i2s_set_dac_mode(I2S_DAC_CHANNEL_BOTH_EN); // not sure how this interracts with the i2s channel_format
  if (err != ESP_OK)
  {
    Serial.println("i2s_set_dac_mode err: err");
  }
  //init ADC pad
  err = i2s_set_adc_mode(ADC_UNIT_1, ADC1_CHANNEL_4); //input on gpio32
  if (err != ESP_OK)
  {
    Serial.println("i2s_set_adc_mode err: err");
  }
  err = adc1_config_channel_atten(ADC1_CHANNEL_4, ADC_ATTEN_11db);
  if (err != ESP_OK)
  {
    Serial.println("adc1_config_channel_atten err: err");
  }
  err = adc1_config_width(ADC_WIDTH_12Bit);
  if (err != ESP_OK)
  {
    Serial.println("adc1_config_width err: err");
  }
  // start the adc
  err = i2s_adc_enable(i2s_port);
  if (err != ESP_OK)
  {
    Serial.println("i2s_adc_enable err: err");
  }
  Serial.println("Done with setup");
}

void loop()
{
  size_t bytes_read, bytes_written;
  if (record)
  {
    if (cnt >= SAVE_MAX_SIZE)
    {
      Serial.println("in record: ");
      Serial.printf("%d\n", record);
      cnt = 0;
      record = 0;
    }
    else
    {
      uint16_t *i2s_read_buff = (uint16_t *)calloc(SAMPLES_PER_CHUNK, sizeof(uint16_t));

      i2s_read(i2s_port, (void *)i2s_read_buff, SAMPLES_PER_CHUNK * 2, &bytes_read, portMAX_DELAY);

      for (int i = 0; i < SAMPLES_PER_CHUNK; i++)
      {
        save_buf[cnt + i] = i2s_read_buff[i] << 4;
      }
      cnt += SAMPLES_PER_CHUNK;
      free(i2s_read_buff);
      i2s_read_buff = NULL;
    }
  }
  else
  {
    if (cnt > SAVE_MAX_SIZE)
    {
      Serial.println("in play");
      Serial.printf("%d\n", record);
      cnt = 0;
      record = 1;
    }
    else
    {
      uint16_t *i2s_read_buff = (uint16_t *)calloc(SAMPLES_PER_CHUNK, sizeof(uint16_t));

      for (int i = 0; i < SAMPLES_PER_CHUNK; i++)
      {
        i2s_read_buff[i] = save_buf[cnt + i];
        save_buf[cnt + i] = 0;
      }
      i2s_write(i2s_port, i2s_read_buff, SAMPLES_PER_CHUNK * 2, &bytes_written, portMAX_DELAY);
      cnt += SAMPLES_PER_CHUNK;
      free(i2s_read_buff);
      i2s_read_buff = NULL;
    }
  }
}

The sound starts crushing after it completes first record -> playback loop,

My guess is there's something wrong with bytes_read, bytes_written but I couldn't figure out how to handle them. Any guesses?

Thanks in advance!