esp-idf: I2S ADC channel order is unexpected/doubled up (IDFGH-1408)
Environment
- Development Kit: Wemos Lolin D32
- Module or chip used: ESP32-WROOM-32
- IDF version:
v3.3-beta2andmaster(58df1d93) but note modifications below - Build System: Cmake
- Compiler version:
1.22.0-80-g6c4433a - Operating System: Linux (Ubuntu 18.04)
- Power Supply: USB
Problem Description
I’m trying to use the I2S direct-from-ADC functionality in my firmware, with multiple channels.
Note: I have modified the IDF code with the following changes:
- Exposed (removed static, added to header)
adc_set_i2s_data_len()fromcomponents/driver/rtc_module.cviacomponents/driver/include/driver/adc.h - Exposed
adc_set_i2s_data_pattern()(same). - Removed the call to
_i2s_adc_mode_recover()ini2s_adc_enable()incomponents/driver/i2s.c
These changes are necessary to allow connecting multiple ADCs to the I2S module (see #2980 , @montaguk’s comment).
I have some example code that simply reads the I2S data as it is generated and extracts the channel it originated from. The Technical Manual says:
ESP32 supports up to a 12-bit SAR ADC resolution. The 16-bit data in DMA is composed of the ADC result and some necessary information related to the scanning mode:
- For single mode, only 4-bit information on channel selection is added.
So this is done with code like:
(data & 0xF0) >> 4
…applied to every second char, offset by one (to get the MSB).
But when I actually run my code (below), I do not see alternating channels eg 5, 6, 5, 6. I see channels doubled up and sometimes skipped eg. 5, 6, 6, 5, 5, 6, 6, 5, 6.
Expected Behavior
The Technical Manual says:
The pattern tables contain the measurement rules mentioned above. Each table has 16 items which store information on channel selection, resolution and attenuation. When scanning starts, the controller reads measurement rules one-by-one from a pattern table. For each controller the scanning sequence includes 16 different rules at most, before repeating itself.
So given the setup:
adc_set_i2s_data_len(ADC_UNIT_1, 2);
adc_set_i2s_data_pattern(ADC_UNIT_1, 0, ADC_CHANNEL_6, ADC_WIDTH_BIT_12, ADC_ATTEN_DB_11);
adc_set_i2s_data_pattern(ADC_UNIT_1, 1, ADC_CHANNEL_5, ADC_WIDTH_BIT_12, ADC_ATTEN_DB_11);
I would expect the I2S-triggered scanning to simply go 6, 5, 6, 5, 6, 5, and the resulting I2S data to have that same pattern.
Actual Behavior
My code prints:
I (30673) i2s-test: CH: 6 5 5 6 6 5 5 6
I (30673) i2s-test: CH: 6 5 5 6 6 5 5 6
I (30673) i2s-test: CH: 6 5 5 6 6 5 5 6
I (30683) i2s-test: CH: 6 5 5 6 6 5 5 6
I (30683) i2s-test: CH: 6 5 5 6 6 5 5 6
I (30693) i2s-test: CH: 6 5 6 6 5 6 6 5
I (30693) i2s-test: CH: 6 6 5 6 5 5 6 5
I (30703) i2s-test: CH: 5 6 6 5 5 6 6 5
Note the repetition of each channel.
Steps to repropduce
Build and run the code I link to with idf.py build and idf.py flash monitor. Don’t forget to make the modifications I mention above.
Code to reproduce this issue
I have put the entire example project on Gitlab here. But here is the essential setup code for the peripherals:
static void app_i2s_adc_init()
{
int i2s_num = I2S_NUM_0;
i2s_config_t i2s_config = {
.mode = I2S_MODE_MASTER | I2S_MODE_RX | I2S_MODE_ADC_BUILT_IN,
.sample_rate = 24000,
.bits_per_sample = 16,
.communication_format = I2S_COMM_FORMAT_I2S_MSB,
.channel_format = I2S_CHANNEL_FMT_RIGHT_LEFT,
.intr_alloc_flags = 0,
.dma_buf_count = 2,
.dma_buf_len = 1024,
.use_apll = 1,
.fixed_mclk = 0
};
i2s_driver_install(i2s_num, &i2s_config, 0, NULL);
i2s_set_adc_mode(ADC_UNIT_1, ADC1_CHANNEL_6);
adc_set_i2s_data_len(ADC_UNIT_1, 2);
adc_set_i2s_data_pattern(ADC_UNIT_1, 0, ADC_CHANNEL_6, ADC_WIDTH_BIT_12, ADC_ATTEN_DB_11);
adc_set_i2s_data_pattern(ADC_UNIT_1, 1, ADC_CHANNEL_5, ADC_WIDTH_BIT_12, ADC_ATTEN_DB_11);
}
I would also like to know what the proper communication_format is to use for direct-from-ADC operation, because it does seem to affect the result.
Debug Logs
Here’s the log of the first set of reads:
I (0) cpu_start: App cpu up.
I (225) heap_init: Initializing. RAM available for dynamic allocation:
I (232) heap_init: At 3FFAE6E0 len 00001920 (6 KiB): DRAM
I (238) heap_init: At 3FFB2F10 len 0002D0F0 (180 KiB): DRAM
I (244) heap_init: At 3FFE0440 len 00003AE0 (14 KiB): D/IRAM
I (250) heap_init: At 3FFE4350 len 0001BCB0 (111 KiB): D/IRAM
I (257) heap_init: At 400883B0 len 00017C50 (95 KiB): IRAM
I (263) cpu_start: Pro cpu start user code
I (281) cpu_start: Starting scheduler on PRO CPU.
I (0) cpu_start: Starting scheduler on APP CPU.
I (283) I2S: DMA Malloc info, datalen=blocksize=4092, dma_buf_count=2
I (283) I2S: APLL: Req RATE: 24000, real rate: 11999.990, BITS: 16, CLKM: 1, BCK_M: 8, MCLK: 3071997.500, SCLK: 383999.687500, diva: 1, divb: 0
I (303) i2s-test: repeat #0
I (303) i2s-test: enabling I2S ADC
I (303) I2S: APLL: Req RATE: 24000, real rate: 11999.990, BITS: 16, CLKM: 1, BCK_M: 8, MCLK: 3071997.500, SCLK: 383999.687500, diva: 1, divb: 0
I (323) i2s-test: CH: 5 6 6 5 5 6 6 5
I (323) i2s-test: CH: 5 6 6 5 5 6 6 5
I (323) i2s-test: CH: 5 6 6 5 5 6 6 5
I (333) i2s-test: CH: 6 6 5 6 6 5 5 6
I (333) i2s-test: CH: 6 5 5 6 6 5 5 6
I (343) i2s-test: CH: 6 5 5 6 6 5 5 6
I (343) i2s-test: CH: 6 5 5 6 6 5 5 6
I (353) i2s-test: CH: 6 5 5 6 6 5 5 6
I (353) i2s-test: disabling I2S ADC
Happy to provide more information if it helps.
About this issue
- Original URL
- State: closed
- Created 5 years ago
- Reactions: 1
- Comments: 23 (6 by maintainers)
I read this too, I think it was a PR, not an issue. If this is a case, it absolutely needs to be documented front and centre in the TRM and the API, because it is totally at odds with every other embedded system out there, and means that the ADC-to-I2S functionality — an advertised feature of this system — is more or less useless for a great many applications. On top of that, letting engineers waste this much time and effort trying to debug it, instead of simply being honest about the limitation, is unacceptable.
I actually do, I see it quite clearly with a sine tone generator or audio calibration signal.
I’ve read in one of the issues (don’t remember which) here that the HW sampler needs 2 clocks to restart sampling when it’s done a batch (instead of restarting immediately), so it’s always missing one sample, that’s probably the reason for the repetition, in fact, it has missed one value.
If you had a non constant voltage source, you’d see the effect.