Skip to content

Commit a1f008c

Browse files
committed
esp32: ULP and ADC improvements
esp32/adc: Updated the module to use new driver (esp_adc/adc_oneshot.h). Tested and confired working state on several pins using: - esp32 - esp32 C3 - esp32 S2 - esp32 S3 I am unable to test any more hardware as I do not own it yet. esp32/ulp: Enable RISCV ULP by default on targets that support it (esp32 S2 & S3) with an option of reverting back to FSM in a custom build. To use FSM instead, add the line `set(PREFER_FSM_ULP ON)` in your board's mpconfigboard.cmake and add `boards/sdkconfig.ulp_fsm` to the end of SDKCONFIG_DEFAULTS like so: ``` set(SDKCONFIG_DEFAULTS boards/sdkconfig.base boards/sdkconfig.usb boards/sdkconfig.ble boards/sdkconfig.spiram_sx boards/ESP32_GENERIC_S3/sdkconfig.board boards/sdkconfig.ulp_fsm ) ``` To embed ULP code, define the sources and dependant files using the generated `ulp_embedded.h` file The app will always be called `ulp_embedded` internally An example board definition that uses custom ulp code located at `boards/${board}/ulp/main.c` has the following mpconfigboard.cmake ``` set(MICROPY_SOURCE_BOARD ${MICROPY_SOURCE_BOARD} ${MICROPY_BOARD_DIR}/cmodules/modtest.c ) set(ulp_embedded_sources ${MICROPY_BOARD_DIR}/ulp/main_pin.c) set(ulp_dependants ${MICROPY_BOARD_DIR}/cmodules/modtest.c) ``` The `ulp_dependants` is only needed if you have custom c modules that want to reference ULP variables directly. You can also read and write the embedded ULP variables using `ULP.read(address)` and `ULP.write(address, value)` By default, when you embed a ULP app, all c variables with the prefix `var_` will be added as constant address offsets to the ULP class, so you can call `ulp.read(ulp.VAR_TEST)` to get a value that way. I have confirmed that it *is* possible to load the binary ULP code after flashing a built in version.
2 parents 596daba + f536032 commit a1f008c

File tree

15 files changed

+592
-164
lines changed

15 files changed

+592
-164
lines changed

docs/library/esp32.rst

Lines changed: 120 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -204,7 +204,7 @@ with each number, the bitstream is ``0101`` with durations of [100ns, 2000ns,
204204
For more details see Espressif's `ESP-IDF RMT documentation.
205205
<https://docs.espressif.com/projects/esp-idf/en/latest/api-reference/peripherals/rmt.html>`_.
206206

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

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

306-
.. warning::
308+
.. note::
309+
By default RISCV is preferred on supported chips, however, you can configure a custom build
310+
of micropython to revert back to using the FSM ULP.
311+
Modify your mpconfigboard.cmake by specifying::
312+
313+
set(PREFER_FSM_ULP ON)
314+
315+
You can compile a custom board definition which uses the ULP and embeds the app image into the firmware.
316+
To do this, edit or create a board definition in the ports/esp32/boards folder.
317+
Make sure the mpconfigboard.cmake file has the following definition::
318+
319+
set(ulp_embedded_sources ${MICROPY_BOARD_DIR}/ulp/main_pin.c)
320+
# and optionally
321+
set(ulp_embedded_sources ${MICROPY_BOARD_DIR}/cmodules/somemodule.c)
322+
323+
When any sources are defined like this, the ULP code will be automatically added the the firmware.
324+
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
325+
326+
Example usage with embedded code::
327+
328+
import esp32
329+
u = esp32.ULP()
330+
u.run_embedded()
331+
print(u.read(u.VAR_TEST))
332+
333+
334+
Example usage with external code::
335+
336+
import esp32
337+
u = esp32.ULP()
338+
339+
with open("test.bin","rb") as file:
340+
buf = file.read()
341+
342+
u.run(buf)
343+
print(u.read(0x500000dc))
344+
345+
Example usage with external code (FSM)::
346+
347+
import esp32
348+
u = esp32.ULP()
349+
350+
with open("fsm.bin","rb") as file:
351+
buf = file.read()
352+
353+
u.run(0x1c,buf)
354+
print(u.write(0x500000dc, 42))
355+
356+
Example usage with embedded code using ADC1 channel 1 on ESP32 S3 (GPIO_2)::
357+
358+
import esp32
359+
u = esp32.ULP()
360+
361+
u.adc_init(0,1)
362+
u.set_wakeup_period(100)
363+
u.run_embedded()
364+
print(u.write(u.VAR_DO_ADC_SAMPLING, 1))
307365

308-
This class does not provide access to the RISCV ULP co-processor available
309-
on the ESP32-S2 and ESP32-S3 chips.
366+
.. note::
367+
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.
310368

311369
.. class:: ULP()
312370

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

315373
.. method:: ULP.set_wakeup_period(period_index, period_us)
374+
ULP.set_wakeup_period(period_us)
375+
376+
Set the wake-up period. Time specified in micro-seconds.
377+
*period_index* only on FSM.
378+
Since only one slot is used by the RISCV ULP, you only provide the time period in this function.
379+
380+
.. method:: ULP.run(entry_point, program_binary)
381+
ULP.run(program_binary)
382+
383+
Load a *program_binary* into the ULP and start from the beginning.
384+
*program_binary* should be a bytearray.
385+
386+
*entry_point is only used for FSM*
387+
Start the ULP running at the given *entry_point*, if applicable.
388+
389+
.. method:: ULP.pause()
390+
391+
Stop the ULP wakeup timer from triggering. On RISCV, the ULP will also be halted.
392+
393+
.. method:: ULP.resume()
394+
395+
Resume the ULP wakeup timer. On RISCV, the ULP will also be restarted.
316396

317-
Set the wake-up period.
397+
.. method:: ULP.read(address)
318398

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

321-
Load a *program_binary* into the ULP at the given *load_addr*.
402+
.. method:: ULP.write(address, value)
322403

323-
.. method:: ULP.run(entry_point)
404+
Write to the contents of RTC memory, specifying either an absolute address within the RTC range
405+
or use embedded variable constants, defined during compilation of firmware.
406+
407+
**Be careful** as you can overwrite the running ULP program instructions, resulting in a required reload.
408+
The function automatically prevents writing to any other location other than the RTC memory.
324409

325-
Start the ULP running at the given *entry_point*.
410+
.. method:: ULP.rtc_init(pin_number)
411+
412+
Initialize the give GPIO pin number (different RTC_IO number on ESP32).
413+
414+
.. method:: ULP.rtc_deinit(pin_number)
415+
416+
Un-initialize the give GPIO pin number (different RTC_IO number on ESP32).
417+
418+
.. method:: ULP.adc_init(unit, channel)
419+
420+
Prepare the given ADC *unit* for use by the ULP on the specified RTC_IO *channel*.
421+
Units are zero-indexed:
422+
- ADC1 = 0
423+
- ADC2 = 1, if chip has 2 ADCs
424+
425+
.. method:: ULP.run_embedded()
426+
427+
.. note:: Only available if firmware has compiled in a ULP program image
428+
Start the ULP running the embedded ULP app image.
429+
430+
This will load and run the embedded binary, exposing any variables onto the class, for reading and writing.
326431

327432

328433
Constants
329434
---------
330435

436+
.. data:: ULP.RESERVE_MEM
437+
438+
The amount of reserved RTC_SLOW_MEM in bytes.
439+
331440
.. data:: esp32.WAKEUP_ALL_LOW
332441
esp32.WAKEUP_ANY_HIGH
333442

ports/esp32/adc.c

Lines changed: 36 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -27,82 +27,65 @@
2727

2828
#include "py/mphal.h"
2929
#include "adc.h"
30-
#include "driver/adc.h"
30+
#include "esp_adc/adc_oneshot.h"
3131
#include "esp_adc/adc_cali_scheme.h"
3232

33-
#define DEFAULT_VREF 1100
3433

35-
void madcblock_bits_helper(machine_adc_block_obj_t *self, mp_int_t bits) {
36-
if (bits < SOC_ADC_RTC_MIN_BITWIDTH && bits > SOC_ADC_RTC_MAX_BITWIDTH) {
37-
// Invalid value for the current chip, raise exception in the switch below.
38-
bits = -1;
39-
}
40-
switch (bits) {
41-
case 9:
42-
self->width = ADC_BITWIDTH_9;
43-
break;
44-
case 10:
45-
self->width = ADC_BITWIDTH_10;
46-
break;
47-
case 11:
48-
self->width = ADC_BITWIDTH_11;
49-
break;
50-
case 12:
51-
self->width = ADC_BITWIDTH_12;
52-
break;
53-
case 13:
54-
self->width = ADC_BITWIDTH_13;
55-
break;
56-
default:
57-
mp_raise_ValueError(MP_ERROR_TEXT("invalid bits"));
58-
}
59-
self->bits = bits;
34+
static esp_err_t ensure_adc_calibration(machine_adc_block_obj_t *self, adc_atten_t atten);
6035

61-
if (self->unit_id == ADC_UNIT_1) {
62-
adc1_config_width(self->width);
63-
}
36+
37+
esp_err_t apply_self_adc_channel_atten(const machine_adc_obj_t *self, uint8_t atten){
38+
adc_oneshot_chan_cfg_t config = {
39+
.atten = atten,
40+
.bitwidth = self->block->bitwidth,
41+
};
42+
esp_err_t ret = adc_oneshot_config_channel(self->block->handle, self->channel_id, &config);
43+
return ret;
6444
}
6545

6646
mp_int_t madcblock_read_helper(machine_adc_block_obj_t *self, adc_channel_t channel_id) {
67-
int raw = 0;
68-
if (self->unit_id == ADC_UNIT_1) {
69-
raw = adc1_get_raw(channel_id);
70-
} else {
71-
#if (SOC_ADC_PERIPH_NUM >= 2)
72-
check_esp_err(adc2_get_raw(channel_id, self->width, &raw));
73-
#endif
74-
}
75-
return raw;
47+
int reading = 0;
48+
adc_oneshot_read(self->handle, channel_id, &reading);
49+
return reading;
50+
}
51+
52+
/*
53+
During testing, it turned out that the function `adc_cali_raw_to_voltage` does not account for the lower resolution,
54+
instead it expects the full resolution value as an argument, hence the scaling applied here
55+
*/
56+
mp_int_t madcblock_read_uv_helper(machine_adc_block_obj_t *self, adc_channel_t channel_id, adc_atten_t atten) {
57+
int raw = madcblock_read_helper(self, channel_id);
58+
int uv = 0;
59+
60+
check_esp_err(ensure_adc_calibration(self, atten));
61+
check_esp_err(adc_cali_raw_to_voltage(self->calib[atten], (raw << (ADC_WIDTH_MAX-self->bitwidth)), &uv));
62+
return (mp_int_t)uv * 1000;
7663
}
7764

7865
static esp_err_t ensure_adc_calibration(machine_adc_block_obj_t *self, adc_atten_t atten) {
79-
if (self->handle[atten] != NULL) {
66+
if (self->calib[atten] != NULL) {
8067
return ESP_OK;
8168
}
8269

70+
esp_err_t ret = ESP_OK;
71+
8372
#if ADC_CALI_SCHEME_CURVE_FITTING_SUPPORTED
8473
adc_cali_curve_fitting_config_t cali_config = {
8574
.unit_id = self->unit_id,
8675
.atten = atten,
87-
.bitwidth = self->width,
76+
.bitwidth = self->bitwidth,
8877
};
89-
return adc_cali_create_scheme_curve_fitting(&cali_config, &self->handle[atten]);
78+
ret = adc_cali_create_scheme_curve_fitting(&cali_config, &self->calib[atten]);
9079
#else
9180
adc_cali_line_fitting_config_t cali_config = {
9281
.unit_id = self->unit_id,
9382
.atten = atten,
94-
.bitwidth = self->width,
83+
.bitwidth = self->bitwidth,
9584
};
96-
return adc_cali_create_scheme_line_fitting(&cali_config, &self->handle[atten]);
85+
ret = adc_cali_create_scheme_line_fitting(&cali_config, &self->calib[atten]);
9786
#endif
98-
}
9987

100-
mp_int_t madcblock_read_uv_helper(machine_adc_block_obj_t *self, adc_channel_t channel_id, adc_atten_t atten) {
101-
int raw = madcblock_read_helper(self, channel_id);
102-
int uv;
103-
104-
check_esp_err(ensure_adc_calibration(self, atten));
105-
check_esp_err(adc_cali_raw_to_voltage(self->handle[atten], raw, &uv));
106-
107-
return (mp_int_t)uv * 1000;
88+
89+
return ret;
10890
}
91+

ports/esp32/adc.h

Lines changed: 42 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -29,33 +29,68 @@
2929
#define MICROPY_INCLUDED_ESP32_ADC_H
3030

3131
#include "py/runtime.h"
32-
#include "esp_adc_cal.h"
32+
#include "esp_adc/adc_oneshot.h"
3333
#include "esp_adc/adc_cali_scheme.h"
3434

35-
#define ADC_ATTEN_MAX SOC_ADC_ATTEN_NUM
35+
#define ADC_ATTEN_COUNT SOC_ADC_ATTEN_NUM
36+
#define ADC_ATTEN_MIN ADC_ATTEN_DB_0
37+
#define ADC_ATTEN_MAX ADC_ATTEN_DB_11
38+
39+
/*
40+
https://github.com/espressif/esp-idf/issues/13128
41+
https://github.com/espressif/esp-idf/blob/release/v5.2/components/soc/esp32s3/include/soc/soc_caps.h
42+
https://docs.espressif.com/projects/esp-chip-errata/en/latest/esp32s2/03-errata-description/index.html
43+
https://docs.espressif.com/projects/esp-idf/en/v4.2/esp32/api-reference/peripherals/adc.html
44+
45+
Looks like only the original esp32 is capable of bitwidth adjustment, all others are stuck at 12 bits,
46+
except the S2, which is locked at 13 bits, otherwise attenuation doesn't work.
47+
*/
48+
#if CONFIG_IDF_TARGET_ESP32S2
49+
50+
#define ADC_WIDTH_MIN ADC_BITWIDTH_13
51+
#define ADC_WIDTH_MAX ADC_BITWIDTH_13
52+
53+
#elif CONFIG_IDF_TARGET_ESP32
54+
55+
#define ADC_WIDTH_MIN ADC_BITWIDTH_9
56+
#define ADC_WIDTH_MAX ADC_BITWIDTH_12
57+
58+
#else
59+
60+
#define ADC_WIDTH_MIN ADC_BITWIDTH_12
61+
#define ADC_WIDTH_MAX ADC_BITWIDTH_12
62+
63+
#endif
3664

3765
typedef struct _machine_adc_block_obj_t {
3866
mp_obj_base_t base;
3967
adc_unit_t unit_id;
40-
mp_int_t bits;
41-
adc_bits_width_t width;
42-
adc_cali_handle_t handle[ADC_ATTEN_MAX];
68+
adc_oneshot_unit_handle_t handle;
69+
adc_cali_handle_t calib[ADC_ATTEN_COUNT];
70+
adc_bitwidth_t bitwidth;
4371
} machine_adc_block_obj_t;
4472

4573
typedef struct _machine_adc_obj_t {
4674
mp_obj_base_t base;
4775
machine_adc_block_obj_t *block;
4876
adc_channel_t channel_id;
49-
gpio_num_t gpio_id;
77+
gpio_num_t gpio_id;
5078
} machine_adc_obj_t;
5179

5280
extern machine_adc_block_obj_t madcblock_obj[];
5381

54-
void madcblock_bits_helper(machine_adc_block_obj_t *self, mp_int_t bits);
82+
esp_err_t apply_self_adc_channel_atten(const machine_adc_obj_t *self, uint8_t atten);
83+
5584
mp_int_t madcblock_read_helper(machine_adc_block_obj_t *self, adc_channel_t channel_id);
5685
mp_int_t madcblock_read_uv_helper(machine_adc_block_obj_t *self, adc_channel_t channel_id, adc_atten_t atten);
5786

5887
const machine_adc_obj_t *madc_search_helper(machine_adc_block_obj_t *block, adc_channel_t channel_id, gpio_num_t gpio_id);
5988
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);
6089

90+
mp_int_t mp_machine_adc_atten_get_helper(const machine_adc_obj_t *self);
91+
void mp_machine_adc_atten_set_helper(const machine_adc_obj_t *self, mp_int_t atten);
92+
93+
mp_int_t mp_machine_adc_width_get_helper(const machine_adc_obj_t *self);
94+
void mp_machine_adc_block_width_set_helper(machine_adc_block_obj_t *self, mp_int_t width);
95+
6196
#endif // MICROPY_INCLUDED_ESP32_ADC_H

ports/esp32/boards/sdkconfig.base

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -90,7 +90,9 @@ CONFIG_MBEDTLS_DEFAULT_MEM_ALLOC=y
9090
# Only on: ESP32, ESP32S2, ESP32S3
9191
CONFIG_ULP_COPROC_ENABLED=y
9292
CONFIG_ULP_COPROC_TYPE_FSM=y
93-
CONFIG_ULP_COPROC_RESERVE_MEM=2040
93+
# Allow SoCs that support RISCV to have it enabled by default, has no effect otherwise
94+
CONFIG_ULP_COPROC_TYPE_RISCV=y
95+
CONFIG_ULP_COPROC_RESERVE_MEM=4096
9496

9597
# For cmake build
9698
CONFIG_ESPTOOLPY_FLASHSIZE_4MB=y
@@ -115,7 +117,7 @@ CONFIG_ADC_CAL_LUT_ENABLE=y
115117
CONFIG_UART_ISR_IN_IRAM=y
116118

117119
# IDF 5 deprecated
118-
CONFIG_ADC_SUPPRESS_DEPRECATE_WARN=y
120+
CONFIG_ADC_SUPPRESS_DEPRECATE_WARN=n
119121
CONFIG_RMT_SUPPRESS_DEPRECATE_WARN=y
120122

121123
CONFIG_ETH_USE_SPI_ETHERNET=y

ports/esp32/boards/sdkconfig.ulp_fsm

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
CONFIG_ULP_COPROC_TYPE_FSM=y
2+
CONFIG_ULP_COPROC_TYPE_RISCV=n

0 commit comments

Comments
 (0)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy