ESP32: I2S ADC read. Two inputs possible?

Hi Guys,

I need to read an analog input quickly.

analogRead() was way too slow and was messing the rest of my code up.

adc1_get_raw(ADC1_CHANNEL_3) was somewhat faster, but still not exactly what I was looking for.

I started looking at I2S, and although I don't understand it fully yet, I managed to whittle away at some code I found online and came up with this:

#include <driver/i2s.h>

// I2S
#define I2S_SAMPLE_RATE (3000)
#define ADC_INPUT (ADC1_CHANNEL_4) //pin 32
#define I2S_DMA_BUF_LEN (8)

// The 4 high bits are the channel, and the data is inverted
size_t bytes_read;
uint16_t buffer[I2S_DMA_BUF_LEN] = {0};

unsigned long lastTimePrinted;
unsigned long loopTime = 0;

void i2sInit() {
  i2s_config_t i2s_config = {
    .mode = (i2s_mode_t)(I2S_MODE_MASTER | I2S_MODE_RX | I2S_MODE_ADC_BUILT_IN),
    .sample_rate =  I2S_SAMPLE_RATE,              // The format of the signal using ADC_BUILT_IN
    .bits_per_sample = I2S_BITS_PER_SAMPLE_16BIT, // is fixed at 12bit, stereo, MSB
    .channel_format = I2S_CHANNEL_FMT_RIGHT_LEFT,
    .communication_format = I2S_COMM_FORMAT_I2S_MSB,
    .intr_alloc_flags = ESP_INTR_FLAG_LEVEL1,
    .dma_buf_count = 2,
    .dma_buf_len = I2S_DMA_BUF_LEN,
    .use_apll = false,
    .tx_desc_auto_clear = false,
    .fixed_mclk = 0
  };

  i2s_driver_install(I2S_NUM_0, &i2s_config, 0, NULL);
  i2s_set_adc_mode(ADC_UNIT_1, ADC_INPUT);
  i2s_adc_enable(I2S_NUM_0);
  adc1_config_channel_atten(ADC_INPUT, ADC_ATTEN_DB_11);
}

void setup() {
  Serial.begin(115200);

  i2sInit();
}

void loop() {

  unsigned long startMicros = ESP.getCycleCount();

  i2s_read(I2S_NUM_0, &buffer, sizeof(buffer), &bytes_read, 0);

  unsigned long stopMicros = ESP.getCycleCount();

  loopTime = stopMicros - startMicros;

  if (millis() - lastTimePrinted >= 100) {
    Serial.println("------------------");
    Serial.println(buffer[0] & 0x0FFF);
    Serial.println(loopTime);
    lastTimePrinted = millis();
  }
}

This works really well, and I'm happy. Need to study I2S some more to get a better grasp of it.

However, I'd like to read another analog input but I'm not sure how it can be done (if at all) using I2S.

I tried fiddling a bit but not really understanding it 100%. With any changes it either doesn't compile, or has no output.

Any ideas? Any snippets of code doing something similar?

Cheers,

Matt

Hi Matt, can you tell us what hardware you are using please?

@johnerrington
Apologies for the vagueness.

ESP32 dev kit.

The analog value I am reading is from a 5k potentiometer. 3.3v on one side and ground on the other. The pot is limited in movement so the output values are 700 ~ 3200.

The pot is connected to a motor. The motor is driven by a TB6612FNG driver. The position of the motor is relative to rpm. The motor will control the movement of an exhaust valve in a motorcycle engine.

It's a small part of a bigger project, which is a capacitive discharge ignition system.

It all works well, so at tis point the hardware and code works well. I'd just like to know if I can add another pot and read it with I2S.

So that would be two 5k pots. Both 3.3V.

Hope that's enough info.

Cheers

Matt

The pot provides feedback on the motor position.

Hi,

Have now stumbled across another issue. While the I2S is doing it's thing, I can't read another analog input.

Tried a couple of things like i2s_adc_disable(I2S_NUM_0); and i2s_stop(I2S_NUM_0); but not having any joy.

A bit out of my depth.

Any clues?

Cheers,

Matt

Matt, I'm not understanding. What is the device you are connecting to the ESP32 via I2S?

2 pots should not be moving quickly enough that the time taken for an analog read would be an issue. Perhaps you are reading them too frequently?

@johnerrington
I've just got the wiper from a potentiometer hooked up to pin 32. The potentiometer gives feedback from a motor so that I can monitor the position and turn it CW or CCW as necessary.

No, the pots don't move fast enough, but an analogRead() takes forever.

I times my loop() and it's running start to finish in 25us using the I2S to read the pot.

As to reading it too frequently....good point. Probably only need to read it every 100~200ms, so let me give that a try using the adc1_get_raw(ADC1_CHANNEL_4) function and see what happens.

Will report back on that.

However, regardless of the above, is it possible to set the I2S up to read two channels? I found the following in the Espressif docs:

i2s_channel_disable and i2s_channel_enable

I suspect that I have to use these somehow, and then change the config to a different pin while disabled, and then re-enable. Or something like that.

Cheers for your assistance.

Matt

That's not how I2S works. I2S is a digital interface, it doesn't read analog pot values. You need to connect an external ADC with an I2S interface to the ESP32.

There is a variant of I2S called TDM that allows connecting multiple devices. However, both the processor and external devices must support it. The Teensy 3.2 and above boards support TDM. Here's an example interface board: https://oshpark.com/shared_projects/2Yj6rFaW

PS - I'd probably go with a digital means of determining motor position. Perhaps a rotary encoder.

Inter-IC Sound (I2S) - ESP32 - — ESP-IDF Programming Guide latest documentation <<the docs

Some words from the docs,

ADC and DAC modes only exist on ESP32 and are only supported on I2S0. Actually, they are two sub-modes of LCD/Camera mode. I2S0 can be routed directly to the internal analog-to-digital converter(ADC) and digital-to-analog converter(DAC). In other words, ADC and DAC peripherals can read or write continuously via I2S0 DMA. As they are not an actual communication mode, the I2S driver does not implement them.

@gfvalvo
Thanks. With my pot wiper as the input I am getting very fast reading of the input value and the value is exactly as expected, so using I2S for this "analog read" is working perfectly as far as I see it.

@Idahowalker
Been through that doc a few times. Will need to go through a few more times to fully 'get it'. But thanks for the link.

I've since found some code that disables the I2S, does a regular analogRead() and then enables the I2S once more. Not just the disable and then enable as I tried. Here's the code required, although I haven't tested it yet:

  i2s_adc_disable(I2S_NUM_0);
  i2s_driver_uninstall(I2S_NUM_0);
  analogRead(34);
  i2s_driver_install(I2S_NUM_0, &i2s_config, 0, NULL);
  i2s_set_adc_mode(ADC_UNIT_1, ADC1_CHANNEL_0);
  i2s_adc_enable(I2S_NUM_0);

I'll give it a bash and see what happens.

Cheers,

Matt

1 Like
  size_t bytesRead = 0;
  i2s_set_adc_mode(ADC_UNIT_1, ADC1_CHANNEL_6);
  i2s_read(I2S_NUM_0, (void*)samples, sizeof(samples), &bytesRead, portMAX_DELAY); 
FFT01 handle

-------------------------------------------------------

   bytesRead = 0;
  i2s_set_adc_mode(ADC_UNIT_1, ADC1_CHANNEL_7);
  i2s_read(I2S_NUM_0, (void*)samples02, sizeof(samples02), &bytesRead, portMAX_DELAY);
FFT02 handle

The difference is 3us, but two FFT processing will take a lot of time.

I'm trying to read two channels from the internal esp32 adc. Dispite all the tips I didn't manage to get result. One channel works fine. Is it possible the post an example reading two adc channels via i2s?

This topic was automatically closed 180 days after the last reply. New replies are no longer allowed.