diff --git a/lib/gpio/adc.toit b/lib/gpio/adc.toit index 6b9a4c102..921ba7974 100644 --- a/lib/gpio/adc.toit +++ b/lib/gpio/adc.toit @@ -10,11 +10,10 @@ Analog-to-Digital Conversion. This library provides ways to read analogue voltage values from GPIO pins that support it. -On the ESP32, there are two ADCs. ADC1 (pins 32-39) should be preferred as - ADC2 (pins 0, 2, 4, 12-15, 25-27) has lots of restrictions. It can't be - used when WiFi is active, and some of the pins are - strapping pins). By default, ADC2 is disabled, and users need to pass in a flag to - allow its use. +On most ESP32 variants, there are two ADCs. ADC1 should be preferred as + ADC2 has lots of restrictions. It can't be used when WiFi is active, and some + of the pins are strapping pins. +By default, ADC2 is disabled, and users need to pass in a flag to allow its use. # Examples ``` @@ -22,10 +21,36 @@ import gpio import gpio.adc show Adc main: - adc := Adc (gpio.Pin 34) + pin := gpio.Pin 34 + adc := Adc pin print adc.get adc.close + pin.close ``` + +# Pins + +## ESP32 +ADC1: Pins 32-39 +ADC2: Pins 0, 2, 4, 12-15, 25-27. + +## ESP32C3 +ADC1: Pins 0-4 +Pin 5 is an ADC pin of channel 2. However, the controller of ADC channel 2 + may enter an inoperative state and was therefore disabled by Espressif. + An errata was published. + +## ESP32C6 +ADC1: Pins 0-6 +No ADC2. + +## ESP32S2 +ADC1: Pins 1-10 +ADC2: Pins 11-20 + +## ESP32S3 +ADC1: Pins 1-10 +ADC2: Pins 11-20 */ /** @@ -44,17 +69,14 @@ class Adc: tune the attenuation of the underlying ADC unit. If no $max-voltage is provided, the ADC uses the maximum voltage range of the pin. - If $allow-restricted is true, allows pins that are restricted. - See the ESP32 section below. - - # ESP32 - On the ESP32, there are two ADCs. ADC1 (pins 32-39) should be preferred as - ADC2 (pins 0, 2, 4, 12-15, 25-27) has lots of restrictions. It can't be - used when WiFi is active, and some of the pins are - strapping pins). By default, ADC2 is disabled, and users need to pass in the + # ESP32 and variants. + On most ESP32 variants, there are two ADCs. Typically, ADC1 should be + preferred as ADC2 has lots of restrictions. It can't be + used when WiFi is active, and some of the pins are strapping pins. + By default, ADC2 is disabled, and users need to pass in the $allow-restricted flag to allow its use. */ - constructor .pin --max-voltage/float?=null --allow-restricted/bool=false: + constructor .pin --max-voltage/float?=null --allow-restricted/bool?=false: resource_ = adc-init_ resource-freeing-module_ pin.num allow-restricted (max-voltage ? max-voltage : 0.0) /** diff --git a/lib/gpio/gpio.toit b/lib/gpio/gpio.toit index 2f265f0fd..21af019e9 100644 --- a/lib/gpio/gpio.toit +++ b/lib/gpio/gpio.toit @@ -37,8 +37,9 @@ Pins 12-17 are normally connected to flash/PSRAM, and should not be used. Pins 18-19 are JTAG pins, and should not be used if JTAG support is needed. Pins 0-5 are RTC pins and can be used in deep-sleep. Pins 0-4 are ADC pins of channel 1. -Pin 5 is an ADC pin of channel 2. ADC channel 2 has restrictions and should be - avoided if possible. +Pin 5 is an ADC pin of channel 2. However, the controller of ADC channel 2 + may enter an inoperative state and was therefore disabled by Espressif. + An errata was published. # ESP23C6 The ESP32C6 has 31 physical pins (0-30). Each pin can be used as diff --git a/src/resources/adc_esp32.cc b/src/resources/adc_esp32.cc index bcf6448c6..60f9c7ca7 100644 --- a/src/resources/adc_esp32.cc +++ b/src/resources/adc_esp32.cc @@ -17,9 +17,10 @@ #ifdef TOIT_ESP32 -#include -#include -#include +#include +#include +#include +#include #include "../entropy_mixer.h" #include "../objects_inline.h" @@ -36,31 +37,31 @@ namespace toit { static int get_adc1_channel(int pin) { switch (pin) { - case 36: return ADC1_CHANNEL_0; - case 37: return ADC1_CHANNEL_1; - case 38: return ADC1_CHANNEL_2; - case 39: return ADC1_CHANNEL_3; - case 32: return ADC1_CHANNEL_4; - case 33: return ADC1_CHANNEL_5; - case 34: return ADC1_CHANNEL_6; - case 35: return ADC1_CHANNEL_7; - default: return adc1_channel_t(-1); + case 36: return ADC_CHANNEL_0; + case 37: return ADC_CHANNEL_1; + case 38: return ADC_CHANNEL_2; + case 39: return ADC_CHANNEL_3; + case 32: return ADC_CHANNEL_4; + case 33: return ADC_CHANNEL_5; + case 34: return ADC_CHANNEL_6; + case 35: return ADC_CHANNEL_7; + default: return -1; } } static int get_adc2_channel(int pin) { switch (pin) { - case 4: return ADC2_CHANNEL_0; - case 0: return ADC2_CHANNEL_1; - case 2: return ADC2_CHANNEL_2; - case 15: return ADC2_CHANNEL_3; - case 13: return ADC2_CHANNEL_4; - case 12: return ADC2_CHANNEL_5; - case 14: return ADC2_CHANNEL_6; - case 27: return ADC2_CHANNEL_7; - case 25: return ADC2_CHANNEL_8; - case 26: return ADC2_CHANNEL_9; - default: return adc2_channel_t(-1); + case 4: return ADC_CHANNEL_0; + case 0: return ADC_CHANNEL_1; + case 2: return ADC_CHANNEL_2; + case 15: return ADC_CHANNEL_3; + case 13: return ADC_CHANNEL_4; + case 12: return ADC_CHANNEL_5; + case 14: return ADC_CHANNEL_6; + case 27: return ADC_CHANNEL_7; + case 25: return ADC_CHANNEL_8; + case 26: return ADC_CHANNEL_9; + default: return -1; } } @@ -68,34 +69,34 @@ static int get_adc2_channel(int pin) { static int get_adc1_channel(int pin) { switch (pin) { - case 0: return ADC1_CHANNEL_0; - case 1: return ADC1_CHANNEL_1; - case 2: return ADC1_CHANNEL_2; - case 3: return ADC1_CHANNEL_3; - case 4: return ADC1_CHANNEL_4; - default: return adc1_channel_t(-1); + case 0: return ADC_CHANNEL_0; + case 1: return ADC_CHANNEL_1; + case 2: return ADC_CHANNEL_2; + case 3: return ADC_CHANNEL_3; + case 4: return ADC_CHANNEL_4; + default: return -1; } } static int get_adc2_channel(int pin) { - switch (pin) { - case 5: return ADC2_CHANNEL_0; - default: return adc2_channel_t(-1); - } + // On ESP32C3, ADC2 is no longer supported, due to its HW limitation. + // There was an errata on the espressif website. + // Pin 5 is still connected to ADC2, but we don't allow to use it. + return -1; } #elif CONFIG_IDF_TARGET_ESP32C6 static int get_adc1_channel(int pin) { switch (pin) { - case 0: return ADC1_CHANNEL_0; - case 1: return ADC1_CHANNEL_1; - case 2: return ADC1_CHANNEL_2; - case 3: return ADC1_CHANNEL_3; - case 4: return ADC1_CHANNEL_4; - case 5: return ADC1_CHANNEL_5; - case 6: return ADC1_CHANNEL_6; - default: return adc1_channel_t(-1); + case 0: return ADC_CHANNEL_0; + case 1: return ADC_CHANNEL_1; + case 2: return ADC_CHANNEL_2; + case 3: return ADC_CHANNEL_3; + case 4: return ADC_CHANNEL_4; + case 5: return ADC_CHANNEL_5; + case 6: return ADC_CHANNEL_6; + default: return -1; } } @@ -108,33 +109,33 @@ static int get_adc2_channel(int pin) { static int get_adc1_channel(int pin) { switch (pin) { - case 1: return ADC1_CHANNEL_0; - case 2: return ADC1_CHANNEL_1; - case 3: return ADC1_CHANNEL_2; - case 4: return ADC1_CHANNEL_3; - case 5: return ADC1_CHANNEL_4; - case 6: return ADC1_CHANNEL_5; - case 7: return ADC1_CHANNEL_6; - case 8: return ADC1_CHANNEL_7; - case 9: return ADC1_CHANNEL_8; - case 10: return ADC1_CHANNEL_9; - default: return adc1_channel_t(-1); + case 1: return ADC_CHANNEL_0; + case 2: return ADC_CHANNEL_1; + case 3: return ADC_CHANNEL_2; + case 4: return ADC_CHANNEL_3; + case 5: return ADC_CHANNEL_4; + case 6: return ADC_CHANNEL_5; + case 7: return ADC_CHANNEL_6; + case 8: return ADC_CHANNEL_7; + case 9: return ADC_CHANNEL_8; + case 10: return ADC_CHANNEL_9; + default: return -1; } } static int get_adc2_channel(int pin) { switch (pin) { - case 11: return ADC2_CHANNEL_0; - case 12: return ADC2_CHANNEL_1; - case 13: return ADC2_CHANNEL_2; - case 14: return ADC2_CHANNEL_3; - case 15: return ADC2_CHANNEL_4; - case 16: return ADC2_CHANNEL_5; - case 17: return ADC2_CHANNEL_6; - case 18: return ADC2_CHANNEL_7; - case 19: return ADC2_CHANNEL_8; - case 20: return ADC2_CHANNEL_9; - default: return adc2_channel_t(-1); + case 11: return ADC_CHANNEL_0; + case 12: return ADC_CHANNEL_1; + case 13: return ADC_CHANNEL_2; + case 14: return ADC_CHANNEL_3; + case 15: return ADC_CHANNEL_4; + case 16: return ADC_CHANNEL_5; + case 17: return ADC_CHANNEL_6; + case 18: return ADC_CHANNEL_7; + case 19: return ADC_CHANNEL_8; + case 20: return ADC_CHANNEL_9; + default: return -1; } } @@ -142,60 +143,187 @@ static int get_adc2_channel(int pin) { static int get_adc1_channel(int pin) { switch (pin) { - case 1: return ADC1_CHANNEL_0; - case 2: return ADC1_CHANNEL_1; - case 3: return ADC1_CHANNEL_2; - case 4: return ADC1_CHANNEL_3; - case 5: return ADC1_CHANNEL_4; - case 6: return ADC1_CHANNEL_5; - case 7: return ADC1_CHANNEL_6; - case 8: return ADC1_CHANNEL_7; - case 9: return ADC1_CHANNEL_8; - case 10: return ADC1_CHANNEL_9; - default: return adc1_channel_t(-1); + case 1: return ADC_CHANNEL_0; + case 2: return ADC_CHANNEL_1; + case 3: return ADC_CHANNEL_2; + case 4: return ADC_CHANNEL_3; + case 5: return ADC_CHANNEL_4; + case 6: return ADC_CHANNEL_5; + case 7: return ADC_CHANNEL_6; + case 8: return ADC_CHANNEL_7; + case 9: return ADC_CHANNEL_8; + case 10: return ADC_CHANNEL_9; + default: return -1; } } static int get_adc2_channel(int pin) { switch (pin) { - case 11: return ADC2_CHANNEL_0; - case 12: return ADC2_CHANNEL_1; - case 13: return ADC2_CHANNEL_2; - case 14: return ADC2_CHANNEL_3; - case 15: return ADC2_CHANNEL_4; - case 16: return ADC2_CHANNEL_5; - case 17: return ADC2_CHANNEL_6; - case 18: return ADC2_CHANNEL_7; - case 19: return ADC2_CHANNEL_8; - case 20: return ADC2_CHANNEL_9; - default: return adc2_channel_t(-1); + case 11: return ADC_CHANNEL_0; + case 12: return ADC_CHANNEL_1; + case 13: return ADC_CHANNEL_2; + case 14: return ADC_CHANNEL_3; + case 15: return ADC_CHANNEL_4; + case 16: return ADC_CHANNEL_5; + case 17: return ADC_CHANNEL_6; + case 18: return ADC_CHANNEL_7; + case 19: return ADC_CHANNEL_8; + case 20: return ADC_CHANNEL_9; + default: return -1; } } #else #error "Unsupported target" +// For future targets: +// The default bitwidth can be found in 'components/hal/esp32XX/include/hal/adc_ll.h'. +// The channel mapping is described in the GPIO page of the documentation. Google +// for 'esp32xx pins'. #endif -static adc_atten_t get_atten(int mv) { +static adc_atten_t get_attenuation(int mv) { if (mv <= 1100) return ADC_ATTEN_DB_0; if (mv <= 1500) return ADC_ATTEN_DB_2_5; if (mv <= 2200) return ADC_ATTEN_DB_6; - return ADC_ATTEN_DB_11; + return ADC_ATTEN_DB_12; } +static adc_oneshot_unit_handle_t adc1_unit = null; +static int adc1_use_count = 0; +#if SOC_ADC_PERIPH_NUM == 2 +static adc_oneshot_unit_handle_t adc2_unit = null; +static int adc2_use_count = 0; +#elif SOC_ADC_PERIPH_NUM > 2 +#error "unexpected ADC peripheral count" +#endif + +static void adc_use_count_from_handle(adc_oneshot_unit_handle_t* unit_handle, + int** use_count, + adc_unit_t* unit_id) { + if (unit_handle == &adc1_unit) { + *use_count = &adc1_use_count; + *unit_id = ADC_UNIT_1; + } +#if SOC_ADC_PERIPH_NUM > 1 + if (unit_handle == &adc2_unit) { + *use_count = &adc2_use_count; + *unit_id = ADC_UNIT_2; + } +#endif + if (*use_count == null) FATAL("unexpected ADC unit handle"); +} + +static esp_err_t adc_use(adc_oneshot_unit_handle_t* unit_handle) { + { Locker locker(OS::global_mutex()); + int* use_count = null; + adc_unit_t unit_id; + adc_use_count_from_handle(unit_handle, &use_count, &unit_id); + if (*use_count > 0) { + ASSERT(*unit_handle != null); + (*use_count)++; + return ESP_OK; + } + ASSERT(*unit_handle == null); + adc_oneshot_unit_init_cfg_t init_config = { + .unit_id = unit_id, + .clk_src = ADC_RTC_CLK_SRC_DEFAULT, + .ulp_mode = ADC_ULP_MODE_DISABLE, + + }; + esp_err_t err = adc_oneshot_new_unit(&init_config, unit_handle); + if (err == ESP_OK) { + *use_count = 1; + } else { + *unit_handle = null; + } + return err; + } +} + +static void adc_unuse(adc_oneshot_unit_handle_t* unit_handle) { + { Locker locker(OS::global_mutex()); + int* use_count = null; + adc_unit_t unit_id; + adc_use_count_from_handle(unit_handle, &use_count, &unit_id); + (*use_count)--; + if (*use_count == 0) { + adc_oneshot_del_unit(*unit_handle); + *unit_handle = null; + } + } +} + +static esp_err_t calibration_init(adc_unit_t unit, + adc_channel_t channel, + adc_atten_t atten, + adc_cali_handle_t* handle) { + esp_err_t err = ESP_FAIL; + +#if ADC_CALI_SCHEME_CURVE_FITTING_SUPPORTED + adc_cali_curve_fitting_config_t cali_config = { + .unit_id = unit, + .chan = channel, + .atten = atten, + .bitwidth = ADC_BITWIDTH_DEFAULT, + }; + err = adc_cali_create_scheme_curve_fitting(&cali_config, handle); +#elif ADC_CALI_SCHEME_LINE_FITTING_SUPPORTED + adc_cali_line_fitting_config_t cali_config = { + .unit_id = unit, + .atten = atten, + .bitwidth = ADC_BITWIDTH_DEFAULT, + // If the chip wasn't calibrated just use the default vref. + .default_vref = 1100, + }; + err = adc_cali_create_scheme_line_fitting(&cali_config, handle); +#else +// This might not be fatal. Maybe there are chips that don't support +// software calibration. In that case it should also fall back to +// no calibration. +#error "no supported calibration scheme" +#endif + if (err != ESP_OK) *handle = null; + return err; +} + +static void calibration_deinit(adc_cali_handle_t handle) { + if (handle == null) return; +#if ADC_CALI_SCHEME_CURVE_FITTING_SUPPORTED + adc_cali_delete_scheme_curve_fitting(handle); +#elif ADC_CALI_SCHEME_LINE_FITTING_SUPPORTED + adc_cali_delete_scheme_line_fitting(handle); +#else +#error "no supported calibration scheme" +#endif +} class AdcResource : public SimpleResource { public: TAG(AdcResource); - AdcResource(SimpleResourceGroup* group, adc_unit_t unit, int chan) : SimpleResource(group), unit(unit), chan(chan) {} + AdcResource(SimpleResourceGroup* group, + adc_oneshot_unit_handle_t* unit, + adc_channel_t channel, + adc_atten_t attenuation, + adc_cali_handle_t calibration) + : SimpleResource(group) + , unit(unit) + , channel(channel) + , attenuation(attenuation) + , calibration(calibration) {} + + virtual ~AdcResource() { + adc_unuse(unit); + if (calibration != null) { + calibration_deinit(calibration); + } + } - adc_unit_t unit; - int chan; -#ifndef CONFIG_IDF_TARGET_ESP32C6 - esp_adc_cal_characteristics_t calibration; -#endif + adc_oneshot_unit_handle_t* unit; + adc_channel_t channel; + adc_atten_t attenuation; + adc_cali_handle_t calibration; }; MODULE_IMPLEMENTATION(adc, MODULE_ADC) @@ -203,106 +331,109 @@ MODULE_IMPLEMENTATION(adc, MODULE_ADC) PRIMITIVE(init) { ARGS(SimpleResourceGroup, group, int, pin, bool, allow_restricted, double, max); -#ifdef CONFIG_IDF_TARGET_ESP32C6 - FAIL(UNIMPLEMENTED); -#else - if (max < 0.0) FAIL(INVALID_ARGUMENT); + // Allocate the proxy early, as it is the easiest to handle when there + // are memory issues. + ByteArray* proxy = process->object_heap()->allocate_proxy(); + if (proxy == null) FAIL(ALLOCATION_FAILED); + int max_mv = static_cast(max * 1000.0); if (max_mv == 0) max_mv = 3900; - adc_atten_t atten = get_atten(max_mv); - adc_unit_t unit; + adc_atten_t attenuation = get_attenuation(max_mv); - int chan = get_adc1_channel(pin); - if (chan >= 0) { - unit = ADC_UNIT_1; - esp_err_t err = adc1_config_width(static_cast(ADC_WIDTH_BIT_DEFAULT)); - if (err != ESP_OK) return Primitive::os_error(err, process); + adc_oneshot_unit_handle_t* unit_handle = null; + adc_unit_t unit_id; - err = adc1_config_channel_atten(static_cast(chan), atten); - if (err != ESP_OK) return Primitive::os_error(err, process); - } else if (allow_restricted) { - chan = get_adc2_channel(pin); - if (chan >= 0) { - unit = ADC_UNIT_2; - esp_err_t err = adc2_config_channel_atten(static_cast(chan), atten); - if (err != ESP_OK) return Primitive::os_error(err, process); - } else { - FAIL(OUT_OF_RANGE); + int channel = get_adc1_channel(pin); + if (channel >= 0) { + unit_handle = &adc1_unit; + unit_id = ADC_UNIT_1; + } +#if SOC_ADC_PERIPH_NUM > 1 + if (channel < 0 && allow_restricted) { + channel = get_adc2_channel(pin); + if (channel >= 0) { + unit_handle = &adc2_unit; + unit_id = ADC_UNIT_2; } - } else { - FAIL(OUT_OF_RANGE); } - - ByteArray* proxy = process->object_heap()->allocate_proxy(); - if (proxy == null) FAIL(ALLOCATION_FAILED); +#endif + if (channel < 0) FAIL(OUT_OF_RANGE); + + bool successful_return = false; + + esp_err_t err = adc_use(unit_handle); + if (err != ESP_OK) return Primitive::os_error(err, process); + Defer unuse_handle { [&] { if (!successful_return) adc_unuse(unit_handle); } }; + + adc_oneshot_chan_cfg_t channel_config = { + .atten = attenuation, + .bitwidth = ADC_BITWIDTH_DEFAULT, + }; + err = adc_oneshot_config_channel(*unit_handle, static_cast(channel), &channel_config); + if (err != ESP_OK) return Primitive::os_error(err, process); + + adc_cali_handle_t calibration = null; + err = calibration_init(unit_id, static_cast(channel), attenuation, &calibration); + if (err == ESP_ERR_NOT_SUPPORTED) { + // We have seen this for early ESP32S3 dev boards. + ESP_LOGW("ADC", "eFuse not burned, no calibration"); + } else if (err != ESP_OK) { + return Primitive::os_error(err, process); + } + Defer deinit_calib { [&] { if (!successful_return) calibration_deinit(calibration); } }; AdcResource* resource = null; { HeapTagScope scope(ITERATE_CUSTOM_TAGS + EXTERNAL_BYTE_ARRAY_MALLOC_TAG); - resource = _new AdcResource(group, unit, chan); + resource = _new AdcResource(group, + unit_handle, + static_cast(channel), + attenuation, + calibration); if (!resource) FAIL(MALLOC_FAILED); } - const int DEFAULT_VREF = 1100; - esp_adc_cal_characterize(unit, atten, static_cast(ADC_WIDTH_BIT_DEFAULT), - DEFAULT_VREF, &resource->calibration); - proxy->set_external_address(resource); + successful_return = true; return proxy; -#endif } PRIMITIVE(get) { ARGS(AdcResource, resource, int, samples); -#ifdef CONFIG_IDF_TARGET_ESP32C6 - FAIL(UNIMPLEMENTED); -#else if (samples < 1 || samples > 64) FAIL(OUT_OF_RANGE); + if (resource->calibration == null) FAIL(UNSUPPORTED); + uint32_t adc_reading = 0; // Multisampling. for (int i = 0; i < samples; i++) { - if (resource->unit == ADC_UNIT_1) { - adc_reading += adc1_get_raw(static_cast(resource->chan)); - } else { - int value = 0; - esp_err_t err = adc2_get_raw(static_cast(resource->chan), - static_cast(ADC_WIDTH_BIT_DEFAULT), &value); - if (err != ESP_OK) return Primitive::os_error(err, process); - adc_reading += value; - } + int raw; + esp_err_t err = adc_oneshot_read(*resource->unit, resource->channel, &raw); + if (err != ESP_OK) return Primitive::os_error(err, process); + adc_reading += raw; } adc_reading /= samples; // Convert adc_reading to voltage in mV. - uint32_t voltage = esp_adc_cal_raw_to_voltage(adc_reading, &resource->calibration); + int voltage; + esp_err_t err = adc_cali_raw_to_voltage(resource->calibration, adc_reading, &voltage); + if (err != ESP_OK) return Primitive::os_error(err, process); return Primitive::allocate_double(voltage / 1000.0, process); -#endif } PRIMITIVE(get_raw) { ARGS(AdcResource, resource); + int raw; + esp_err_t err = adc_oneshot_read(*resource->unit, resource->channel, &raw); + if (err != ESP_OK) return Primitive::os_error(err, process); -#ifdef CONFIG_IDF_TARGET_ESP32C6 - FAIL(UNIMPLEMENTED); -#else - int adc_reading; - if (resource->unit == ADC_UNIT_1) { - adc_reading = adc1_get_raw(static_cast(resource->chan)); - } else { - esp_err_t err = adc2_get_raw(static_cast(resource->chan), - static_cast(ADC_WIDTH_BIT_DEFAULT), &adc_reading); - if (err != ESP_OK) return Primitive::os_error(err, process); - } - - return Smi::from(adc_reading); -#endif + return Smi::from(raw); } PRIMITIVE(close) { diff --git a/src/resources/gpio_esp32.cc b/src/resources/gpio_esp32.cc index 194f43ba1..ed969a30f 100644 --- a/src/resources/gpio_esp32.cc +++ b/src/resources/gpio_esp32.cc @@ -18,8 +18,6 @@ #ifdef TOIT_ESP32 #include -#include -#include #include #include diff --git a/src/resources/i2s_esp32.cc b/src/resources/i2s_esp32.cc index c8c3404dc..ecbc01c11 100644 --- a/src/resources/i2s_esp32.cc +++ b/src/resources/i2s_esp32.cc @@ -15,7 +15,7 @@ #include "../top.h" -#ifdef TOIT_ESP32 +#if 0 // def TOIT_ESP32 #include