Skip to content

esp32: Add the ability to disable ULP module using mpconfigport.h and ULP images built into custom builds #16469

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 7 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
131 changes: 120 additions & 11 deletions docs/library/esp32.rst
Original file line number Diff line number Diff line change
Expand Up @@ -204,7 +204,7 @@ with each number, the bitstream is ``0101`` with durations of [100ns, 2000ns,
For more details see Espressif's `ESP-IDF RMT documentation.
<https://docs.espressif.com/projects/esp-idf/en/latest/api-reference/peripherals/rmt.html>`_.

.. Warning::
.. warning::
The current MicroPython RMT implementation lacks some features, most notably
receiving pulses. RMT should be considered a
*beta feature* and the interface may change in the future.
Expand Down Expand Up @@ -300,34 +300,143 @@ Constants
Ultra-Low-Power co-processor
----------------------------

This class gives access to the Ultra Low Power (ULP) co-processor on the ESP32,
ESP32-S2 and ESP32-S3 chips.
This class gives access to the Finite State Machine (FSM) Ultra Low Power (ULP)
co-processor on the ESP32, ESP32-S2 and ESP32-S3 chips. In addition the ESP32-S2
and ESP32-S3 chips contain a RISCV co-processor which can be accessed through this
class.

.. warning::
.. note::
By default RISCV is preferred on supported chips, however, you can configure a custom build
of micropython to revert back to using the FSM ULP.
Modify your mpconfigboard.cmake by specifying::

set(PREFER_FSM_ULP ON)

You can compile a custom board definition which uses the ULP and embeds the app image into the firmware.
To do this, edit or create a board definition in the ports/esp32/boards folder.
Make sure the mpconfigboard.cmake file has the following definition::

set(ulp_embedded_sources ${MICROPY_BOARD_DIR}/ulp/main_pin.c)
# and optionally
set(ulp_embedded_sources ${MICROPY_BOARD_DIR}/cmodules/somemodule.c)

When any sources are defined like this, the ULP code will be automatically added the the firmware.
Any variables prefixed with *var_* will be automatically added to the ULP class and you can use them to reference a memory location for reading and writing

Example usage with embedded code::

import esp32
u = esp32.ULP()
u.run_embedded()
print(u.read(u.VAR_TEST))


Example usage with external code::

import esp32
u = esp32.ULP()

with open("test.bin","rb") as file:
buf = file.read()

u.run(buf)
print(u.read(0x500000dc))

Example usage with external code (FSM)::

import esp32
u = esp32.ULP()

with open("fsm.bin","rb") as file:
buf = file.read()

u.run(0x1c,buf)
print(u.write(0x500000dc, 42))

Example usage with embedded code using ADC1 channel 1 on ESP32 S3 (GPIO_2)::

import esp32
u = esp32.ULP()

u.adc_init(0,1)
u.set_wakeup_period(100)
u.run_embedded()
print(u.write(u.VAR_DO_ADC_SAMPLING, 1))

This class does not provide access to the RISCV ULP co-processor available
on the ESP32-S2 and ESP32-S3 chips.
.. note::
Exposed variables are relative address offsets from RTC_SLOW_MEM address, however you can use full address values also, if loading ULP code from outside the compilation process.

.. class:: ULP()

This class provides access to the Ultra-Low-Power co-processor.

.. method:: ULP.set_wakeup_period(period_index, period_us)
ULP.set_wakeup_period(period_us)

Set the wake-up period. Time specified in micro-seconds.
*period_index* only on FSM.
Since only one slot is used by the RISCV ULP, you only provide the time period in this function.

.. method:: ULP.run(entry_point, program_binary)
ULP.run(program_binary)

Load a *program_binary* into the ULP and start from the beginning.
*program_binary* should be a bytearray.

*entry_point is only used for FSM*
Start the ULP running at the given *entry_point*, if applicable.

.. method:: ULP.pause()

Stop the ULP wakeup timer from triggering. On RISCV, the ULP will also be halted.

.. method:: ULP.resume()

Resume the ULP wakeup timer. On RISCV, the ULP will also be restarted.

Set the wake-up period.
.. method:: ULP.read(address)

.. method:: ULP.load_binary(load_addr, program_binary)
Read the contents of RTC memory, specifying either an absolute address within the RTC range
or use embedded variable constants, defined during compilation of firmware.

Load a *program_binary* into the ULP at the given *load_addr*.
.. method:: ULP.write(address, value)

.. method:: ULP.run(entry_point)
Write to the contents of RTC memory, specifying either an absolute address within the RTC range
or use embedded variable constants, defined during compilation of firmware.

**Be careful** as you can overwrite the running ULP program instructions, resulting in a required reload.
The function automatically prevents writing to any other location other than the RTC memory.

Start the ULP running at the given *entry_point*.
.. method:: ULP.rtc_init(pin_number)

Initialize the give GPIO pin number (different RTC_IO number on ESP32).

.. method:: ULP.rtc_deinit(pin_number)

Un-initialize the give GPIO pin number (different RTC_IO number on ESP32).

.. method:: ULP.adc_init(unit, channel)

Prepare the given ADC *unit* for use by the ULP on the specified RTC_IO *channel*.
Units are zero-indexed:
- ADC1 = 0
- ADC2 = 1, if chip has 2 ADCs

.. method:: ULP.run_embedded()

.. note:: Only available if firmware has compiled in a ULP program image
Start the ULP running the embedded ULP app image.

This will load and run the embedded binary, exposing any variables onto the class, for reading and writing.


Constants
---------

.. data:: ULP.RESERVE_MEM

The amount of reserved RTC_SLOW_MEM in bytes.

.. data:: esp32.WAKEUP_ALL_LOW
esp32.WAKEUP_ANY_HIGH

Expand Down
86 changes: 34 additions & 52 deletions ports/esp32/adc.c
Original file line number Diff line number Diff line change
Expand Up @@ -27,82 +27,64 @@

#include "py/mphal.h"
#include "adc.h"
#include "driver/adc.h"
#include "esp_adc/adc_oneshot.h"
#include "esp_adc/adc_cali_scheme.h"

#define DEFAULT_VREF 1100

void madcblock_bits_helper(machine_adc_block_obj_t *self, mp_int_t bits) {
if (bits < SOC_ADC_RTC_MIN_BITWIDTH && bits > SOC_ADC_RTC_MAX_BITWIDTH) {
// Invalid value for the current chip, raise exception in the switch below.
bits = -1;
}
switch (bits) {
case 9:
self->width = ADC_BITWIDTH_9;
break;
case 10:
self->width = ADC_BITWIDTH_10;
break;
case 11:
self->width = ADC_BITWIDTH_11;
break;
case 12:
self->width = ADC_BITWIDTH_12;
break;
case 13:
self->width = ADC_BITWIDTH_13;
break;
default:
mp_raise_ValueError(MP_ERROR_TEXT("invalid bits"));
}
self->bits = bits;
static esp_err_t ensure_adc_calibration(machine_adc_block_obj_t *self, adc_atten_t atten);

if (self->unit_id == ADC_UNIT_1) {
adc1_config_width(self->width);
}

esp_err_t apply_self_adc_channel_atten(const machine_adc_obj_t *self, uint8_t atten) {
adc_oneshot_chan_cfg_t config = {
.atten = atten,
.bitwidth = self->block->bitwidth,
};
esp_err_t ret = adc_oneshot_config_channel(self->block->handle, self->channel_id, &config);
return ret;
}

mp_int_t madcblock_read_helper(machine_adc_block_obj_t *self, adc_channel_t channel_id) {
int raw = 0;
if (self->unit_id == ADC_UNIT_1) {
raw = adc1_get_raw(channel_id);
} else {
#if (SOC_ADC_PERIPH_NUM >= 2)
check_esp_err(adc2_get_raw(channel_id, self->width, &raw));
#endif
}
return raw;
int reading = 0;
adc_oneshot_read(self->handle, channel_id, &reading);
return reading;
}

/*
During testing, it turned out that the function `adc_cali_raw_to_voltage` does not account for the lower resolution,
instead it expects the full resolution value as an argument, hence the scaling applied here
*/
mp_int_t madcblock_read_uv_helper(machine_adc_block_obj_t *self, adc_channel_t channel_id, adc_atten_t atten) {
int raw = madcblock_read_helper(self, channel_id);
int uv = 0;

check_esp_err(ensure_adc_calibration(self, atten));
check_esp_err(adc_cali_raw_to_voltage(self->calib[atten], (raw << (ADC_WIDTH_MAX - self->bitwidth)), &uv));
return (mp_int_t)uv * 1000;
}

static esp_err_t ensure_adc_calibration(machine_adc_block_obj_t *self, adc_atten_t atten) {
if (self->handle[atten] != NULL) {
if (self->calib[atten] != NULL) {
return ESP_OK;
}

esp_err_t ret = ESP_OK;

#if ADC_CALI_SCHEME_CURVE_FITTING_SUPPORTED
adc_cali_curve_fitting_config_t cali_config = {
.unit_id = self->unit_id,
.atten = atten,
.bitwidth = self->width,
.bitwidth = self->bitwidth,
};
return adc_cali_create_scheme_curve_fitting(&cali_config, &self->handle[atten]);
ret = adc_cali_create_scheme_curve_fitting(&cali_config, &self->calib[atten]);
#else
adc_cali_line_fitting_config_t cali_config = {
.unit_id = self->unit_id,
.atten = atten,
.bitwidth = self->width,
.bitwidth = self->bitwidth,
};
return adc_cali_create_scheme_line_fitting(&cali_config, &self->handle[atten]);
ret = adc_cali_create_scheme_line_fitting(&cali_config, &self->calib[atten]);
#endif
}

mp_int_t madcblock_read_uv_helper(machine_adc_block_obj_t *self, adc_channel_t channel_id, adc_atten_t atten) {
int raw = madcblock_read_helper(self, channel_id);
int uv;

check_esp_err(ensure_adc_calibration(self, atten));
check_esp_err(adc_cali_raw_to_voltage(self->handle[atten], raw, &uv));

return (mp_int_t)uv * 1000;
return ret;
}
47 changes: 41 additions & 6 deletions ports/esp32/adc.h
Original file line number Diff line number Diff line change
Expand Up @@ -29,17 +29,45 @@
#define MICROPY_INCLUDED_ESP32_ADC_H

#include "py/runtime.h"
#include "esp_adc_cal.h"
#include "esp_adc/adc_oneshot.h"
#include "esp_adc/adc_cali_scheme.h"

#define ADC_ATTEN_MAX SOC_ADC_ATTEN_NUM
#define ADC_ATTEN_COUNT SOC_ADC_ATTEN_NUM
#define ADC_ATTEN_MIN ADC_ATTEN_DB_0
#define ADC_ATTEN_MAX ADC_ATTEN_DB_11

/*
https://github.com/espressif/esp-idf/issues/13128
https://github.com/espressif/esp-idf/blob/release/v5.2/components/soc/esp32s3/include/soc/soc_caps.h
https://docs.espressif.com/projects/esp-chip-errata/en/latest/esp32s2/03-errata-description/index.html
https://docs.espressif.com/projects/esp-idf/en/v4.2/esp32/api-reference/peripherals/adc.html

Looks like only the original esp32 is capable of bitwidth adjustment, all others are stuck at 12 bits,
except the S2, which is locked at 13 bits, otherwise attenuation doesn't work.
*/
#if CONFIG_IDF_TARGET_ESP32S2

#define ADC_WIDTH_MIN ADC_BITWIDTH_13
#define ADC_WIDTH_MAX ADC_BITWIDTH_13

#elif CONFIG_IDF_TARGET_ESP32

#define ADC_WIDTH_MIN ADC_BITWIDTH_9
#define ADC_WIDTH_MAX ADC_BITWIDTH_12

#else

#define ADC_WIDTH_MIN ADC_BITWIDTH_12
#define ADC_WIDTH_MAX ADC_BITWIDTH_12

#endif

typedef struct _machine_adc_block_obj_t {
mp_obj_base_t base;
adc_unit_t unit_id;
mp_int_t bits;
adc_bits_width_t width;
adc_cali_handle_t handle[ADC_ATTEN_MAX];
adc_oneshot_unit_handle_t handle;
adc_cali_handle_t calib[ADC_ATTEN_COUNT];
adc_bitwidth_t bitwidth;
} machine_adc_block_obj_t;

typedef struct _machine_adc_obj_t {
Expand All @@ -51,11 +79,18 @@ typedef struct _machine_adc_obj_t {

extern machine_adc_block_obj_t madcblock_obj[];

void madcblock_bits_helper(machine_adc_block_obj_t *self, mp_int_t bits);
esp_err_t apply_self_adc_channel_atten(const machine_adc_obj_t *self, uint8_t atten);

mp_int_t madcblock_read_helper(machine_adc_block_obj_t *self, adc_channel_t channel_id);
mp_int_t madcblock_read_uv_helper(machine_adc_block_obj_t *self, adc_channel_t channel_id, adc_atten_t atten);

const machine_adc_obj_t *madc_search_helper(machine_adc_block_obj_t *block, adc_channel_t channel_id, gpio_num_t gpio_id);
void madc_init_helper(const machine_adc_obj_t *self, size_t n_pos_args, const mp_obj_t *pos_args, mp_map_t *kw_args);

mp_int_t mp_machine_adc_atten_get_helper(const machine_adc_obj_t *self);
void mp_machine_adc_atten_set_helper(const machine_adc_obj_t *self, mp_int_t atten);

mp_int_t mp_machine_adc_width_get_helper(const machine_adc_obj_t *self);
void mp_machine_adc_block_width_set_helper(machine_adc_block_obj_t *self, mp_int_t width);

#endif // MICROPY_INCLUDED_ESP32_ADC_H
6 changes: 4 additions & 2 deletions ports/esp32/boards/sdkconfig.base
Original file line number Diff line number Diff line change
Expand Up @@ -90,7 +90,9 @@ CONFIG_MBEDTLS_DEFAULT_MEM_ALLOC=y
# Only on: ESP32, ESP32S2, ESP32S3
CONFIG_ULP_COPROC_ENABLED=y
CONFIG_ULP_COPROC_TYPE_FSM=y
CONFIG_ULP_COPROC_RESERVE_MEM=2040
# Allow SoCs that support RISCV to have it enabled by default, has no effect otherwise
CONFIG_ULP_COPROC_TYPE_RISCV=y
CONFIG_ULP_COPROC_RESERVE_MEM=4096

# For cmake build
CONFIG_ESPTOOLPY_FLASHSIZE_4MB=y
Expand All @@ -115,7 +117,7 @@ CONFIG_ADC_CAL_LUT_ENABLE=y
CONFIG_UART_ISR_IN_IRAM=y

# IDF 5 deprecated
CONFIG_ADC_SUPPRESS_DEPRECATE_WARN=y
CONFIG_ADC_SUPPRESS_DEPRECATE_WARN=n
CONFIG_RMT_SUPPRESS_DEPRECATE_WARN=y

CONFIG_ETH_USE_SPI_ETHERNET=y
Expand Down
2 changes: 2 additions & 0 deletions ports/esp32/boards/sdkconfig.ulp_fsm
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
CONFIG_ULP_COPROC_TYPE_FSM=y
CONFIG_ULP_COPROC_TYPE_RISCV=n
Loading
Loading