Skip to content

Commit

Permalink
adc_i2s: solve the i2s_adc issue when using wifi
Browse files Browse the repository at this point in the history
  • Loading branch information
mythbuster5 committed Dec 8, 2020
1 parent 24b9109 commit 5490dbc
Show file tree
Hide file tree
Showing 7 changed files with 203 additions and 36 deletions.
10 changes: 0 additions & 10 deletions components/driver/adc1_i2s_private.h
Original file line number Diff line number Diff line change
Expand Up @@ -21,16 +21,6 @@ extern "C" {

#include "esp_err.h"


/**
* @brief Force power on for SAR ADC.
* This function should be called for the scenario in which ADC are controlled by digital function like DMA.
* When the ADC power is always on, RTC FSM can still be functional.
* This is an internal API for I2S module to call to enable I2S-ADC function.
* Note that adc_power_off() can still power down ADC.
*/
void adc_power_always_on();

/**
* @brief For I2S dma to claim the usage of ADC1.
*
Expand Down
2 changes: 1 addition & 1 deletion components/driver/i2s.c
Original file line number Diff line number Diff line change
Expand Up @@ -903,7 +903,7 @@ static esp_err_t i2s_param_config(i2s_port_t i2s_num, const i2s_config_t *i2s_co
//initialize the specific ADC channel.
//in the current stage, we only support ADC1 and single channel mode.
//In default data mode, the ADC data is in 12-bit resolution mode.
adc_power_always_on();
adc_power_acquire();
}
// configure I2S data port interface.
//reset i2s
Expand Down
24 changes: 21 additions & 3 deletions components/driver/include/driver/adc.h
Original file line number Diff line number Diff line change
Expand Up @@ -228,14 +228,32 @@ int adc1_get_voltage(adc1_channel_t channel) __attribute__((deprecated));

/**
* @brief Enable ADC power
* @deprecated Use adc_power_acquire and adc_power_release instead.
*/
void adc_power_on();
void adc_power_on(void) __attribute__((deprecated));

/**
* @brief Power off SAR ADC
* This function will force power down for ADC
* @deprecated Use adc_power_acquire and adc_power_release instead.
* This function will force power down for ADC.
* This function is deprecated because forcing power ADC power off may
* disrupt operation of other components which may be using the ADC.
*/
void adc_power_off();
void adc_power_off(void) __attribute__((deprecated));

/**
* @brief Increment the usage counter for ADC module.
* ADC will stay powered on while the counter is greater than 0.
* Call adc_power_release when done using the ADC.
*/
void adc_power_acquire(void);

/**
* @brief Decrement the usage counter for ADC module.
* ADC will stay powered on while the counter is greater than 0.
* Call this function when done using the ADC.
*/
void adc_power_release(void);

/**
* @brief Initialize ADC pad
Expand Down
4 changes: 3 additions & 1 deletion components/driver/include/driver/gpio.h
Original file line number Diff line number Diff line change
Expand Up @@ -276,9 +276,11 @@ esp_err_t gpio_set_intr_type(gpio_num_t gpio_num, gpio_int_type_t intr_type);
/**
* @brief Enable GPIO module interrupt signal
*
* @note Please do not use the interrupt of GPIO36 and GPIO39 when using ADC.
* @note Please do not use the interrupt of GPIO36 and GPIO39 when using ADC or Wi-Fi with sleep mode enabled.
* Please refer to the comments of `adc1_get_raw`.
* Please refer to section 3.11 of 'ECO_and_Workarounds_for_Bugs_in_ESP32' for the description of this issue.
* As a workaround, call adc_power_acquire() in the app. This will result in higher power consumption (by ~1mA),
* but will remove the glitches on GPIO36 and GPIO39.
*
* @param gpio_num GPIO number. If you want to enable an interrupt on e.g. GPIO16, gpio_num should be GPIO_NUM_16 (16);
*
Expand Down
72 changes: 51 additions & 21 deletions components/driver/rtc_module.c
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,14 @@ In ADC2, there're two locks used for different cases:
adc2_spinlock should be acquired first, then adc2_wifi_lock or rtc_spinlock.
*/
// This gets incremented when adc_power_acquire() is called, and decremented when
// adc_power_release() is called. ADC is powered down when the value reaches zero.
// Should be modified within critical section (ADC_ENTER/EXIT_CRITICAL).
static int s_adc_power_on_cnt;

static void adc_power_on_internal(void);
static void adc_power_off_internal(void);

//prevent ADC2 being used by wifi and other tasks at the same time.
static _lock_t adc2_wifi_lock;
//prevent ADC2 being used by tasks (regardless of WIFI)
Expand Down Expand Up @@ -1143,32 +1151,49 @@ static esp_err_t adc_set_atten(adc_unit_t adc_unit, adc_channel_t channel, adc_a
return ESP_OK;
}

void adc_power_always_on()
void adc_power_acquire()
{
bool powered_on = false;
portENTER_CRITICAL(&rtc_spinlock);
SENS.sar_meas_wait2.force_xpd_sar = SENS_FORCE_XPD_SAR_PU;
s_adc_power_on_cnt++;
if (s_adc_power_on_cnt == 1) {
adc_power_on_internal();
powered_on = true;
}
portEXIT_CRITICAL(&rtc_spinlock);
if (powered_on) {
ESP_LOGV(TAG, "%s: ADC powered on", __func__);
}
}

void adc_power_release(void)
{
bool powered_off = false;
portENTER_CRITICAL(&rtc_spinlock);
s_adc_power_on_cnt--;
if (s_adc_power_on_cnt < 0) {
portEXIT_CRITICAL(&rtc_spinlock);
} else if (s_adc_power_on_cnt == 0) {
adc_power_off_internal();
powered_off = true;
}
portEXIT_CRITICAL(&rtc_spinlock);
if (powered_off) {
ESP_LOGV(TAG, "%s: ADC powered off", __func__);
}
}

void adc_power_on()
static void adc_power_on_internal(void)
{
portENTER_CRITICAL(&rtc_spinlock);
//The power FSM controlled mode saves more power, while the ADC noise may get increased.
#ifndef CONFIG_ADC_FORCE_XPD_FSM
//Set the power always on to increase precision.
SENS.sar_meas_wait2.force_xpd_sar = SENS_FORCE_XPD_SAR_PU;
#else
//Use the FSM to turn off the power while not used to save power.
if (SENS.sar_meas_wait2.force_xpd_sar & SENS_FORCE_XPD_SAR_SW_M) {
SENS.sar_meas_wait2.force_xpd_sar = SENS_FORCE_XPD_SAR_PU;
} else {
SENS.sar_meas_wait2.force_xpd_sar = SENS_FORCE_XPD_SAR_FSM;
}
#endif
portEXIT_CRITICAL(&rtc_spinlock);
}

void adc_power_off()
void adc_power_on(void) __attribute__((alias("adc_power_on_internal")));

static void adc_power_off_internal(void)
{
portENTER_CRITICAL(&rtc_spinlock);
//Bit1 0:Fsm 1: SW mode
Expand All @@ -1177,6 +1202,8 @@ void adc_power_off()
portEXIT_CRITICAL(&rtc_spinlock);
}

void adc_power_off(void) __attribute__((alias("adc_power_off_internal")));

esp_err_t adc_set_clk_div(uint8_t clk_div)
{
portENTER_CRITICAL(&rtc_spinlock);
Expand Down Expand Up @@ -1395,7 +1422,7 @@ esp_err_t adc_i2s_mode_init(adc_unit_t adc_unit, adc_channel_t channel)

uint8_t table_len = 1;
//POWER ON SAR
adc_power_always_on();
adc_power_acquire();
adc_gpio_init(adc_unit, channel);
adc_set_i2s_data_len(adc_unit, table_len);
adc_set_i2s_data_pattern(adc_unit, 0, channel, ADC_WIDTH_BIT_12, ADC_ATTEN_DB_11);
Expand Down Expand Up @@ -1540,7 +1567,7 @@ int adc1_get_raw(adc1_channel_t channel)
uint16_t adc_value;
RTC_MODULE_CHECK(channel < ADC1_CHANNEL_MAX, "ADC Channel Err", ESP_ERR_INVALID_ARG);
adc1_adc_mode_acquire();
adc_power_on();
adc_power_acquire();

portENTER_CRITICAL(&rtc_spinlock);
//disable other peripherals
Expand All @@ -1551,6 +1578,7 @@ int adc1_get_raw(adc1_channel_t channel)
//start conversion
adc_value = adc_convert( ADC_UNIT_1, channel );
portEXIT_CRITICAL(&rtc_spinlock);
adc_power_release();
adc1_lock_release();
return adc_value;
}
Expand All @@ -1562,7 +1590,7 @@ int adc1_get_voltage(adc1_channel_t channel) //Deprecated. Use adc1_get_raw()

void adc1_ulp_enable(void)
{
adc_power_on();
adc_power_acquire();

portENTER_CRITICAL(&rtc_spinlock);
adc_set_controller( ADC_UNIT_1, ADC_CTRL_ULP );
Expand Down Expand Up @@ -1701,14 +1729,15 @@ esp_err_t adc2_get_raw(adc2_channel_t channel, adc_bits_width_t width_bit, int*
RTC_MODULE_CHECK(channel < ADC2_CHANNEL_MAX, "ADC Channel Err", ESP_ERR_INVALID_ARG);

//in critical section with whole rtc module
adc_power_on();
adc_power_acquire();

//avoid collision with other tasks
portENTER_CRITICAL(&adc2_spinlock);
//lazy initialization
//try the lock, return if failed (wifi using).
if ( _lock_try_acquire( &adc2_wifi_lock ) == -1 ) {
portEXIT_CRITICAL( &adc2_spinlock );
adc_power_release();
return ESP_ERR_TIMEOUT;
}

Expand All @@ -1725,7 +1754,7 @@ esp_err_t adc2_get_raw(adc2_channel_t channel, adc_bits_width_t width_bit, int*
adc_value = adc_convert( ADC_UNIT_2, channel );
_lock_release( &adc2_wifi_lock );
portEXIT_CRITICAL(&adc2_spinlock);

adc_power_release();
*raw_out = (int)adc_value;
return ESP_OK;
}
Expand All @@ -1750,7 +1779,7 @@ esp_err_t adc2_vref_to_gpio(gpio_num_t gpio)
rtc_gpio_pullup_dis(gpio);
rtc_gpio_pulldown_dis(gpio);
//force fsm
adc_power_always_on(); //Select power source of ADC
adc_power_acquire(); //Select power source of ADC

RTCCNTL.bias_conf.dbg_atten = 0; //Check DBG effect outside sleep mode
//set dtest (MUX_SEL : 0 -> RTC; 1-> vdd_sar2)
Expand Down Expand Up @@ -1931,7 +1960,7 @@ static int hall_sensor_get_value() //hall sensor without LNA
int Sens_Vn1;
int hall_value;

adc_power_on();
adc_power_acquire();

portENTER_CRITICAL(&rtc_spinlock);
//disable other peripherals
Expand All @@ -1948,6 +1977,7 @@ static int hall_sensor_get_value() //hall sensor without LNA
Sens_Vn1 = adc_convert( ADC_UNIT_1, ADC1_CHANNEL_3 );
portEXIT_CRITICAL(&rtc_spinlock);
hall_value = (Sens_Vp1 - Sens_Vp0) - (Sens_Vn1 - Sens_Vn0);
adc_power_release();

return hall_value;
}
Expand Down
108 changes: 108 additions & 0 deletions components/driver/test/test_adc2.c
Original file line number Diff line number Diff line change
Expand Up @@ -11,11 +11,16 @@
#include "esp_log.h"
#include "nvs_flash.h"
#include "test_utils.h"
#include "driver/i2s.h"
#include "driver/gpio.h"

static const char* TAG = "test_adc2";

#define DEFAULT_SSID "TEST_SSID"
#define DEFAULT_PWD "TEST_PASS"
#define ADC1_CHANNEL_4_IO (32)
#define SAMPLE_RATE (36000)
#define SAMPLE_BITS (16)

static esp_err_t event_handler(void *ctx, system_event_t *event)
{
Expand Down Expand Up @@ -116,3 +121,106 @@ TEST_CASE("adc2 work with wifi","[adc]")

TEST_IGNORE_MESSAGE("this test case is ignored due to the critical memory leak of tcpip_adapter and event_loop.");
}

static void i2s_adc_init(void)
{
i2s_config_t i2s_config = {
.mode = I2S_MODE_MASTER | I2S_MODE_RX | I2S_MODE_ADC_BUILT_IN,
.sample_rate = SAMPLE_RATE,
.bits_per_sample = SAMPLE_BITS,
.channel_format = I2S_CHANNEL_FMT_RIGHT_LEFT,
.intr_alloc_flags = 0,
.dma_buf_count = 2,
.dma_buf_len = 1024,
.use_apll = 0,
};
// install and start I2S driver
i2s_driver_install(I2S_NUM_0, &i2s_config, 0, NULL);
// init ADC pad
i2s_set_adc_mode(ADC_UNIT_1, ADC1_CHANNEL_4);
// enable adc sampling, ADC_WIDTH_BIT_12, ADC_ATTEN_DB_11 hard-coded in adc_i2s_mode_init
i2s_adc_enable(I2S_NUM_0);
}

static void i2s_adc_test(void)
{
uint16_t *i2sReadBuffer = (uint16_t *)calloc(1024, sizeof(uint16_t));
size_t bytesRead;
for (int loop = 0; loop < 10; loop++) {
for (int level = 0; level <= 1; level++) {
if (level == 0) {
gpio_set_pull_mode(ADC1_CHANNEL_4_IO, GPIO_PULLDOWN_ONLY);
} else {
gpio_set_pull_mode(ADC1_CHANNEL_4_IO, GPIO_PULLUP_ONLY);
}
vTaskDelay(200 / portTICK_RATE_MS);
// read data from adc, will block until buffer is full
i2s_read(I2S_NUM_0, (void *)i2sReadBuffer, 1024 * sizeof(uint16_t), &bytesRead, portMAX_DELAY);

// calc average
int64_t adcSumValue = 0;
for (size_t i = 0; i < 1024; i++) {
adcSumValue += i2sReadBuffer[i] & 0xfff;
}
int adcAvgValue = adcSumValue / 1024;
printf("adc average val: %d\n", adcAvgValue);

if (level == 0) {
TEST_ASSERT_LESS_THAN(100, adcAvgValue);
} else {
TEST_ASSERT_GREATER_THAN(4000, adcAvgValue);
}
}
}
free(i2sReadBuffer);
}

static void i2s_adc_release(void)
{
i2s_adc_disable(I2S_NUM_0);
i2s_driver_uninstall(I2S_NUM_0);
}

TEST_CASE("adc1 and i2s work with wifi","[adc]")
{

i2s_adc_init();
//init wifi
printf("nvs init\n");
esp_err_t r = nvs_flash_init();
if (r == ESP_ERR_NVS_NO_FREE_PAGES || r == ESP_ERR_NVS_NEW_VERSION_FOUND) {
printf("no free pages or nvs version mismatch, erase..\n");
TEST_ESP_OK(nvs_flash_erase());
r = nvs_flash_init();
}
TEST_ESP_OK(r);
tcpip_adapter_init();
wifi_init_config_t cfg = WIFI_INIT_CONFIG_DEFAULT();
TEST_ESP_OK(esp_wifi_init(&cfg));
wifi_config_t wifi_config = {
.sta = {
.ssid = DEFAULT_SSID,
.password = DEFAULT_PWD
},
};
TEST_ESP_OK(esp_wifi_set_mode(WIFI_MODE_STA));
TEST_ESP_OK(esp_wifi_set_config(ESP_IF_WIFI_STA, &wifi_config));
i2s_adc_test();
//now start wifi
printf("wifi start...\n");
TEST_ESP_OK(esp_wifi_start());
//test reading during wifi on
i2s_adc_test();
//wifi stop again
printf("wifi stop...\n");

TEST_ESP_OK( esp_wifi_stop() );

TEST_ESP_OK(esp_wifi_deinit());

nvs_flash_deinit();
i2s_adc_test();
i2s_adc_release();
printf("test passed...\n");
TEST_IGNORE_MESSAGE("this test case is ignored due to the critical memory leak of esp_netif and event_loop.");
}
Loading

0 comments on commit 5490dbc

Please sign in to comment.